From 9abe0b1b76daae18dbb3a87125a106e13f0b1415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=A2=E5=AE=87=E5=AE=81?= Date: Tue, 29 Sep 2026 15:30:33 +0700 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/web/account_pagination.go | 52 - app/api/web/auth.go | 290 ------ app/api/web/chapterLike.go | 120 --- app/api/web/chapterUnlock.go | 64 -- app/api/web/chapterWatch.go | 75 -- app/api/web/comment.go | 112 --- app/api/web/favorites.go | 338 ------- app/api/web/feedback.go | 3 +- app/api/web/history.go | 375 ------- app/api/web/index.go | 8 - app/api/web/login.go | 11 - app/api/web/play.go | 83 -- app/api/web/playletLike.go | 64 -- app/api/web/playlet_collect_set.go | 64 -- app/api/web/profile.go | 229 ----- app/api/web/purchased.go | 87 -- app/api/web/recharge.go | 279 ------ app/api/web/register.go | 11 - app/api/web/render.go | 62 +- app/api/web/setting.go | 85 -- app/api/web/tracker_page.go | 80 -- app/api/web/videoCollect.go | 64 -- app/api/web/videoLike.go | 64 -- app/appg/appg.go | 30 +- app/conf/app.yaml | 8 +- app/router/gin.go | 43 +- app/service/authser/auth.go | 290 ------ app/service/authser/guest_enter.go | 314 ------ app/service/authser/guest_enter_test.go | 345 ------- app/service/authser/request_state.go | 67 -- app/service/authser/session.go | 58 -- app/service/authser/types.go | 95 -- app/service/authser/view.go | 97 -- app/service/authser/vip.go | 187 ---- app/service/commentser/like.go | 22 - app/service/commentser/list.go | 8 - app/service/commentser/send.go | 43 - app/service/configser/info.go | 4 +- app/service/configser/tracker.go | 25 - app/service/configser/types.go | 14 +- app/service/configser/view.go | 2 - app/service/feedbackser/feedback.go | 28 +- app/service/feedbackser/types.go | 24 + app/service/playletser/bought.go | 56 -- app/service/playletser/chapter.go | 17 - app/service/playletser/chapter_collect.go | 84 -- app/service/playletser/chapter_like.go | 63 -- app/service/playletser/chapter_unlock.go | 40 - app/service/playletser/chapter_watch.go | 39 - app/service/playletser/collect.go | 161 --- app/service/playletser/collect_view.go | 89 -- app/service/playletser/detail.go | 10 - app/service/playletser/history.go | 166 ---- app/service/playletser/history_view.go | 229 ----- app/service/playletser/like.go | 35 - app/service/playletser/search.go | 19 - app/service/playletser/tplfuncs.go | 141 ++- app/service/rechargeser/list.go | 281 ------ app/service/rechargeser/types.go | 125 --- app/service/rechargeser/view.go | 496 ---------- app/service/translationser/list.go | 13 + app/service/translationser/view.go | 6 - app/service/usercenterser/center.go | 42 - app/service/videoser/collect.go | 130 --- app/service/videoser/history.go | 141 --- app/service/videoser/like.go | 37 - app/static/common/web-sdk-v1.2.0.js | 1 - app/static/css/account-empty.css | 72 -- app/static/css/base.css | 32 + app/static/css/favorites-mobile.css | 266 ----- app/static/css/favorites.css | 352 ------- app/static/css/footer.css | 96 ++ app/static/css/header.css | 10 + app/static/css/history-mobile.css | 256 ----- app/static/css/history.css | 256 ----- app/static/css/index.css | 75 ++ app/static/css/login.css | 283 ------ app/static/css/loginPop.css | 308 ------ app/static/css/play.css | 48 +- app/static/css/profile-mobile.css | 590 ----------- app/static/css/profile.css | 557 ----------- app/static/css/purchased-mobile.css | 152 --- app/static/css/purchased.css | 148 --- app/static/css/recharge-mobile.css | 117 --- app/static/css/recharge-modal.css | 516 ---------- app/static/css/recharge.css | 271 ------ app/static/css/register.css | 255 ----- app/static/css/registerPop.css | 304 ------ app/static/css/setting.css | 65 -- app/static/images/check.svg | 3 - app/static/images/eye-off.svg | 4 - app/static/images/eye.svg | 4 - .../images/favorites-empty/arrow-right.svg | 4 - .../images/favorites-empty/bookmark.svg | 3 - app/static/images/favorites/sliders.svg | 11 - app/static/images/gift-dim.svg | 7 - app/static/images/logout.svg | 5 - app/static/images/menu_gkls.svg | 4 - app/static/images/menu_grzx.svg | 4 - app/static/images/menu_jbcz.svg | 3 - app/static/images/menu_sc.svg | 3 - .../images/purchased-empty/bag-play.svg | 5 - app/static/images/recharge/award.svg | 4 - app/static/images/recharge/check-circle.svg | 4 - app/static/images/recharge/check.svg | 3 - app/static/images/recharge/close.svg | 4 - app/static/images/recharge/coin-star.svg | 3 - app/static/images/recharge/coin3d.png | Bin 348795 -> 0 bytes app/static/images/recharge/download.svg | 5 - app/static/images/recharge/lock.svg | 4 - app/static/images/recharge/zap.svg | 3 - app/static/images/user.svg | 4 - app/static/js/common/auth.js | 515 ---------- app/static/js/common/channel.js | 92 -- app/static/js/common/cookie.js | 204 ---- app/static/js/common/request.js | 246 +---- app/static/js/favorites-manage.js | 56 -- app/static/js/favorites.js | 111 --- app/static/js/header.js | 172 ---- app/static/js/history.js | 115 --- app/static/js/pages/dialogAd.js | 21 - app/static/js/pages/feedback.js | 4 - app/static/js/pages/login.js | 137 --- app/static/js/pages/loginPop.js | 252 ----- app/static/js/pages/play.js | 783 +-------------- app/static/js/pages/playComments.js | 363 +------ app/static/js/pages/register.js | 169 ---- app/static/js/pages/registerPop.js | 256 ----- app/static/js/recharge-modal.js | 246 ----- app/static/js/recharge.js | 322 ------ app/static/js/setting.js | 143 --- app/static/js/tracker.js | 921 ------------------ app/template/base/accountPagination.html | 9 - app/template/base/ads.html | 2 +- .../base/{trackerInit.html => analytics.html} | 15 +- app/template/base/cardMeta.html | 1 + app/template/base/dialogAd.html | 8 - app/template/base/footer.html | 53 +- app/template/base/header.html | 168 +--- app/template/base/loginPop.html | 93 -- app/template/base/playComments.html | 60 +- app/template/base/rechargeModal.html | 87 -- app/template/base/registerPop.html | 124 --- app/template/base/seoCommon.html | 3 +- app/template/base/subpageHeader.html | 2 +- app/template/base/userCenterShell.html | 46 - app/template/pages/2257.html | 2 +- app/template/pages/404.html | 2 +- app/template/pages/about.html | 2 +- app/template/pages/dmca.html | 2 +- app/template/pages/favorites.html | 69 -- app/template/pages/feedback.html | 8 +- app/template/pages/help.html | 20 +- app/template/pages/history.html | 79 -- app/template/pages/index.html | 79 +- app/template/pages/list.html | 13 +- app/template/pages/login.html | 121 --- app/template/pages/moreList.html | 2 +- app/template/pages/moreSeriesList.html | 2 +- app/template/pages/nav.html | 2 +- app/template/pages/play.html | 153 +-- app/template/pages/popularRecommend.html | 2 +- app/template/pages/privacy.html | 2 +- app/template/pages/profile.html | 62 -- app/template/pages/purchased.html | 51 - app/template/pages/rank.html | 21 +- app/template/pages/recentlyAdded.html | 2 +- app/template/pages/recharge.html | 103 -- app/template/pages/register.html | 146 --- app/template/pages/search.html | 2 +- app/template/pages/searchMobile.html | 2 +- app/template/pages/series.html | 2 +- app/template/pages/seriesPlay.html | 83 +- app/template/pages/setting.html | 86 -- app/template/pages/tags.html | 2 +- app/template/pages/terms.html | 2 +- app/template/pages/topic.html | 2 +- app/template/pages/topicDetail.html | 2 +- pub/common/useragent/build.go | 229 +---- pub/common/useragent/sdk_common.go | 80 -- pub/pubdef/pubdef.go | 5 - 181 files changed, 608 insertions(+), 18501 deletions(-) delete mode 100644 app/api/web/account_pagination.go delete mode 100644 app/api/web/auth.go delete mode 100644 app/api/web/chapterLike.go delete mode 100644 app/api/web/chapterUnlock.go delete mode 100644 app/api/web/chapterWatch.go delete mode 100644 app/api/web/favorites.go delete mode 100644 app/api/web/history.go delete mode 100644 app/api/web/login.go delete mode 100644 app/api/web/playletLike.go delete mode 100644 app/api/web/playlet_collect_set.go delete mode 100644 app/api/web/profile.go delete mode 100644 app/api/web/purchased.go delete mode 100644 app/api/web/recharge.go delete mode 100644 app/api/web/register.go delete mode 100644 app/api/web/setting.go delete mode 100644 app/api/web/tracker_page.go delete mode 100644 app/api/web/videoCollect.go delete mode 100644 app/api/web/videoLike.go delete mode 100644 app/service/authser/auth.go delete mode 100644 app/service/authser/guest_enter.go delete mode 100644 app/service/authser/guest_enter_test.go delete mode 100644 app/service/authser/request_state.go delete mode 100644 app/service/authser/session.go delete mode 100644 app/service/authser/types.go delete mode 100644 app/service/authser/view.go delete mode 100644 app/service/authser/vip.go delete mode 100644 app/service/commentser/like.go delete mode 100644 app/service/commentser/send.go delete mode 100644 app/service/configser/tracker.go delete mode 100644 app/service/playletser/bought.go delete mode 100644 app/service/playletser/chapter_collect.go delete mode 100644 app/service/playletser/chapter_like.go delete mode 100644 app/service/playletser/chapter_unlock.go delete mode 100644 app/service/playletser/chapter_watch.go delete mode 100644 app/service/playletser/collect.go delete mode 100644 app/service/playletser/collect_view.go delete mode 100644 app/service/playletser/history.go delete mode 100644 app/service/playletser/history_view.go delete mode 100644 app/service/playletser/like.go delete mode 100644 app/service/rechargeser/list.go delete mode 100644 app/service/rechargeser/types.go delete mode 100644 app/service/rechargeser/view.go delete mode 100644 app/service/usercenterser/center.go delete mode 100644 app/service/videoser/collect.go delete mode 100644 app/service/videoser/history.go delete mode 100644 app/service/videoser/like.go delete mode 100644 app/static/common/web-sdk-v1.2.0.js delete mode 100644 app/static/css/account-empty.css delete mode 100644 app/static/css/favorites-mobile.css delete mode 100644 app/static/css/favorites.css delete mode 100644 app/static/css/history-mobile.css delete mode 100644 app/static/css/history.css delete mode 100644 app/static/css/login.css delete mode 100644 app/static/css/loginPop.css delete mode 100644 app/static/css/profile-mobile.css delete mode 100644 app/static/css/profile.css delete mode 100644 app/static/css/purchased-mobile.css delete mode 100644 app/static/css/purchased.css delete mode 100644 app/static/css/recharge-mobile.css delete mode 100644 app/static/css/recharge-modal.css delete mode 100644 app/static/css/recharge.css delete mode 100644 app/static/css/register.css delete mode 100644 app/static/css/registerPop.css delete mode 100644 app/static/css/setting.css delete mode 100644 app/static/images/check.svg delete mode 100644 app/static/images/eye-off.svg delete mode 100644 app/static/images/eye.svg delete mode 100644 app/static/images/favorites-empty/arrow-right.svg delete mode 100644 app/static/images/favorites-empty/bookmark.svg delete mode 100644 app/static/images/favorites/sliders.svg delete mode 100644 app/static/images/gift-dim.svg delete mode 100644 app/static/images/logout.svg delete mode 100644 app/static/images/menu_gkls.svg delete mode 100644 app/static/images/menu_grzx.svg delete mode 100644 app/static/images/menu_jbcz.svg delete mode 100644 app/static/images/menu_sc.svg delete mode 100644 app/static/images/purchased-empty/bag-play.svg delete mode 100644 app/static/images/recharge/award.svg delete mode 100644 app/static/images/recharge/check-circle.svg delete mode 100644 app/static/images/recharge/check.svg delete mode 100644 app/static/images/recharge/close.svg delete mode 100644 app/static/images/recharge/coin-star.svg delete mode 100644 app/static/images/recharge/coin3d.png delete mode 100644 app/static/images/recharge/download.svg delete mode 100644 app/static/images/recharge/lock.svg delete mode 100644 app/static/images/recharge/zap.svg delete mode 100644 app/static/images/user.svg delete mode 100644 app/static/js/common/auth.js delete mode 100644 app/static/js/common/channel.js delete mode 100644 app/static/js/common/cookie.js delete mode 100644 app/static/js/favorites-manage.js delete mode 100644 app/static/js/favorites.js delete mode 100644 app/static/js/history.js delete mode 100644 app/static/js/pages/login.js delete mode 100644 app/static/js/pages/loginPop.js delete mode 100644 app/static/js/pages/register.js delete mode 100644 app/static/js/pages/registerPop.js delete mode 100644 app/static/js/recharge-modal.js delete mode 100644 app/static/js/recharge.js delete mode 100644 app/static/js/setting.js delete mode 100644 app/static/js/tracker.js delete mode 100644 app/template/base/accountPagination.html rename app/template/base/{trackerInit.html => analytics.html} (72%) create mode 100644 app/template/base/cardMeta.html delete mode 100644 app/template/base/loginPop.html delete mode 100644 app/template/base/rechargeModal.html delete mode 100644 app/template/base/registerPop.html delete mode 100644 app/template/base/userCenterShell.html delete mode 100644 app/template/pages/favorites.html delete mode 100644 app/template/pages/history.html delete mode 100644 app/template/pages/login.html delete mode 100644 app/template/pages/profile.html delete mode 100644 app/template/pages/purchased.html delete mode 100644 app/template/pages/recharge.html delete mode 100644 app/template/pages/register.html delete mode 100644 app/template/pages/setting.html delete mode 100644 pub/common/useragent/sdk_common.go diff --git a/app/api/web/account_pagination.go b/app/api/web/account_pagination.go deleted file mode 100644 index 8bbdbf8..0000000 --- a/app/api/web/account_pagination.go +++ /dev/null @@ -1,52 +0,0 @@ -package web - -import ( - "strconv" - "strings" - - "github.com/gin-gonic/gin" -) - -const maxAccountPage = 10000 - -type accountPage struct { - Current int - Total int - Count int64 - PrevURL string - NextURL string -} - -func accountPageNum(c *gin.Context) (int, bool) { - if c.Request.URL.Query().Has("page") { - return 0, false - } - raw := strings.TrimSpace(c.Param("page")) - if raw == "" { - return 1, true - } - page, err := strconv.Atoi(raw) - if err != nil || page < 1 || page > maxAccountPage { - return 0, false - } - return page, true -} - -func makeAccountPage(path string, page, size int, count int64) accountPage { - total := int((count + int64(size) - 1) / int64(size)) - p := accountPage{Current: page, Total: total, Count: count} - link := func(n int) string { - path = strings.TrimRight(path, "/") - if n <= 1 { - return path - } - return path + "/page/" + strconv.Itoa(n) - } - if page > 1 { - p.PrevURL = link(page - 1) - } - if page < total { - p.NextURL = link(page + 1) - } - return p -} diff --git a/app/api/web/auth.go b/app/api/web/auth.go deleted file mode 100644 index 8697013..0000000 --- a/app/api/web/auth.go +++ /dev/null @@ -1,290 +0,0 @@ -package web - -import ( - "net/http" - "strings" - "videoweb/app/service/authser" - "videoweb/pub/common/log" - - "github.com/gin-gonic/gin" -) - -type registerBody struct { - AccountNo string `json:"accountNo"` - Email string `json:"email"` - Password string `json:"password"` - AgentInviteCode string `json:"agentInviteCode"` - DevID string `json:"devID"` - Channel string `json:"channel"` -} - -type loginBody struct { - Account string `json:"account"` - Password string `json:"password"` - DevID string `json:"devID"` - Channel string `json:"channel"` -} - -// Register 代理上游注册(POST /api/app/auth/register),成功后写入登录 cookie。 -// 请求头带游客 Authorization 时升级该游客;邮箱缺省时用账号生成占位邮箱。 -func Register(c *gin.Context) { - var body registerBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - - body.AccountNo = strings.TrimSpace(body.AccountNo) - body.Email = strings.TrimSpace(body.Email) - body.AgentInviteCode = strings.TrimSpace(body.AgentInviteCode) - body.DevID = strings.TrimSpace(body.DevID) - body.Channel = authser.ResolveChannel(c, body.Channel) - - if body.AccountNo == "" || body.Password == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "请填写账号和密码", - "data": nil, - }) - return - } - - // 上游 registerReq 要求 email;前端无邮箱时用账号生成唯一占位邮箱。 - if body.Email == "" { - body.Email = body.AccountNo + "@hgdj.local" - } - - env, user, err := authser.Register(c, authser.RegisterReq{ - AccountNo: body.AccountNo, - Email: body.Email, - Password: body.Password, - AgentInviteCode: body.AgentInviteCode, - DevID: body.DevID, - Channel: body.Channel, - }) - if err != nil { - log.Error("register upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "register failed", - "tip": "注册失败,请稍后重试", - "data": nil, - }) - return - } - - if env == nil || env.Code != 200 || user == nil { - tip := "注册失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - if t := strings.TrimSpace(env.Tip); t != "" { - tip = t - } else if m := strings.TrimSpace(env.Msg); m != "" { - tip = m - } - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - authser.SetAuthCookies(c, user) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmptyAuth(env.Msg, "success"), - "tip": firstNonEmptyAuth(env.Tip, "注册成功"), - "data": user, - }) -} - -// Login 代理上游登录(POST /api/app/auth/login),成功后写入登录 cookie。 -func Login(c *gin.Context) { - var body loginBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - - body.Account = strings.TrimSpace(body.Account) - body.DevID = strings.TrimSpace(body.DevID) - body.Channel = authser.ResolveChannel(c, body.Channel) - if body.Account == "" || body.Password == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "请填写账号和密码", - "data": nil, - }) - return - } - - env, user, err := authser.Login(c, authser.LoginReq{ - Account: body.Account, - Password: body.Password, - DevID: body.DevID, - Channel: body.Channel, - }) - if err != nil { - log.Error("login upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "login failed", - "tip": "登录失败,请稍后重试", - "data": nil, - }) - return - } - - if env == nil || env.Code != 200 || user == nil { - tip := "登录失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - if t := strings.TrimSpace(env.Tip); t != "" { - tip = t - } else if m := strings.TrimSpace(env.Msg); m != "" { - tip = m - } - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - authser.SetAuthCookies(c, user) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmptyAuth(env.Msg, "success"), - "tip": firstNonEmptyAuth(env.Tip, "登录成功"), - "data": user, - }) -} - -// Guest 代理上游游客登录,成功后写入登录 cookie。 -// 对应 POST /api/app/auth/guest;需传 devID,上游要求 X-User-Agent / X-Real-IP(由 BFF 组装)。 -func Guest(c *gin.Context) { - var body struct { - DevID string `json:"devID"` - DeviceID string `json:"deviceId"` - Mac string `json:"mac"` - Channel string `json:"channel"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - - body.DevID = strings.TrimSpace(body.DevID) - body.DeviceID = strings.TrimSpace(body.DeviceID) - body.Mac = strings.TrimSpace(body.Mac) - body.Channel = authser.ResolveChannel(c, body.Channel) - if body.DevID == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少设备标识", - "data": nil, - }) - return - } - if body.DeviceID == "" { - body.DeviceID = body.DevID - } - - env, user, err := authser.GuestLogin(c, authser.GuestLoginReq{ - DevID: body.DevID, - DeviceID: body.DeviceID, - Mac: body.Mac, - Channel: body.Channel, - }) - if err != nil { - log.Error("guest login upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "guest login failed", - "tip": "游客登录失败,请稍后重试", - "data": nil, - }) - return - } - - if env == nil || env.Code != 200 || user == nil { - tip := "游客登录失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - if t := strings.TrimSpace(env.Tip); t != "" { - tip = t - } else if m := strings.TrimSpace(env.Msg); m != "" { - tip = m - } - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - authser.SetAuthCookies(c, user) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmptyAuth(env.Msg, "success"), - "tip": firstNonEmptyAuth(env.Tip, "游客登录成功"), - "data": user, - }) -} - -// Logout 清除登录 cookie,并标记跳过紧接着的一次自动游客登录。 -func Logout(c *gin.Context) { - authser.ClearAuthCookies(c) - authser.MarkGuestSkipAuto(c) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": "success", - "tip": "已退出登录", - "data": nil, - }) -} - -func firstNonEmptyAuth(values ...string) string { - for _, v := range values { - if strings.TrimSpace(v) != "" { - return v - } - } - return "" -} diff --git a/app/api/web/chapterLike.go b/app/api/web/chapterLike.go deleted file mode 100644 index a65358c..0000000 --- a/app/api/web/chapterLike.go +++ /dev/null @@ -1,120 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - - "github.com/gin-gonic/gin" -) - -// ChapterLikeSet BFF:代理 POST /api/app/playlet/chapter/like/set。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_chapter_like_set -func ChapterLikeSet(c *gin.Context) { - var body struct { - ChapterID int64 `json:"chapterId"` - IsLike *bool `json:"isLike"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ChapterID <= 0 || body.IsLike == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "chapterId/isLike required", - "tip": "缺少剧集 ID 或点赞状态", - "data": nil, - }) - return - } - - env, err := playletser.ChapterLikeSet(c, playletser.PlayletChapterLikeSetReq{ - ChapterID: body.ChapterID, - IsLike: *body.IsLike, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} - -// ChapterCollectSet BFF:代理 POST /api/app/playlet/chapter/collect/set。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_chapter_collect_set -func ChapterCollectSet(c *gin.Context) { - var body struct { - ChapterID int64 `json:"chapterId"` - IsCollect *bool `json:"isCollect"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ChapterID <= 0 || body.IsCollect == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "chapterId/isCollect required", - "tip": "缺少剧集 ID 或收藏状态", - "data": nil, - }) - return - } - - env, err := playletser.ChapterCollectSet(c, playletser.PlayletChapterCollectSetReq{ - ChapterID: body.ChapterID, - IsCollect: *body.IsCollect, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} diff --git a/app/api/web/chapterUnlock.go b/app/api/web/chapterUnlock.go deleted file mode 100644 index 7082bc7..0000000 --- a/app/api/web/chapterUnlock.go +++ /dev/null @@ -1,64 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - - "github.com/gin-gonic/gin" -) - -// ChapterUnlock BFF:代理 POST /api/app/playlet/chapter/unlock。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_chapter_unlock -func ChapterUnlock(c *gin.Context) { - var body struct { - ChapterID int64 `json:"chapterId"` - PlayletID int64 `json:"playletId"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ChapterID <= 0 || body.PlayletID <= 0 { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "chapterId/playletId required", - "tip": "缺少剧集或短剧 ID", - "data": nil, - }) - return - } - - env, err := playletser.ChapterUnlock(c, playletser.PlayletChapterUnlockReq{ - ChapterID: body.ChapterID, - PlayletID: body.PlayletID, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "解锁失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "解锁失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} diff --git a/app/api/web/chapterWatch.go b/app/api/web/chapterWatch.go deleted file mode 100644 index 4ec026a..0000000 --- a/app/api/web/chapterWatch.go +++ /dev/null @@ -1,75 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -// ChapterWatchReport BFF:代理 POST /api/app/playlet/chapter/watch/report。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_chapter_watch_report -func ChapterWatchReport(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录", - "data": nil, - }) - return - } - - var body struct { - ChapterID int64 `json:"chapterId"` - PlayletID int64 `json:"playletId"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ChapterID <= 0 { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "chapterId required", - "tip": "缺少剧集 ID", - "data": nil, - }) - return - } - - env, err := playletser.ChapterWatchReport(c, playletser.PlayletChapterWatchReportReq{ - ChapterID: body.ChapterID, - PlayletID: body.PlayletID, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "上报失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "上报失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} diff --git a/app/api/web/comment.go b/app/api/web/comment.go index 6f3c044..a62c99e 100644 --- a/app/api/web/comment.go +++ b/app/api/web/comment.go @@ -3,7 +3,6 @@ package web import ( "net/http" "strconv" - "strings" "videoweb/app/service/commentser" "github.com/gin-gonic/gin" @@ -42,114 +41,3 @@ func CommentList(c *gin.Context) { } // CommentSend BFF:代理 POST /api/app/comment/send。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/评论APP/post_api_app_comment_send -func CommentSend(c *gin.Context) { - var req commentser.CommentSendReq - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - env, err := commentser.Send(c, req) - if err != nil { - tip := "发布失败" - msg := err.Error() - switch { - case strings.Contains(msg, "empty text"): - tip = "请输入评论内容" - case strings.Contains(msg, "too long"): - tip = "评论不能超过 200 字" - case strings.Contains(msg, "invalid objectId"): - tip = "缺少评论对象" - } - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "发布失败", - "data": nil, - }) - return - } - tip := env.Tip - if tip == "" { - tip = env.Msg - } - // 上游参数错误兜底提示(例如 objectType 不被支持) - if env.Code == 1001 && (tip == "" || tip == "参数错误") { - tip = "评论参数错误,请稍后重试" - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": tip, - "data": env.Data, - }) -} - -// CommentLikeSet BFF:代理 POST /api/app/comment/like/set。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/评论APP/post_api_app_comment_like_set -func CommentLikeSet(c *gin.Context) { - var body struct { - CommentID int64 `json:"commentId"` - IsLike *bool `json:"isLike"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.CommentID <= 0 || body.IsLike == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "commentId/isLike required", - "tip": "缺少评论 ID 或点赞状态", - "data": nil, - }) - return - } - env, err := commentser.LikeSet(c, commentser.CommentLikeSetReq{ - CommentID: body.CommentID, - IsLike: *body.IsLike, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} diff --git a/app/api/web/favorites.go b/app/api/web/favorites.go deleted file mode 100644 index d01bb57..0000000 --- a/app/api/web/favorites.go +++ /dev/null @@ -1,338 +0,0 @@ -package web - -import ( - "fmt" - "strconv" - "strings" - "videoweb/app/service/authser" - "videoweb/app/service/playletser" - "videoweb/app/service/videoser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type favoriteWork struct { - ID int64 - Title string - Genre string - DesktopMeta string - Episodes string - Badge string - Image string - Kind string - URL string - DramaID int64 - Episode int - VideoID int64 - TotalEpisode int -} - -// Favorites 我的收藏:短剧 / 剧集(视频) / 章节收藏。 -// 文档: -// http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_collect_list -// http://172.104.181.99:25319/swagger/index.html#/视频相关接口/get_api_app_video_collect_list -func Favorites(c *gin.Context) { - filter, page, validRoute := favoritesRoute(c) - if !validRoute { - NotFound(c) - return - } - - data := gin.H{"title": "我的收藏 - 黄果短剧", "navActive": "favorites", "accountTab": "favorites"} - responses := gin.H{} - loadAccountSummary(c, data, responses) - - status := 0 - switch filter { - case "short", "chapter", "series": - case "following": - status = 1 - case "finished": - status = 2 - default: - filter = "all" - } - data["filter"] = filter - - pageSize := 12 - query := playletser.CollectListQuery{PageNum: page, PageSize: pageSize, WatchStatus: status} - - if useragent.TokenFromRequest(c.Request) == "" { - responses["playlet/collect/list"] = gin.H{"error": "暂无登录会话"} - responses["video/collect/list"] = gin.H{"error": "暂无登录会话"} - data["favoritesResponse"] = responses["playlet/collect/list"] - data["playletCollectResponse"] = responses["playlet/collect/list"] - data["videoCollectResponse"] = responses["video/collect/list"] - data["favoritesDebug"] = responses - renderFavorites(c, data) - return - } - - if filter == "chapter" { - list, code, err := playletser.FetchChapterCollectList(c, query) - if retry, _ := authser.OnSessionExpired(c, code); retry { - list, _, err = playletser.FetchChapterCollectList(c, query) - } - if err != nil { - log.Error("fetch chapter favorites page failed", log.E(err)) - responses["playlet/chapter/collect/list"] = gin.H{"error": "加载失败", "msg": err.Error()} - data["favoritesUnavailable"] = true - } else if list != nil { - responses["playlet/chapter/collect/list"] = gin.H{"code": 200, "data": list} - data["paging"] = makeFavoritesPage(query.PageNum, query.PageSize, list.TotalCount, filter) - data["favorites"] = chapterFavoriteWorks(list.List) - } else { - responses["playlet/chapter/collect/list"] = gin.H{"code": code, "data": list} - } - data["favoritesResponse"] = responses["playlet/chapter/collect/list"] - data["playletCollectResponse"] = responses["playlet/chapter/collect/list"] - data["favoritesDebug"] = responses - renderFavorites(c, data) - return - } - - if filter == "series" { - loadVideoFavorites(c, data, responses, page, pageSize) - data["favoritesResponse"] = responses["video/collect/list"] - data["playletCollectResponse"] = gin.H{} - data["videoCollectResponse"] = responses["video/collect/list"] - data["favoritesDebug"] = responses - renderFavorites(c, data) - return - } - - var works []favoriteWork - var totalCount int64 - playletFailed := false - videoFailed := false - - if filter == "all" || filter == "short" || filter == "following" || filter == "finished" { - list, code, err := playletser.FetchCollectList(c, query) - if retry, _ := authser.OnSessionExpired(c, code); retry { - list, code, err = playletser.FetchCollectList(c, query) - } - if err == nil && list != nil { - responses["playlet/collect/list"] = gin.H{"code": 200, "data": list} - works = append(works, playletFavoriteWorks(list.List)...) - totalCount += list.TotalCount - } else if err != nil { - responses["playlet/collect/list"] = gin.H{"error": "加载失败", "msg": err.Error()} - log.Error("fetch favorites page failed", log.E(err)) - playletFailed = true - } else { - responses["playlet/collect/list"] = gin.H{"code": code, "data": list} - } - } - - if filter == "all" { - videoQuery := videoser.CollectListQuery{PageNum: page, PageSize: pageSize} - videoList, videoCode, videoErr := videoser.FetchCollectList(c, videoQuery) - if retry, _ := authser.OnSessionExpired(c, videoCode); retry { - videoList, videoCode, videoErr = videoser.FetchCollectList(c, videoQuery) - } - if videoErr == nil && videoList != nil { - responses["video/collect/list"] = gin.H{"code": 200, "data": videoList} - works = append(works, videoFavoriteWorks(videoList.List)...) - totalCount += videoList.TotalCount - } else if videoErr != nil { - responses["video/collect/list"] = gin.H{"error": "加载失败", "msg": videoErr.Error()} - log.Error("fetch video favorites page failed", log.E(videoErr)) - videoFailed = true - } else { - responses["video/collect/list"] = gin.H{"code": videoCode, "data": videoList} - } - } - - if playletFailed && (filter != "all" || videoFailed) { - data["favoritesUnavailable"] = true - } - if filter == "all" && playletFailed && videoFailed { - data["favoritesUnavailable"] = true - } - - data["paging"] = makeFavoritesPage(page, pageSize, totalCount, filter) - data["favorites"] = works - data["favoritesResponse"] = responses["playlet/collect/list"] - data["playletCollectResponse"] = responses["playlet/collect/list"] - data["videoCollectResponse"] = responses["video/collect/list"] - data["favoritesDebug"] = responses - renderFavorites(c, data) -} - -// renderFavorites 收藏为空时附带「大家都在看」后渲染。 -func renderFavorites(c *gin.Context, data gin.H) { - attachFavoritesRecommended(c, data) - HTMLOK(c, "favorites.html", data) -} - -// attachFavoritesRecommended 列表为空时拉取最热短剧(与搜索无结果一致)。 -// 上游:GET /api/app/playlet/all/list sortType=2 -func attachFavoritesRecommended(c *gin.Context, data gin.H) { - if works, ok := data["favorites"].([]favoriteWork); ok && len(works) > 0 { - return - } - watchingQ := playletser.AllListQuery{ - PageNum: 1, - PageSize: 6, - SortType: 2, // 最热 - UpdateStatus: 0, - } - watching := playletser.FetchAllListOrEmpty(c, watchingQ) - if watching == nil || len(watching.List) == 0 { - return - } - list := watching.List - if len(list) > 6 { - list = list[:6] - } - data["recommended"] = list -} - -func loadVideoFavorites(c *gin.Context, data gin.H, responses gin.H, page, pageSize int) { - videoQuery := videoser.CollectListQuery{PageNum: page, PageSize: pageSize} - list, code, err := videoser.FetchCollectList(c, videoQuery) - if retry, _ := authser.OnSessionExpired(c, code); retry { - list, code, err = videoser.FetchCollectList(c, videoQuery) - } - if err != nil { - log.Error("fetch video favorites page failed", log.E(err)) - responses["video/collect/list"] = gin.H{"error": "加载失败", "msg": err.Error()} - data["favoritesUnavailable"] = true - return - } - if list == nil { - responses["video/collect/list"] = gin.H{"code": code, "data": list} - return - } - responses["video/collect/list"] = gin.H{"code": 200, "data": list} - data["paging"] = makeFavoritesPage(page, pageSize, list.TotalCount, "series") - data["favorites"] = videoFavoriteWorks(list.List) -} - -func favoritesRoute(c *gin.Context) (filter string, page int, valid bool) { - if c.Request.URL.Query().Has("filter") { - return "", 0, false - } - filter = strings.TrimSpace(c.Param("filter")) - switch filter { - case "": - filter = "all" - case "short", "chapter", "series", "following", "finished": - default: - return "", 0, false - } - - page, valid = accountPageNum(c) - if !valid { - return "", 0, false - } - return filter, page, true -} - -func favoritesPagePath(filter string, page int) string { - path := "/favorites" - if filter != "" && filter != "all" { - path += "/" + filter - } - if page > 1 { - path += "/page/" + strconv.Itoa(page) - } - return path -} - -func makeFavoritesPage(page, size int, count int64, filter string) accountPage { - total := int((count + int64(size) - 1) / int64(size)) - pagination := accountPage{Current: page, Total: total, Count: count} - if page > 1 { - pagination.PrevURL = favoritesPagePath(filter, page-1) - } - if page < total { - pagination.NextURL = favoritesPagePath(filter, page+1) - } - return pagination -} - -func playletFavoriteWorks(items []playletser.PlayletCollectData) []favoriteWork { - works := make([]favoriteWork, 0, len(items)) - for _, item := range items { - title := strings.TrimSpace(item.PlayName) - if title == "" { - title = strings.TrimSpace(item.Title) - } - if title == "" { - continue - } - work := favoriteWork{ - ID: item.ID, Title: title, Genre: strings.Join(item.ThemeNames, " "), - Image: profileCover(item.Cover), Kind: "short", - URL: playletser.ChapterPlayPath(item.Slug, item.ID, item.LastChapterID), - DramaID: item.ID, Episode: item.LastEpisode, VideoID: item.LastChapterID, - TotalEpisode: item.TotalEpisode, - } - if work.Episode <= 0 { - work.Episode = 1 - } - if item.TotalEpisode > 0 { - work.Episodes = fmt.Sprintf("全 %d 集", item.TotalEpisode) - work.DesktopMeta = work.Episodes - } - if item.IsFeatured { - work.Badge = "爆剧" - } - works = append(works, work) - } - return works -} - -func chapterFavoriteWorks(items []playletser.PlayletChapterCollectData) []favoriteWork { - works := make([]favoriteWork, 0, len(items)) - for _, item := range items { - title := strings.TrimSpace(item.PlayletTitle) - if title == "" { - continue - } - work := favoriteWork{ - ID: item.ChapterID, Title: title, Image: profileCover(item.PlayletCover), Kind: "chapter", - URL: playletser.ChapterPlayPath(item.PlayletSlug, item.PlayletID, item.ChapterID), DesktopMeta: item.ChapterTitle, - DramaID: item.PlayletID, Episode: item.CurrentEpisode, VideoID: item.ChapterID, - TotalEpisode: item.TotalEpisode, - } - if work.Episode <= 0 { - work.Episode = 1 - } - if item.CurrentEpisode > 0 { - work.Genre = fmt.Sprintf("第 %d 集", item.CurrentEpisode) - } - if item.TotalEpisode > 0 { - work.Episodes = fmt.Sprintf("全 %d 集", item.TotalEpisode) - } - works = append(works, work) - } - return works -} - -func videoFavoriteWorks(items []videoser.VideoCollectData) []favoriteWork { - works := make([]favoriteWork, 0, len(items)) - for _, item := range items { - title := strings.TrimSpace(item.Title) - if title == "" { - continue - } - work := favoriteWork{ - ID: item.ID, Title: title, Genre: strings.Join(item.Keyworld, " "), - Image: profileCover(item.CoverImg), Kind: "series", - URL: videoser.VideoPlayURL(item.Slug, item.ID), - } - if item.VideoTime > 0 { - work.Episodes = formatVideoDuration(item.VideoTime) - work.DesktopMeta = work.Episodes - } else { - work.Episodes = "剧集" - work.DesktopMeta = "剧集" - } - works = append(works, work) - } - return works -} diff --git a/app/api/web/feedback.go b/app/api/web/feedback.go index d67942d..a37f4bc 100644 --- a/app/api/web/feedback.go +++ b/app/api/web/feedback.go @@ -4,7 +4,6 @@ import ( "net/http" "path/filepath" "strings" - "videoweb/app/service/authser" "videoweb/app/service/feedbackser" "github.com/gin-gonic/gin" @@ -113,7 +112,7 @@ func UploadImage(c *gin.Context) { if name == "" || name == "." || name == "/" { name = "image.jpg" } - resp, err := authser.UploadImage(c, name, f) + resp, err := feedbackser.UploadImage(c, name, f) if err != nil { c.JSON(http.StatusOK, gin.H{ "code": 500, diff --git a/app/api/web/history.go b/app/api/web/history.go deleted file mode 100644 index 626dc76..0000000 --- a/app/api/web/history.go +++ /dev/null @@ -1,375 +0,0 @@ -package web - -import ( - "fmt" - "net/http" - "sort" - "strings" - "time" - "videoweb/app/service/authser" - "videoweb/app/service/playletser" - "videoweb/app/service/videoser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type historyEntry struct { - ID int64 - Title string - Current int - Total int - EpisodeText string - Genre string - Time string - Image string - Progress string - Kind string - URL string - sortAt time.Time -} - -type historyGroup struct { - Title string - Entries []historyEntry -} - -// History 观看历史页:短剧 GET /api/app/playlet/history/list + 剧集 GET /api/app/video/history/list。 -// 文档: -// http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_history_list -// http://172.104.181.99:25319/swagger/index.html#/视频相关接口/get_api_app_video_history_list -func History(c *gin.Context) { - page, validPage := accountPageNum(c) - if !validPage { - NotFound(c) - return - } - - data := gin.H{"title": "观看历史 - 黄果短剧", "navActive": "history", "accountTab": "history"} - responses := gin.H{} - loadAccountSummary(c, data, responses) - - pageSize := 20 - var entries []historyEntry - var totalCount int64 - - if useragent.TokenFromRequest(c.Request) == "" { - responses["playlet/history/list"] = gin.H{"error": "暂无登录会话"} - responses["video/history/list"] = gin.H{"error": "暂无登录会话"} - } else { - playletQuery := playletser.HistoryListQuery{PageNum: page, PageSize: pageSize} - playletList, playletCode, playletErr := playletser.FetchHistoryList(c, playletQuery) - if retry, _ := authser.OnSessionExpired(c, playletCode); retry { - playletList, playletCode, playletErr = playletser.FetchHistoryList(c, playletQuery) - } - if playletErr == nil && playletList != nil { - responses["playlet/history/list"] = gin.H{"code": 200, "data": playletList} - entries = append(entries, playletHistoryEntries(playletList.List)...) - totalCount += playletList.TotalCount - } else if playletErr != nil { - responses["playlet/history/list"] = gin.H{"error": "加载失败", "msg": playletErr.Error()} - log.Error("fetch playlet history page failed", log.E(playletErr)) - } else { - responses["playlet/history/list"] = gin.H{"code": playletCode, "data": playletList} - } - - videoQuery := videoser.HistoryListQuery{PageNum: page, PageSize: pageSize} - videoList, videoCode, videoErr := videoser.FetchHistoryList(c, videoQuery) - if retry, _ := authser.OnSessionExpired(c, videoCode); retry { - videoList, videoCode, videoErr = videoser.FetchHistoryList(c, videoQuery) - } - if videoErr == nil && videoList != nil { - responses["video/history/list"] = gin.H{"code": 200, "data": videoList} - entries = append(entries, videoHistoryEntries(videoList.List)...) - totalCount += videoList.TotalCount - } else if videoErr != nil { - responses["video/history/list"] = gin.H{"error": "加载失败", "msg": videoErr.Error()} - log.Error("fetch video history page failed", log.E(videoErr)) - } else { - responses["video/history/list"] = gin.H{"code": videoCode, "data": videoList} - } - - if playletErr != nil && videoErr != nil { - data["historyUnavailable"] = true - } - } - - data["paging"] = makeAccountPage("/history", page, pageSize, totalCount) - data["groups"] = groupHistoryEntries(entries) - data["historyResponse"] = responses["playlet/history/list"] - data["playletHistoryResponse"] = responses["playlet/history/list"] - data["videoHistoryResponse"] = responses["video/history/list"] - data["historyDebug"] = responses - HTMLOK(c, "history.html", data) -} - -// HistoryClear BFF:代理 POST /api/app/playlet/history/clear。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_history_clear -func HistoryClear(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) - return - } - env, err := playletser.HistoryClear(c) - if err != nil { - log.Error("clear playlet history failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "清空失败,请稍后重试", "data": nil}) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "empty response", "tip": "清空失败,请稍后重试", "data": nil}) - return - } - if env.Code != 200 { - tip := strings.TrimSpace(env.Tip) - if tip == "" { - tip = strings.TrimSpace(env.Msg) - } - if tip == "" { - tip = "清空失败,请稍后重试" - } - c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data}) - return - } - c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "ok", "tip": "", "data": env.Data}) -} - -// VideoHistoryClear BFF:代理 POST /api/app/video/history/clear。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_history_clear -func VideoHistoryClear(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) - return - } - env, err := videoser.HistoryClear(c) - if err != nil { - log.Error("clear video history failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "清空失败,请稍后重试", "data": nil}) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "empty response", "tip": "清空失败,请稍后重试", "data": nil}) - return - } - if env.Code != 200 { - tip := strings.TrimSpace(env.Tip) - if tip == "" { - tip = strings.TrimSpace(env.Msg) - } - if tip == "" { - tip = "清空失败,请稍后重试" - } - c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data}) - return - } - c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "ok", "tip": "", "data": env.Data}) -} - -// HistoryDelete BFF:代理 POST /api/app/playlet/history/delete。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_history_delete -func HistoryDelete(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) - return - } - var body playletser.PlayletHistoryDeleteReq - if err := c.ShouldBindJSON(&body); err != nil || body.PlayletID <= 0 { - c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "playletId required", "tip": "参数错误", "data": nil}) - return - } - env, err := playletser.HistoryDelete(c, body) - if err != nil { - log.Error("delete playlet history failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "删除失败,请稍后重试", "data": nil}) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "empty response", "tip": "删除失败,请稍后重试", "data": nil}) - return - } - if env.Code != 200 { - tip := strings.TrimSpace(env.Tip) - if tip == "" { - tip = strings.TrimSpace(env.Msg) - } - if tip == "" { - tip = "删除失败,请稍后重试" - } - c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data}) - return - } - c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "ok", "tip": "", "data": env.Data}) -} - -// VideoHistoryDelete BFF:代理 POST /api/app/video/history/delete。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_history_delete -func VideoHistoryDelete(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) - return - } - var body videoser.VideoHistoryDeleteReq - if err := c.ShouldBindJSON(&body); err != nil || body.VideoID <= 0 { - c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "videoId required", "tip": "参数错误", "data": nil}) - return - } - env, err := videoser.HistoryDelete(c, body) - if err != nil { - log.Error("delete video history failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "删除失败,请稍后重试", "data": nil}) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "empty response", "tip": "删除失败,请稍后重试", "data": nil}) - return - } - if env.Code != 200 { - tip := strings.TrimSpace(env.Tip) - if tip == "" { - tip = strings.TrimSpace(env.Msg) - } - if tip == "" { - tip = "删除失败,请稍后重试" - } - c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data}) - return - } - c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "ok", "tip": "", "data": env.Data}) -} - -func playletHistoryEntries(items []playletser.PlayletHistoryData) []historyEntry { - out := make([]historyEntry, 0, len(items)) - for _, item := range items { - title := strings.TrimSpace(item.PlayName) - if title == "" { - title = strings.TrimSpace(item.Title) - } - if title == "" { - continue - } - progress := 0 - if item.TotalEpisode > 0 { - progress = item.WatchedCount * 100 / item.TotalEpisode - } - if progress < 0 { - progress = 0 - } - if progress > 100 { - progress = 100 - } - out = append(out, historyEntry{ - ID: item.ID, - Title: title, - Current: item.LastEpisode, - Total: item.TotalEpisode, - Genre: strings.Join(item.ThemeNames, " "), - Time: playletser.FormatHistoryListTime(item.HistoryAt), - Image: profileCover(item.Cover), - Progress: fmt.Sprintf("%d%%", progress), - Kind: "short", - URL: playletser.ChapterPlayPath(item.Slug, item.ID, item.LastChapterID), - sortAt: historySortAt(item.HistoryAt), - }) - } - return out -} - -func videoHistoryEntries(items []videoser.VideoHistoryData) []historyEntry { - out := make([]historyEntry, 0, len(items)) - for _, item := range items { - title := strings.TrimSpace(item.Title) - if title == "" { - continue - } - out = append(out, historyEntry{ - ID: item.ID, - Title: title, - EpisodeText: videoHistoryEpisodeText(item), - Genre: strings.Join(item.Keyworld, " "), - Time: playletser.FormatHistoryListTime(item.HistoryAt), - Image: profileCover(item.CoverImg), - Progress: "", - Kind: "series", - URL: videoser.VideoPlayURL(item.Slug, item.ID), - sortAt: historySortAt(item.HistoryAt), - }) - } - return out -} - -func videoHistoryEpisodeText(item videoser.VideoHistoryData) string { - if item.VideoTime > 0 { - return formatVideoDuration(item.VideoTime) - } - return "剧集" -} - -func formatVideoDuration(sec int) string { - if sec <= 0 { - return "" - } - h := sec / 3600 - m := (sec % 3600) / 60 - s := sec % 60 - if h > 0 { - return fmt.Sprintf("%d:%02d:%02d", h, m, s) - } - return fmt.Sprintf("%d:%02d", m, s) -} - -func historySortAt(raw string) time.Time { - t, ok := playletser.ParseHistoryTime(raw) - if !ok { - return time.Time{} - } - return t -} - -func groupHistoryEntries(entries []historyEntry) []historyGroup { - if len(entries) == 0 { - return nil - } - sort.SliceStable(entries, func(i, j int) bool { - if entries[i].sortAt.Equal(entries[j].sortAt) { - return entries[i].ID > entries[j].ID - } - return entries[i].sortAt.After(entries[j].sortAt) - }) - order := []string{"今天", "昨天", "更早"} - buckets := map[string][]historyEntry{ - "今天": {}, - "昨天": {}, - "更早": {}, - } - for _, entry := range entries { - bucket := historyBucketFromTime(entry.sortAt) - buckets[bucket] = append(buckets[bucket], entry) - } - groups := make([]historyGroup, 0, 3) - for _, title := range order { - if len(buckets[title]) == 0 { - continue - } - groups = append(groups, historyGroup{Title: title, Entries: buckets[title]}) - } - return groups -} - -func historyBucketFromTime(t time.Time) string { - if t.IsZero() { - return "更早" - } - 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) - switch { - case dayDiff <= 0: - return "今天" - case dayDiff == 1: - return "昨天" - default: - return "更早" - } -} diff --git a/app/api/web/index.go b/app/api/web/index.go index f9fb0a2..4cc7598 100644 --- a/app/api/web/index.go +++ b/app/api/web/index.go @@ -9,7 +9,6 @@ import ( // Index 首页 SSR:拉取 GET /api/app/playlet/home,按接口字段注入模板。 // 接口文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_home -// 继续观看:GET /api/app/playlet/history/list?pageNum=1&pageSize=6 // 热播榜:短剧 hot/rank/list;剧集 video/all/list sortType=最热 func Index(c *gin.Context) { raw := playletser.FetchHomeOrEmpty(c) @@ -17,12 +16,6 @@ func Index(c *gin.Context) { if raw != nil && raw.SeoInfo.Title != "" { title = raw.SeoInfo.Title } - history := playletser.FetchHistoryListOrEmpty(c, playletser.HistoryListQuery{PageNum: 1, PageSize: 6}) - continueWatch := history.List - if len(continueWatch) > 6 { - continueWatch = continueWatch[:6] - } - hotPlaylets := playletser.FetchHotRankListOrEmpty(c, playletser.HotRankQuery{ PageNum: 1, PageSize: 10, @@ -52,7 +45,6 @@ func Index(c *gin.Context) { "homeConfigData": playletser.ActiveHomeConfigs(raw.HomeConfigData), // GET /api/app/playlet/tag/options → list(已上架标签) "tagOptions": playletser.FetchTagOptionsOrEmpty(c), - "continueWatch": continueWatch, "hotRankPlaylets": hotPlaylets, "hotRankVideos": hotVideos, }) diff --git a/app/api/web/login.go b/app/api/web/login.go deleted file mode 100644 index 4736777..0000000 --- a/app/api/web/login.go +++ /dev/null @@ -1,11 +0,0 @@ -package web - -import "github.com/gin-gonic/gin" - -// LoginPage H5 登录页:渲染 template/pages/login.html。 -// PC 端由页面内脚本回首页并打开 loginPop;H5 直接展示本页。 -func LoginPage(c *gin.Context) { - HTMLOK(c, "login.html", gin.H{ - "title": "登录 - 黄果短剧", - }) -} diff --git a/app/api/web/play.go b/app/api/web/play.go index b5cdeef..9798e42 100644 --- a/app/api/web/play.go +++ b/app/api/web/play.go @@ -1,9 +1,7 @@ package web import ( - "strconv" "strings" - "videoweb/app/service/authser" "videoweb/app/service/commentser" "videoweb/app/service/playletser" "videoweb/app/service/videoser" @@ -92,13 +90,9 @@ func Play(c *gin.Context) { SortOrder: commentser.SortHot, }) - unlockCost := int64(0) - balance := int64(0) canWatch := false episode := 0 if chapter != nil { - unlockCost = chapter.UnlockCost() - balance = chapter.Balance canWatch = chapter.AllowWatch() episode = chapter.CurrentEpisode } @@ -117,9 +111,6 @@ func Play(c *gin.Context) { if episode <= 0 { episode = 1 } - if unlockCost <= 0 { - unlockCost = int64(playletser.FirstUnlockCost(playlet.PlayletChapterInfo)) - } previewURL := strings.TrimSpace(playlet.Preview) mainURL := "" @@ -139,9 +130,6 @@ func Play(c *gin.Context) { "mainURL": mainURL, "previewURL": previewURL, "hasPreview": previewURL != "", - "unlockCost": unlockCost, - "balance": balance, - "balanceText": authser.FormatRecharge(balance), "nextChapterID": playletser.NextChapterID(playlet, chapterID), "advitise": playlet.Advitise, "related": playlet.RelatedRecommendData, @@ -157,7 +145,6 @@ func Play(c *gin.Context) { "commentSize": comments.PageSize, "commentObjectID": commentObjectID, "commentObjectType": commentObjectType, - "trackerDrama": dramaTrackingData(playlet, chapter, chapterID, episode), "playResponse": gin.H{ "playlet": playlet, "chapter": chapter, @@ -174,76 +161,9 @@ func Play(c *gin.Context) { }, } data["canonicalPath"] = playletser.PlayPath(playlet.Slug, playlet.ID) - attachRechargeModal(c, data) HTMLOK(c, "play.html", data) } -func dramaTrackingData(playlet *playletser.PlayletInfoResp, chapter *playletser.PlayletChapterInfoResp, chapterID int64, episode int) gin.H { - name := strings.TrimSpace(playlet.PlayName) - if name == "" { - name = strings.TrimSpace(playlet.Title) - } - totalEpisode := playlet.TotalEpisode - if totalEpisode <= 0 { - totalEpisode = len(playlet.PlayletChapterInfo) - } - if totalEpisode <= 0 { - totalEpisode = 1 - } - data := gin.H{ - "MediaID": "", - "DramaID": playlet.ID, - "DramaTitle": name, - "Episode": max(1, episode), - "TotalEpisode": totalEpisode, - "VideoID": chapterID, - "DramaTypeID": "default", - "DramaTypeName": "默认分类", - "DramaTagKey": "default", - "DramaTagName": "默认标签", - "DramaDuration": 0, - "RecommendTraceID": "", - } - if chapter != nil { - if chapter.ID > 0 { - data["VideoID"] = chapter.ID - } - if chapter.MediaTime > 0 { - data["DramaDuration"] = chapter.MediaTime - } - } - for i, id := range playlet.ThemeIDs { - if id <= 0 || i >= len(playlet.ThemeNames) { - continue - } - name := strings.TrimSpace(playlet.ThemeNames[i]) - if name == "" { - continue - } - data["DramaTypeID"] = strconv.FormatInt(id, 10) - data["DramaTypeName"] = name - break - } - tagIDs := make([]string, 0, len(playlet.TagIDs)) - tagNames := make([]string, 0, len(playlet.TagIDs)) - for i, id := range playlet.TagIDs { - if id <= 0 || i >= len(playlet.TagNames) { - continue - } - name := strings.TrimSpace(playlet.TagNames[i]) - if name == "" { - continue - } - tagIDs = append(tagIDs, strconv.FormatInt(id, 10)) - tagNames = append(tagNames, name) - } - if len(tagIDs) > 0 { - data["DramaTagKey"] = strings.Join(tagIDs, ",") - data["DramaTagName"] = strings.Join(tagNames, ",") - } - return data -} - // SeriesPlay 视频播放页:GET /api/app/video/detail。 // 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/get_api_app_video_detail func SeriesPlay(c *gin.Context) { @@ -369,8 +289,6 @@ func SeriesPlay(c *gin.Context) { "list": comments.List, "apis": []string{ "GET /api/app/comment/list", - "POST /api/app/comment/send", - "POST /api/app/comment/like/set", }, }, "playDebug": gin.H{ @@ -383,6 +301,5 @@ func SeriesPlay(c *gin.Context) { if strings.TrimSpace(c.Param("guessPage")) != "" { data["canonicalPath"] = videoser.VideoPlayURL(video.Slug, video.ID) } - attachRechargeModal(c, data) HTMLOK(c, "seriesPlay.html", data) } diff --git a/app/api/web/playletLike.go b/app/api/web/playletLike.go deleted file mode 100644 index 593531e..0000000 --- a/app/api/web/playletLike.go +++ /dev/null @@ -1,64 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - - "github.com/gin-gonic/gin" -) - -// PlayletLikeSet BFF:代理 POST /api/app/playlet/like/set。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_like_set -func PlayletLikeSet(c *gin.Context) { - var body struct { - ID int64 `json:"id"` - IsLike *bool `json:"isLike"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ID <= 0 || body.IsLike == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "id/isLike required", - "tip": "缺少短剧 ID 或点赞状态", - "data": nil, - }) - return - } - - env, err := playletser.LikeSet(c, playletser.PlayletLikeSetReq{ - ID: body.ID, - IsLike: *body.IsLike, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} diff --git a/app/api/web/playlet_collect_set.go b/app/api/web/playlet_collect_set.go deleted file mode 100644 index eeb7523..0000000 --- a/app/api/web/playlet_collect_set.go +++ /dev/null @@ -1,64 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - - "github.com/gin-gonic/gin" -) - -// PlayletCollectSet BFF:代理 POST /api/app/playlet/collect/set。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_collect_set -func PlayletCollectSet(c *gin.Context) { - var body struct { - ID int64 `json:"id"` - IsCollect *bool `json:"isCollect"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ID <= 0 || body.IsCollect == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "id/isCollect required", - "tip": "缺少短剧 ID 或收藏状态", - "data": nil, - }) - return - } - - env, err := playletser.CollectSet(c, playletser.PlayletCollectSetReq{ - ID: body.ID, - IsCollect: *body.IsCollect, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} diff --git a/app/api/web/profile.go b/app/api/web/profile.go deleted file mode 100644 index 95b7b48..0000000 --- a/app/api/web/profile.go +++ /dev/null @@ -1,229 +0,0 @@ -package web - -import ( - "encoding/json" - "fmt" - "net/url" - "strings" - "videoweb/app/service/authser" - "videoweb/app/service/playletser" - "videoweb/app/service/usercenterser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type profileWork struct { - Title, Meta, Episodes, Badge, Image, Progress, Remaining, URL string -} - -// Profile 从当前会话拉取用户信息、观看历史和收藏,不展示演示账号数据。 -func Profile(c *gin.Context) { - data := gin.H{ - "title": "个人中心 - 黄果短剧", "navActive": "profile", "accountTab": "profile", - } - responses := gin.H{} - loadAccountSummary(c, data, responses) - if useragent.TokenFromRequest(c.Request) != "" { - fetchProfileLists(c, data, responses) - } - if pretty, err := json.MarshalIndent(responses, "", " "); err == nil { - data["responseJSON"] = string(pretty) - } - HTMLOK(c, "profile.html", data) -} - -func loadAccountSummary(c *gin.Context, data, responses gin.H) { - data["userName"] = "未登录" - data["balance"] = "—" - data["historyCount"] = "—" - data["favoriteCount"] = "—" - data["chasingCount"] = "—" - data["boughtChapterCount"] = "—" - user := authser.SyncUserOnEnter(c) - if user == nil { - if useragent.TokenFromRequest(c.Request) != "" { - responses["auth/getInfo"] = gin.H{"error": "加载失败"} - fetchProfileStats(c, data, responses) - return - } - responses["auth/getInfo"] = gin.H{"error": "暂无登录会话"} - return - } - applyProfileInfo(data, responses, user) - if useragent.TokenFromRequest(c.Request) != "" { - fetchProfileStats(c, data, responses) - } -} - -func fetchProfileStats(c *gin.Context, data, responses gin.H) { - stats, code, err := usercenterser.Fetch(c) - if retry, _ := authser.OnSessionExpired(c, code); retry { - stats, code, err = usercenterser.Fetch(c) - } - if err != nil || stats == nil { - responses["user/center/data"] = gin.H{"error": "加载失败"} - if err != nil { - log.Error("fetch user center data failed", log.E(err)) - } - return - } - responses["user/center/data"] = gin.H{"code": code, "data": stats} - data["historyCount"] = fmt.Sprint(stats.WatchCount) - data["favoriteCount"] = fmt.Sprint(stats.CollectCount) - data["chasingCount"] = fmt.Sprint(stats.ChasingCount) - data["boughtChapterCount"] = fmt.Sprint(stats.BoughtChapterCount) -} - -func fetchProfileLists(c *gin.Context, data, responses gin.H) { - query := playletser.HistoryListQuery{PageNum: 1, PageSize: 6} - history, code, err := playletser.FetchHistoryList(c, query) - if retry, _ := authser.OnSessionExpired(c, code); retry { - history, _, err = playletser.FetchHistoryList(c, query) - } - if err == nil && history != nil { - responses["playlet/history/list"] = gin.H{"code": 200, "data": history} - data["history"] = profileHistoryWorks(history.List) - } else if err != nil { - responses["playlet/history/list"] = gin.H{"error": "加载失败"} - log.Error("fetch profile history failed", log.E(err)) - data["historyUnavailable"] = true - } - - collectQuery := playletser.CollectListQuery{PageNum: 1, PageSize: 6, WatchStatus: 0} - favorites, code, err := playletser.FetchCollectList(c, collectQuery) - if retry, _ := authser.OnSessionExpired(c, code); retry { - favorites, _, err = playletser.FetchCollectList(c, collectQuery) - } - if err == nil && favorites != nil { - responses["playlet/collect/list"] = gin.H{"code": 200, "data": favorites} - data["favorites"] = profileFavoriteWorks(favorites.List) - } else if err != nil { - responses["playlet/collect/list"] = gin.H{"error": "加载失败"} - log.Error("fetch profile favorites failed", log.E(err)) - data["favoritesUnavailable"] = true - } -} - -func applyProfileInfo(data, responses gin.H, user *authser.LoginResp) { - if user == nil { - return - } - // 登录响应可能包含 token;调试区域只展示非凭证字段。 - rawUser, _ := json.Marshal(user) - var safeUser map[string]any - _ = json.Unmarshal(rawUser, &safeUser) - delete(safeUser, "token") - responses["auth/getInfo"] = gin.H{"code": 200, "data": safeUser} - name := strings.TrimSpace(user.NickName) - if name == "" { - name = strings.TrimSpace(user.AccountNo) - } - if name == "" { - name = "用户" - } - data["userName"] = name - data["setting"] = authser.BuildSettingVM(user) - data["userID"] = user.UserID - data["userId"] = fmt.Sprint(user.UserID) - data["balance"] = fmt.Sprint(user.Recharge) - data["recharge"] = user.Recharge - data["rechargeText"] = authser.FormatRecharge(user.Recharge) - if avatar := safeProfileImage(user.Avatar); avatar != "" { - data["avatar"] = avatar - } -} - -func safeProfileImage(raw string) string { - raw = strings.TrimSpace(raw) - if strings.HasPrefix(raw, "/static/") { - return raw - } - u, err := url.Parse(raw) - if err == nil && (u.Scheme == "https" || u.Scheme == "http") && u.Host != "" { - return raw - } - return "" -} - -func profileHistoryWorks(items []playletser.PlayletHistoryData) []profileWork { - works := make([]profileWork, 0, len(items)) - for _, item := range items { - if len(works) == 6 { - break - } - name := strings.TrimSpace(item.Title) - if name == "" { - name = strings.TrimSpace(item.PlayName) - } - if name == "" { - continue - } - progress := 0 - if item.TotalEpisode > 0 { - progress = item.WatchedCount * 100 / item.TotalEpisode - } - if progress < 0 { - progress = 0 - } - if progress > 100 { - progress = 100 - } - meta := "观看记录" - if item.LastEpisode > 0 { - meta = fmt.Sprintf("看至第 %d 集", item.LastEpisode) - } - remaining := "" - if item.UnwatchedCount > 0 { - remaining = fmt.Sprintf("剩 %d 集", item.UnwatchedCount) - } - works = append(works, profileWork{Title: name, Meta: meta, Image: profileCover(item.Cover), Progress: fmt.Sprintf("%d%%", progress), Remaining: remaining, URL: profilePlayURL(item.Slug, item.ID, item.LastEpisode)}) - } - return works -} - -func profileFavoriteWorks(items []playletser.PlayletCollectData) []profileWork { - works := make([]profileWork, 0, len(items)) - for _, item := range items { - if len(works) == 6 { - break - } - name := strings.TrimSpace(item.Title) - if name == "" { - name = strings.TrimSpace(item.PlayName) - } - if name == "" { - continue - } - work := profileWork{Title: name, Meta: strings.Join(item.ThemeNames, " "), Image: profileCover(item.Cover), URL: profilePlayURL(item.Slug, item.ID, 1)} - if item.TotalEpisode > 0 { - work.Episodes = fmt.Sprintf("全 %d 集", item.TotalEpisode) - } - if item.IsFeatured { - work.Badge = "爆剧" - } - works = append(works, work) - } - return works -} - -func profileCover(raw string) string { - if safe := safeProfileImage(raw); safe != "" { - return safe - } - return "/static/images/placeholder.png?v=2" -} - -func profilePlayURL(slug string, id int64, episode int) string { - if slug == "" { - if id <= 0 { - return "" - } - slug = fmt.Sprint(id) - } - if episode < 1 { - episode = 1 - } - return "/play/" + url.PathEscape(slug) + "/" + fmt.Sprint(episode) -} diff --git a/app/api/web/purchased.go b/app/api/web/purchased.go deleted file mode 100644 index f19cd5c..0000000 --- a/app/api/web/purchased.go +++ /dev/null @@ -1,87 +0,0 @@ -package web - -import ( - "fmt" - "strings" - "videoweb/app/service/authser" - "videoweb/app/service/playletser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type purchasedWork struct { - Title, Image, Info, Date, Progress, URL string -} - -// PurchasedSeries 已购剧集:GET /api/app/playlet/bought/list。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/get_api_app_playlet_bought_list -func PurchasedSeries(c *gin.Context) { - page, validPage := accountPageNum(c) - if !validPage { - NotFound(c) - return - } - - data := gin.H{"title": "已购剧集 - 黄果短剧", "navActive": "purchased", "accountTab": "purchased"} - responses := gin.H{} - loadAccountSummary(c, data, responses) - - size := 20 - if useragent.TokenFromRequest(c.Request) == "" { - responses["playlet/bought/list"] = gin.H{"error": "暂无登录会话"} - data["purchasedResponse"] = responses["playlet/bought/list"] - HTMLOK(c, "purchased.html", data) - return - } - - list, code, err := playletser.FetchBoughtList(c, page, size) - if retry, _ := authser.OnSessionExpired(c, code); retry { - list, _, err = playletser.FetchBoughtList(c, page, size) - } - if err == nil && list != nil { - responses["playlet/bought/list"] = gin.H{"code": 200, "data": list} - data["paging"] = makeAccountPage("/purchased", page, size, list.TotalCount) - works := make([]purchasedWork, 0, len(list.List)) - for _, item := range list.List { - title := strings.TrimSpace(item.PlayName) - if title == "" { - title = strings.TrimSpace(item.Title) - } - if title == "" { - continue - } - info := fmt.Sprintf("已购 %d 集", item.BoughtChapterCount) - if item.TotalEpisode > 0 { - info += fmt.Sprintf(" / 共 %d 集", item.TotalEpisode) - } - info += fmt.Sprintf(" · %d 金币", item.CostCoins) - progress := 0 - if item.TotalEpisode > 0 { - progress = item.WatchedCount * 100 / item.TotalEpisode - } - if progress < 0 { - progress = 0 - } - if progress > 100 { - progress = 100 - } - works = append(works, purchasedWork{ - Title: title, Image: profileCover(item.Cover), Info: info, - Date: playletser.FormatHistoryAt(item.BoughtAt), Progress: fmt.Sprintf("%d%%", progress), - URL: playletser.ChapterPlayPath(item.Slug, item.ID, item.LastChapterID), - }) - } - data["purchased"] = works - } else if err != nil { - responses["playlet/bought/list"] = gin.H{"error": "加载失败", "msg": err.Error()} - log.Error("fetch bought playlets failed", log.E(err)) - data["purchasedUnavailable"] = true - } else { - responses["playlet/bought/list"] = gin.H{"code": code, "data": list} - } - - data["purchasedResponse"] = responses["playlet/bought/list"] - HTMLOK(c, "purchased.html", data) -} diff --git a/app/api/web/recharge.go b/app/api/web/recharge.go deleted file mode 100644 index 1dc5777..0000000 --- a/app/api/web/recharge.go +++ /dev/null @@ -1,279 +0,0 @@ -package web - -import ( - "net/http" - "net/url" - "strconv" - "strings" - "videoweb/app/service/authser" - "videoweb/app/service/rechargeser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -const rechargePageSize = 5 - -// Recharge 金币充值页:套餐 GET /api/app/recharge/package/list,购买 POST /api/app/coin/buy,流水 GET /api/app/coin/trans/list。 -func Recharge(c *gin.Context) { - data := gin.H{ - "title": "金币充值 - 黄果短剧", "pageHeading": "金币充值", - "backHref": "/profile", "helpHref": "/help", - } - responses := gin.H{} - loadAccountSummary(c, data, responses) - - packages, err := rechargeser.FetchPackageList(c) - if err != nil { - log.Error("fetch recharge packages failed", log.E(err)) - data["packageUnavailable"] = true - packages = &rechargeser.RechargePackageListResp{} - responses["recharge/package/list"] = gin.H{"error": "加载失败", "msg": err.Error()} - } else { - responses["recharge/package/list"] = gin.H{"code": 200, "data": packages} - } - vm := rechargeser.BuildDepositPageVM(packages, "coin") - data["packages"] = vm.CoinList - data["paymentMethods"] = vm.PayMethods - data["rechargePackageResponse"] = packages - - balance := authser.RechargeFromCookies(c) - if _, ok := data["recharge"]; ok { - balance = toInt64(data["recharge"]) - } - data["balance"] = balance - data["balanceText"] = authser.FormatRecharge(balance) - - page, _, err := fetchRechargeTransactions(c, 1, rechargePageSize, 0) - if err != nil { - log.Error("fetch recharge transactions failed", log.E(err)) - data["transactionsUnavailable"] = true - page = &rechargeser.CoinTransPage{PageNum: 1, PageSize: rechargePageSize} - responses["coin/trans/list"] = gin.H{"error": "加载失败", "msg": err.Error()} - } else { - responses["coin/trans/list"] = gin.H{"code": 200, "data": page} - } - data["transactions"] = rechargeser.BuildTxListFromCoinTrans(page.List, 0) - data["transactionPage"] = page - data["transactionResponse"] = page - data["rechargeDebug"] = responses - HTMLOK(c, "recharge.html", data) -} - -// RechargeCoinBuy BFF:代理 POST /api/app/coin/buy。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/金币/post_api_app_coin_buy -func RechargeCoinBuy(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) - return - } - var body rechargeser.BuyPayReq - if err := c.ShouldBindJSON(&body); err != nil || body.PackageID <= 0 { - c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "packageId required", "tip": "请选择充值套餐", "data": nil}) - return - } - body.PayMode = rechargeser.NormalizePayMode(body.PayMode) - if body.PayMode == "" { - c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "payMode required", "tip": "请选择支付方式", "data": nil}) - return - } - if !validCoinPackagePayment(c, body.PackageID, body.PayMode) { - c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "invalid package/payMode", "tip": "套餐或支付方式不可用,请刷新后重试", "data": nil}) - return - } - - env, err := rechargeser.BuyCoin(c, body) - if err != nil { - log.Error("buy coin failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "创建支付订单失败,请稍后重试", "data": nil}) - return - } - if env != nil { - if retry, needLogin := authser.OnSessionExpired(c, env.Code); needLogin { - c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": "登录已过期,请重新登录", "data": nil}) - return - } else if retry { - env, err = rechargeser.BuyCoin(c, body) - if err != nil { - log.Error("buy coin retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "创建支付订单失败,请稍后重试", "data": nil}) - return - } - } - } - if env == nil || env.Code != 200 { - code, tip, msg := 500, "创建支付订单失败", "buy failed" - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmptyAuth(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{"code": code, "msg": msg, "tip": tip, "data": nil}) - return - } - safeURL := safePaymentURL(env.Data.PayURL) - if safeURL == "" { - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "invalid payUrl", "tip": "支付地址无效,请稍后重试", "data": nil}) - return - } - env.Data.PayURL = safeURL - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": env.Msg, - "tip": firstNonEmptyAuth(env.Tip, "订单创建成功"), - "data": env.Data, - }) -} - -// RechargeTransactions BFF:代理 GET /api/app/coin/trans/list。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/金币/get_api_app_coin_trans_list -// query:page / pageSize / type(0全部 1充值 2消费 3奖励) -func RechargeTransactions(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) - return - } - page := queryInt(c, "page", 1) - pageSize := queryInt(c, "pageSize", rechargePageSize) - typ := queryIntAllowZero(c, "type", 0) - if typ < 0 || typ > 3 { - typ = 0 - } - result, code, err := fetchRechargeTransactions(c, page, pageSize, typ) - if err != nil { - log.Error("fetch recharge transactions failed", log.E(err)) - tip := "交易明细加载失败" - if code > 0 { - c.JSON(http.StatusOK, gin.H{"code": code, "msg": err.Error(), "tip": tip, "data": nil}) - return - } - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": tip, "data": nil}) - return - } - if result == nil { - result = &rechargeser.CoinTransPage{PageNum: page, PageSize: pageSize} - } - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": "ok", - "tip": "", - "data": gin.H{ - "list": rechargeser.BuildTxListFromCoinTrans(result.List, 0), - "rawList": result.List, - "pageNum": result.PageNum, - "pageSize": result.PageSize, - "totalCount": result.TotalCount, - }, - }) -} - -// RechargeCoinTransExport BFF:代理 GET /api/app/coin/trans/export。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/金币/get_api_app_coin_trans_export -// query:type(0全部 1充值 2消费 3奖励),返回 CSV。 -func RechargeCoinTransExport(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) - return - } - typ := queryIntAllowZero(c, "type", 0) - if typ < 0 || typ > 3 { - typ = 0 - } - result, err := rechargeser.FetchCoinTransExport(c, typ) - if err != nil { - log.Error("export coin trans failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "导出失败,请稍后重试", "data": nil}) - return - } - ct := strings.TrimSpace(result.ContentType) - if ct == "" || strings.Contains(strings.ToLower(ct), "json") { - ct = "text/csv; charset=utf-8" - } - disposition := strings.TrimSpace(result.ContentDisposition) - if disposition == "" { - disposition = `attachment; filename="coin-trans.csv"` - } - c.Header("Content-Type", ct) - c.Header("Content-Disposition", disposition) - c.Header("Cache-Control", "no-store") - c.Data(http.StatusOK, ct, result.Body) -} - -func fetchRechargeTransactions(c *gin.Context, page, pageSize, typ int) (*rechargeser.CoinTransPage, int, error) { - result, code, err := rechargeser.FetchCoinTransPage(c, page, pageSize, typ) - if retry, _ := authser.OnSessionExpired(c, code); retry { - result, code, err = rechargeser.FetchCoinTransPage(c, page, pageSize, typ) - } - return result, code, err -} - -// attachRechargeModal 注入充值弹窗数据:GET /api/app/recharge/package/list → coinList。 -func attachRechargeModal(c *gin.Context, data gin.H) { - if data == nil { - return - } - packages := rechargeser.FetchPackageListOrEmpty(c) - vm := rechargeser.BuildDepositPageVM(packages, "coin") - data["rechargePackages"] = vm.CoinList - data["rechargePayMethods"] = vm.PayMethods - data["rechargePackageResponse"] = packages - if _, ok := data["unlockCost"]; !ok { - data["unlockCost"] = int64(0) - } - if _, ok := data["balance"]; !ok { - data["balance"] = authser.RechargeFromCookies(c) - } - if _, ok := data["balanceText"]; !ok { - data["balanceText"] = authser.FormatRecharge(toInt64(data["balance"])) - } -} - -func validCoinPackagePayment(c *gin.Context, packageID int64, payMode string) bool { - data, err := rechargeser.FetchPackageList(c) - if err != nil || data == nil { - return false - } - for _, item := range data.CoinList { - if item.ID != packageID { - continue - } - for _, method := range item.PayMethods { - if rechargeser.NormalizePayMode(method.Type) == payMode { - return true - } - } - return false - } - return false -} - -func safePaymentURL(raw string) string { - raw = strings.TrimSpace(raw) - u, err := url.Parse(raw) - if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" { - return "" - } - return raw -} - -func queryInt(c *gin.Context, name string, fallback int) int { - v, err := strconv.Atoi(strings.TrimSpace(c.Query(name))) - if err != nil || v <= 0 { - return fallback - } - return v -} - -// queryIntAllowZero 允许 0(如 type=0 全部)。 -func queryIntAllowZero(c *gin.Context, name string, fallback int) int { - raw := strings.TrimSpace(c.Query(name)) - if raw == "" { - return fallback - } - v, err := strconv.Atoi(raw) - if err != nil { - return fallback - } - return v -} diff --git a/app/api/web/register.go b/app/api/web/register.go deleted file mode 100644 index ead157b..0000000 --- a/app/api/web/register.go +++ /dev/null @@ -1,11 +0,0 @@ -package web - -import "github.com/gin-gonic/gin" - -// RegisterPage H5 注册页:渲染 template/pages/register.html。 -// PC 端由页面内脚本回首页并打开 registerPop;H5 直接展示本页。 -func RegisterPage(c *gin.Context) { - HTMLOK(c, "register.html", gin.H{ - "title": "注册账号 - 黄果短剧", - }) -} diff --git a/app/api/web/render.go b/app/api/web/render.go index f39059a..9ccf5af 100644 --- a/app/api/web/render.go +++ b/app/api/web/render.go @@ -4,7 +4,6 @@ import ( "net/http" "strconv" "strings" - "videoweb/app/service/authser" "videoweb/app/service/configser" "videoweb/app/service/playletser" "videoweb/app/service/seoser" @@ -14,68 +13,12 @@ import ( "github.com/gin-gonic/gin" ) -// HTML 渲染页面并注入登录态(以 cookie 为准)。 -// 无 token 时自动游客登录(POST /api/app/auth/guest),保证默认进入即有会话。 +// HTML 渲染页面并注入公共导航、配置与 SEO 数据。 func HTML(c *gin.Context, code int, name string, data gin.H) { if data == nil { data = gin.H{} } - // 所有服务端页面先完成渠道首次归因;已有会话也不会漏记落地页渠道。 - if channel := authser.ResolveChannel(c, ""); channel != "" { - data["channel"] = channel - } - authser.SyncUserOnEnter(c) - - loggedIn, userName, avatar, email := authser.AuthFromCookies(c) - registered := authser.IsRegisteredFromCookies(c) - data["isLoggedIn"] = loggedIn - data["isRegistered"] = registered - // 已登录(含游客)展示用户菜单;未登录才展示「登录」按钮。 - if _, ok := data["guestHeader"]; !ok { - data["guestHeader"] = !loggedIn - } - if _, ok := data["isVip"]; !ok { - data["isVip"] = loggedIn && authser.IsActiveVipFromCookies(c) - } - isVip, _ := data["isVip"].(bool) - data["isVip"] = isVip - - if loggedIn { - if _, provided := data["userName"]; !provided && userName != "" { - data["userName"] = userName - } - if _, provided := data["avatar"]; !provided && avatar != "" { - data["avatar"] = avatar - } - if email != "" { - data["email"] = email - } - if _, ok := data["userId"]; !ok { - if uid := authser.UserIDFromCookies(c); uid > 0 { - data["userId"] = authser.FormatUserID(uid) - } - } - if _, ok := data["recharge"]; !ok { - recharge := authser.RechargeFromCookies(c) - data["recharge"] = recharge - data["rechargeText"] = authser.FormatRecharge(recharge) - } else if _, ok := data["rechargeText"]; !ok { - data["rechargeText"] = authser.FormatRecharge(toInt64(data["recharge"])) - } - if _, ok := data["vipType"]; !ok { - data["vipType"] = authser.VipTypeFromCookies(c) - } - } else { - delete(data, "userName") - delete(data, "avatar") - delete(data, "email") - delete(data, "userId") - delete(data, "recharge") - delete(data, "rechargeText") - delete(data, "vipType") - } - // 导航分类:GET /api/app/translation/category/list → categoryList(包含分类 seoInfo) if _, ok := data["categoryList"]; !ok { data["categoryList"] = translationser.FetchHeaderCategoryListOrDefault(c) @@ -119,9 +62,6 @@ func HTML(c *gin.Context, code int, name string, data gin.H) { } } - data["trackerAppId"] = configser.ResolveTrackerAppID() - attachTrackerPage(c.Request.URL.Path, code, data) - attachPageSEO(c, code, data) c.HTML(code, name, data) } diff --git a/app/api/web/setting.go b/app/api/web/setting.go deleted file mode 100644 index 9572866..0000000 --- a/app/api/web/setting.go +++ /dev/null @@ -1,85 +0,0 @@ -package web - -import ( - "net/http" - "strings" - "videoweb/app/service/authser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -// Setting 展示账号设置,字段来自最新的用户信息接口。 -func Setting(c *gin.Context) { - data := gin.H{"title": "账号设置 - 黄果短剧", "navActive": "setting", "accountTab": "setting"} - loadAccountSummary(c, data, gin.H{}) - HTMLOK(c, "setting.html", data) -} - -// UpdateProfile 代理账号资料修改。可选字段必须显式传入,避免覆盖未修改内容。 -func UpdateProfile(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "tip": "请先登录"}) - return - } - var body authser.UpdateProfileReq - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"code": 400, "tip": "参数错误"}) - return - } - if body.NickName != nil { - v := strings.TrimSpace(*body.NickName) - if v == "" || len([]rune(v)) > 40 { - c.JSON(http.StatusBadRequest, gin.H{"code": 400, "tip": "昵称须为 1 至 40 个字符"}) - return - } - body.NickName = &v - } - if body.Introduction != nil { - v := strings.TrimSpace(*body.Introduction) - if len([]rune(v)) > 300 { - c.JSON(http.StatusBadRequest, gin.H{"code": 400, "tip": "简介不能超过 300 个字符"}) - return - } - body.Introduction = &v - } - if body.Avatar != nil { - v := strings.TrimSpace(*body.Avatar) - if safeProfileImage(v) == "" || strings.HasPrefix(v, "/static/") { - c.JSON(http.StatusBadRequest, gin.H{"code": 400, "tip": "头像地址无效"}) - return - } - body.Avatar = &v - } - if (body.OldPassword == nil) != (body.NewPassword == nil) { - c.JSON(http.StatusBadRequest, gin.H{"code": 400, "tip": "请输入旧密码和新密码"}) - return - } - if body.OldPassword != nil && (*body.OldPassword == "" || *body.NewPassword == "") { - c.JSON(http.StatusBadRequest, gin.H{"code": 400, "tip": "密码不能为空"}) - return - } - if body.NickName == nil && body.Introduction == nil && body.Avatar == nil && body.OldPassword == nil && body.PrivacyMode == nil { - c.JSON(http.StatusBadRequest, gin.H{"code": 400, "tip": "没有需要保存的修改"}) - return - } - env, err := authser.UpdateProfile(c, body) - if err != nil { - log.Error("update profile failed", log.E(err)) - c.JSON(http.StatusBadGateway, gin.H{"code": 500, "tip": "保存失败,请稍后重试"}) - return - } - if env == nil || env.Code != 200 { - if env == nil { - c.JSON(http.StatusBadGateway, gin.H{"code": 500, "tip": "保存失败,请稍后重试"}) - return - } - c.JSON(http.StatusOK, gin.H{"code": env.Code, "tip": firstNonEmptyAuth(env.Tip, env.Msg, "保存失败")}) - return - } - if infoEnv, user, err := authser.GetInfo(c); err == nil && infoEnv != nil && infoEnv.Code == 200 && user != nil { - authser.SetAuthCookies(c, user) - } - c.JSON(http.StatusOK, gin.H{"code": 200, "tip": firstNonEmptyAuth(env.Tip, "保存成功")}) -} diff --git a/app/api/web/tracker_page.go b/app/api/web/tracker_page.go deleted file mode 100644 index c3c422b..0000000 --- a/app/api/web/tracker_page.go +++ /dev/null @@ -1,80 +0,0 @@ -package web - -import ( - "net/http" - "strings" -) - -// attachTrackerPage 为所有 HTML 页面补齐稳定的 pageKey/pageName,避免事件落入 unknown。 -func attachTrackerPage(path string, code int, data map[string]any) { - if data == nil { - return - } - key, name := trackerPageFromRequest(path, code) - if current, _ := data["pageKey"].(string); strings.TrimSpace(current) == "" { - data["pageKey"] = key - } - if current, _ := data["pageName"].(string); strings.TrimSpace(current) == "" { - data["pageName"] = name - } -} - -func trackerPageFromRequest(path string, code int) (string, string) { - if code >= http.StatusInternalServerError { - return "server_error", "服务异常页面" - } - if code >= http.StatusBadRequest { - return "not_found", "404页面" - } - return trackerPageFromPath(path) -} - -func trackerPageFromPath(path string) (string, string) { - p := strings.TrimSuffix(strings.TrimSpace(path), "/") - if p == "" { - return "home", "首页" - } - rules := []struct { - prefix string - key string - name string - }{ - {"/series-play", "series_play", "AI成人剧集播放页"}, - {"/series", "series", "AI成人剧集"}, - {"/play", "play", "短剧播放页"}, - {"/detail", "detail", "短剧详情"}, - {"/list", "list", "短剧列表"}, - {"/recently-added", "recently_added", "最新短剧"}, - {"/popular-recommend", "popular_recommend", "热门推荐"}, - {"/rank", "rank", "排行榜"}, - {"/topic-detail", "topic_detail", "专题详情"}, - {"/topic", "topic", "专题列表"}, - {"/tags", "tags", "标签详情"}, - {"/more-series-list", "more_series_list", "成人剧集列表"}, - {"/more-list", "more_list", "短剧扩展列表"}, - {"/search-mobile", "search", "搜索结果页"}, - {"/search", "search", "搜索结果页"}, - {"/history", "history", "观看历史"}, - {"/favorites", "favorites", "我的收藏"}, - {"/purchased", "purchased", "已购剧集"}, - {"/profile", "profile", "个人中心"}, - {"/setting", "setting", "设置"}, - {"/recharge", "recharge", "充值中心"}, - {"/download-app", "download_app", "下载APP"}, - {"/feedback", "feedback", "问题反馈"}, - {"/help", "help", "帮助中心"}, - {"/about", "about", "关于我们"}, - {"/terms", "terms", "用户协议"}, - {"/privacy", "privacy", "隐私政策"}, - {"/2257", "compliance_2257", "2257合规声明"}, - {"/dmca", "dmca", "DMCA版权声明"}, - {"/login", "login", "登录"}, - {"/register", "register", "注册"}, - } - for _, rule := range rules { - if p == rule.prefix || strings.HasPrefix(p, rule.prefix+"/") { - return rule.key, rule.name - } - } - return "internal_other", "站内其他页面" -} diff --git a/app/api/web/videoCollect.go b/app/api/web/videoCollect.go deleted file mode 100644 index b911572..0000000 --- a/app/api/web/videoCollect.go +++ /dev/null @@ -1,64 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/videoser" - - "github.com/gin-gonic/gin" -) - -// VideoCollectSet BFF:代理 POST /api/app/video/collect/set。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_collect_set -func VideoCollectSet(c *gin.Context) { - var body struct { - ID int64 `json:"id"` - IsCollect *bool `json:"isCollect"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ID <= 0 || body.IsCollect == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "id/isCollect required", - "tip": "缺少视频 ID 或收藏状态", - "data": nil, - }) - return - } - - env, err := videoser.CollectSet(c, videoser.VideoCollectSetReq{ - ID: body.ID, - IsCollect: *body.IsCollect, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} diff --git a/app/api/web/videoLike.go b/app/api/web/videoLike.go deleted file mode 100644 index 50226c0..0000000 --- a/app/api/web/videoLike.go +++ /dev/null @@ -1,64 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/videoser" - - "github.com/gin-gonic/gin" -) - -// VideoLikeSet BFF:代理 POST /api/app/video/like/set。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_like_set -func VideoLikeSet(c *gin.Context) { - var body struct { - ID int64 `json:"id"` - IsLike *bool `json:"isLike"` - } - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 400, - "msg": "invalid body", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ID <= 0 || body.IsLike == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 400, - "msg": "id/isLike required", - "tip": "缺少视频 ID 或点赞状态", - "data": nil, - }) - return - } - - env, err := videoser.LikeSet(c, videoser.VideoLikeSetReq{ - ID: body.ID, - IsLike: *body.IsLike, - }) - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": err.Error(), - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty response", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - c.JSON(http.StatusOK, gin.H{ - "code": env.Code, - "msg": env.Msg, - "tip": env.Tip, - "data": env.Data, - }) -} diff --git a/app/appg/appg.go b/app/appg/appg.go index 7a7d43c..7a42961 100644 --- a/app/appg/appg.go +++ b/app/appg/appg.go @@ -1,9 +1,6 @@ package appg import ( - "strings" - "videoweb/pub/pubdef" - "github.com/patrickmn/go-cache" ) @@ -15,13 +12,12 @@ var ( ) type GlobalCfgEx struct { - ServerAddr string `yaml:"serverAddr"` - ListenAddr string `yaml:"listenAddr"` - DHListenAddr string `yaml:"dhListenAddr"` - RunMod string `yaml:"runMod"` - TsAuthKey string `yaml:"tsAuthKey"` - SEO SEOConf `yaml:"seo"` - Tracker TrackerConf `yaml:"tracker"` + ServerAddr string `yaml:"serverAddr"` + ListenAddr string `yaml:"listenAddr"` + DHListenAddr string `yaml:"dhListenAddr"` + RunMod string `yaml:"runMod"` + TsAuthKey string `yaml:"tsAuthKey"` + SEO SEOConf `yaml:"seo"` } // SEOConf 站长平台验证与 SEO 相关配置。 @@ -30,17 +26,3 @@ type SEOConf struct { BaiduHTMLFile string `yaml:"baiduHtmlFile"` // 如 baidu_verify_codeva-xxx.html IndexNowKey string `yaml:"indexNowKey"` // Bing IndexNow 密钥,提供 /{key}.txt } - -// TrackerConf 数据上报 SDK 本地兜底(上游 reportSdkConfig 优先)。 -type TrackerConf struct { - AppID string `yaml:"appId"` - EncryptedConfig string `yaml:"encryptedConfig"` -} - -// TrackerAppID 返回页面 SDK 与 BFF 透传校验共用的可信应用 ID。 -func TrackerAppID() string { - if id := strings.TrimSpace(Conf.Tracker.AppID); id != "" { - return id - } - return pubdef.TrackerAppId() -} diff --git a/app/conf/app.yaml b/app/conf/app.yaml index 47b019f..c9853f3 100644 --- a/app/conf/app.yaml +++ b/app/conf/app.yaml @@ -1,11 +1,5 @@ -serverAddr: "http://172.104.181.99:25319" +serverAddr: "https://webhg2api.mfcg.me" listenAddr: "25121" dhListenAddr: "26813" runMod: "dev" tsAuthKey: "fT5xSg4hltHpzVy6aV9rVECDJ1J1pN" - -# 数据上报 SDK(web-sdk)。encryptedConfig 优先用上游 config/info.reportSdkConfig; -# 若上游暂未下发,可在此填写密文兜底。 -tracker: - appId: "JHA-1045" - encryptedConfig: "" diff --git a/app/router/gin.go b/app/router/gin.go index 9c55ece..876f290 100644 --- a/app/router/gin.go +++ b/app/router/gin.go @@ -147,24 +147,6 @@ func registerRoutes(r *gin.Engine) { r.GET("/search", web.Search) r.GET("/search/:q", web.Search) r.GET("/search/:q/page/:page", web.Search) - r.GET("/profile", web.Profile) - r.GET("/history", web.History) - r.GET("/history/page/:page", web.History) - r.POST("/api/playlet/history/clear", web.HistoryClear) - r.POST("/api/playlet/history/delete", web.HistoryDelete) - r.POST("/api/video/history/clear", web.VideoHistoryClear) - r.POST("/api/video/history/delete", web.VideoHistoryDelete) - r.GET("/favorites", web.Favorites) - r.GET("/favorites/page/:page", web.Favorites) - r.GET("/favorites/:filter", web.Favorites) - r.GET("/favorites/:filter/page/:page", web.Favorites) - r.GET("/purchased", web.PurchasedSeries) - r.GET("/purchased/page/:page", web.PurchasedSeries) - r.GET("/setting", web.Setting) - r.GET("/settings", func(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "/setting") - }) - r.GET("/recharge", web.Recharge) r.GET("/about", web.About) r.GET("/help", web.Help) r.GET("/terms", web.Terms) @@ -219,38 +201,15 @@ func registerRoutes(r *gin.Engine) { r.GET("/play/:slug/:ep/recommendations/page/:guessPage", web.Play) r.GET("/series-play/:slug", web.SeriesPlay) r.GET("/series-play/:slug/recommendations/page/:guessPage", web.SeriesPlay) - r.GET("/login", web.LoginPage) - r.GET("/register", web.RegisterPage) r.GET("/feedback", web.Feedback) - // 认证 BFF:SSR 进页已自动游客;前端登录/注册/补游客会话走以下接口。 - r.POST("/api/auth/guest", web.Guest) - r.POST("/api/auth/login", web.Login) - r.POST("/api/auth/register", web.Register) - r.POST("/api/auth/logout", web.Logout) - r.POST("/api/auth/profile", web.UpdateProfile) - r.POST("/api/recharge/coin/buy", web.RechargeCoinBuy) - r.POST("/api/coin/buy", web.RechargeCoinBuy) - r.GET("/api/recharge/transactions", web.RechargeTransactions) - r.GET("/api/coin/trans/list", web.RechargeTransactions) - r.GET("/api/coin/trans/export", web.RechargeCoinTransExport) - r.GET("/api/recharge/transactions/export", web.RechargeCoinTransExport) + // 意见反馈(含截图上传) r.POST("/api/upload/image", web.UploadImage) r.POST("/api/feedback/add", web.FeedbackAdd) // 播放页 BFF r.GET("/api/playlet/guess-like/list", web.GuessLikeList) r.GET("/api/comment/list", web.CommentList) - r.POST("/api/comment/send", web.CommentSend) - r.POST("/api/comment/like/set", web.CommentLikeSet) - r.POST("/api/playlet/chapter/like/set", web.ChapterLikeSet) - r.POST("/api/playlet/chapter/collect/set", web.ChapterCollectSet) - r.POST("/api/playlet/chapter/watch/report", web.ChapterWatchReport) - r.POST("/api/playlet/chapter/unlock", web.ChapterUnlock) - r.POST("/api/playlet/like/set", web.PlayletLikeSet) - r.POST("/api/playlet/collect/set", web.PlayletCollectSet) - r.POST("/api/video/like/set", web.VideoLikeSet) - r.POST("/api/video/collect/set", web.VideoCollectSet) r.GET("/api/web/search/suggest", web.SearchSuggest) // 搜索引擎抓取、发现与站点所有权验证。 diff --git a/app/service/authser/auth.go b/app/service/authser/auth.go deleted file mode 100644 index 22b5d46..0000000 --- a/app/service/authser/auth.go +++ /dev/null @@ -1,290 +0,0 @@ -package authser - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - "time" - "videoweb/app/service/upstream" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -const ( - registerPath = "/api/app/auth/register" - loginPath = "/api/app/auth/login" - resetPath = "/api/app/auth/resetPassword" - guestPath = "/api/app/auth/guest" - getInfoPath = "/api/app/auth/getInfo" - profilePath = "/api/app/auth/profile" - uploadPath = "/api/app/upload/image" - cookieMaxAge = 7 * 24 * 3600 -) - -// Register 调用上游注册接口。 -func Register(c *gin.Context, req RegisterReq) (*APIEnvelope, *LoginResp, error) { - var env APIEnvelope - if _, err := upstream.PostJSON(c, registerPath, req, &env); err != nil { - return nil, nil, err - } - if env.Code != 200 { - return &env, nil, nil - } - user, err := decodeLoginData(env.Data) - if err != nil { - return &env, nil, err - } - return &env, user, nil -} - -// ResetPassword 通过账号和邮箱校验后重置密码。 -func ResetPassword(c *gin.Context, req ResetPasswordReq) (*APIEnvelope, error) { - var env APIEnvelope - if _, err := upstream.PostJSON(c, resetPath, req, &env); err != nil { - return nil, err - } - return &env, nil -} - -// Login 调用上游登录接口。 -func Login(c *gin.Context, req LoginReq) (*APIEnvelope, *LoginResp, error) { - var env APIEnvelope - if _, err := upstream.PostJSON(c, loginPath, req, &env); err != nil { - return nil, nil, err - } - if env.Code != 200 { - return &env, nil, nil - } - user, err := decodeLoginData(env.Data) - if err != nil { - return &env, nil, err - } - return &env, user, nil -} - -// GuestLogin 调用上游游客登录(按设备 ID 创建或找回游客账号)。 -func GuestLogin(c *gin.Context, req GuestLoginReq) (*APIEnvelope, *LoginResp, error) { - var env APIEnvelope - if _, err := upstream.PostJSON(c, guestPath, req, &env); err != nil { - return nil, nil, err - } - if env.Code != 200 { - return &env, nil, nil - } - user, err := decodeLoginData(env.Data) - if err != nil { - return &env, nil, err - } - return &env, user, nil -} - -// GetInfo 通过 token 拉取最新用户信息。 -func GetInfo(c *gin.Context) (*APIEnvelope, *LoginResp, error) { - var env APIEnvelope - if err := upstream.GetJSON(c, getInfoPath, &env); err != nil { - return nil, nil, err - } - if env.Code != 200 { - return &env, nil, nil - } - user, err := decodeLoginData(env.Data) - if err != nil { - return &env, nil, err - } - return &env, user, nil -} - -// UpdateProfile 修改资料(昵称 / 头像等)。 -func UpdateProfile(c *gin.Context, req UpdateProfileReq) (*APIEnvelope, error) { - var env APIEnvelope - if _, err := upstream.PostJSON(c, profilePath, req, &env); err != nil { - return nil, err - } - return &env, nil -} - -// UploadImage 上传图片;头像场景不加水印。 -func UploadImage(c *gin.Context, fileName string, r io.Reader) (*ImageUploadResp, error) { - var resp ImageUploadResp - _, err := upstream.PostMultipart(c, uploadPath, map[string]string{ - "watermark": "0", - }, &upstream.MultipartFile{ - FieldName: "file", - FileName: fileName, - Reader: r, - }, &resp) - if err != nil { - return nil, err - } - return &resp, nil -} - -func decodeLoginData(raw json.RawMessage) (*LoginResp, error) { - if len(raw) == 0 || string(raw) == "null" || string(raw) == `""` { - return nil, fmt.Errorf("empty login data") - } - var user LoginResp - if err := json.Unmarshal(raw, &user); err != nil { - return nil, err - } - return &user, nil -} - -// SetAuthCookies 只持久化 SSR 与前端登录态实际依赖的字段。 -// 个人简介、邮箱、邀请码、展示卡信息由 getInfo 即时提供,不再随每个页面响应重复写入; -// sid 已存在于请求时也无需原样回写。ClearAuthCookies 仍保留这些历史键用于兼容清理。 -func SetAuthCookies(c *gin.Context, user *LoginResp) { - if c == nil || user == nil { - return - } - secure := c.Request != nil && c.Request.TLS != nil - if strings.TrimSpace(user.Token) != "" { - setCookie(c, "token", user.Token, secure) - } - setCookie(c, "userId", strconv.FormatInt(user.UserID, 10), secure) - setCookie(c, "accountNo", user.AccountNo, secure) - setCookie(c, "nickName", user.NickName, secure) - setCookie(c, "avatar", user.Avatar, secure) - setCookie(c, "recharge", strconv.FormatInt(user.Recharge, 10), secure) - setCookie(c, "vipType", strconv.Itoa(user.VipType), secure) - setCookie(c, "currentVipExpire", user.CurrentVipExpire, secure) - setCookie(c, "userType", strconv.Itoa(user.Type), secure) - isReg := "0" - if user.IsRegister || user.Type == 1 { - isReg = "1" - } - setCookie(c, "isRegister", isReg, secure) - // 后续 SSR 服务必须能立即读取本次刚建立/刷新的会话;同时记录为本请求的 - // 权威结果,避免 HTML 渲染阶段再次调用 getInfo 并重复写整套 Cookie。 - patchAuthCookiesToRequest(c, user) - rememberRequestAuthUser(c, user) -} - -func setCookie(c *gin.Context, name, value string, secure bool) { - // Cookie Value 仅允许 ASCII;中文昵称等需 URL 编码。 - // 使用 PathEscape(空格→%20),与前端 encodeURIComponent 一致; - // 避免 QueryEscape 的空格→+,否则 JS decodeURIComponent 会留下字面量 "+"。 - writeCookie(c, &http.Cookie{ - Name: name, - Value: url.PathEscape(value), - Path: "/", - MaxAge: cookieMaxAge, - HttpOnly: false, - Secure: secure, - SameSite: http.SameSiteLaxMode, - Expires: time.Now().Add(7 * 24 * time.Hour), - }) -} - -// writeCookie 对同一响应中同名、同 Path、同 Domain 的 Cookie 做 upsert,避免会话恢复的 -// 「先清除、再重建」产生两套响应头,同时保留其他作用域下可能存在的同名 Cookie。 -func writeCookie(c *gin.Context, cookie *http.Cookie) { - if c == nil || c.Writer == nil || cookie == nil || cookie.Name == "" { - return - } - header := c.Writer.Header() - existing := header.Values("Set-Cookie") - if len(existing) > 0 { - header.Del("Set-Cookie") - for _, raw := range existing { - if sameCookieScope(raw, cookie) { - continue - } - header.Add("Set-Cookie", raw) - } - } - http.SetCookie(c.Writer, cookie) -} - -func sameCookieScope(raw string, target *http.Cookie) bool { - response := &http.Response{Header: http.Header{"Set-Cookie": []string{raw}}} - for _, existing := range response.Cookies() { - if existing.Name == target.Name && existing.Path == target.Path && strings.EqualFold(existing.Domain, target.Domain) { - return true - } - } - return false -} - -func cookieValue(c *gin.Context, name string) string { - ck, err := c.Request.Cookie(name) - if err != nil || ck == nil { - return "" - } - raw := strings.TrimSpace(ck.Value) - if raw == "" { - return "" - } - // QueryUnescape 同时兼容历史 QueryEscape(+)与 PathEscape(%20) - if decoded, err := url.QueryUnescape(raw); err == nil { - return strings.TrimSpace(decoded) - } - return raw -} - -// ClearAuthCookies 清除登录相关 cookie。 -func ClearAuthCookies(c *gin.Context) { - forgetRequestAuthUser(c) - for _, name := range authCookieNames() { - writeCookie(c, &http.Cookie{ - Name: name, - Value: "", - Path: "/", - MaxAge: -1, - HttpOnly: false, - SameSite: http.SameSiteLaxMode, - }) - } -} - -func authCookieNames() []string { - return []string{ - "token", "_token_", "sid", "_s_token_", - "userId", "accountNo", "nickName", "introduction", "avatar", - "email", "inviteCode", "recharge", "vipType", "currentVipExpire", "showCardId", "showCardName", - "userType", "isRegister", - } -} - -// AuthFromCookies 从 cookie 读取登录态,供 SSR 头部 / 个人中心展示。 -func AuthFromCookies(c *gin.Context) (loggedIn bool, userName, avatar, email string) { - token := useragent.TokenFromRequest(c.Request) - if token == "" { - return false, "", "", "" - } - userName = cookieValue(c, "nickName") - if userName == "" { - userName = cookieValue(c, "accountNo") - } - avatar = usableAvatarURL(cookieValue(c, "avatar")) - email = cookieValue(c, "email") - return true, userName, avatar, email -} - -// IsRegisteredFromCookies 是否正式注册账号(非游客)。 -func IsRegisteredFromCookies(c *gin.Context) bool { - if useragent.TokenFromRequest(c.Request) == "" { - return false - } - if cookieValue(c, "isRegister") == "1" { - return true - } - return cookieValue(c, "userType") == "1" -} - -func usableAvatarURL(raw string) string { - raw = strings.TrimSpace(raw) - if raw == "" { - return "" - } - if strings.HasPrefix(raw, "http://") || strings.HasPrefix(raw, "https://") || strings.HasPrefix(raw, "/static/") { - return raw - } - // 无域名的相对路径暂不可用(解密拉取需要完整 URL) - return "" -} diff --git a/app/service/authser/guest_enter.go b/app/service/authser/guest_enter.go deleted file mode 100644 index 589a8c6..0000000 --- a/app/service/authser/guest_enter.go +++ /dev/null @@ -1,314 +0,0 @@ -package authser - -import ( - "crypto/rand" - "encoding/hex" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - "time" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -const guestDevIDCookie = "guest_dev_id" -const guestSkipAutoCookie = "guest_skip_auto" - -// EnsureGuestOnEnter 页面进入时:无 token(未自己登录)则走游客登录并写 cookie。 -// 对应上游 POST /api/app/auth/guest。成功后会把会话补进当前 Request,便于同一次 SSR 读到登录态。 -// 手动退出后带 guest_skip_auto 时跳过本次;清掉标记后,下次进页再自动游客。 -func EnsureGuestOnEnter(c *gin.Context) *LoginResp { - if c == nil || c.Request == nil { - return nil - } - if user := freshRequestAuthUser(c); user != nil { - return user - } - if requestAutoAuthSuppressed(c) { - return nil - } - // 已有任意会话(游客或正式账号)则跳过 - if useragent.TokenFromRequest(c.Request) != "" { - return nil - } - // 手动退出:本次进页不自动游客,并清除标记,供下次进页恢复 - if IsGuestSkipAuto(c) { - ClearGuestSkipAuto(c) - dropRequestCookie(c, guestSkipAutoCookie) - suppressRequestAutoAuth(c) - return nil - } - - devID := ensureGuestDevID(c) - env, user, err := GuestLogin(c, GuestLoginReq{ - DevID: devID, - DeviceID: devID, - Channel: guestChannel(c), - }) - if err != nil { - log.Error("ensure guest on enter failed", log.E(err)) - return nil - } - if env == nil || env.Code != 200 || user == nil || strings.TrimSpace(user.Token) == "" { - tip := "" - if env != nil { - tip = firstNonEmptyTip(env.Tip, env.Msg) - } - log.Error("ensure guest on enter rejected", log.E(fmt.Errorf("code=%v tip=%s", envCode(env), tip))) - return nil - } - - SetAuthCookies(c, user) - return user -} - -// SyncUserOnEnter 是 SSR 页面统一的会话同步入口。同一请求已经通过游客登录、 -// getInfo 或其他认证流程取得过最新用户时直接复用,避免再次访问上游和重复写 Cookie。 -func SyncUserOnEnter(c *gin.Context) *LoginResp { - if c == nil || c.Request == nil { - return nil - } - if user := freshRequestAuthUser(c); user != nil { - return user - } - if requestAutoAuthSuppressed(c) { - return nil - } - if useragent.TokenFromRequest(c.Request) == "" { - return EnsureGuestOnEnter(c) - } - return RefreshUserOnEnter(c) -} - -// RefreshUserOnEnter 页面进入时用 GET /api/app/auth/getInfo 刷新用户信息并写回 cookie。 -// 保证刷新后头部昵称、头像、金币等与上游一致(如解锁后余额变化)。 -func RefreshUserOnEnter(c *gin.Context) *LoginResp { - if c == nil || c.Request == nil { - return nil - } - if user := freshRequestAuthUser(c); user != nil { - return user - } - if requestAutoAuthSuppressed(c) { - return nil - } - token := useragent.TokenFromRequest(c.Request) - if token == "" { - return nil - } - - env, user, err := GetInfo(c) - if err != nil { - log.Error("refresh user on enter failed", log.E(err)) - return nil - } - if env != nil && IsSessionExpiredCode(env.Code) { - if retry, _ := OnSessionExpired(c, env.Code); retry { - // 游客重建已经返回完整用户并写好 Cookie,无需立刻再 getInfo 一次。 - if fresh := freshRequestAuthUser(c); fresh != nil { - return fresh - } - env, user, err = GetInfo(c) - if err != nil { - log.Error("refresh user on enter retry failed", log.E(err)) - return nil - } - } else { - return nil - } - } - if env == nil || env.Code != 200 || user == nil { - return nil - } - if strings.TrimSpace(user.Token) == "" { - user.Token = useragent.TokenFromRequest(c.Request) - if user.Token == "" { - user.Token = token - } - } - SetAuthCookies(c, user) - return user -} - -// RefreshGuestSession 游客会话失效时(如上游 6001 用户不存在)重新游客登录。 -// 正式注册账号不处理,避免把用户踢成游客。 -func RefreshGuestSession(c *gin.Context) *LoginResp { - if c == nil || c.Request == nil { - return nil - } - if IsRegisteredFromCookies(c) { - return nil - } - forgetRequestAuthUser(c) - dropRequestCookie(c, "token") - dropRequestCookie(c, "_token_") - return EnsureGuestOnEnter(c) -} - -func dropRequestCookie(c *gin.Context, name string) { - if c == nil || c.Request == nil || name == "" { - return - } - var parts []string - for _, ck := range c.Request.Cookies() { - if ck == nil || ck.Name == name { - continue - } - parts = append(parts, ck.Name+"="+ck.Value) - } - c.Request.Header.Set("Cookie", strings.Join(parts, "; ")) -} - -// MarkGuestSkipAuto 手动退出后标记:跳过紧接着的一次自动游客登录。 -func MarkGuestSkipAuto(c *gin.Context) { - if c == nil { - return - } - suppressRequestAutoAuth(c) - secure := c.Request != nil && c.Request.TLS != nil - writeCookie(c, &http.Cookie{ - Name: guestSkipAutoCookie, - Value: "1", - Path: "/", - MaxAge: 0, // session cookie - HttpOnly: false, - Secure: secure, - SameSite: http.SameSiteLaxMode, - }) -} - -// IsGuestSkipAuto 是否处于「手动退出后跳过自动游客」状态。 -func IsGuestSkipAuto(c *gin.Context) bool { - return cookieValue(c, guestSkipAutoCookie) == "1" -} - -// ClearGuestSkipAuto 清除手动退出跳过标记。 -func ClearGuestSkipAuto(c *gin.Context) { - if c == nil { - return - } - writeCookie(c, &http.Cookie{ - Name: guestSkipAutoCookie, - Value: "", - Path: "/", - MaxAge: -1, - HttpOnly: false, - SameSite: http.SameSiteLaxMode, - }) -} - -func ensureGuestDevID(c *gin.Context) string { - if id := cookieValue(c, guestDevIDCookie); id != "" { - return id - } - id := newGuestDevID() - secure := c.Request != nil && c.Request.TLS != nil - setCookie(c, guestDevIDCookie, id, secure) - patchRequestCookie(c, guestDevIDCookie, id) - return id -} - -func newGuestDevID() string { - b := make([]byte, 8) - if _, err := rand.Read(b); err != nil { - now := time.Now() - return fmt.Sprintf("%016X%d", uint64(now.UnixNano()), now.UnixMilli()) - } - return strings.ToUpper(hex.EncodeToString(b)) + strconv.FormatInt(time.Now().UnixMilli(), 10) -} - -// ResolveChannel 统一解析渠道码并按首次归因落盘。 -// 优先级:已有 channel cookie > 调用方传入值 > dc/ch/pc 查询参数;已有渠道不覆盖。 -func ResolveChannel(c *gin.Context, candidate string) string { - channel := strings.TrimSpace(candidate) - if c == nil || c.Request == nil { - return channel - } - if stored := cookieValue(c, "channel"); stored != "" { - return stored - } - if channel == "" { - for _, key := range []string{"dc", "ch", "pc"} { - if value := strings.TrimSpace(c.Query(key)); value != "" { - channel = value - break - } - } - } - if channel == "" { - return "" - } - - secure := c.Request.TLS != nil - setCookie(c, "channel", channel, secure) - patchRequestCookie(c, "channel", channel) - return channel -} - -// guestChannel 为首屏服务端自动游客登录补渠道;cookie 用于复用前端首次归因结果。 -func guestChannel(c *gin.Context) string { - return ResolveChannel(c, "") -} - -func patchAuthCookiesToRequest(c *gin.Context, user *LoginResp) { - if c == nil || user == nil { - return - } - if strings.TrimSpace(user.Token) != "" { - patchRequestCookie(c, "token", user.Token) - } - patchRequestCookie(c, "userId", strconv.FormatInt(user.UserID, 10)) - patchRequestCookie(c, "accountNo", user.AccountNo) - patchRequestCookie(c, "nickName", user.NickName) - patchRequestCookie(c, "introduction", user.Introduction) - patchRequestCookie(c, "avatar", user.Avatar) - patchRequestCookie(c, "email", user.Email) - patchRequestCookie(c, "inviteCode", user.InviteCode) - patchRequestCookie(c, "recharge", strconv.FormatInt(user.Recharge, 10)) - patchRequestCookie(c, "vipType", strconv.Itoa(user.VipType)) - patchRequestCookie(c, "currentVipExpire", user.CurrentVipExpire) - patchRequestCookie(c, "showCardId", strconv.FormatInt(user.ShowCardID, 10)) - patchRequestCookie(c, "showCardName", user.ShowCardName) - patchRequestCookie(c, "userType", strconv.Itoa(user.Type)) - isReg := "0" - if user.IsRegister || user.Type == 1 { - isReg = "1" - } - patchRequestCookie(c, "isRegister", isReg) -} - -func patchRequestCookie(c *gin.Context, name, value string) { - if c == nil || c.Request == nil || name == "" { - return - } - escaped := url.PathEscape(value) - var parts []string - for _, ck := range c.Request.Cookies() { - if ck == nil || ck.Name == name { - continue - } - parts = append(parts, ck.Name+"="+ck.Value) - } - parts = append(parts, name+"="+escaped) - c.Request.Header.Set("Cookie", strings.Join(parts, "; ")) -} - -func envCode(env *APIEnvelope) int { - if env == nil { - return 0 - } - return env.Code -} - -func firstNonEmptyTip(values ...string) string { - for _, v := range values { - if strings.TrimSpace(v) != "" { - return v - } - } - return "" -} diff --git a/app/service/authser/guest_enter_test.go b/app/service/authser/guest_enter_test.go deleted file mode 100644 index 4dc7bca..0000000 --- a/app/service/authser/guest_enter_test.go +++ /dev/null @@ -1,345 +0,0 @@ -package authser - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "sync/atomic" - "testing" - "videoweb/app/appg" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -var authCookieNamesWritten = []string{ - "token", - "userId", - "accountNo", - "nickName", - "avatar", - "recharge", - "vipType", - "currentVipExpire", - "userType", - "isRegister", -} - -func TestSyncUserOnEnterCreatesGuestWithoutRefreshingIt(t *testing.T) { - var guestCalls atomic.Int32 - var getInfoCalls atomic.Int32 - - useAuthTestUpstream(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case guestPath: - guestCalls.Add(1) - if r.Method != http.MethodPost { - t.Errorf("guest method = %s, want POST", r.Method) - } - var request GuestLoginReq - if err := json.NewDecoder(r.Body).Decode(&request); err != nil { - t.Errorf("decode guest request: %v", err) - } - if request.DevID == "" || request.DeviceID != request.DevID { - t.Errorf("guest device ids = %#v, want matching non-empty ids", request) - } - writeAuthTestEnvelope(w, http.StatusOK, 200, testLoginUser("guest-token", "new-guest")) - case getInfoPath: - getInfoCalls.Add(1) - writeAuthTestEnvelope(w, http.StatusOK, 200, testLoginUser("guest-token", "unexpected-refresh")) - default: - t.Errorf("unexpected upstream request: %s %s", r.Method, r.URL.Path) - http.NotFound(w, r) - } - })) - - c, recorder := newAuthTestContext() - user := SyncUserOnEnter(c) - - if user == nil || user.Token != "guest-token" { - t.Fatalf("SyncUserOnEnter user = %#v, want guest-token", user) - } - if got := guestCalls.Load(); got != 1 { - t.Errorf("guest calls = %d, want 1", got) - } - if got := getInfoCalls.Load(); got != 0 { - t.Errorf("getInfo calls = %d, want 0", got) - } - assertRequestToken(t, c, "guest-token") - assertSingleAuthCookieSet(t, recorder) -} - -func TestSyncUserOnEnterRefreshesExistingTokenOnlyOnce(t *testing.T) { - var guestCalls atomic.Int32 - var getInfoCalls atomic.Int32 - authorization := make(chan string, 1) - - useAuthTestUpstream(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case guestPath: - guestCalls.Add(1) - writeAuthTestEnvelope(w, http.StatusOK, 200, testLoginUser("unexpected-guest-token", "unexpected-guest")) - case getInfoPath: - getInfoCalls.Add(1) - select { - case authorization <- r.Header.Get("Authorization"): - default: - } - // getInfo may omit token; the existing request token must be preserved. - writeAuthTestEnvelope(w, http.StatusOK, 200, testLoginUser("", "fresh-user")) - default: - t.Errorf("unexpected upstream request: %s %s", r.Method, r.URL.Path) - http.NotFound(w, r) - } - })) - - c, recorder := newAuthTestContext( - &http.Cookie{Name: "token", Value: "existing-token"}, - &http.Cookie{Name: "sid", Value: "existing-sid"}, - ) - first := SyncUserOnEnter(c) - second := SyncUserOnEnter(c) - - if first == nil || first.Token != "existing-token" || first.NickName != "fresh-user" { - t.Fatalf("first SyncUserOnEnter user = %#v, want refreshed user with existing token", first) - } - if second == nil || second.Token != "existing-token" || second.NickName != "fresh-user" { - t.Fatalf("second SyncUserOnEnter user = %#v, want memoized refreshed user", second) - } - if got := guestCalls.Load(); got != 0 { - t.Errorf("guest calls = %d, want 0", got) - } - if got := getInfoCalls.Load(); got != 1 { - t.Errorf("getInfo calls = %d, want 1", got) - } - select { - case got := <-authorization: - if got != "existing-token" { - t.Errorf("Authorization = %q, want existing-token", got) - } - default: - t.Error("getInfo did not capture Authorization header") - } - assertRequestToken(t, c, "existing-token") - if got := cookieValue(c, "nickName"); got != "fresh-user" { - t.Errorf("request nickName cookie = %q, want fresh-user", got) - } - assertSingleAuthCookieSet(t, recorder) -} - -func TestSyncUserOnEnterReusesGuestCreatedBeforeRender(t *testing.T) { - var guestCalls atomic.Int32 - var getInfoCalls atomic.Int32 - - useAuthTestUpstream(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case guestPath: - guestCalls.Add(1) - writeAuthTestEnvelope(w, http.StatusOK, 200, testLoginUser("pre-render-token", "pre-render-guest")) - case getInfoPath: - getInfoCalls.Add(1) - writeAuthTestEnvelope(w, http.StatusOK, 200, testLoginUser("pre-render-token", "unexpected-refresh")) - default: - t.Errorf("unexpected upstream request: %s %s", r.Method, r.URL.Path) - http.NotFound(w, r) - } - })) - - c, recorder := newAuthTestContext() - guest := EnsureGuestOnEnter(c) - user := SyncUserOnEnter(c) - - if guest == nil || guest.Token != "pre-render-token" { - t.Fatalf("EnsureGuestOnEnter user = %#v, want pre-render guest", guest) - } - if user == nil || user.Token != "pre-render-token" || user.NickName != "pre-render-guest" { - t.Fatalf("SyncUserOnEnter user = %#v, want memoized pre-render guest", user) - } - if got := guestCalls.Load(); got != 1 { - t.Errorf("guest calls = %d, want 1", got) - } - if got := getInfoCalls.Load(); got != 0 { - t.Errorf("getInfo calls = %d, want 0", got) - } - assertRequestToken(t, c, "pre-render-token") - assertSingleAuthCookieSet(t, recorder) -} - -func TestRefreshUserOnEnterReturnsRebuiltGuestWithoutRefreshingItAgain(t *testing.T) { - var guestCalls atomic.Int32 - var getInfoCalls atomic.Int32 - getInfoAuthorization := make(chan string, 1) - guestAuthorization := make(chan string, 1) - - useAuthTestUpstream(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case getInfoPath: - getInfoCalls.Add(1) - select { - case getInfoAuthorization <- r.Header.Get("Authorization"): - default: - } - writeAuthTestEnvelope(w, http.StatusOK, 6001, nil) - case guestPath: - guestCalls.Add(1) - select { - case guestAuthorization <- r.Header.Get("Authorization"): - default: - } - writeAuthTestEnvelope(w, http.StatusOK, 200, testLoginUser("rebuilt-guest-token", "rebuilt-guest")) - default: - t.Errorf("unexpected upstream request: %s %s", r.Method, r.URL.Path) - http.NotFound(w, r) - } - })) - - c, recorder := newAuthTestContext( - &http.Cookie{Name: "token", Value: "expired-guest-token"}, - &http.Cookie{Name: "isRegister", Value: "0"}, - &http.Cookie{Name: "userType", Value: "0"}, - ) - user := RefreshUserOnEnter(c) - // The rebuilt guest must also be reusable by the page-level sync call. - synced := SyncUserOnEnter(c) - - if user == nil || user.Token != "rebuilt-guest-token" || user.NickName != "rebuilt-guest" { - t.Fatalf("RefreshUserOnEnter user = %#v, want rebuilt guest", user) - } - if synced == nil || synced.Token != "rebuilt-guest-token" { - t.Fatalf("SyncUserOnEnter user = %#v, want memoized rebuilt guest", synced) - } - if got := getInfoCalls.Load(); got != 1 { - t.Errorf("getInfo calls = %d, want 1 (no refresh of rebuilt guest)", got) - } - if got := guestCalls.Load(); got != 1 { - t.Errorf("guest calls = %d, want 1", got) - } - select { - case got := <-getInfoAuthorization: - if got != "expired-guest-token" { - t.Errorf("getInfo Authorization = %q, want expired-guest-token", got) - } - default: - t.Error("getInfo did not capture Authorization header") - } - select { - case got := <-guestAuthorization: - if got != "" { - t.Errorf("guest Authorization = %q, want empty after expired token is removed", got) - } - default: - t.Error("guest login did not capture Authorization header") - } - assertRequestToken(t, c, "rebuilt-guest-token") - assertSingleAuthCookieSet(t, recorder) -} - -func TestSetAuthCookiesUpsertsSameNameWithinResponse(t *testing.T) { - c, recorder := newAuthTestContext() - SetAuthCookies(c, testLoginUser("first-token", "first-user")) - SetAuthCookies(c, testLoginUser("latest-token", "latest-user")) - - assertSingleAuthCookieSet(t, recorder) - assertRequestToken(t, c, "latest-token") - if got := cookieValue(c, "nickName"); got != "latest-user" { - t.Errorf("request nickName cookie = %q, want latest-user", got) - } - for _, cookie := range recorder.Result().Cookies() { - if cookie.Name == "token" && cookie.Value != "latest-token" { - t.Errorf("response token cookie = %q, want latest-token", cookie.Value) - } - } -} - -func useAuthTestUpstream(t *testing.T, handler http.Handler) { - t.Helper() - server := httptest.NewServer(handler) - previousServerAddr := appg.Conf.ServerAddr - appg.Conf.ServerAddr = server.URL - t.Cleanup(func() { - appg.Conf.ServerAddr = previousServerAddr - server.Close() - }) -} - -func newAuthTestContext(cookies ...*http.Cookie) (*gin.Context, *httptest.ResponseRecorder) { - recorder := httptest.NewRecorder() - c, _ := gin.CreateTestContext(recorder) - c.Request = httptest.NewRequest(http.MethodGet, "https://site.test/page", nil) - for _, cookie := range cookies { - c.Request.AddCookie(cookie) - } - return c, recorder -} - -func writeAuthTestEnvelope(w http.ResponseWriter, status, code int, data *LoginResp) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(struct { - Code int `json:"code"` - Msg string `json:"msg"` - Data *LoginResp `json:"data"` - }{ - Code: code, - Msg: http.StatusText(status), - Data: data, - }) -} - -func testLoginUser(token, nickName string) *LoginResp { - return &LoginResp{ - Token: token, - UserID: 101, - AccountNo: "account-101", - Email: "user101@example.test", - NickName: nickName, - // This field is intentionally large: it must stay out of Set-Cookie headers. - Introduction: strings.Repeat("简介", 150), - Avatar: "https://cdn.example.test/avatar.png", - InviteCode: "INVITE101", - VipType: 1, - CurrentVipExpire: "2099-01-01T00:00:00Z", - ShowCardID: 7, - ShowCardName: "monthly", - Type: 0, - Recharge: 88, - } -} - -func assertRequestToken(t *testing.T, c *gin.Context, want string) { - t.Helper() - if got := useragent.TokenFromRequest(c.Request); got != want { - t.Errorf("request token = %q, want %q", got, want) - } -} - -func assertSingleAuthCookieSet(t *testing.T, recorder *httptest.ResponseRecorder) { - t.Helper() - counts := make(map[string]int) - for _, cookie := range recorder.Result().Cookies() { - counts[cookie.Name]++ - } - for name, count := range counts { - if count > 1 { - t.Errorf("Set-Cookie %q written %d times, want at most once", name, count) - } - } - for _, name := range authCookieNamesWritten { - if got := counts[name]; got != 1 { - t.Errorf("auth Set-Cookie %q written %d times, want exactly once", name, got) - } - } - for _, name := range []string{"introduction", "email", "inviteCode", "showCardId", "showCardName", "sid"} { - if got := counts[name]; got != 0 { - t.Errorf("non-session Set-Cookie %q written %d times, want none", name, got) - } - } - var headerBytes int - for _, raw := range recorder.Header().Values("Set-Cookie") { - headerBytes += len("Set-Cookie: ") + len(raw) + len("\r\n") - } - if headerBytes >= 4096 { - t.Errorf("Set-Cookie response headers = %d bytes, want below 4096", headerBytes) - } -} diff --git a/app/service/authser/request_state.go b/app/service/authser/request_state.go deleted file mode 100644 index a693ee3..0000000 --- a/app/service/authser/request_state.go +++ /dev/null @@ -1,67 +0,0 @@ -package authser - -import "github.com/gin-gonic/gin" - -const requestAuthStateKey = "authser.request-auth-state" - -// requestAuthState 只在单个 Gin 请求内有效,用来避免多个 SSR 服务重复刷新同一会话。 -// freshUser 表示本请求已经从上游取得并写回过的最新用户;suppressAutoAuth -// 表示本请求明确不应再自动创建游客(例如用户刚退出或正式账号已过期)。 -type requestAuthState struct { - freshUser *LoginResp - suppressAutoAuth bool -} - -func authState(c *gin.Context) *requestAuthState { - if c == nil { - return nil - } - if value, ok := c.Get(requestAuthStateKey); ok { - if state, ok := value.(*requestAuthState); ok && state != nil { - return state - } - } - state := &requestAuthState{} - c.Set(requestAuthStateKey, state) - return state -} - -func freshRequestAuthUser(c *gin.Context) *LoginResp { - state := authState(c) - if state == nil { - return nil - } - return state.freshUser -} - -func rememberRequestAuthUser(c *gin.Context, user *LoginResp) { - state := authState(c) - if state == nil { - return - } - state.freshUser = user - state.suppressAutoAuth = false -} - -func forgetRequestAuthUser(c *gin.Context) { - if state := authState(c); state != nil { - state.freshUser = nil - } -} - -func suppressRequestAutoAuth(c *gin.Context) { - if state := authState(c); state != nil { - state.suppressAutoAuth = true - } -} - -func allowRequestAutoAuth(c *gin.Context) { - if state := authState(c); state != nil { - state.suppressAutoAuth = false - } -} - -func requestAutoAuthSuppressed(c *gin.Context) bool { - state := authState(c) - return state != nil && state.suppressAutoAuth -} diff --git a/app/service/authser/session.go b/app/service/authser/session.go deleted file mode 100644 index b373447..0000000 --- a/app/service/authser/session.go +++ /dev/null @@ -1,58 +0,0 @@ -package authser - -import "github.com/gin-gonic/gin" - -// TokenExpiredCode 上游约定:token 失效,需重新登录。 -const TokenExpiredCode = 1000 - -// IsSessionExpiredCode 是否为会话失效类业务码。 -func IsSessionExpiredCode(code int) bool { - switch code { - case TokenExpiredCode, 1002, 1012, 6001: // InvalidToken / NoToken / InvalidateToken / 用户不存在 - return true - default: - return false - } -} - -// OnSessionExpired 处理上游会话失效。 -// retry=true:游客已重新登录,调用方应重试原请求; -// needLogin=true:注册账号已清 cookie,调用方应跳转登录弹窗/页。 -func OnSessionExpired(c *gin.Context, code int) (retry, needLogin bool) { - if c == nil || !IsSessionExpiredCode(code) { - return false, false - } - if IsRegisteredFromCookies(c) { - ClearAuthCookies(c) - dropRequestCookie(c, "token") - dropRequestCookie(c, "_token_") - // 正式账号失效后,本请求保持退出态,避免后续 HTML 渲染又静默转成游客。 - suppressRequestAutoAuth(c) - return false, true - } - if RefreshGuestSession(c) != nil { - return true, false - } - ClearAuthCookies(c) - dropRequestCookie(c, "token") - dropRequestCookie(c, "_token_") - return false, false -} - -// RecoverContentSession 用于允许游客访问的内容读取接口。 -// 正式账号或游客 token 失效后,清理旧登录态、重新建立游客会话,调用方随后重试一次原请求。 -// 写操作仍应使用 OnSessionExpired,避免把用户操作静默降级为游客操作。 -func RecoverContentSession(c *gin.Context, code int) bool { - if c == nil || !IsSessionExpiredCode(code) { - return false - } - ClearAuthCookies(c) - for _, name := range authCookieNames() { - dropRequestCookie(c, name) - } - dropRequestCookie(c, guestSkipAutoCookie) - ClearGuestSkipAuto(c) - // 内容读取接口明确允许降级为游客,因此覆盖本请求可能已有的自动认证抑制。 - allowRequestAutoAuth(c) - return EnsureGuestOnEnter(c) != nil -} diff --git a/app/service/authser/types.go b/app/service/authser/types.go deleted file mode 100644 index 2a9c93c..0000000 --- a/app/service/authser/types.go +++ /dev/null @@ -1,95 +0,0 @@ -package authser - -import "encoding/json" - -// RegisterReq 对应 swagger authapi.registerReq -type RegisterReq struct { - AccountNo string `json:"accountNo"` - Email string `json:"email,omitempty"` - Password string `json:"password"` - AgentInviteCode string `json:"agentInviteCode,omitempty"` - DevID string `json:"devID,omitempty"` - Channel string `json:"channel,omitempty"` -} - -// LoginReq 对应 swagger authapi.loginReq -type LoginReq struct { - Account string `json:"account"` - Password string `json:"password"` - DevID string `json:"devID,omitempty"` - Channel string `json:"channel,omitempty"` -} - -// ResetPasswordReq 对应 swagger authapi.resetPasswordReq。 -type ResetPasswordReq struct { - AccountNo string `json:"accountNo"` - Email string `json:"email"` - NewPassword string `json:"newPassword"` -} - -// GuestLoginReq 对应 swagger authser.GuestLoginReq -// POST /api/app/auth/guest -type GuestLoginReq struct { - DevID string `json:"devID"` - DeviceID string `json:"deviceId,omitempty"` - Mac string `json:"mac,omitempty"` - Channel string `json:"channel,omitempty"` -} - -// UpdateProfileReq 对应 swagger authapi.updateProfileReq -type UpdateProfileReq struct { - Avatar *string `json:"avatar,omitempty"` - Introduction *string `json:"introduction,omitempty"` - NickName *string `json:"nickName,omitempty"` - OldPassword *string `json:"oldPassword,omitempty"` - NewPassword *string `json:"newPassword,omitempty"` - PrivacyMode *bool `json:"privacyMode,omitempty"` -} - -// LoginResp 对应 swagger authser.LoginResp -// GET /api/app/auth/getInfo、登录/注册/游客成功 data。 -type LoginResp struct { - Token string `json:"token"` - UserID int64 `json:"userId"` - AccountNo string `json:"accountNo"` - Email string `json:"email"` - NickName string `json:"nickName"` - Introduction string `json:"introduction"` // 简介 - Avatar string `json:"avatar"` // 头像 - InviteCode string `json:"inviteCode"` // 邀请码 - VipType int `json:"vipType"` // 0没卡 1月卡 2年卡 - CurrentVipExpire string `json:"currentVipExpire"` // VIP 过期时间,需结合 vipType 判断是否有效 - ShowCardID int64 `json:"showCardId"` // 最新会员卡 id - ShowCardName string `json:"showCardName"` // 最新会员卡名称,如「1个月会员」 - Status int `json:"status"` // 0正常 1封禁 2禁言 - Type int `json:"type"` // 身份类型:0游客 1普通用户 - IsNewUser bool `json:"isNewUser"` // 本次是否新建游客 - IsRegister bool `json:"isRegister"` // 是否已注册 - HasPassword *bool `json:"hasPassword,omitempty"` // 是否已设置密码;上游未返回时由账号状态推断 - LoginType int `json:"loginType"` // 登录方式 - PrivacyMode bool `json:"privacyMode"` // 隐私模式 - Score int64 `json:"score"` // 积分 - Recharge int64 `json:"recharge"` // 金币余额 -} - -// APIEnvelope 上游统一响应壳。Data 在失败时可能是 string/null。 -type APIEnvelope struct { - Code int `json:"code"` - Msg string `json:"msg"` - Tip string `json:"tip"` - Data json.RawMessage `json:"data"` -} - -// ImageUploadData 对应 swagger uploadser.ImageUploadData -type ImageUploadData struct { - ID string `json:"id"` - SourceNameFs string `json:"sourceNameFs"` -} - -// ImageUploadResp 对应 swagger uploadser.ImageUploadResp -type ImageUploadResp struct { - Code int `json:"code"` - Msg string `json:"msg"` - Tip string `json:"tip"` - Data *ImageUploadData `json:"data"` -} diff --git a/app/service/authser/view.go b/app/service/authser/view.go deleted file mode 100644 index ae5ac84..0000000 --- a/app/service/authser/view.go +++ /dev/null @@ -1,97 +0,0 @@ -package authser - -import ( - "fmt" - "strings" -) - -// SettingPageVM 账号设置页展示模型。 -type SettingPageVM struct { - UserID int64 - AccountNo string - HasAccount bool - HasPassword bool - NickName string - Introduction string - Email string - EmailMasked string - EmailBound bool - Avatar string // 可用头像 URL;空则模板用默认图 - InviteCode string - PrivacyMode bool - VipType int - VipExpire string - Recharge int64 - Score int64 - DisplayName string -} - -// BuildSettingVM 由 LoginResp 构建设置页 VM。 -func BuildSettingVM(user *LoginResp) SettingPageVM { - if user == nil { - return SettingPageVM{} - } - nick := strings.TrimSpace(user.NickName) - account := strings.TrimSpace(user.AccountNo) - registered := user.IsRegister || user.Type == 1 - hasAccount := registered && account != "" - hasPassword := hasAccount - if user.HasPassword != nil { - hasPassword = *user.HasPassword - } - email := strings.TrimSpace(user.Email) - intro := strings.TrimSpace(user.Introduction) - display := nick - if display == "" { - display = account - } - if display == "" { - display = "用户" - } - return SettingPageVM{ - UserID: user.UserID, - AccountNo: account, - HasAccount: hasAccount, - HasPassword: hasPassword, - NickName: nick, - Introduction: intro, - Email: email, - EmailMasked: MaskEmail(email), - EmailBound: email != "", - Avatar: usableAvatarURL(user.Avatar), - InviteCode: strings.TrimSpace(user.InviteCode), - PrivacyMode: user.PrivacyMode, - VipType: user.VipType, - VipExpire: strings.TrimSpace(user.CurrentVipExpire), - Recharge: user.Recharge, - Score: user.Score, - DisplayName: display, - } -} - -// MaskEmail 邮箱脱敏,如 ab***@mail.com。 -func MaskEmail(email string) string { - email = strings.TrimSpace(email) - if email == "" { - return "" - } - at := strings.LastIndex(email, "@") - if at <= 0 || at == len(email)-1 { - return email - } - local := email[:at] - domain := email[at:] - runes := []rune(local) - if len(runes) <= 2 { - return string(runes[:1]) + "***" + domain - } - return string(runes[:2]) + "***" + domain -} - -// FormatUserID 展示用 ID。 -func FormatUserID(id int64) string { - if id <= 0 { - return "" - } - return fmt.Sprintf("%d", id) -} diff --git a/app/service/authser/vip.go b/app/service/authser/vip.go deleted file mode 100644 index 70a634a..0000000 --- a/app/service/authser/vip.go +++ /dev/null @@ -1,187 +0,0 @@ -package authser - -import ( - "strconv" - "strings" - "time" - - "github.com/gin-gonic/gin" -) - -func parseVipExpireMs(raw string) int64 { - raw = strings.TrimSpace(raw) - if raw == "" { - return 0 - } - if n, err := strconv.ParseInt(raw, 10, 64); err == nil { - if n > 0 && n < 1e12 { - return n * 1000 - } - return n - } - layouts := []string{ - time.RFC3339Nano, - time.RFC3339, - "2006-01-02T15:04:05Z07:00", - "2006-01-02 15:04:05", - } - for _, layout := range layouts { - if t, err := time.Parse(layout, raw); err == nil { - return t.UnixMilli() - } - } - return 0 -} - -// UserIDFromCookies 读取 cookie userId。 -func UserIDFromCookies(c *gin.Context) int64 { - if c == nil { - return 0 - } - n, _ := strconv.ParseInt(strings.TrimSpace(cookieValue(c, "userId")), 10, 64) - if n < 0 { - return 0 - } - return n -} - -// VipTypeFromCookies 读取 cookie vipType。 -func VipTypeFromCookies(c *gin.Context) int { - if c == nil { - return 0 - } - n, _ := strconv.Atoi(strings.TrimSpace(cookieValue(c, "vipType"))) - return n -} - -// VipTypeLabel vipType 文案:0普通 1月卡 2年卡(与 91ly / 编辑资料注释一致)。 -func VipTypeLabel(vipType int) string { - switch vipType { - case 1: - return "月卡会员" - case 2: - return "年卡会员" - default: - return "普通会员" - } -} - -// ShowCardNameFromCookies 读取 cookie showCardName。 -func ShowCardNameFromCookies(c *gin.Context) string { - if c == nil { - return "" - } - return strings.TrimSpace(cookieValue(c, "showCardName")) -} - -// ShowCardIDFromCookies 读取 cookie showCardId。 -func ShowCardIDFromCookies(c *gin.Context) int64 { - if c == nil { - return 0 - } - n, _ := strconv.ParseInt(strings.TrimSpace(cookieValue(c, "showCardId")), 10, 64) - if n < 0 { - return 0 - } - return n -} - -// VipDisplayLabel 优先使用 getInfo.showCardName,否则回退 vipType 文案。 -func VipDisplayLabel(showCardName string, vipType int) string { - if s := strings.TrimSpace(showCardName); s != "" { - return s - } - return VipTypeLabel(vipType) -} - -// VipDisplayLabelFromCookies 从 cookie 组装会员展示名。 -func VipDisplayLabelFromCookies(c *gin.Context) string { - return VipDisplayLabel(ShowCardNameFromCookies(c), VipTypeFromCookies(c)) -} - -// IsActiveVipUser 根据 vipType + currentVipExpire 判断是否有效会员。 -func IsActiveVipUser(vipType int, currentVipExpire string) bool { - if vipType == 0 { - return false - } - expireMs := parseVipExpireMs(currentVipExpire) - if expireMs <= 0 { - return false - } - return expireMs > time.Now().UnixMilli() -} - -// FormatVipExpireText 格式化到期时间展示。 -func FormatVipExpireText(currentVipExpire string) string { - ms := parseVipExpireMs(currentVipExpire) - if ms <= 0 { - return "" - } - return time.UnixMilli(ms).In(time.Local).Format("有效期至 2006/01/02") -} - -// VipExpireTextFromCookies 格式化到期时间展示。 -func VipExpireTextFromCookies(c *gin.Context) string { - if c == nil { - return "" - } - return FormatVipExpireText(cookieValue(c, "currentVipExpire")) -} - -// IsActiveVipFromCookies vipType≠0 且 currentVipExpire 未过期(与 91ly 一致)。 -func IsActiveVipFromCookies(c *gin.Context) bool { - if c == nil { - return false - } - return IsActiveVipUser(VipTypeFromCookies(c), cookieValue(c, "currentVipExpire")) -} - -// RechargeFromCookies 读取 cookie recharge(金币余额)。 -func RechargeFromCookies(c *gin.Context) int64 { - if c == nil { - return 0 - } - n, _ := strconv.ParseInt(strings.TrimSpace(cookieValue(c, "recharge")), 10, 64) - if n < 0 { - return 0 - } - return n -} - -// SetRechargeCookie 更新金币余额 cookie(解锁/充值后同步)。 -func SetRechargeCookie(c *gin.Context, balance int64) { - if c == nil { - return - } - if balance < 0 { - balance = 0 - } - secure := c.Request != nil && c.Request.TLS != nil - setCookie(c, "recharge", strconv.FormatInt(balance, 10), secure) -} - -// FormatRecharge 金币余额千分位展示。 -func FormatRecharge(n int64) string { - if n < 0 { - n = 0 - } - s := strconv.FormatInt(n, 10) - if len(s) <= 3 { - return s - } - var b strings.Builder - rem := len(s) % 3 - if rem > 0 { - b.WriteString(s[:rem]) - if len(s) > rem { - b.WriteByte(',') - } - } - for i := rem; i < len(s); i += 3 { - b.WriteString(s[i : i+3]) - if i+3 < len(s) { - b.WriteByte(',') - } - } - return b.String() -} diff --git a/app/service/commentser/like.go b/app/service/commentser/like.go deleted file mode 100644 index 7f58280..0000000 --- a/app/service/commentser/like.go +++ /dev/null @@ -1,22 +0,0 @@ -package commentser - -import ( - "fmt" - "videoweb/app/service/upstream" - - "github.com/gin-gonic/gin" -) - -const likeSetPath = "/api/app/comment/like/set" - -// LikeSet 设置评论点赞/取消点赞。 -func LikeSet(c *gin.Context, req CommentLikeSetReq) (*APIEnvelope[CommentLikeSetResp], error) { - if req.CommentID <= 0 { - return nil, fmt.Errorf("comment like invalid commentId") - } - var env APIEnvelope[CommentLikeSetResp] - if _, err := upstream.PostJSON(c, likeSetPath, req, &env); err != nil { - return nil, err - } - return &env, nil -} diff --git a/app/service/commentser/list.go b/app/service/commentser/list.go index c45c741..05c41f4 100644 --- a/app/service/commentser/list.go +++ b/app/service/commentser/list.go @@ -6,7 +6,6 @@ import ( "strconv" "strings" "time" - "videoweb/app/service/authser" "videoweb/app/service/upstream" "videoweb/pub/common/log" @@ -57,18 +56,11 @@ func FetchList(c *gin.Context, q ListQuery) (*GetCommentListResp, error) { params.Set("pageSize", strconv.Itoa(q.PageSize)) params.Set("sortOrder", strconv.Itoa(q.SortOrder)) - authser.EnsureGuestOnEnter(c) requestPath := listPath + "?" + params.Encode() env, err := fetchListOnce(c, requestPath) if err != nil { return nil, err } - if env.Code != 200 && authser.IsSessionExpiredCode(env.Code) && authser.RecoverContentSession(c, env.Code) { - env, err = fetchListOnce(c, requestPath) - if err != nil { - return nil, err - } - } if env.Code != 200 { return nil, fmt.Errorf("comment list code=%d msg=%s", env.Code, env.Msg) } diff --git a/app/service/commentser/send.go b/app/service/commentser/send.go deleted file mode 100644 index c1f5ddc..0000000 --- a/app/service/commentser/send.go +++ /dev/null @@ -1,43 +0,0 @@ -package commentser - -import ( - "fmt" - "strings" - "unicode/utf8" - "videoweb/app/service/upstream" - - "github.com/gin-gonic/gin" -) - -const sendPath = "/api/app/comment/send" - -// Send 发布评论(一级 / 二级)。 -func Send(c *gin.Context, req CommentSendReq) (*APIEnvelope[any], error) { - req.Text = strings.TrimSpace(req.Text) - if req.ObjectID <= 0 { - return nil, fmt.Errorf("comment send invalid objectId") - } - req.ObjectType = NormalizeObjectType(req.ObjectType) - if req.Text == "" { - return nil, fmt.Errorf("comment send empty text") - } - if utf8.RuneCountInString(req.Text) > 200 { - return nil, fmt.Errorf("comment send text too long") - } - if req.ParentsID < 0 { - req.ParentsID = 0 - } - if req.ReplyCommentID < 0 { - req.ReplyCommentID = 0 - } - // 二级评论:未显式指定 replyCommentId 时默认回复父评论 - if req.ParentsID > 0 && req.ReplyCommentID <= 0 { - req.ReplyCommentID = req.ParentsID - } - - var env APIEnvelope[any] - if _, err := upstream.PostJSON(c, sendPath, req, &env); err != nil { - return nil, err - } - return &env, nil -} diff --git a/app/service/configser/info.go b/app/service/configser/info.go index de6ea43..af32775 100644 --- a/app/service/configser/info.go +++ b/app/service/configser/info.go @@ -47,9 +47,9 @@ func FetchAppConfigVMOrEmpty(c *gin.Context) AppConfigVM { if configCache != nil { return *configCache } - return ApplyTrackerFallback(AppConfigVM{}) + return AppConfigVM{} } - vm := ApplyTrackerFallback(BuildAppConfigVM(info)) + vm := BuildAppConfigVM(info) configCache = &vm configAt = time.Now() return vm diff --git a/app/service/configser/tracker.go b/app/service/configser/tracker.go deleted file mode 100644 index 87c66a3..0000000 --- a/app/service/configser/tracker.go +++ /dev/null @@ -1,25 +0,0 @@ -package configser - -import ( - "strings" - "videoweb/app/appg" -) - -// ResolveTrackerAppID 优先 yaml,否则固定 JHA-1045。 -func ResolveTrackerAppID() string { - return appg.TrackerAppID() -} - -// ResolveReportSDKConfig 上游 reportSdkConfig 优先,本地 yaml 兜底。 -func ResolveReportSDKConfig(fromAPI string) string { - if v := strings.TrimSpace(fromAPI); v != "" { - return v - } - return strings.TrimSpace(appg.Conf.Tracker.EncryptedConfig) -} - -// ApplyTrackerFallback 补齐 AppConfigVM 中的上报密文。 -func ApplyTrackerFallback(vm AppConfigVM) AppConfigVM { - vm.ReportSDKConfig = ResolveReportSDKConfig(vm.ReportSDKConfig) - return vm -} diff --git a/app/service/configser/types.go b/app/service/configser/types.go index 4c542f8..c39e644 100644 --- a/app/service/configser/types.go +++ b/app/service/configser/types.go @@ -52,16 +52,14 @@ type FooterContact struct { // ConfigInfoResp 对应 swagger proto.ConfigInfoResp // GET /api/app/config/info -// reportSdkConfig 与 proto.ConfigInfo 对齐;上游若尚未下发则为空,可由本地 yaml 兜底。 // config 为上游实际下发的完整站点配置(swagger 未完整声明,但含 potaoGroup 等字段)。 type ConfigInfoResp struct { - NewAddressInfo NewAddressInfo `json:"newAddressInfo"` - Config ConfigInfo `json:"config"` - Advitise AdvitiseData `json:"advitise"` - SeoInfo SeoInfo `json:"seoInfo"` - SeoNavInfo SeoNavInfo `json:"seoNavInfo"` - NavPageList []NavPageItem `json:"navPageList"` - ReportSDKConfig string `json:"reportSdkConfig"` + NewAddressInfo NewAddressInfo `json:"newAddressInfo"` + Config ConfigInfo `json:"config"` + Advitise AdvitiseData `json:"advitise"` + SeoInfo SeoInfo `json:"seoInfo"` + SeoNavInfo SeoNavInfo `json:"seoNavInfo"` + NavPageList []NavPageItem `json:"navPageList"` } // ConfigInfo 对应上游 data.config(proto.ConfigInfo 子集,按页面需要取用)。 diff --git a/app/service/configser/view.go b/app/service/configser/view.go index 1bc0380..83d4d29 100644 --- a/app/service/configser/view.go +++ b/app/service/configser/view.go @@ -29,7 +29,6 @@ type AppConfigVM struct { SeoInfo SeoInfo `json:"seoInfo,omitempty"` SeoNavInfo SeoNavInfo `json:"seoNavInfo,omitempty"` NavPageList []NavPageItem `json:"navPageList,omitempty"` - ReportSDKConfig string `json:"reportSdkConfig,omitempty"` Advitise AdvitiseData `json:"advitise"` DetailTopList []Advertisement `json:"detailTopList,omitempty"` NineGridList []Advertisement `json:"nineGridList,omitempty"` @@ -64,7 +63,6 @@ func BuildAppConfigVM(info *ConfigInfoResp) AppConfigVM { TwitterAddr: firstNonEmptyURL(cfg.TwitterAddr, addr.TwitterAddr), GitAddr: sanitizeURL(cfg.GitAddr), FreeVPN: sanitizeURL(cfg.FreeVPN), - ReportSDKConfig: strings.TrimSpace(info.ReportSDKConfig), NavPageList: buildNavPageList(info.NavPageList), Advitise: info.Advitise, DetailTopList: buildAdvertisementList(info.Advitise.DetailTopList), diff --git a/app/service/feedbackser/feedback.go b/app/service/feedbackser/feedback.go index 3448d15..6de8e9e 100644 --- a/app/service/feedbackser/feedback.go +++ b/app/service/feedbackser/feedback.go @@ -1,7 +1,7 @@ package feedbackser import ( - "videoweb/app/service/authser" + "io" "videoweb/app/service/upstream" "github.com/gin-gonic/gin" @@ -11,9 +11,11 @@ const addPath = "/api/app/feedback/add" const contactUsAddPath = "/api/app/contactus/add" +const uploadPath = "/api/app/upload/image" + // Add 调用上游提交意见反馈。 -func Add(c *gin.Context, req FeedbackReq) (*authser.APIEnvelope, error) { - var env authser.APIEnvelope +func Add(c *gin.Context, req FeedbackReq) (*APIEnvelope, error) { + var env APIEnvelope if _, err := upstream.PostJSON(c, addPath, req, &env); err != nil { return nil, err } @@ -21,10 +23,26 @@ func Add(c *gin.Context, req FeedbackReq) (*authser.APIEnvelope, error) { } // AddContactUs 调用上游联系我们接口。 -func AddContactUs(c *gin.Context, req ContactUsAddReq) (*authser.APIEnvelope, error) { - var env authser.APIEnvelope +func AddContactUs(c *gin.Context, req ContactUsAddReq) (*APIEnvelope, error) { + var env APIEnvelope if _, err := upstream.PostJSON(c, contactUsAddPath, req, &env); err != nil { return nil, err } return &env, nil } + +// UploadImage 上传反馈截图;不加水印。 +func UploadImage(c *gin.Context, fileName string, r io.Reader) (*ImageUploadResp, error) { + var resp ImageUploadResp + _, err := upstream.PostMultipart(c, uploadPath, map[string]string{ + "watermark": "0", + }, &upstream.MultipartFile{ + FieldName: "file", + FileName: fileName, + Reader: r, + }, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} diff --git a/app/service/feedbackser/types.go b/app/service/feedbackser/types.go index 8aab388..e62ff91 100644 --- a/app/service/feedbackser/types.go +++ b/app/service/feedbackser/types.go @@ -1,5 +1,29 @@ package feedbackser +import "encoding/json" + +// APIEnvelope 上游通用响应。 +type APIEnvelope struct { + Code int `json:"code"` + Msg string `json:"msg"` + Tip string `json:"tip"` + Data json.RawMessage `json:"data"` +} + +// ImageUploadData 对应 swagger uploadser.ImageUploadData +type ImageUploadData struct { + ID string `json:"id"` + SourceNameFs string `json:"sourceNameFs"` +} + +// ImageUploadResp 对应 swagger uploadser.ImageUploadResp +type ImageUploadResp struct { + Code int `json:"code"` + Msg string `json:"msg"` + Tip string `json:"tip"` + Data *ImageUploadData `json:"data"` +} + // FeedbackReq 对应 swagger proto.FeedbackReq。 // type(接口说明):1-功能建议、2-播放问题、3-账号问题、4-其他 type FeedbackReq struct { diff --git a/app/service/playletser/bought.go b/app/service/playletser/bought.go deleted file mode 100644 index bb7cee3..0000000 --- a/app/service/playletser/bought.go +++ /dev/null @@ -1,56 +0,0 @@ -package playletser - -import ( - "fmt" - "net/url" - "strconv" - "videoweb/app/service/upstream" - - "github.com/gin-gonic/gin" -) - -const boughtListPath = "/api/app/playlet/bought/list" - -// PlayletBoughtData 对应 proto.PlayletBoughtData;只列出页面所需字段。 -type PlayletBoughtData struct { - ID int64 `json:"id"` - Title string `json:"title"` - PlayName string `json:"playName"` - Slug string `json:"slug"` - Cover string `json:"cover"` - ThemeNames []string `json:"themeNames"` - TotalEpisode int `json:"totalEpisode"` - BoughtChapterCount int `json:"boughtChapterCount"` - CostCoins int64 `json:"costCoins"` - BoughtAt string `json:"boughtAt"` - LastChapterID int64 `json:"lastChapterId"` - LastEpisode int `json:"lastEpisode"` - WatchedCount int `json:"watchedCount"` -} - -type PlayletBoughtListResp struct { - List []PlayletBoughtData `json:"list"` - PageNum int `json:"pageNum"` - PageSize int `json:"pageSize"` - TotalCount int64 `json:"totalCount"` -} - -func FetchBoughtList(c *gin.Context, pageNum, pageSize int) (*PlayletBoughtListResp, int, error) { - if pageNum < 1 { - pageNum = 1 - } - if pageSize < 1 { - pageSize = 20 - } - params := url.Values{} - params.Set("pageNum", strconv.Itoa(pageNum)) - params.Set("pageSize", strconv.Itoa(pageSize)) - var env APIEnvelope[PlayletBoughtListResp] - if err := upstream.GetJSON(c, boughtListPath+"?"+params.Encode(), &env); err != nil { - return nil, 0, err - } - if env.Code != 200 { - return nil, env.Code, fmt.Errorf("playlet bought list code=%d msg=%s", env.Code, env.Msg) - } - return &env.Data, env.Code, nil -} diff --git a/app/service/playletser/chapter.go b/app/service/playletser/chapter.go index 9ee569d..0a6a8fa 100644 --- a/app/service/playletser/chapter.go +++ b/app/service/playletser/chapter.go @@ -5,7 +5,6 @@ import ( "net/url" "strconv" "strings" - "videoweb/app/service/authser" "videoweb/app/service/upstream" "videoweb/pub/common/log" @@ -49,18 +48,10 @@ func FetchChapterDetail(c *gin.Context, chapterID, playletID int64, playletSlug } else if playletID > 0 { params.Set("playletId", strconv.FormatInt(playletID, 10)) } - authser.EnsureGuestOnEnter(c) var env APIEnvelope[PlayletChapterInfoResp] if err := upstream.GetJSON(c, chapterDetailPath+"?"+params.Encode(), &env); err != nil { return nil, err } - if env.Code != 200 && authser.IsSessionExpiredCode(env.Code) { - if authser.RecoverContentSession(c, env.Code) { - if err := upstream.GetJSON(c, chapterDetailPath+"?"+params.Encode(), &env); err != nil { - return nil, err - } - } - } if env.Code != 200 { return nil, fmt.Errorf("chapter detail code=%d msg=%s tip=%s", env.Code, env.Msg, env.Tip) } @@ -91,18 +82,10 @@ func FetchChapterList(c *gin.Context, playletID int64, playletSlug string) (*Pla } else { return nil, fmt.Errorf("chapter list missing playletSlug or playletId") } - authser.EnsureGuestOnEnter(c) var env APIEnvelope[PlayletChapterListResp] if err := upstream.GetJSON(c, chapterListPath+"?"+params.Encode(), &env); err != nil { return nil, err } - if env.Code != 200 && authser.IsSessionExpiredCode(env.Code) { - if authser.RecoverContentSession(c, env.Code) { - if err := upstream.GetJSON(c, chapterListPath+"?"+params.Encode(), &env); err != nil { - return nil, err - } - } - } if env.Code != 200 { return nil, fmt.Errorf("playlet chapter list code=%d msg=%s tip=%s", env.Code, env.Msg, env.Tip) } diff --git a/app/service/playletser/chapter_collect.go b/app/service/playletser/chapter_collect.go deleted file mode 100644 index 28681ef..0000000 --- a/app/service/playletser/chapter_collect.go +++ /dev/null @@ -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} -} diff --git a/app/service/playletser/chapter_like.go b/app/service/playletser/chapter_like.go deleted file mode 100644 index 3d20ee5..0000000 --- a/app/service/playletser/chapter_like.go +++ /dev/null @@ -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 -} diff --git a/app/service/playletser/chapter_unlock.go b/app/service/playletser/chapter_unlock.go deleted file mode 100644 index 6f62c67..0000000 --- a/app/service/playletser/chapter_unlock.go +++ /dev/null @@ -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 -} diff --git a/app/service/playletser/chapter_watch.go b/app/service/playletser/chapter_watch.go deleted file mode 100644 index 2fd6c1f..0000000 --- a/app/service/playletser/chapter_watch.go +++ /dev/null @@ -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 -} diff --git a/app/service/playletser/collect.go b/app/service/playletser/collect.go deleted file mode 100644 index cdc17b0..0000000 --- a/app/service/playletser/collect.go +++ /dev/null @@ -1,161 +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"` - PlayName string `json:"playName"` - 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 - WatchStatus int // 0 全部、1 追剧中、2 已看完;仅短剧收藏列表使用 - 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 - } - if q.WatchStatus < 0 || q.WatchStatus > 2 { - q.WatchStatus = 0 - } - 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)) - params.Set("watchStatus", strconv.Itoa(q.WatchStatus)) - - 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} -} diff --git a/app/service/playletser/collect_view.go b/app/service/playletser/collect_view.go deleted file mode 100644 index e5c5dc1..0000000 --- a/app/service/playletser/collect_view.go +++ /dev/null @@ -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) -} diff --git a/app/service/playletser/detail.go b/app/service/playletser/detail.go index 5434b9b..ffa5b4e 100644 --- a/app/service/playletser/detail.go +++ b/app/service/playletser/detail.go @@ -5,7 +5,6 @@ import ( "net/url" "strconv" "strings" - "videoweb/app/service/authser" "videoweb/app/service/upstream" "videoweb/pub/common/log" @@ -45,19 +44,10 @@ func FetchDetailByKey(c *gin.Context, key string) (*PlayletInfoResp, error) { } func fetchDetail(c *gin.Context, queryKey, value string) (*PlayletInfoResp, error) { - authser.EnsureGuestOnEnter(c) env, err := fetchDetailOnce(c, queryKey, value) if err != nil { return nil, err } - if env.Code != 200 && authser.IsSessionExpiredCode(env.Code) { - if authser.RecoverContentSession(c, env.Code) { - env, err = fetchDetailOnce(c, queryKey, value) - if err != nil { - return nil, err - } - } - } if env.Code != 200 { return nil, fmt.Errorf("playlet detail code=%d msg=%s tip=%s", env.Code, env.Msg, env.Tip) } diff --git a/app/service/playletser/history.go b/app/service/playletser/history.go deleted file mode 100644 index 318ac08..0000000 --- a/app/service/playletser/history.go +++ /dev/null @@ -1,166 +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" - historyDeletePath = "/api/app/playlet/history/delete" - 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"` - PlayName string `json:"playName"` - 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"` -} - -// PlayletHistoryDeleteReq 对应 swagger proto.PlayletHistoryDeleteReq -type PlayletHistoryDeleteReq struct { - PlayletID int64 `json:"playletId"` -} - -// PlayletHistoryDeleteResp 对应 swagger proto.PlayletHistoryDeleteResp -type PlayletHistoryDeleteResp 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,无请求体。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/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 -} - -// HistoryDelete 删除单条短剧观看历史。 -// POST /api/app/playlet/history/delete -// 文档:http://172.104.181.99:25319/swagger/index.html#/app短剧相关接口/post_api_app_playlet_history_delete -func HistoryDelete(c *gin.Context, req PlayletHistoryDeleteReq) (*APIEnvelope[PlayletHistoryDeleteResp], error) { - if req.PlayletID <= 0 { - return nil, fmt.Errorf("playlet history delete invalid playletId") - } - var env APIEnvelope[PlayletHistoryDeleteResp] - if _, err := upstream.PostJSON(c, historyDeletePath, req, &env); err != nil { - return nil, err - } - return &env, nil -} diff --git a/app/service/playletser/history_view.go b/app/service/playletser/history_view.go deleted file mode 100644 index 3b98629..0000000 --- a/app/service/playletser/history_view.go +++ /dev/null @@ -1,229 +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) -} - -// historyContinueMeta 首页「继续观看」文案:看至第 N 集 / 剩 M 集。 -func historyContinueMeta(item PlayletHistoryData) string { - if item.LastEpisode <= 0 { - return "观看记录" - } - meta := fmt.Sprintf("看至第 %d 集", item.LastEpisode) - if item.UnwatchedCount > 0 { - meta += fmt.Sprintf(" 剩 %d 集", item.UnwatchedCount) - } - return meta -} - -// historyProgressPct 观看进度百分比,供卡片 --p 使用。 -func historyProgressPct(item PlayletHistoryData) string { - progress := 0 - if item.TotalEpisode > 0 { - progress = item.WatchedCount * 100 / item.TotalEpisode - } - if progress < 0 { - progress = 0 - } - if progress > 100 { - progress = 100 - } - return fmt.Sprintf("%d%%", progress) -} - -// FormatHistoryAt 最近观看时间展示。 -func FormatHistoryAt(raw string) string { - return formatHistoryAt(raw) -} - -// FormatHistoryListTime 观看历史列表时间:今天/昨天带时刻,更早仅日期。 -func FormatHistoryListTime(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 - default: - return fmt.Sprintf("%d 月 %d 日", int(t.Month()), t.Day()) - } -} - -// HistoryDayBucket 列表分组标题:今天 / 昨天 / 更早。 -func HistoryDayBucket(raw string) string { - raw = strings.TrimSpace(raw) - t, ok := parseHistoryTime(raw) - if !ok { - return "更早" - } - 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) - switch { - case dayDiff <= 0: - return "今天" - case dayDiff == 1: - return "昨天" - default: - return "更早" - } -} - -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 fmt.Sprintf("%d 月 %d 日", int(t.Month()), t.Day()) - } -} - -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 -} - -// ParseHistoryTime 解析观看历史时间字符串。 -func ParseHistoryTime(raw string) (time.Time, bool) { - return parseHistoryTime(strings.TrimSpace(raw)) -} diff --git a/app/service/playletser/like.go b/app/service/playletser/like.go deleted file mode 100644 index bf8e462..0000000 --- a/app/service/playletser/like.go +++ /dev/null @@ -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 -} diff --git a/app/service/playletser/search.go b/app/service/playletser/search.go index af6af65..4cd76e6 100644 --- a/app/service/playletser/search.go +++ b/app/service/playletser/search.go @@ -5,7 +5,6 @@ import ( "net/url" "strconv" "strings" - "videoweb/app/service/authser" "videoweb/app/service/upstream" "videoweb/pub/common/log" @@ -46,19 +45,10 @@ func FetchSearch(c *gin.Context, q SearchQuery) (*PlayletSearchResp, error) { if q.Keyword == "" { return nil, fmt.Errorf("playlet search keyword is empty") } - authser.EnsureGuestOnEnter(c) env, err := fetchSearchOnce(c, q) if err != nil { return nil, err } - if env.Code != 200 && authser.IsSessionExpiredCode(env.Code) { - if authser.RecoverContentSession(c, env.Code) { - env, err = fetchSearchOnce(c, q) - if err != nil { - return nil, err - } - } - } if env.Code != 200 { return nil, fmt.Errorf("playlet search code=%d msg=%s tip=%s", env.Code, env.Msg, env.Tip) } @@ -98,19 +88,10 @@ func FetchSearchSuggest(c *gin.Context, keyword string) (*PlayletSearchSuggestRe if keyword == "" { return nil, fmt.Errorf("playlet search suggest keyword is empty") } - authser.EnsureGuestOnEnter(c) env, err := fetchSearchSuggestOnce(c, keyword) if err != nil { return nil, err } - if env.Code != 200 && authser.IsSessionExpiredCode(env.Code) { - if authser.RecoverContentSession(c, env.Code) { - env, err = fetchSearchSuggestOnce(c, keyword) - if err != nil { - return nil, err - } - } - } if env.Code != 200 { return nil, fmt.Errorf("playlet search suggest code=%d msg=%s tip=%s", env.Code, env.Msg, env.Tip) } diff --git a/app/service/playletser/tplfuncs.go b/app/service/playletser/tplfuncs.go index 5483905..820d374 100644 --- a/app/service/playletser/tplfuncs.go +++ b/app/service/playletser/tplfuncs.go @@ -12,83 +12,70 @@ import ( // TemplateFuncs 模板辅助:仅做展示格式化 / URL,不把接口字段映射成另一套名字。 func TemplateFuncs() template.FuncMap { return template.FuncMap{ - "detailURL": detailURL, - "playURL": playURL, - "chapterPlayURL": chapterPlayURL, - "categoryURL": categoryURL, - "categoryDetailURL": categoryDetailURL, - "themeListURL": themeListURLByKey, - "tagOptionsURL": tagListURLByKey, - "allListURL": AllListURL, - "moreListURL": MoreListURL, - "moreSeriesListURL": MoreSeriesListURL, - "navMegaIcon": NavMegaIcon, - "topicListURL": TopicListURL, - "topicDetailURL": TopicDetailURL, - "topicCoverURLs": TopicCoverURLs, - "tagPageURL": TagListURL, - "hotRankPageURL": HotRankPageURL, - "searchResultsURL": SearchResultsURL, - "coverURL": coverURL, - "formatHeat": formatHeat, - "formatCollect": formatCollectHeat, - "formatLikeCount": formatLikeCount, - "formatMediaTime": formatMediaTime, - "playletDisplayName": playletDisplayName, - "rankNo": RankNo, - "episodeLabel": episodeText, - "displayTag": displayTag, - "firstTagName": firstTagName, - "themeTagText": themeTagText, - "tagListURL": tagListURL, - "cardTag": cardTag, - "cardTags": cardTags, - "namedTags": namedTags, - "namedThemeTags": namedThemeTags, - "firstName": firstName, - "rankClass": rankClass, - "add": addInt, - "mod": modInt, - "contains": strings.Contains, - "hasPrefix": strings.HasPrefix, - "footerLinkURL": footerLinkURL, - "footerLinkIsExternal": footerLinkIsExternal, - "externalLinkAttrs": externalLinkAttrs, - "footerContactURL": footerContactURL, - "footerContactName": footerContactName, - "footerContactValue": footerContactValue, - "navDisplayURL": navDisplayURL, - "urlquery": url.QueryEscape, - "pathescape": url.PathEscape, - "metaLine": metaLine, - "adSlice": AdSlice, - "cssCover": cssCover, - "cssCoverPlaceholder": cssCoverPlaceholder, - "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, - "historyContinueMeta": historyContinueMeta, - "historyProgressPct": historyProgressPct, - "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, + "tagOptionsURL": tagListURLByKey, + "allListURL": AllListURL, + "moreListURL": MoreListURL, + "moreSeriesListURL": MoreSeriesListURL, + "navMegaIcon": NavMegaIcon, + "topicListURL": TopicListURL, + "topicDetailURL": TopicDetailURL, + "topicCoverURLs": TopicCoverURLs, + "tagPageURL": TagListURL, + "hotRankPageURL": HotRankPageURL, + "searchResultsURL": SearchResultsURL, + "coverURL": coverURL, + "formatHeat": formatHeat, + "formatCollect": formatCollectHeat, + "formatLikeCount": formatLikeCount, + "formatMediaTime": formatMediaTime, + "playletDisplayName": playletDisplayName, + "rankNo": RankNo, + "episodeLabel": episodeText, + "displayTag": displayTag, + "firstTagName": firstTagName, + "themeTagText": themeTagText, + "tagListURL": tagListURL, + "cardTag": cardTag, + "cardTags": cardTags, + "namedTags": namedTags, + "namedThemeTags": namedThemeTags, + "firstName": firstName, + "rankClass": rankClass, + "add": addInt, + "mod": modInt, + "contains": strings.Contains, + "hasPrefix": strings.HasPrefix, + "footerLinkURL": footerLinkURL, + "footerLinkIsExternal": footerLinkIsExternal, + "externalLinkAttrs": externalLinkAttrs, + "footerContactURL": footerContactURL, + "footerContactName": footerContactName, + "footerContactValue": footerContactValue, + "navDisplayURL": navDisplayURL, + "urlquery": url.QueryEscape, + "pathescape": url.PathEscape, + "metaLine": metaLine, + "adSlice": AdSlice, + "cssCover": cssCover, + "cssCoverPlaceholder": cssCoverPlaceholder, + "updateStatusText": updateStatusText, + "browseText": browseText, + "usableAnchor": usableAnchor, + "playletYearText": playletYearText, + "playletDateText": playletDateText, + "isPlayletAllFree": isPlayletAllFree, + "formatShortDateTime": formatShortDateTime, + "absInt64": absInt64Tpl, + "absURL": absURLTpl, + "canonicalOr": canonicalOr, + "joinStrings": joinStrings, + "dict": dict, } } diff --git a/app/service/rechargeser/list.go b/app/service/rechargeser/list.go deleted file mode 100644 index c81695e..0000000 --- a/app/service/rechargeser/list.go +++ /dev/null @@ -1,281 +0,0 @@ -package rechargeser - -import ( - "encoding/json" - "fmt" - "net/url" - "strconv" - "strings" - "videoweb/app/service/authser" - "videoweb/app/service/upstream" - "videoweb/pub/common/log" - - "github.com/gin-gonic/gin" -) - -const ( - packageListPath = "/api/app/recharge/package/list" - coinBuyPath = "/api/app/coin/buy" - vipPayPath = "/api/app/vip/pay" - vipOrdersPath = "/api/app/vip/orders" - coinOrdersPath = "/api/app/coin/orders" - coinTransPath = "/api/app/coin/trans/list" - coinTransExportPath = "/api/app/coin/trans/export" -) - -// FetchPackageList 拉取购买套餐列表(会员 + 金币)。 -func FetchPackageList(c *gin.Context) (*RechargePackageListResp, error) { - var env APIEnvelope[RechargePackageListResp] - if err := upstream.GetJSON(c, packageListPath, &env); err != nil { - return nil, err - } - if env.Code != 200 { - return nil, fmt.Errorf("recharge package list code=%d msg=%s", env.Code, env.Msg) - } - return &env.Data, nil -} - -// FetchPackageListOrEmpty 失败时返回空列表,保证 SSR 可渲染。 -func FetchPackageListOrEmpty(c *gin.Context) *RechargePackageListResp { - data, err := FetchPackageList(c) - if err != nil { - log.Error("fetch recharge package list failed", log.E(err)) - return &RechargePackageListResp{} - } - if data == nil { - return &RechargePackageListResp{} - } - return data -} - -// BuyCoin 购买金币套餐:POST /api/app/coin/buy -func BuyCoin(c *gin.Context, req BuyPayReq) (*APIEnvelope[PayOrderResp], error) { - var env APIEnvelope[PayOrderResp] - if _, err := upstream.PostJSON(c, coinBuyPath, req, &env); err != nil { - return nil, err - } - return &env, nil -} - -// VipPay 购买会员套餐:POST /api/app/vip/pay -func VipPay(c *gin.Context, req BuyPayReq) (*APIEnvelope[PayOrderResp], error) { - var env APIEnvelope[PayOrderResp] - if _, err := upstream.PostJSON(c, vipPayPath, req, &env); err != nil { - return nil, err - } - return &env, nil -} - -// NormalizePayMode 规范化支付方式。 -func NormalizePayMode(raw string) string { - v := strings.ToLower(strings.TrimSpace(raw)) - switch v { - case "wx", "weixin": - return "wechat" - case "unionpay": - return "union" - default: - return v - } -} - -// FetchVipOrders 查询 VIP 充值记录。 -func FetchVipOrders(c *gin.Context, page, pageSize int) ([]VipOrder, error) { - return fetchOrders(c, vipOrdersPath, page, pageSize) -} - -// FetchCoinOrders 查询金币购买记录。 -func FetchCoinOrders(c *gin.Context, page, pageSize int) ([]VipOrder, error) { - return fetchOrders(c, coinOrdersPath, page, pageSize) -} - -// FetchRecentOrdersOrEmpty 合并 VIP + 金币订单,失败返回空。 -func FetchRecentOrdersOrEmpty(c *gin.Context, limit int) []VipOrder { - if limit <= 0 { - limit = 20 - } - pageSize := limit - vipOrders, err1 := FetchVipOrders(c, 1, pageSize) - coinOrders, err2 := FetchCoinOrders(c, 1, pageSize) - if err1 != nil { - log.Error("fetch vip orders failed", log.E(err1)) - } - if err2 != nil { - log.Error("fetch coin orders failed", log.E(err2)) - } - out := make([]VipOrder, 0, len(vipOrders)+len(coinOrders)) - out = append(out, vipOrders...) - out = append(out, coinOrders...) - return out -} - -// FetchCoinTransList 查询金币余额明细:GET /api/app/coin/trans/list -// 第二个返回值为上游业务 code(网络/解码失败时为 0)。 -func FetchCoinTransList(c *gin.Context, page, pageSize int) ([]CoinTransListItem, int, error) { - data, code, err := FetchCoinTransPage(c, page, pageSize, 0) - if data == nil { - return nil, code, err - } - return data.List, code, err -} - -// FetchCoinTransPage 查询金币余额明细并保留分页信息。 -// typ: 0全部、1充值、2消费、3奖励。 -func FetchCoinTransPage(c *gin.Context, page, pageSize, typ int) (*CoinTransPage, int, error) { - if page <= 0 { - page = 1 - } - if pageSize <= 0 { - pageSize = 10 - } - if pageSize > 100 { - pageSize = 100 - } - if typ < 0 || typ > 3 { - typ = 0 - } - query := url.Values{} - query.Set("page", strconv.Itoa(page)) - query.Set("pageSize", strconv.Itoa(pageSize)) - query.Set("type", strconv.Itoa(typ)) - full := coinTransPath + "?" + query.Encode() - var env APIEnvelopeRaw - if err := upstream.GetJSON(c, full, &env); err != nil { - return nil, 0, err - } - if env.Code != 200 { - return nil, env.Code, fmt.Errorf("%s code=%d msg=%s", coinTransPath, env.Code, env.Msg) - } - return decodeCoinTransPage(env.Data, page, pageSize), env.Code, nil -} - -// FetchCoinTransExport 导出金币流水 CSV:GET /api/app/coin/trans/export?type= -// typ: 0全部、1充值、2消费、3奖励。 -func FetchCoinTransExport(c *gin.Context, typ int) (*upstream.BinaryResult, error) { - if typ < 0 || typ > 3 { - typ = 0 - } - query := url.Values{} - query.Set("type", strconv.Itoa(typ)) - full := coinTransExportPath + "?" + query.Encode() - result, err := upstream.GetBinary(c, full) - if err != nil { - return nil, err - } - if result == nil { - return nil, fmt.Errorf("%s empty response", coinTransExportPath) - } - if result.StatusCode < 200 || result.StatusCode >= 300 { - return result, fmt.Errorf("%s status=%d body=%s", coinTransExportPath, result.StatusCode, truncateBytes(result.Body, 200)) - } - // 上游偶发返回 JSON 错误包装 - ct := strings.ToLower(result.ContentType) - if strings.Contains(ct, "json") { - var env APIEnvelopeRaw - if json.Unmarshal(result.Body, &env) == nil && env.Code != 0 && env.Code != 200 { - return result, fmt.Errorf("%s code=%d msg=%s", coinTransExportPath, env.Code, env.Msg) - } - } - return result, nil -} - -func truncateBytes(b []byte, n int) string { - s := string(b) - if len(s) <= n { - return s - } - return s[:n] + "..." -} - -// FetchCoinTransListOrEmpty 失败返回空列表;游客会话失效时会尝试重登后重试一次。 -func FetchCoinTransListOrEmpty(c *gin.Context, page, pageSize int) []CoinTransListItem { - list, code, err := FetchCoinTransList(c, page, pageSize) - if err == nil { - return list - } - if retry, _ := authser.OnSessionExpired(c, code); retry { - list, _, err = FetchCoinTransList(c, page, pageSize) - if err == nil { - return list - } - } - log.Error("fetch coin trans list failed", log.E(err)) - return nil -} - -func decodeCoinTransList(raw json.RawMessage) []CoinTransListItem { - return decodeCoinTransPage(raw, 1, 10).List -} - -func decodeCoinTransPage(raw json.RawMessage, page, pageSize int) *CoinTransPage { - empty := &CoinTransPage{PageNum: page, PageSize: pageSize} - if len(raw) == 0 || string(raw) == "null" { - return empty - } - var arr []CoinTransListItem - if err := json.Unmarshal(raw, &arr); err == nil { - empty.List = arr - empty.TotalCount = int64(len(arr)) - return empty - } - var obj CoinTransPage - if err := json.Unmarshal(raw, &obj); err != nil { - return empty - } - if obj.PageNum <= 0 { - obj.PageNum = page - } - if obj.PageSize <= 0 { - obj.PageSize = pageSize - } - return &obj -} - -func fetchOrders(c *gin.Context, path string, page, pageSize int) ([]VipOrder, error) { - if page <= 0 { - page = 1 - } - if pageSize <= 0 { - pageSize = 10 - } - full := fmt.Sprintf("%s?page=%d&pageSize=%d", path, page, pageSize) - var env APIEnvelopeRaw - if err := upstream.GetJSON(c, full, &env); err != nil { - return nil, err - } - if env.Code != 200 { - return nil, fmt.Errorf("%s code=%d msg=%s", path, env.Code, env.Msg) - } - return decodeOrderList(env.Data), nil -} - -func decodeOrderList(raw json.RawMessage) []VipOrder { - if len(raw) == 0 || string(raw) == "null" { - return nil - } - var arr []VipOrder - if err := json.Unmarshal(raw, &arr); err == nil { - return arr - } - var obj struct { - List []VipOrder `json:"list"` - Orders []VipOrder `json:"orders"` - Records []VipOrder `json:"records"` - Items []VipOrder `json:"items"` - } - if err := json.Unmarshal(raw, &obj); err != nil { - return nil - } - switch { - case len(obj.List) > 0: - return obj.List - case len(obj.Orders) > 0: - return obj.Orders - case len(obj.Records) > 0: - return obj.Records - case len(obj.Items) > 0: - return obj.Items - default: - return nil - } -} diff --git a/app/service/rechargeser/types.go b/app/service/rechargeser/types.go deleted file mode 100644 index 1c35892..0000000 --- a/app/service/rechargeser/types.go +++ /dev/null @@ -1,125 +0,0 @@ -package rechargeser - -import "encoding/json" - -// PackageType 对应 rechargepackagemod.PackageType -const ( - PackageTypeVip = 1 // 会员畅享 - PackageTypeCoin = 2 // 金币充值 -) - -// PayMethodInfo 对应 swagger proto.PayMethodInfo -type PayMethodInfo struct { - Type string `json:"type"` // alipay / wechat / union - TypeName string `json:"typeName"` // 支付宝 / 微信 / 银联 -} - -// RechargePackageItem 对应 swagger proto.RechargePackageItem -type RechargePackageItem struct { - ID int64 `json:"id"` - Name string `json:"name"` - SubTitle string `json:"subTitle"` - PackageType int `json:"packageType"` - Price int64 `json:"price"` // 售价,分 - OriginalPrice int64 `json:"originalPrice"` // 原价,分 - BaseCoin int64 `json:"baseCoin"` - GiftCoin int64 `json:"giftCoin"` - TotalCoin int64 `json:"totalCoin"` - DurationDays int `json:"durationDays"` - DurationHours int `json:"durationHours"` - Sort int `json:"sort"` - PayMethods []PayMethodInfo `json:"payMethods"` -} - -// RechargePackageListResp 对应 swagger proto.RechargePackageListResp -// GET /api/app/recharge/package/list -type RechargePackageListResp struct { - VipList []RechargePackageItem `json:"vipList"` - CoinList []RechargePackageItem `json:"coinList"` -} - -// BuyPayReq 对应 coinapi.buyCoinReq / rechargeapi.submitOrderReq -type BuyPayReq struct { - PackageID int64 `json:"packageId"` - PayMode string `json:"payMode"` // alipay / wechat / union ... -} - -// PayOrderResp 对应 coinser.BuyCoinResp / rechargeser.SubmitOrderResp -type PayOrderResp struct { - IsOpenNewBrowser bool `json:"isOpenNewBrowser"` - PayURL string `json:"payUrl"` - PayURLMode string `json:"payUrlMode"` - TradeNo string `json:"tradeNo"` -} - -// VipOrder 对应 swagger vipordermod.VipOrder(会员/金币订单共用结构) -type VipOrder struct { - ID int64 `json:"id"` - UserID int64 `json:"userId"` - OrderType int `json:"orderType"` // 1会员 2金币 - Status int `json:"status"` // 1待支付 2成功 3失败 - Amount int64 `json:"amount"` // 分 - Coin int64 `json:"coin"` - CardID int64 `json:"cardId"` - CardName string `json:"cardName"` - CoinCfgID int64 `json:"coinCfgId"` - PayMode string `json:"payMode"` - TradeNo string `json:"tradeNo"` - CreatedAt string `json:"createdAt"` - FinishedAt string `json:"finishedAt"` -} - -// CoinTransListItem 对应 swagger proto.CoinTransListItemResp -// GET /api/app/coin/trans/list -type CoinTransListItem struct { - ID int64 `json:"id"` - Title string `json:"title"` - Remark string `json:"remark"` - TranType int `json:"tranType"` - TranTypeName string `json:"tranTypeName"` - FlowDir int `json:"flowDir"` // 1收入 -1支出 - FlowDirName string `json:"flowDirName"` - Amount int64 `json:"amount"` - ChangeAmount int64 `json:"changeAmount"` - BalanceBefore int64 `json:"balanceBefore"` - BalanceAfter int64 `json:"balanceAfter"` - OrderNo string `json:"orderNo"` - RelatedID int64 `json:"relatedId"` - CreatedAt string `json:"createdAt"` -} - -// CoinTransPage 对应 response.BasePageResp 与金币流水列表组合。 -type CoinTransPage struct { - List []CoinTransListItem `json:"list"` - PageNum int `json:"pageNum"` - PageSize int `json:"pageSize"` - TotalCount int64 `json:"totalCount"` -} - -const ( - OrderTypeVip = 1 - OrderTypeCoin = 2 - - OrderStatusPending = 1 - OrderStatusSuccess = 2 - OrderStatusFailed = 3 - - FlowDirIn = 1 - FlowDirOut = -1 -) - -// APIEnvelope 上游统一响应壳 -type APIEnvelope[T any] struct { - Code int `json:"code"` - Msg string `json:"msg"` - Tip string `json:"tip"` - Data T `json:"data"` -} - -// APIEnvelopeRaw Data 形态不固定时使用。 -type APIEnvelopeRaw struct { - Code int `json:"code"` - Msg string `json:"msg"` - Tip string `json:"tip"` - Data json.RawMessage `json:"data"` -} diff --git a/app/service/rechargeser/view.go b/app/service/rechargeser/view.go deleted file mode 100644 index b0eab1c..0000000 --- a/app/service/rechargeser/view.go +++ /dev/null @@ -1,496 +0,0 @@ -package rechargeser - -import ( - "encoding/json" - "fmt" - "sort" - "strconv" - "strings" -) - -// PayMethodVM 支付方式展示。 -type PayMethodVM struct { - Type string - TypeName string - Active bool -} - -// PackageCardVM 充值页套餐卡片(字段来自 package/list)。 -type PackageCardVM struct { - ID int64 - Name string - Hint string // 优先 subTitle - BaseCoinText string - GiftCoinText string - TotalCoinText string - PriceText string // 售价(元),不含 ¥ - OriginalPriceText string // 原价(元),仅当原价>售价时有值 - PriceFen int64 - Active bool - PayMethods []PayMethodVM - PayMethodsJSON string // 供 data-pay-methods 使用 -} - -// DepositPageVM 充值页模板数据。 -type DepositPageVM struct { - VipList []PackageCardVM - CoinList []PackageCardVM - PayMethods []PayMethodVM // 当前选中套餐的支付方式(接口 payMethods) - // TxList 交易明细:直接透传 /api/app/coin/trans/list 返回的字段 - //(不再做 Name/TimeText/AmountText 的自定义字段映射)。 - TxList []CoinTransListItem -} - -// TxItemVM 交易明细行(历史遗留:旧逻辑会把 CoinTransListItem 映射到此结构)。 -// 当前“交易明细调试”需求:DepositPageVM 直接透传 CoinTransListItem, -// 但旧函数可能仍被其它页面复用,因此保留结构体定义。 -type TxItemVM struct { - Name string `json:"name"` - Kind string `json:"kind"` - Description string `json:"description"` - Category string `json:"category"` - TimeText string `json:"time"` - MobileTime string `json:"mobileTime"` - AmountText string `json:"amount"` - AmountClass string `json:"amountClass"` // deposit-tx-amount--plus / --minus - Credit bool `json:"credit"` - StatusText string `json:"status"` -} - -var defaultPayMethods = []PayMethodInfo{ - {Type: "alipay", TypeName: "支付宝"}, - {Type: "wechat", TypeName: "微信"}, - {Type: "union", TypeName: "银联"}, -} - -// BuildDepositPageVM 按接口 vipList / coinList 组装展示数据(支付方式不造假)。 -// preferTab: "coin" 优先选中金币套餐,其它/空则优先会员。 -func BuildDepositPageVM(raw *RechargePackageListResp, preferTab string) DepositPageVM { - if raw == nil { - return DepositPageVM{} - } - vip := buildCards(raw.VipList, true) - coin := buildCards(raw.CoinList, false) - activatePreferredPackage(vip, coin, preferTab) - - var activePays []PayMethodVM - for _, item := range coin { - if item.Active { - activePays = item.PayMethods - break - } - } - if activePays == nil { - for _, item := range vip { - if item.Active { - activePays = item.PayMethods - break - } - } - } - if len(activePays) > 0 { - activePays[0].Active = true - } - return DepositPageVM{ - VipList: vip, - CoinList: coin, - PayMethods: activePays, - } -} - -// activatePreferredPackage 默认选中:优先有支付方式的套餐,否则选第一项。 -// preferTab=coin 时优先金币,否则优先会员。 -func activatePreferredPackage(vip, coin []PackageCardVM, preferTab string) { - pick := func(list []PackageCardVM) bool { - for i := range list { - if len(list[i].PayMethods) > 0 { - list[i].Active = true - return true - } - } - if len(list) > 0 { - list[0].Active = true - return true - } - return false - } - preferCoin := strings.EqualFold(strings.TrimSpace(preferTab), "coin") - if preferCoin { - if pick(coin) { - return - } - pick(vip) - return - } - if pick(vip) { - return - } - pick(coin) -} - -// AttachTxList 附加交易明细(金币流水)。 -func (vm *DepositPageVM) AttachTxList(list []CoinTransListItem) { - if vm == nil { - return - } - vm.TxList = list -} - -// BuildTxListFromCoinTrans 将金币流水转为展示列表。 -func BuildTxListFromCoinTrans(list []CoinTransListItem, limit int) []TxItemVM { - items := append([]CoinTransListItem(nil), list...) - if limit > 0 && len(items) > limit { - items = items[:limit] - } - out := make([]TxItemVM, 0, len(items)) - for _, item := range items { - name := strings.TrimSpace(item.Title) - if name == "" { - name = strings.TrimSpace(item.TranTypeName) - } - if name == "" { - name = strings.TrimSpace(item.Remark) - } - if name == "" { - name = "金币变动" - } - change := item.ChangeAmount - if change == 0 { - change = item.Amount - if item.FlowDir == FlowDirOut { - change = -change - } - } - amountClass := "deposit-tx-amount--plus" - amountText := "+" + formatCount(absInt64(change)) + " 金币" - if change < 0 || item.FlowDir == FlowDirOut { - amountClass = "deposit-tx-amount--minus" - amountText = "-" + formatCount(absInt64(change)) + " 金币" - } - out = append(out, TxItemVM{ - Name: name, - Kind: transactionKind(item), - Description: name, - Category: transactionCategory(item), - TimeText: formatTransTime(item.CreatedAt), - MobileTime: formatMobileTransTime(item.CreatedAt), - AmountText: amountText, - AmountClass: amountClass, - Credit: change >= 0 && item.FlowDir != FlowDirOut, - StatusText: strings.TrimSpace(item.FlowDirName), - }) - } - return out -} - -func transactionKind(item CoinTransListItem) string { - if name := strings.TrimSpace(item.TranTypeName); name != "" { - return name - } - if item.FlowDir == FlowDirOut { - return "消费" - } - return "收入" -} - -func transactionCategory(item CoinTransListItem) string { - switch item.TranType { - case 1: - return "topup" - case 2: - return "reward" - case 4: - return "spend" - default: - if item.FlowDir == FlowDirOut { - return "spend" - } - return "reward" - } -} - -func formatMobileTransTime(raw string) string { - formatted := formatTransTime(raw) - if len(formatted) >= 16 { - return formatted[5:16] - } - return formatted -} - -func absInt64(n int64) int64 { - if n < 0 { - return -n - } - return n -} - -func formatTransTime(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) > 19 { - raw = raw[:19] - } - return raw -} - -// BuildTxList 将订单转为展示列表(按时间倒序)。保留供订单场景复用。 -func BuildTxList(orders []VipOrder, limit int) []TxItemVM { - items := append([]VipOrder(nil), orders...) - sort.SliceStable(items, func(i, j int) bool { - return orderTimeKey(items[i]) > orderTimeKey(items[j]) - }) - if limit > 0 && len(items) > limit { - items = items[:limit] - } - out := make([]TxItemVM, 0, len(items)) - for _, o := range items { - name := strings.TrimSpace(o.CardName) - if name == "" { - if o.OrderType == OrderTypeCoin || o.CoinCfgID > 0 { - name = "金币充值" - } else { - name = "会员充值" - } - } - status := orderStatusText(o.Status) - if status != "" { - name = name + " · " + status - } - amountClass := "deposit-tx-amount--minus" - amountText := "-¥" + FormatYuan(o.Amount) - if o.OrderType == OrderTypeCoin || o.Coin > 0 { - if o.Status == OrderStatusSuccess && o.Coin > 0 { - amountClass = "deposit-tx-amount--plus" - amountText = "+" + formatCount(o.Coin) + " 金币" - } - } - out = append(out, TxItemVM{ - Name: name, - TimeText: formatOrderTime(o), - AmountText: amountText, - AmountClass: amountClass, - StatusText: status, - }) - } - return out -} - -func orderTimeKey(o VipOrder) string { - if t := strings.TrimSpace(o.FinishedAt); t != "" && !strings.HasPrefix(t, "0001") { - return t - } - return strings.TrimSpace(o.CreatedAt) -} - -func formatOrderTime(o VipOrder) string { - raw := orderTimeKey(o) - if raw == "" || strings.HasPrefix(raw, "0001") { - return "" - } - // 常见 RFC3339 / 带 T 的时间,截成可读 - 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) > 19 { - raw = raw[:19] - } - return raw -} - -func orderStatusText(status int) string { - switch status { - case OrderStatusPending: - return "待支付" - case OrderStatusSuccess: - return "已完成" - case OrderStatusFailed: - return "失败" - default: - return "" - } -} - -func buildCards(list []RechargePackageItem, isVIP bool) []PackageCardVM { - items := append([]RechargePackageItem(nil), list...) - // 仅按接口 sort 排序;sort 相同保持接口原顺序 - sort.SliceStable(items, func(i, j int) bool { - return items[i].Sort < items[j].Sort - }) - out := make([]PackageCardVM, 0, len(items)) - for _, item := range items { - name := strings.TrimSpace(item.Name) - if name == "" { - if isVIP { - name = "会员套餐" - } else { - name = "金币套餐" - } - } - pays := buildPayMethodVMs(item.PayMethods, false) - card := PackageCardVM{ - ID: item.ID, - Name: name, - Hint: packageHint(item, isVIP), - BaseCoinText: formatCount(item.BaseCoin), - GiftCoinText: formatCount(item.GiftCoin), - TotalCoinText: formatCount(packageTotalCoin(item)), - PriceText: FormatYuan(item.Price), - PriceFen: item.Price, - PayMethods: pays, - PayMethodsJSON: marshalPayMethodsJSON(pays), - } - if item.OriginalPrice > item.Price && item.OriginalPrice > 0 { - card.OriginalPriceText = FormatYuan(item.OriginalPrice) - } - out = append(out, card) - } - return out -} - -func packageTotalCoin(item RechargePackageItem) int64 { - if item.TotalCoin > 0 { - return item.TotalCoin - } - return item.BaseCoin + item.GiftCoin -} - -func buildPayMethodVMs(list []PayMethodInfo, withFallback bool) []PayMethodVM { - src := list - if len(src) == 0 && withFallback { - src = defaultPayMethods - } - out := make([]PayMethodVM, 0, len(src)) - seen := map[string]struct{}{} - for _, item := range src { - typ := strings.ToLower(strings.TrimSpace(item.Type)) - name := strings.TrimSpace(item.TypeName) - if typ == "" && name == "" { - continue - } - if typ == "" { - typ = strings.ToLower(name) - } - if name == "" { - name = defaultPayTypeName(typ) - } - if _, ok := seen[typ]; ok { - continue - } - seen[typ] = struct{}{} - out = append(out, PayMethodVM{Type: typ, TypeName: name}) - } - if len(out) == 0 && withFallback { - return buildPayMethodVMs(defaultPayMethods, false) - } - return out -} - -func defaultPayTypeName(typ string) string { - switch typ { - case "alipay": - return "支付宝" - case "wechat", "wx", "weixin": - return "微信" - case "union", "unionpay": - return "银联" - case "dcep", "ecny": - return "数字人民币" - case "usdt": - return "USDT" - default: - if typ == "" { - return "支付" - } - return typ - } -} - -func marshalPayMethodsJSON(list []PayMethodVM) string { - type row struct { - Type string `json:"type"` - TypeName string `json:"typeName"` - } - rows := make([]row, 0, len(list)) - for _, item := range list { - rows = append(rows, row{Type: item.Type, TypeName: item.TypeName}) - } - b, err := json.Marshal(rows) - if err != nil || len(b) == 0 { - return "[]" - } - return string(b) -} - -func packageHint(item RechargePackageItem, isVIP bool) string { - // 优先接口 subTitle - if s := strings.TrimSpace(item.SubTitle); s != "" { - return s - } - if isVIP { - if item.DurationDays > 0 { - return fmt.Sprintf("%d天会员", item.DurationDays) - } - if item.DurationHours > 0 { - return fmt.Sprintf("%d小时会员", item.DurationHours) - } - return "" - } - base := item.BaseCoin - gift := item.GiftCoin - total := item.TotalCoin - if total <= 0 { - total = base + gift - } - if gift > 0 && base > 0 { - return fmt.Sprintf("基础 %s + 赠送 %s", formatCount(base), formatCount(gift)) - } - if total > 0 { - return formatCount(total) + " 金币" - } - return "" -} - -// FormatYuan 分转元展示(整数不带小数)。 -func FormatYuan(fen int64) string { - if fen < 0 { - fen = 0 - } - if fen%100 == 0 { - return strconv.FormatInt(fen/100, 10) - } - return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", float64(fen)/100), "0"), ".") -} - -func formatCount(n int64) string { - if n < 0 { - n = 0 - } - s := strconv.FormatInt(n, 10) - if len(s) <= 3 { - return s - } - var b strings.Builder - rem := len(s) % 3 - if rem == 0 { - rem = 3 - } - b.WriteString(s[:rem]) - for i := rem; i < len(s); i += 3 { - b.WriteByte(',') - b.WriteString(s[i : i+3]) - } - return b.String() -} diff --git a/app/service/translationser/list.go b/app/service/translationser/list.go index dd1527d..ebe4676 100644 --- a/app/service/translationser/list.go +++ b/app/service/translationser/list.go @@ -2,6 +2,7 @@ package translationser import ( "fmt" + "strings" "sync" "time" "videoweb/app/service/upstream" @@ -67,11 +68,23 @@ func filterHeaderCategories(info *AppTranslationInfo) []TranslationCategory { if cat.Name == "" && cat.URLValue == "" { continue } + // 网页版已移除账号体系,后台若仍配置个人中心/已购入口则不展示。 + if isAccountNavPath(cat.URLValue) { + continue + } out = append(out, cat) } return out } +func isAccountNavPath(raw string) bool { + switch strings.TrimRight(strings.TrimSpace(raw), "/") { + case "/user-center", "user-center", "/profile", "profile", "/purchased-series", "purchased-series", "/purchased", "purchased": + return true + } + return false +} + // DefaultCategoryList 接口失败时的兜底导航(字段与接口一致)。 func DefaultCategoryList() []TranslationCategory { return []TranslationCategory{ diff --git a/app/service/translationser/view.go b/app/service/translationser/view.go index 50d82af..f8bb907 100644 --- a/app/service/translationser/view.go +++ b/app/service/translationser/view.go @@ -290,10 +290,6 @@ func rewriteNavPath(path string) string { return "/popular-recommend" case "/tag", "/tag-list": return "/tags" - case "/user-center": - return "/profile" - case "/purchased-series": - return "/purchased" case "/search-results": return "/search" case "/more": @@ -455,8 +451,6 @@ func navActiveKey(href, name string, download bool) string { return "aiSeries" case "/home-road": return "homeRoad" - case "/profile": - return "userCenter" case "/download-app": return "downloadApp" default: diff --git a/app/service/usercenterser/center.go b/app/service/usercenterser/center.go deleted file mode 100644 index fdb4713..0000000 --- a/app/service/usercenterser/center.go +++ /dev/null @@ -1,42 +0,0 @@ -package usercenterser - -import ( - "encoding/json" - "fmt" - "videoweb/app/service/upstream" - - "github.com/gin-gonic/gin" -) - -const centerDataPath = "/api/app/user/center/data" - -// Data 对应 Swagger proto.UserCenterDataResp。 -type Data struct { - WatchCount int64 `json:"watchCount"` - CollectCount int64 `json:"collectCount"` - ChasingCount int64 `json:"chasingCount"` - BoughtChapterCount int64 `json:"boughtChapterCount"` -} - -// Fetch 获取当前用户的个人中心统计。code 为上游业务码,供会话失效重试。 -func Fetch(c *gin.Context) (*Data, int, error) { - var env struct { - Code int `json:"code"` - Msg string `json:"msg"` - Data json.RawMessage `json:"data"` - } - if err := upstream.GetJSON(c, centerDataPath, &env); err != nil { - return nil, 0, err - } - if env.Code != 200 { - return nil, env.Code, fmt.Errorf("user center data code=%d msg=%s", env.Code, env.Msg) - } - var data Data - if len(env.Data) == 0 || string(env.Data) == "null" { - return nil, env.Code, fmt.Errorf("user center data is empty") - } - if err := json.Unmarshal(env.Data, &data); err != nil { - return nil, env.Code, fmt.Errorf("decode user center data: %w", err) - } - return &data, env.Code, nil -} diff --git a/app/service/videoser/collect.go b/app/service/videoser/collect.go deleted file mode 100644 index c591f7e..0000000 --- a/app/service/videoser/collect.go +++ /dev/null @@ -1,130 +0,0 @@ -package videoser - -import ( - "fmt" - "net/url" - "strconv" - "videoweb/app/service/authser" - "videoweb/app/service/upstream" - "videoweb/pub/common/log" - - "github.com/gin-gonic/gin" -) - -const ( - collectSetPath = "/api/app/video/collect/set" - collectListPath = "/api/app/video/collect/list" - defaultCollectPageSize = 40 -) - -// VideoCollectSetReq 对应 swagger proto.VideoCollectSetReq。 -// POST /api/app/video/collect/set -type VideoCollectSetReq struct { - ID int64 `json:"id"` - IsCollect bool `json:"isCollect"` -} - -// VideoCollectSetResp 对应 swagger proto.VideoCollectSetResp。 -type VideoCollectSetResp struct { - ID int64 `json:"id"` - CountCollect int64 `json:"countCollect"` - IsCollect int `json:"isCollect"` // 1-已收藏 2-未收藏 -} - -// VideoCollectData 对应 swagger proto.VideoCollectData。 -// GET /api/app/video/collect/list -type VideoCollectData struct { - ID int64 `json:"id"` - Title string `json:"title"` - Slug string `json:"slug"` - CoverImg string `json:"coverImg"` - Keyworld []string `json:"keyworld"` - Tags []int64 `json:"tags"` - VideoTime int `json:"videoTime"` - WatchTimes int64 `json:"watchTimes"` - CountLike int64 `json:"countLike"` - CountCollect int64 `json:"countCollect"` - CountComment int64 `json:"countComment"` - IsCollect int `json:"isCollect"` - CollectedAt string `json:"collectedAt"` -} - -// VideoCollectListResp 对应 swagger proto.VideoCollectListResp。 -type VideoCollectListResp struct { - List []VideoCollectData `json:"list"` - PageNum int `json:"pageNum"` - PageSize int `json:"pageSize"` - TotalCount int64 `json:"totalCount"` -} - -// CollectListQuery 视频收藏列表分页。 -type CollectListQuery struct { - PageNum int - PageSize int -} - -// Normalize 校正分页默认值。 -func (q *CollectListQuery) Normalize() { - if q.PageNum <= 0 { - q.PageNum = 1 - } - if q.PageSize <= 0 { - q.PageSize = defaultCollectPageSize - } - if q.PageSize > 100 { - q.PageSize = 100 - } -} - -// CollectSet 设置视频收藏/取消收藏。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_collect_set -func CollectSet(c *gin.Context, req VideoCollectSetReq) (*APIEnvelope[VideoCollectSetResp], error) { - if req.ID <= 0 { - return nil, fmt.Errorf("video collect invalid id") - } - var env APIEnvelope[VideoCollectSetResp] - if _, err := upstream.PostJSON(c, collectSetPath, req, &env); err != nil { - return nil, err - } - return &env, nil -} - -// FetchCollectList 拉取视频收藏列表:GET /api/app/video/collect/list。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/get_api_app_video_collect_list -func FetchCollectList(c *gin.Context, q CollectListQuery) (*VideoCollectListResp, int, error) { - q.Normalize() - params := url.Values{} - params.Set("pageNum", strconv.Itoa(q.PageNum)) - params.Set("pageSize", strconv.Itoa(q.PageSize)) - - var env APIEnvelope[VideoCollectListResp] - 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("video collect list code=%d msg=%s", env.Code, env.Msg) - } - return &env.Data, env.Code, nil -} - -// FetchCollectListOrEmpty 失败返回空列表;游客会话失效时尝试重登后重试一次。 -func FetchCollectListOrEmpty(c *gin.Context, q CollectListQuery) *VideoCollectListResp { - data, code, err := FetchCollectList(c, q) - if err == nil { - if data == nil { - return &VideoCollectListResp{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 &VideoCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - return data - } - } - log.Error("fetch video collect list failed", log.E(err)) - return &VideoCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} -} diff --git a/app/service/videoser/history.go b/app/service/videoser/history.go deleted file mode 100644 index 33c1c44..0000000 --- a/app/service/videoser/history.go +++ /dev/null @@ -1,141 +0,0 @@ -package videoser - -import ( - "fmt" - "net/url" - "strconv" - "videoweb/app/service/authser" - "videoweb/app/service/upstream" - "videoweb/pub/common/log" - - "github.com/gin-gonic/gin" -) - -const ( - videoHistoryListPath = "/api/app/video/history/list" - videoHistoryClearPath = "/api/app/video/history/clear" - videoHistoryDeletePath = "/api/app/video/history/delete" - defaultHistoryPageSize = 40 -) - -// VideoHistoryData 对应 swagger proto.VideoHistoryData。 -// GET /api/app/video/history/list -type VideoHistoryData struct { - ID int64 `json:"id"` - Title string `json:"title"` - Slug string `json:"slug"` - CoverImg string `json:"coverImg"` - Keyworld []string `json:"keyworld"` - Tags []int64 `json:"tags"` - VideoTime int `json:"videoTime"` - WatchTimes int64 `json:"watchTimes"` - CountLike int64 `json:"countLike"` - CountCollect int64 `json:"countCollect"` - CountComment int64 `json:"countComment"` - IsCollect int `json:"isCollect"` - HistoryAt string `json:"historyAt"` -} - -// VideoHistoryListResp 对应 swagger proto.VideoHistoryListResp。 -type VideoHistoryListResp struct { - List []VideoHistoryData `json:"list"` - PageNum int `json:"pageNum"` - PageSize int `json:"pageSize"` - TotalCount int64 `json:"totalCount"` -} - -// VideoHistoryClearResp 对应 swagger proto.VideoHistoryClearResp。 -type VideoHistoryClearResp struct { - DeletedCount int `json:"deletedCount"` -} - -// VideoHistoryDeleteReq 对应 swagger proto.VideoHistoryDeleteReq。 -type VideoHistoryDeleteReq struct { - VideoID int64 `json:"videoId"` -} - -// VideoHistoryDeleteResp 对应 swagger proto.VideoHistoryDeleteResp。 -type VideoHistoryDeleteResp 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 分页查询视频观看历史:GET /api/app/video/history/list。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/get_api_app_video_history_list -func FetchHistoryList(c *gin.Context, q HistoryListQuery) (*VideoHistoryListResp, int, error) { - q.Normalize() - params := url.Values{} - params.Set("pageNum", strconv.Itoa(q.PageNum)) - params.Set("pageSize", strconv.Itoa(q.PageSize)) - - var env APIEnvelope[VideoHistoryListResp] - if err := upstream.GetJSON(c, videoHistoryListPath+"?"+params.Encode(), &env); err != nil { - return nil, 0, err - } - if env.Code != 200 { - return nil, env.Code, fmt.Errorf("video history list code=%d msg=%s", env.Code, env.Msg) - } - return &env.Data, env.Code, nil -} - -// FetchHistoryListOrEmpty 失败返回空列表;游客会话失效时尝试重登后重试一次。 -func FetchHistoryListOrEmpty(c *gin.Context, q HistoryListQuery) *VideoHistoryListResp { - data, code, err := FetchHistoryList(c, q) - if err == nil { - if data == nil { - return &VideoHistoryListResp{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 &VideoHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - return data - } - } - log.Error("fetch video history list failed", log.E(err)) - return &VideoHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize} -} - -// HistoryClear 清空视频观看历史:POST /api/app/video/history/clear。 -func HistoryClear(c *gin.Context) (*APIEnvelope[VideoHistoryClearResp], error) { - var env APIEnvelope[VideoHistoryClearResp] - if _, err := upstream.PostJSON(c, videoHistoryClearPath, nil, &env); err != nil { - return nil, err - } - return &env, nil -} - -// HistoryDelete 删除单条视频观看历史:POST /api/app/video/history/delete。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_history_delete -func HistoryDelete(c *gin.Context, req VideoHistoryDeleteReq) (*APIEnvelope[VideoHistoryDeleteResp], error) { - if req.VideoID <= 0 { - return nil, fmt.Errorf("video history delete invalid videoId") - } - var env APIEnvelope[VideoHistoryDeleteResp] - if _, err := upstream.PostJSON(c, videoHistoryDeletePath, req, &env); err != nil { - return nil, err - } - return &env, nil -} diff --git a/app/service/videoser/like.go b/app/service/videoser/like.go deleted file mode 100644 index 866374d..0000000 --- a/app/service/videoser/like.go +++ /dev/null @@ -1,37 +0,0 @@ -package videoser - -import ( - "fmt" - "videoweb/app/service/upstream" - - "github.com/gin-gonic/gin" -) - -const likeSetPath = "/api/app/video/like/set" - -// VideoLikeSetReq 对应 swagger proto.VideoLikeSetReq。 -// POST /api/app/video/like/set -type VideoLikeSetReq struct { - ID int64 `json:"id"` - IsLike bool `json:"isLike"` -} - -// VideoLikeSetResp 对应 swagger proto.VideoLikeSetResp。 -type VideoLikeSetResp struct { - ID int64 `json:"id"` - CountLike int64 `json:"countLike"` - IsLike int `json:"isLike"` // 1-已点赞 2-未点赞 -} - -// LikeSet 设置视频点赞/取消点赞。 -// 文档:http://172.104.181.99:25319/swagger/index.html#/视频相关接口/post_api_app_video_like_set -func LikeSet(c *gin.Context, req VideoLikeSetReq) (*APIEnvelope[VideoLikeSetResp], error) { - if req.ID <= 0 { - return nil, fmt.Errorf("video like invalid id") - } - var env APIEnvelope[VideoLikeSetResp] - if _, err := upstream.PostJSON(c, likeSetPath, req, &env); err != nil { - return nil, err - } - return &env, nil -} diff --git a/app/static/common/web-sdk-v1.2.0.js b/app/static/common/web-sdk-v1.2.0.js deleted file mode 100644 index 250d471..0000000 --- a/app/static/common/web-sdk-v1.2.0.js +++ /dev/null @@ -1 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).WebSDK=t()}(this,function(){"use strict";function e(e){var t,n,r,o,i,a,s,c,u,l,d,_,p,f,h,g,m,y,v,w,k,b,C,S,P=function(e){var t,n,r,o,i,a;for(t=(null==e?"":e)+"",n=[],r=0;t.length>r;r++)128>(o=t.charCodeAt(r))?n.push(o):2048>o?n.push(192|o>>6,128|63&o):55296>o||o>56319||r+1>=t.length||56320>(i=t.charCodeAt(r+1))||i>57343?n.push(224|o>>12,128|o>>6&63,128|63&o):(n.push(240|(a=i-56320+(o-55296<<10)+65536)>>18,128|a>>12&63,128|a>>6&63,128|63&a),r++);return n}((null==e?"":e)+""),E=8*P.length;for(P.push(128);P.length%64!=56;)P.push(0);for(t=0;8>t;t++)P.push(E>>>8*t&255);for(n=function(e,t){return e<>>32-t},r=function(e,t){return((e>>>0)+(t>>>0)&4294967295)>>>0},o=function(e,t,n,o){return r(r(e,t),r(n,o))},i=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],a=Array(64),s=0;64>s;s++)a[s]=Math.floor(4294967296*Math.abs(Math.sin(s+1)))>>>0;for(c=1732584193,u=4023233417,l=2562383102,d=271733878,_=0;P.length>_;_+=64){for(p=Array(16),f=0;16>f;f++)p[f]=(P[h=_+4*f]|P[h+1]<<8|P[h+2]<<16|P[h+3]<<24)>>>0;for(g=c,m=u,y=l,v=d,w=0;64>w;w++)16>w?(k=m&y|~m&v,b=w):32>w?(k=v&m|~v&y,b=(5*w+1)%16):48>w?(k=m^y^v,b=(3*w+5)%16):(k=y^(m|~v),b=7*w%16),C=v,v=y,y=m,m=r(m,n(o(g,k,a[w],p[b]),i[w])),g=C;c=r(c,g),u=r(u,m),l=r(l,y),d=r(d,v)}return S=function(e){for(var t="",n=0;4>n;n++)t+=(256+(e>>>8*n&255)).toString(16).slice(1);return t},S(c)+S(u)+S(l)+S(d)}function t(e,t){return(e>>>t|e<<32-t)>>>0}function n(e,t,n){return(e&t^~e&n)>>>0}function r(e,t,n){return(e&t^e&n^t&n)>>>0}function o(e){return(t(e,2)^t(e,13)^t(e,22))>>>0}function i(e){return(t(e,6)^t(e,11)^t(e,25))>>>0}function a(e){return(t(e,7)^t(e,18)^e>>>3)>>>0}function s(e){return(t(e,17)^t(e,19)^e>>>10)>>>0}function c(e){for(var t=(e>>>0).toString(16);8>t.length;)t="0"+t;return t}function u(e,t){return((255&e[t])<<24|(255&e[t+1])<<16|(255&e[t+2])<<8|255&e[t+3])>>>0}function l(e){for(var t=function(e){var t,c,l,d,_,p,f,h,g,m,y,v,w,k,b,C,S,P,E,T,x,D=e.slice(),M=0,I=8*D.length>>>0;for(D.length>=536870912&&(M=Math.floor(D.length/536870912),0==(I=D.length%536870912*8>>>0)&&D.length>0&&M--),D.push(128);D.length%64!=56;)D.push(0);for(D.push(M>>>24&255,M>>>16&255,M>>>8&255,255&M,I>>>24&255,I>>>16&255,I>>>8&255,255&I),t=1779033703,c=3144134277,l=1013904242,d=2773480762,_=1359893119,p=2600822924,f=528734635,h=1541459225,g=0;D.length>g;g+=64){for(y=Array(64),m=0;16>m;m++)y[m]=u(D,g+4*m);for(m=16;64>m;m++)y[m]=s(y[m-2])+y[m-7]+a(y[m-15])+y[m-16]>>>0;for(v=t,w=c,k=l,b=d,C=_,S=p,P=f,E=h,m=0;64>m;m++)T=E+i(C)+n(C,S,P)+zt[m]+y[m]>>>0,x=o(v)+r(v,w,k)>>>0,E=P,P=S,S=C,C=b+T>>>0,b=k,k=w,w=v,v=T+x>>>0;t=t+v>>>0,c=c+w>>>0,l=l+k>>>0,d=d+b>>>0,_=_+C>>>0,p=p+S>>>0,f=f+P>>>0,h=h+E>>>0}return[t,c,l,d,_,p,f,h]}(e||[]),l="",d=0;t.length>d;d++)l+=c(t[d]);return l}function d(e){return l(function(e){var t,n,r,o,i,a;for(t=(null==e?"":e)+"",n=[],r=0;t.length>r;r++)128>(o=t.charCodeAt(r))?n.push(o):2048>o?n.push(192|o>>6,128|63&o):55296>o||o>56319||r+1>=t.length||56320>(i=t.charCodeAt(r+1))||i>57343?n.push(224|o>>12,128|o>>6&63,128|63&o):(n.push(240|(a=i-56320+(o-55296<<10)+65536)>>18,128|a>>12&63,128|a>>6&63,128|63&a),r++);return n}((null==e?"":e)+""))}function _(e){var t,n,r,o,i,a;for(t=(null==e?"":e)+"",n=[],r=0;t.length>r;r++)128>(o=t.charCodeAt(r))?n.push(o):2048>o?n.push(192|o>>6,128|63&o):55296>o||o>56319||r+1>=t.length||56320>(i=t.charCodeAt(r+1))||i>57343?n.push(224|o>>12,128|o>>6&63,128|63&o):(n.push(240|(a=i-56320+(o-55296<<10)+65536)>>18,128|a>>12&63,128|a>>6&63,128|63&a),r++);return n}function p(e){for(var t=(null==e?"":e)+"",n=[],r=0;t.length>r;r+=2)n.push(parseInt(t.substr(r,2),16));return n}function f(e,t){var n,r=Array(e.length+t.length);for(n=0;e.length>n;n++)r[n]=e[n];for(n=0;t.length>n;n++)r[e.length+n]=t[n];return r}function h(e,t){var n,r;return null===e?"__t:null__":"string"==typeof e?"__t:str__:"+e:"number"==typeof e&&isFinite(e)?"__t:num__:"+e:"boolean"==typeof e?"__t:bool__:"+(e?"1":"0"):Array.isArray(e)?-1!==t.indexOf(e)?"__t:circular__":(t.push(e),n="["+e.map(function(e){return h(e,t)}).join(",")+"]",t.splice(t.indexOf(e),1),n):null!==e&&"object"==typeof e?-1!==t.indexOf(e)?"__t:circular__":(t.push(e),r="{"+Object.keys(e).sort().map(function(n){return n+":"+h(e[n],t)}).join(",")+"}",t.splice(t.indexOf(e),1),r):"__t:any__:"+e}function g(e){return h(e,[])}function m(t){var n,r=t||{},o=Object.keys(r).filter(function(e){return"event_id"!==e}).sort();return o.length?(n=o.map(function(t){return e(g(r[t]))}).join(""),e(n)):e("")}function y(){if("undefined"==typeof navigator)return"PC";var e=navigator.userAgent||"";return/iPhone|iPad|iPod/i.test(e)?"iOS":function(e){return/HarmonyOS|OpenHarmony/i.test((null==e?"":e)+"")}(e)||/Android/i.test(e)?"Android":"PC"}function v(e){var t=((null==e?"":e)+"").trim();return t&&/^\d+\.\d+\.\d+$/.test(t)?t:"1.0.0"}function w(e,t){return null==e||""===e?"":"int"===t?parseInt(e,10)+"":(e+"").replace(/^\s+|\s+$/g,"").toLowerCase()}function k(e,t){try{return e()}catch(e){return t}}function b(t){return e((null==t?"":t)+"").substr(0,32)}function C(e){return b((e||[]).slice().sort().join("|"))}function S(){return k(function(){var e,t,n,r,o,i,a,s="undefined"!=typeof OfflineAudioContext?OfflineAudioContext:"undefined"!=typeof webkitOfflineAudioContext?webkitOfflineAudioContext:null;if(!s)return"";if((t=(e=new s(1,44100,44100)).createOscillator()).type="triangle",t.frequency.value=1e4,n=e.createDynamicsCompressor(),t.connect(n),n.connect(e.destination),t.start(0),(r=e.startRendering())&&"function"==typeof r.then)return"";if(!r||!r.getChannelData)return"";for(o=r.getChannelData(0),i=0,a=4500;5e3>a&&o.length>a;a++)i+=Math.abs(o[a]);return b(i+"")},"")}function P(){return k(function(){function e(e){var t,n=document.createElement("span");return n.style.cssText="position:absolute;left:-9999px;top:-9999px;visibility:hidden;font-size:72px;font-family:"+e+",monospace;",n.textContent="mmmmmmmmmmlli",document.body.appendChild(n),t=n.offsetWidth,document.body.removeChild(n),t}var t,n,r,o;if("undefined"==typeof document||!document.body)return{hash:"",installed:[],count:-1};for(t=e("monospace"),n=[],r=0;$t.length>r;r++)e("'"+(o=$t[r])+"'")!==t&&n.push(o);return n.sort(),{hash:C(n.map(function(e){return e.toLowerCase()})),installed:n,count:n.length}},{hash:"",installed:[],count:-1})}function E(){return k(function(){var e,t,n,r,o,i,a;if("undefined"==typeof document)return{hash:"",supported:[]};for(e=document.createElement("video"),t=document.createElement("audio"),n=[],r=0;Zt.length>r;r++){i="video"===(o=Zt[r]).kind?e:t,a="";try{a=i.canPlayType?(i.canPlayType(o.type)||"")+"":""}catch(e){}a&&"no"!==a&&n.push(o.type+":"+a)}return n.sort(),{hash:C(n.map(function(e){return e.toLowerCase()})),supported:n}},{hash:"",supported:[]})}function T(e){var t;try{return"undefined"==typeof window?0:(t="session"===e?window.sessionStorage:window.localStorage)?(t.setItem("__sdk_probe__","1"),t.removeItem("__sdk_probe__"),1):0}catch(e){return 0}}function x(){try{return"undefined"!=typeof indexedDB?1:0}catch(e){return 0}}function D(){try{return"undefined"!=typeof navigator&&navigator.serviceWorker?1:0}catch(e){return 0}}function M(e){return/Trident|MSIE/i.test(e=(e||"")+"")?"trident":/Edge\/\d/i.test(e)&&!/Edg\//i.test(e)?"edgehtml":/Edg\//i.test(e)||/Chrome\//i.test(e)&&!/Edg\//i.test(e)?"chromium":/Firefox\//i.test(e)?"gecko":/Safari\//i.test(e)&&!/Chrome\//i.test(e)?"webkit":"unknown"}function I(e,t){return t=(t||"")+"",/HarmonyOS|OpenHarmony/i.test(e=(e||"")+"")?"harmonyos":/iPhone|iPad|iPod/i.test(e)?"ios":/Android/i.test(e)?"android":/Mac OS X|Macintosh/i.test(e)||/^Mac/i.test(t)?"macos":/Windows/i.test(e)||/^Win/i.test(t)?"windows":/Linux/i.test(e)||/Linux/i.test(t)?"linux":"unknown"}function A(e){return/iPad|Tablet/i.test(e=(e||"")+"")?"tablet":/Mobile|iPhone|Android/i.test(e)?"phone":"desktop"}function O(e){var t,n,r;for(e=(e||"")+"",t=[[/EdgiOS\/([\d.]+)/i,"Edge"],[/CriOS\/([\d.]+)/i,"Chrome"],[/FxiOS\/([\d.]+)/i,"Firefox"],[/Edg\/([\d.]+)/i,"Edge"],[/OPR\/([\d.]+)/i,"Opera"],[/Chrome\/([\d.]+)/i,"Chrome"],[/Firefox\/([\d.]+)/i,"Firefox"],[/Version\/([\d.]+).*Safari/i,"Safari"]],n=0;t.length>n;n++)if(r=e.match(t[n][0]))return{browser_name:t[n][1],browser_version:r[1]||"unknown"};return{browser_name:"unknown",browser_version:"unknown"}}function L(){var e=k(function(){var e,t="undefined"!=typeof screen?screen:null;if(t&&t.orientation&&t.orientation.type)return t.orientation.type+"";if("undefined"!=typeof window&&"number"==typeof window.orientation){if(0===(e=window.orientation)||180===e)return"portrait-primary";if(90===e||-90===e)return"landscape-primary"}return t&&null!=t.width&&null!=t.height?t.width>t.height?"landscape-primary":"portrait-primary":"unknown"},"unknown");if(-1!==e.indexOf("landscape"))return"landscape";if(-1!==e.indexOf("portrait"))return"portrait";try{if("undefined"!=typeof screen&&null!=screen.width&&null!=screen.height){if(screen.width>screen.height)return"landscape";if(screen.widthu;u++){l=s[u];try{null!=(d=t[l])&&(c[l]=t.getParameter(d)+"")}catch(e){}}for(_=Object.keys(c).sort(),p=[],f=0;_.length>f;f++)p.push((h=_[f])+"="+c[h]);return g=b(p.join("|")),m=b([n,r,a,g].join("|")),{vendor:n,renderer:r,extensions_hash:a,params_hash:g,webgl_fp:m}},{vendor:"",renderer:"",extensions_hash:"",params_hash:"",webgl_fp:""}),H=F.webgl_fp||"",U=S(),X=k(function(){var e,t,n;if("undefined"==typeof navigator||!navigator.plugins)return"";for(e=[],t=0;t0?Math.round(t):-1},-1),J=(R.platform||"")+"",Q=0;try{Q=R.plugins?R.plugins.length:0}catch(R){}e=G.count,t=H?"available":"unavailable",n=!0===R.webdriver?1:0,r=0,o=0;try{K&&K.documentElement&&(r=K.documentElement.clientWidth||B.innerWidth||0,o=K.documentElement.clientHeight||B.innerHeight||0)}catch(R){}i=0,(1===n||"available"!==t&&0===Q&&e>=0&&2>=e&&(0>=r||0>=o))&&(i=1),a="";try{"undefined"!=typeof Intl&&Intl.DateTimeFormat&&(a=Intl.DateTimeFormat().resolvedOptions().timeZone||"")}catch(R){}s=function(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(R){return""}}(),c="",u="",l="";try{"undefined"!=typeof location&&(c=location.hostname||"",u=location.pathname||"",location.search&&(l=b(location.search)))}catch(R){}_=function(e){var t,n=((e||"")+"").trim();if(!n)return{referer_domain:"",referer_path:""};try{return{referer_domain:(t=new URL(n)).hostname||"",referer_path:t.pathname||""}}catch(e){return{referer_domain:"",referer_path:""}}}(d=K&&K.referrer?K.referrer+"":""),p=function(e){var t,n,r,o;if(t=(e=e||{}).userAgent,/iPhone|iPad|iPod/i.test(t=(t||"")+"")&&!/CriOS\/|FxiOS\/|EdgiOS\/|OPiOS\//i.test(t)&&/Version\/[\d.]+.*Safari/i.test(t))return O(e.userAgent);try{if((n=e.userAgentData&&e.userAgentData.brands)&&n.length)for(r=0;n.length>r;r++)if((o=(n[r].brand||"")+"")&&"Chromium"!==o&&0!==o.indexOf("Not")&&"Not.A/Brand"!==o)return{browser_name:o,browser_version:(n[r].version||"unknown")+""}}catch(e){}return O(e.userAgent)}(R),f=z.supported?z.supported.length:-1,g=Math.round(96*(h=B.devicePixelRatio||1)),m=-1;try{null!=R.hardwareConcurrency&&R.hardwareConcurrency>0&&(m=R.hardwareConcurrency)}catch(R){}y=m,v=-1;try{null!=R.deviceMemory&&R.deviceMemory>0&&(v=Math.round(10*R.deviceMemory)/10)}catch(R){}return w=j&&j.type?j.type+"":"unknown",C=R.maxTouchPoints||0,{platform:"web",os_family:I(V,J),device_type:A(V),browser_kernel:M(V),browser_name:p.browser_name,browser_version:p.browser_version,browser_platform:J,display_mode:k(function(){return"undefined"==typeof window||"function"!=typeof window.matchMedia?"unknown":window.matchMedia("(display-mode: standalone)").matches?"standalone":window.matchMedia("(display-mode: fullscreen)").matches?"fullscreen":window.matchMedia("(display-mode: minimal-ui)").matches?"minimal-ui":"browser"},"unknown"),visibility_state:k(function(){if("undefined"==typeof document)return"unknown";var e=document.visibilityState;return"visible"===e||"hidden"===e?e:"unknown"},"unknown"),screen_width:q.width||0,screen_height:q.height||0,screen_dpi:g,device_pixel_ratio:Math.round(100*h),orientation:L(),viewport_width:r>0?Math.round(r):-1,viewport_height:o>0?Math.round(o):-1,color_depth:q.colorDepth||0,max_touch_points:C,touch_support:C>0?1:0,hardware_concurrency:m,cpu_cores:y,device_memory_gb:v,is_debug:0,language:R.language||"",timezone:a,cookie_enabled:R.cookieEnabled?1:0,local_storage_enabled:T("local"),session_storage_enabled:T("session"),indexeddb_enabled:x(),service_worker_enabled:D(),network_type:"unknown",connection_type:w,effective_type:j&&j.effectiveType?j.effectiveType+"":"unknown",downlink:j&&null!=j.downlink?Math.round(1e3*j.downlink):-1,rtt:j&&null!=j.rtt?j.rtt:-1,canvas_fp:N,webgl_fp:H,webgl_vendor:F.vendor||"",webgl_renderer:F.renderer||"",webgl_extensions_hash:F.extensions_hash||"",webgl_params_hash:F.params_hash||"",audio_fp:U,audio_sample_rate:W>0?W:44100,fonts:G.installed||[],fonts_hash:G.hash,plugins:Y,plugins_hash:X,mimes:z.supported||[],mimes_hash:z.hash,mime_count:f,plugin_count:Q,font_count:e,webgl_status:t,has_webdriver:n,is_headless:i,headless_reason:i?n?"webdriver":"composite":"",current_url:s,current_domain:c,current_path:u,current_query_hash:l,referer:d,referer_domain:_.referer_domain,referer_path:_.referer_path}}function q(e){return null==e?"":e+""}function B(e){var t=e||{},n={app_id:(t.appId||"")+"",uid:null!=t.uid?t.uid:0,channel:(t.channel||"")+"",sid:(t.sid||"")+"",client_ts:Math.floor(Date.now()/1e3),device:(t.deviceType||"PC")+"",device_id:(t.deviceId||"")+"",device_brand:(t.deviceBrand||"")+"",device_model:(t.deviceModel||"")+"",device_fingerprint:(t.deviceFingerprint||"")+"",fp_version:(t.fpVersion||"")+"",user_agent:"undefined"!=typeof navigator&&navigator.userAgent||"",sdk_version:(t.sdkVersion||"")+"",app_version:(t.appVersion||"1.0.0")+""};return null!=t.deviceEnv&&(n.device_env=t.deviceEnv),n}function K(){var e,t,n,r=new Uint8Array(16);if("undefined"!=typeof crypto&&crypto.getRandomValues)crypto.getRandomValues(r);else for(e=0;16>e;e++)r[e]=Math.floor(256*Math.random());for(t="",n=0;r.length>n;n++)t+=("0"+r[n].toString(16)).slice(-2);return t}function V(){try{return!("undefined"==typeof window||!window.sessionStorage||(window.sessionStorage.setItem("__sdk_ss_ping__","1"),window.sessionStorage.removeItem("__sdk_ss_ping__"),0))}catch(e){return!1}}function j(){if(!un){if(un=!0,function(){try{return!("undefined"==typeof window||!window.localStorage||(window.localStorage.setItem("__web_sdk_ping__","1"),window.localStorage.removeItem("__web_sdk_ping__"),0))}catch(e){return!1}}())try{window.localStorage.removeItem(rn)}catch(e){}if("undefined"!=typeof document&&"string"==typeof document.cookie)try{var t="path=/;max-age=0;SameSite=Lax";"undefined"!=typeof location&&"https:"===location.protocol&&(t+=";Secure"),document.cookie=encodeURIComponent(on)+"=;"+t}catch(e){}}}function N(t){j();var n=function(e){return JSON.stringify({sid:e.sid,sessionStartTs:e.sessionStartTs,lastValidBehaviorAt:e.lastValidBehaviorAt})}(t);if(V())try{window.sessionStorage.setItem(tn,t.sid),window.sessionStorage.setItem(nn,n)}catch(e){}cn=t}function F(){if(cn)return cn;j();var t=null;if(V())try{t=window.sessionStorage.getItem(nn)}catch(e){}return cn=t?function(t){try{var n=JSON.parse(t);return n&&n.sid?{sid:n.sid+"",sessionStartTs:"number"==typeof n.sessionStartTs?n.sessionStartTs:Date.now(),lastValidBehaviorAt:"number"==typeof n.lastValidBehaviorAt?n.lastValidBehaviorAt:Date.now()}:null}catch(e){return null}}(t):null,cn}function H(e){return{sid:K(),sessionStartTs:e,lastValidBehaviorAt:e}}function U(e){return"number"==typeof e&&isFinite(e)?1e12>e?1e3*e:e:null}function X(e,t,n){var r=U(e.sessionStartTs)||n,o=U(e.lastValidBehaviorAt)||r,i=n;return"idle"===t?i=Math.min(i=Math.max(r,o+an),n):"forced"===t&&(i=Math.min(r+sn,n)),Math.max(r,i)}function Y(e,t,n){var r,o;return n=n||Date.now(),r=U(e.sessionStartTs)||n,o=X(e,t,n),{sid:e.sid,sessionStartTs:r,lastValidBehaviorAt:U(e.lastValidBehaviorAt)||r,sessionEndTs:o,endReason:t}}function G(e){var t,n,r,o,i;return e=e||Date.now(),(t=F())?(r=e-t.sessionStartTs>sn,e-t.lastValidBehaviorAt>an||r?(o=Y(t,r?"forced":"idle",e),N(i=H(e)),{sid:i.sid,isNew:!0,endedSession:o}):{sid:t.sid,isNew:!1}):(N(n=H(e)),{sid:n.sid,isNew:!0})}function z(){var e=F();return e?e.sid:""}function W(){cn=null}function J(){try{return!("undefined"==typeof window||!window.localStorage||(window.localStorage.setItem(dn,"1"),window.localStorage.removeItem(dn),0))}catch(e){return!1}}function Q(){var t,n;if(!J())return Promise.resolve(_n||"NULL");try{return(t=window.localStorage.getItem(ln))&&/^[0-9a-f]{32}$/.test(t)?Promise.resolve(t):(n=function(){var e,t,n,r=new Uint8Array(16);if("undefined"!=typeof crypto&&crypto.getRandomValues)crypto.getRandomValues(r);else for(e=0;16>e;e++)r[e]=Math.floor(256*Math.random());for(t="",n=0;r.length>n;n++)t+=("0"+r[n].toString(16)).slice(-2);return t}(),window.localStorage.setItem(ln,n),_n=n,Promise.resolve(n))}catch(e){return Promise.resolve("NULL")}}function $(t,n){n&&(t.audio_fp=n),fn=t;var r=function(){if(!J())return"";try{var t=window.localStorage.getItem(pn);if(t&&/^[0-9a-f]{16,64}$/i.test(t))return t.toLowerCase()}catch(e){}return""}();return r?hn=r:(hn=function(e){var t,n,r,o,i,a;for(t=e||{},n={screen_width:1,screen_height:1,screen_dpi:1,device_pixel_ratio:1,color_depth:1,max_touch_points:1,cpu_cores:1,cookie_enabled:1,local_storage_enabled:1,session_storage_enabled:1,indexeddb_enabled:1,service_worker_enabled:1},r={canvas_fp:1,webgl_fp:1,audio_fp:1,fonts_hash:1,plugins_hash:1,mimes_hash:1},o=[],i=0;Qt.length>i;i++)o.push((a=Qt[i])+"="+w(t[a],n[a]?"int":r[a]?"hash":"string"));return d("fp_v3|fingerprint_platform=web|"+o.join("|"))}(t),function(t){if(t&&J())try{window.localStorage.setItem(pn,t)}catch(e){}}(hn)),{snapshot:t,fingerprint:hn}}function Z(){return fn&&hn?Promise.resolve({snapshot:fn,fingerprint:hn}):gn||(gn=new Promise(function(e){k(function(){var t,n,r,o,i="undefined"!=typeof OfflineAudioContext?OfflineAudioContext:"undefined"!=typeof webkitOfflineAudioContext?webkitOfflineAudioContext:null;i?((n=(t=new i(1,44100,44100)).createOscillator()).type="triangle",n.frequency.value=1e4,r=t.createDynamicsCompressor(),n.connect(r),r.connect(t.destination),n.start(0),(o=t.startRendering())&&"function"==typeof o.then?o.then(function(t){for(var n=t.getChannelData(0),r=0,o=4500;5e3>o&&n.length>o;o++)r+=Math.abs(n[o]);e(b(r+""))}).catch(function(){e("")}):e(S())):e("")},e(""))}).then(function(e){return $(R(),e)}).catch(function(){return $(R(),"")}),gn)}function ee(){return hn}function te(){return Wt}function ne(){return mn.digest()}function re(){try{if("undefined"!=typeof screen&&screen.orientation&&screen.orientation.type){var t=screen.orientation.type+"";if(-1!==t.indexOf("landscape"))return"landscape";if(-1!==t.indexOf("portrait"))return"portrait"}if("undefined"!=typeof screen&&null!=screen.width&&null!=screen.height){if(screen.width>screen.height)return"landscape";if(screen.width0?Math.round(n):-1}catch(e){return-1}}function ie(e,t){var n=t.accumulated_ms||0;return null!=t.resume_at_ms&&(n+=Math.max(0,e-t.resume_at_ms)),n}function ae(){return xn.pageSeqCounter++,xn.currentPageSeq=xn.pageSeqCounter,xn.currentPageSeq}function se(){try{return!("undefined"==typeof window||!window.sessionStorage||(window.sessionStorage.setItem("__sdk_metrics_ping__","1"),window.sessionStorage.removeItem("__sdk_metrics_ping__"),0))}catch(e){return!1}}function ce(e){var t,n=e||xn;return{eventCountTotal:n.eventCountTotal||0,businessEventCount:n.businessEventCount||0,pageViewCount:n.pageViewCount||0,pageClickCount:n.pageClickCount||0,networkChangeCount:n.networkChangeCount||0,backgroundCount:n.backgroundCount||0,pageKeys:le({},n.pageKeys||{}),pageStayList:(n.pageStayList||[]).slice(),pageSeqCounter:n.pageSeqCounter||0,currentPageSeq:n.currentPageSeq||0,lastEventName:n.lastEventName||"",scrollTotalCount:n.scrollTotalCount||0,scrollTotalDistance:n.scrollTotalDistance||0,scrollUpCount:n.scrollUpCount||0,scrollDownCount:n.scrollDownCount||0,scrollDirectionChangeCount:n.scrollDirectionChangeCount||0,lastScrollDirection:n.lastScrollDirection||0,firstScrollDelayMs:null!=n.firstScrollDelayMs?n.firstScrollDelayMs:-1,hasScrolled:!!n.hasScrolled,maxScrollDepthPercent:n.maxScrollDepthPercent||0,isReachBottom:n.isReachBottom||0,keydownTotalCount:n.keydownTotalCount||0,inputCount:n.inputCount||0,focusTotalCount:n.focusTotalCount||0,firstInteractionDelayMs:null!=n.firstInteractionDelayMs?n.firstInteractionDelayMs:-1,firstInteractionRecorded:!!n.firstInteractionRecorded,lastOrientation:n.lastOrientation||"unknown",touchTotalCount:n.touchTotalCount||0,swipeTotalCount:n.swipeTotalCount||0,longPressTotalCount:n.longPressTotalCount||0,pinchTotalCount:n.pinchTotalCount||0,currentPage:(t=n.currentPage,t&&"object"==typeof t?{page_key:(t.page_key||"")+"",page_name:(t.page_name||"")+"",enter_time:null!=t.enter_time?t.enter_time:Date.now(),enter_time_ms:null!=t.enter_time_ms?t.enter_time_ms:t.enter_time,page_seq_in_sid:t.page_seq_in_sid||0,has_interaction:!!t.has_interaction,accumulated_ms:t.accumulated_ms||0,resume_at_ms:null!=t.resume_at_ms?t.resume_at_ms:null,hidden_paused:!!t.hidden_paused,stay_duration_ms:t.stay_duration_ms}:null)}}function ue(e,t){return null==e||0>e||t>0&&e>t?-1:e}function le(e){var t,n,r,o,i=e||{};for(t=1;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)i[r[o]]=n[r[o]];return i}function de(e){return kn+":"+(e||"")}function _e(t){var n,r;if(!t)return null;if(n=null,se())try{n=window.sessionStorage.getItem(de(t))}catch(e){}if(!n&&ye())try{n=window.localStorage.getItem(de(t))}catch(e){}if(!n)return null;try{return(r=JSON.parse(n))&&r.sid===t?r:null}catch(e){return null}}function pe(t,n){if(t){var r=JSON.stringify(n||{});if(se())try{window.sessionStorage.setItem(de(t),r)}catch(e){}if(ye())try{window.localStorage.setItem(de(t),r)}catch(e){}}}function fe(e){if(!e)return null;var t=_e(e);return t&&t.metrics?t.metrics:null}function he(){var e,t=z();t&&(Cn?Sn||(Sn=t):(Cn=!0,(e=_e(t))&&e.metrics?(xn=ce(e.metrics),e.sessionStartedAt&&(Dn=e.sessionStartedAt),Sn=t):Sn=t))}function ge(e){var t=_e(e);return t&&t.behaviorHash?t.behaviorHash:""}function me(){var e=z();e&&(Sn&&Sn!==e||(Sn||(Sn=e),function(e){e&&pe(e,{sid:e,savedAt:Date.now(),sessionStartedAt:Dn,behaviorHash:ne(),metrics:ce(xn)})}(e)))}function ye(){try{return!("undefined"==typeof window||!window.localStorage||(window.localStorage.setItem("__sdk_metrics_ping__","1"),window.localStorage.removeItem("__sdk_metrics_ping__"),0))}catch(e){return!1}}function ve(){var t,n;if(!ye())return[];try{return(t=window.localStorage.getItem(wn))?(n=JSON.parse(t),Array.isArray(n)?n:[]):[]}catch(e){return[]}}function we(t){if(ye())try{window.localStorage.setItem(wn,JSON.stringify(t||[]))}catch(e){}}function ke(e){we(ve().filter(function(t){return t&&t.sid!==e}))}function be(e){for(var t,n=e.slice(0,yn);n.length>0;){if(t=JSON.stringify(n),vn>=t.length)return n;n.pop()}return n}function Ce(e){var t,n=xn.currentPage;n&&(n.stay_duration_ms=ie(e,n),t={page_key:n.page_key,page_name:n.page_name||"",stay_duration_ms:n.stay_duration_ms,enter_time:Math.floor(n.enter_time/1e3),has_interaction:n.has_interaction?1:0},n.page_seq_in_sid&&(t.page_seq_in_sid=n.page_seq_in_sid),xn.pageStayList.push(t),xn.pageStayList=be(xn.pageStayList),xn.currentPage=null)}function Se(){xn.firstInteractionRecorded||(xn.firstInteractionRecorded=!0,xn.firstInteractionDelayMs=Math.max(0,Date.now()-Dn))}function Pe(){xn.lastOrientation=re()}function Ee(e){e&&xn.currentPage&&xn.currentPage.page_key===e&&(xn.currentPage.has_interaction=!0)}function Te(e,t,n,r){var o=Date.now();Ce(o),xn.currentPage={page_key:(e||"")+"",page_name:(t||"")+"",enter_time:null!=n?1e3*n:o,enter_time_ms:o,page_seq_in_sid:r||xn.currentPageSeq||0,has_interaction:!1,accumulated_ms:0,resume_at_ms:o,hidden_paused:!1}}function xe(){Ce(Date.now())}function De(t){var n,r=t;if(null==r)return null;if("string"==typeof r)try{r=JSON.parse(r)}catch(e){return null}return r&&"object"==typeof r?(n={page_key:(r.page_key||"")+"",page_name:(r.page_name||"")+"",stay_duration_ms:null!=r.stay_duration_ms?r.stay_duration_ms:0,enter_time:null!=r.enter_time?r.enter_time:0,has_interaction:r.has_interaction?1:0},null!=r.page_seq_in_sid&&(n.page_seq_in_sid=r.page_seq_in_sid),n):null}function Me(e,t,n){var r,o,i,a,s,c,u,l,d,_,p,f=e||{},h=f.sid||z(),g=h&&h===z();for(n?r=ce(n):g?(xe(),me(),r=ce(xn)):r=function(e){var t,n;return he(),!(t=e||z())||t!==z()||Sn&&Sn!==t?(n=fe(t))?ce(n):Sn&&Sn!==t?{eventCountTotal:0,businessEventCount:0,pageViewCount:0,pageClickCount:0,networkChangeCount:0,backgroundCount:0,pageKeys:{},pageStayList:[],pageSeqCounter:0,currentPageSeq:0,lastEventName:"",scrollTotalCount:0,scrollTotalDistance:0,scrollUpCount:0,scrollDownCount:0,scrollDirectionChangeCount:0,lastScrollDirection:0,firstScrollDelayMs:-1,hasScrolled:!1,maxScrollDepthPercent:0,isReachBottom:0,keydownTotalCount:0,inputCount:0,focusTotalCount:0,firstInteractionDelayMs:-1,firstInteractionRecorded:!1,lastOrientation:"unknown",touchTotalCount:0,swipeTotalCount:0,longPressTotalCount:0,pinchTotalCount:0,currentPage:null}:ce(xn):ce(xn)}(h),o=function(e,t){var n=Date.now(),r=e||{},o=U(r.sessionStartTs)||n,i=null!=r.sessionEndTs?U(r.sessionEndTs):X(r,t||"idle",n);return{startTs:o,endTs:i=Math.max(o,i||n)}}(f,t),s=Math.max(0,(a=o.endTs)-(i=o.startTs)),c=le({},r.pageKeys||{}),u=function(e){var t,n,r=[];for(t=0;(e||[]).length>t;t++)(n=De(e[t]))&&r.push(n);return r}(function(e){var t,n,r,o={},i=[];for(t=0;e.length>t;t++)null!=(r=(n=e[t])&&n.page_seq_in_sid)&&o[r]||(null!=r&&(o[r]=!0),i.push(n));return i}((r.pageStayList||[]).slice()).sort(function(e,t){return(e.page_seq_in_sid||0)-(t.page_seq_in_sid||0)})),u=function(e,t,n){var r,o,i,a=Math.floor((t||0)/1e3),s=Math.floor((n||Date.now())/1e3),c=Math.max(0,a-5),u=[];for(r=0;(e||[]).length>r;r++)(o=e[r])&&((i=null!=o.enter_time?+o.enter_time:0)>0&&c>i||i>s+5||u.push(o));return u}(u,i,a),l=0;u.length>l;l++)(d=u[l]&&u[l].page_key?u[l].page_key+"":"")&&(c[d]=1);return _=Object.keys(c),p=n?ge(h)||ne():g?ne():ge(h)||ne(),{session_start_ts:Math.floor(i/1e3),session_end_ts:Math.floor(a/1e3),session_duration_ms:s,event_count_total:r.eventCountTotal||0,business_event_count:r.businessEventCount||0,page_count:_.length,page_view_count:r.pageViewCount||0,page_click_count:r.pageClickCount||0,session_end_reason:(t||"idle")+"",last_event_name:r.lastEventName||"",page_stay_list:u,behavior_sequence_hash:p,network_change_count:r.networkChangeCount||0,scroll_total_count:r.scrollTotalCount||0,scroll_total_distance_px:Math.round(r.scrollTotalDistance||0),scroll_up_count:r.scrollUpCount||0,scroll_down_count:r.scrollDownCount||0,scroll_direction_change_count:r.scrollDirectionChangeCount||0,first_scroll_delay_ms:ue(null!=r.firstScrollDelayMs?r.firstScrollDelayMs:-1,s),max_scroll_depth_percent:r.maxScrollDepthPercent||0,is_reach_bottom:r.isReachBottom||0,content_height:oe(),keydown_total_count:r.keydownTotalCount||0,input_count:r.inputCount||0,focus_total_count:r.focusTotalCount||0,first_interaction_delay_ms:ue(null!=r.firstInteractionDelayMs?r.firstInteractionDelayMs:-1,s),orientation:r.lastOrientation||re(),touch_total_count:r.touchTotalCount||0,swipe_total_count:r.swipeTotalCount||0,long_press_total_count:r.longPressTotalCount||0,pinch_total_count:r.pinchTotalCount||0}}function Ie(){On=!0,setTimeout(function(){On=!1},2e3)}function Ae(e){var t,n,r,o,i=e||{};for(t=1;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)i[r[o]]=n[r[o]];return i}function Oe(t){return e((null==t?"":t)+"").substr(0,32)}function Le(e){return null!=e&&""!==(e+"").trim()}function Re(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(e){return""}}function qe(e){var t=(null==e?"":e)+"";return(t=(t=(t=t.replace(/\.html?$/i,"")).replace(/[^a-zA-Z0-9_]+/g,"_")).replace(/_+/g,"_").replace(/^_|_$/g,""))||"index"}function Be(){try{return qe(("undefined"!=typeof location&&location.pathname?location.pathname:"/").replace(/^\/+|\/+$/g,"").replace(/\//g,"_")||"index")}catch(e){return"index"}}function Ke(){try{return document.scrollingElement||document.documentElement||document.body||null}catch(e){return null}}function Ve(){try{var t=Ke();return t?{scroll_top:Math.max(0,Math.round(t.scrollTop||0)),scroll_left:Math.max(0,Math.round(t.scrollLeft||0))}:{scroll_top:-1,scroll_left:-1}}catch(e){return{scroll_top:-1,scroll_left:-1}}}function je(e,t){if(null==e||!isFinite(e))return-1;var n=Math.round(+e);return 0>n&&(n=0),null!=t&&t>0&&n>t&&(n=t),n}function Ne(){var t,n,r;try{return(t=document.documentElement||document.body)?(r=t.scrollHeight||0,(n=t.scrollWidth||0)>0&&r>0?{page_width:Math.round(n),page_height:Math.round(r)}:{page_width:-1,page_height:-1}):{page_width:-1,page_height:-1}}catch(e){return{page_width:-1,page_height:-1}}}function Fe(){var t,n;try{return"undefined"==typeof window?{viewport_width:-1,viewport_height:-1}:(t=window.innerWidth||0,n=window.innerHeight||0,t>0&&n>0?{viewport_width:Math.round(t),viewport_height:Math.round(n)}:{viewport_width:-1,viewport_height:-1})}catch(e){return{viewport_width:-1,viewport_height:-1}}}function He(){var t,n;try{return"undefined"==typeof screen?{screen_width:-1,screen_height:-1}:(t=screen.width||0,n=screen.height||0,t>0&&n>0?{screen_width:Math.round(t),screen_height:Math.round(n)}:{screen_width:-1,screen_height:-1})}catch(e){return{screen_width:-1,screen_height:-1}}}function Ue(){var t,n,r;try{if("undefined"!=typeof performance&&performance.timing&&(t=performance.timing).loadEventEnd&&t.navigationStart&&t.loadEventEnd>0)return Math.max(0,t.loadEventEnd-t.navigationStart);if("undefined"!=typeof performance&&"function"==typeof performance.getEntriesByType&&(n=performance.getEntriesByType("navigation"))&&n.length>0&&(r=n[0]).loadEventEnd>0&&r.fetchStart>=0)return Math.max(0,Math.round(r.loadEventEnd-r.fetchStart))}catch(e){}return 0}function Xe(e,t){return t>0?Math.min(100,Math.max(0,Math.round(e/t*100))):0}function Ye(e){return e?{clientX:null!=e.clientX?e.clientX:null,clientY:null!=e.clientY?e.clientY:null,pageX:null!=e.pageX?e.pageX:null,pageY:null!=e.pageY?e.pageY:null}:{}}function Ge(e){var t,n,r,o,i,a,s,c,u=e||{},l=Fe(),d=Ne(),_=Ve(),p=function(e){return!!e&&(null!=e.clientX||null!=e.clientY||null!=e.pageX||null!=e.pageY)}(u),f=d.page_width>0&&d.page_height>0,h=He(),g=f&&_.scroll_top>=0?_.scroll_top:0,m=f&&_.scroll_left>=0?_.scroll_left:0,y=l.viewport_width>0?l.viewport_width:-1,v=l.viewport_height>0?l.viewport_height:-1;return p?(n=null!=u.clientY?+u.clientY:null,r=null!=u.pageX?+u.pageX:null,o=null!=u.pageY?+u.pageY:null,null!=(t=null!=u.clientX?+u.clientX:null)||null!=n?(i=null!=t&&isFinite(t)?t:0,a=null!=n&&isFinite(n)?n:0,s=f?null!=r&&isFinite(r)?r:i+m:-1,c=f?null!=o&&isFinite(o)?o:a+g:-1):(s=f&&null!=r&&isFinite(r)?r:-1,c=f&&null!=o&&isFinite(o)?o:-1,i=null!=r&&isFinite(r)?r-m:0,a=null!=o&&isFinite(o)?o-g:0),i=je(i,y),a=je(a,v),f&&(s=je(s,d.page_width),c=je(c,d.page_height)),Ae({click_viewport_x:i,click_viewport_y:a,click_document_x:f?s:-1,click_document_y:f?c:-1,click_page_x:i,click_page_y:a,click_x_percent:y>0?Xe(i,y):-1,click_y_percent:v>0?Xe(a,v):-1,screen_width:h.screen_width,screen_height:h.screen_height},l,f?d:{page_width:-1,page_height:-1},f?_:{scroll_top:-1,scroll_left:-1})):{click_viewport_x:-1,click_viewport_y:-1,click_document_x:-1,click_document_y:-1,click_page_x:-1,click_page_y:-1,click_x_percent:-1,click_y_percent:-1,screen_width:h.screen_width,screen_height:h.screen_height,viewport_width:l.viewport_width,viewport_height:l.viewport_height,page_width:f?d.page_width:-1,page_height:f?d.page_height:-1,scroll_top:f?_.scroll_top:-1,scroll_left:f?_.scroll_left:-1}}function ze(t){var n=t||{},r=qe(n.pageKey||Be()),o=n.pageName||"undefined"!=typeof document&&document.title||r,i=function(){try{return"undefined"==typeof location?{}:{page_url:Re(),page_domain:(location.hostname||"")+"",page_path:(location.pathname||"")+"",page_query_hash:location.search?Oe(location.search):""}}catch(e){return{}}}(),a=Fe();return{user_type:"",page_key:r,page_name:o,referrer_page_key:(n.previousPageKey||"")+"",referrer_page_name:(n.previousPageName||"")+"",current_page_key:r,current_page_name:o,recommend_trace_id:"",page_url:i.page_url,viewport_width:a.viewport_width,viewport_height:a.viewport_height,is_sdk_auto_collected:1}}function We(e){var t,n,r,o,i=e||{};for(t=1;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)i[r[o]]=n[r[o]];return i}function Je(e,t){var n,r;return"app_session_summary"===t?We({},e.summaryFields||{},e.payload||{}):(n=function(e){var t=We({},e||{});return delete t._metricsRecorded,delete t._pageSeqPrealloc,delete t._clickSnap,t}(e.payload||{}),r={event:1,payload:1,event_id:1,summaryFields:1,_pageSeqInSid:1,_metricsRecorded:1,_sdkAutoClick:1,_pageSeqPrealloc:1},Object.keys(e).forEach(function(t){r[t]||(n[t]=e[t])}),n)}function Qe(e,t,n,r,o){var i,a,s,c,u,d,h,g=e||{},y=((n.event||"")+"").trim(),v=n.lifecycle_event_key||n.payload&&n.payload.lifecycle_event_key||"";return function(e,t){var n,r=(e||"")+"";return"app_page_view"===r||"page_click"===r||("app_lifecycle"===r?"cold_start"==(n=(t||"")+"")||"web_load"===n||"warm_start"===n||"foreground"===n:"sdk_init"!==r&&"network_state_change"!==r&&"app_session_summary"!==r&&"background"!==r)}(y,v)&&(c=Date.now(),(u=F())?(u.lastValidBehaviorAt=c,N(u)):N(H(c))),i="",n.payload&&null!=n.payload.page_key?i=n.payload.page_key+"":null!=n.page_key&&(i=n.page_key+""),function(e,t,n){"app_lifecycle"===e&&t&&en[t]?mn.append(t,n):("app_page_view"===e||"page_click"===e||e&&!Tn[e])&&mn.append(e,n)}(y,v,i),a=function(e,t){return B({appId:e.appId,channel:e.channel,uid:e.uid,sid:t,deviceId:(n=e.deviceId,((null==n?"":n)+"").trim()||"NULL"),deviceType:e.deviceType||"PC",deviceBrand:e.deviceBrand||"",deviceModel:e.deviceModel||"",deviceFingerprint:ee(),fpVersion:te(),sdkVersion:e.sdkVersion,appVersion:e.appVersion});var n}(g,t),function(e,t){var n,r;if(he(),(n=(e||"")+"")&&"app_session_summary"!==n){if("app_page_view"===n&&t&&t._metricsRecorded)return xn.lastEventName=n,void me();xn.eventCountTotal++,xn.lastEventName=n,Tn[n]||xn.businessEventCount++,r=t&&null!=t.page_key?t.page_key+"":"","app_page_view"===n?(xn.pageViewCount++,r&&(xn.pageKeys[r]=1)):"page_click"===n?(xn.pageClickCount++,Ee(r)):"network_state_change"===n?xn.networkChangeCount++:r&&Ee(r),me()}}(y,n.payload||{}),s=function(e,t){return function(e,t){for(var n=t||{},r={},o=Object.keys(e||{}),i=0;o.length>i;i++)r[o[i]]=e[o[i]];return r.event_sig_version=(n.eventSigVersion||Jt)+"",r.event_sig=n.hmacKey?function(e,t){return t?function(e,t){var n,r,o,i,a=_((null==t?"":t)+"");for(a.length>64&&(a=p(l(a)));64>a.length;)a.push(0);for(n=[],r=[],o=0;64>o;o++)n.push(54^a[o]),r.push(92^a[o]);return i=p(l(f(n,_((null==e?"":e)+"")))),l(f(r,i))}(function(e){var t=e||{};return[q(t.event),q(t.event_id),q(t.app_id),q(t.client_ts),q(t.device_fingerprint)].join("|")}(e),t+""):""}(e,n.hmacKey):"",r}(e,{hmacKey:t&&t.hmacKey?t.hmacKey:"",eventSigVersion:Jt})}(function(e,t,n){return function(e,t){var n=function(){var e,t,n,r,o;for(e={},t=0;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)e[r[o]]=n[r[o]];return e}(e,t);return n.event_id||(n.event_id=m(n)),n}(e,{event:n,payload:Je(t,n)})}(a,n,y),r),"app_page_view"===y&&n.payload&&n.payload.page_key&&(n.payload._metricsRecorded||n._metricsRecorded||Te(n.payload.page_key,n.payload.page_name,n.payload.page_enter_ts,null!=n._pageSeqInSid?n._pageSeqInSid:n.payload.page_seq_in_sid)),"page_click"===y&&n.payload&&!n._sdkAutoClick&&An.push({x:null!=(d=null!=n.payload.click_viewport_x?n.payload.click_viewport_x:n.payload.click_page_x)?d:0,y:null!=(h=null!=n.payload.click_viewport_y?n.payload.click_viewport_y:n.payload.click_page_y)?h:0,t:Date.now()}),s._sessionInfo=o||null,s}function $e(t,n,r){var o=We({},n||{}),i=((o.event||"")+"").trim();return Bn[i]&&o.payload&&1!==o.payload.is_sdk_auto_collected&&(o.payload=function(e,t,n){var r,o=e||{},i=1===o.is_sdk_auto_collected,a=((n||"")+"").trim(),s="app_page_view"===a||null!=o.page_key&&(null!=o.current_page_key||null!=o.user_type),c="page_click"===a,u={}||{},l=qe(o.page_key||Be());return r=s?ze(Ae(u,{pageKey:l,pageName:o.page_name})):c?Ae({page_key:l,page_name:o.page_name||"undefined"!=typeof document&&document.title||l,recommend_trace_id:"",referrer_page_key:u.previousPageKey||""},function(e){var t=e||{},n=Fe(),r=He(),o=Ne(),i=Ve(),a=o.page_width>0&&o.page_height>0,s=a&&i.scroll_top>=0?i.scroll_top:0,c=a&&i.scroll_left>=0?i.scroll_left:0,u=n.viewport_width>0?n.viewport_width:-1,l=n.viewport_height>0?n.viewport_height:-1,d=null!=t.click_page_x?+t.click_page_x:null,_=null!=t.click_page_y?+t.click_page_y:null,p=-1,f=-1,h=-1,g=-1;return null!=d&&isFinite(d)&&null!=_&&isFinite(_)?(p=d,f=_,a&&(h=d+c,g=_+s)):null!=t.click_x_percent&&null!=t.click_y_percent&&u>0&&l>0&&(p=+t.click_x_percent/100*u,f=+t.click_y_percent/100*l,a&&(h=p+c,g=f+s)),-1!==p&&(p=je(p,u)),-1!==f&&(f=je(f,l)),a&&-1!==h&&(h=je(h,o.page_width)),a&&-1!==g&&(g=je(g,o.page_height)),{click_viewport_x:p,click_viewport_y:f,click_document_x:a?h:-1,click_document_y:a?g:-1,screen_width:r.screen_width,screen_height:r.screen_height,viewport_width:n.viewport_width,viewport_height:n.viewport_height,page_width:a?o.page_width:-1,page_height:a?o.page_height:-1,scroll_top:a?i.scroll_top:-1,scroll_left:a?i.scroll_left:-1}}(o)):{page_key:l,page_name:o.page_name||"undefined"!=typeof document&&document.title||l,recommend_trace_id:"",referrer_page_key:u.previousPageKey||""},i||s?i&&(r.is_sdk_auto_collected=1):delete r.is_sdk_auto_collected,function(e,t){var n,r,o=Ae({},e||{}),i=Object.keys(t||{});for(n=0;i.length>n;n++)Le(o[r=i[n]])||(o[r]=t[r]);return o}(o,r)}(o.payload,0,i)),Z().then(function(){var n,a=G();if(a.endedSession&&qn)try{qn(a.endedSession)}catch(e){}return n=a.sid||z(),Bn[i]&&o.payload&&function(t,n){var r,o,i,a=((t.event||"")+"").trim();Bn[a]&&t.payload&&("app_page_view"===a?1===t.payload.is_sdk_auto_collected?(r=t.payload._pageSeqPrealloc||ae(),o=t.payload._metricsRecorded,null!=t.payload._pageSeqPrealloc&&delete t.payload._pageSeqPrealloc,t.payload=function(e,t,n){var r=e||{},o=Fe(),i=Math.floor(Date.now()/1e3),a=Oe((t||"")+"|"+(n||0)+"|"+i),s=Ue(),c=qe(r.page_key||""),u=(r.page_name||"")+"";return{user_type:(null!=r.user_type?r.user_type:"")+"",page_key:c,page_name:u,referrer_page_key:(null!=r.referrer_page_key?r.referrer_page_key:"")+"",referrer_page_name:(null!=r.referrer_page_name?r.referrer_page_name:"")+"",current_page_key:(null!=r.current_page_key?r.current_page_key:c)+"",current_page_name:(null!=r.current_page_name?r.current_page_name:u)+"",recommend_trace_id:(null!=r.recommend_trace_id?r.recommend_trace_id:"")+"",page_id:a,page_enter_ts:i,page_url:r.page_url||Re(),page_load_time:null!=s?s:0,viewport_width:o.viewport_width,viewport_height:o.viewport_height}}(t.payload,n,r),t._pageSeqInSid=r,o&&(t._metricsRecorded=1)):(i=function(t,n){var r,o=t||{},i=null!=o.page_enter_ts?o.page_enter_ts:Math.floor(Date.now()/1e3),a=o.page_seq_in_sid||ae();return{page_seq_in_sid:a,page_id:o.page_id||e((null==(r=(n||"")+"|"+a+"|"+i)?"":r)+"").substr(0,32),page_enter_ts:i}}(t.payload,n),t.payload=We(t.payload,i)):"page_click"===a&&1===t.payload.is_sdk_auto_collected&&(t.payload=function(e){var t=e||{},n=Ge(t._clickSnap||t);return{page_key:qe(t.page_key||""),page_name:(t.page_name||"")+"",recommend_trace_id:(null!=t.recommend_trace_id?t.recommend_trace_id:"")+"",referrer_page_key:(null!=t.referrer_page_key?t.referrer_page_key:"")+"",click_viewport_x:n.click_viewport_x,click_viewport_y:n.click_viewport_y,click_document_x:n.click_document_x,click_document_y:n.click_document_y,click_page_x:n.click_page_x,click_page_y:n.click_page_y,click_x_percent:n.click_x_percent,click_y_percent:n.click_y_percent,screen_width:n.screen_width,screen_height:n.screen_height,viewport_width:n.viewport_width,viewport_height:n.viewport_height,page_width:n.page_width,page_height:n.page_height,scroll_top:n.scroll_top,scroll_left:n.scroll_left}}(t.payload),t._sdkAutoClick=1,delete t.payload.is_sdk_auto_collected,delete t.payload._clickSnap))}(o,n),Qe(t,n,o,r,a)})}function Ze(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(e){return""}}function et(){var t,n;try{return(t=function(){try{return document.scrollingElement||document.documentElement||document.body||null}catch(e){return null}}())&&(n=Math.max(0,(t.scrollHeight||0)-(t.clientHeight||0)))>0?Math.min(100,Math.max(0,Math.round((t.scrollTop||0)/n*100))):0}catch(e){return 0}}function tt(){if(On)return"navigation";try{if("hidden"===document.visibilityState)return"visibility_hidden"}catch(e){}return"unknown"}function nt(){return function(){if(xn.currentPage&&xn.currentPage.page_key)return xn.currentPage.page_key;var e=xn.pageStayList||[];return e.length>0&&e[e.length-1].page_key||""}()||Be()}function rt(){var t,n="unknown";try{n="visible"===document.visibilityState||"hidden"===document.visibilityState?document.visibilityState:"unknown"}catch(e){}return{page_url:Ze(),unload_reason:tt(),scroll_depth_percent_at_unload:et(),web_visibility_state:n,page_stay_duration_ms:(t=xn.currentPage,t?ie(Date.now(),t):0),last_active_page_key:nt()}}function ot(e,t,n,r){var o,i,a=n||{};return a.lifecycle_event_key=t,t===Kn&&(a=at((o=Fe(),{page_url:Ze(),page_load_time_ms:Ue(),viewport_width:o.viewport_width,viewport_height:o.viewport_height}),a)),t===Nn&&(a=at(a,rt())),t===jn&&((i=xn.currentPage)&&!i.hidden_paused&&(i.accumulated_ms=ie(Date.now(),i),i.resume_at_ms=null,i.hidden_paused=!0),a.last_active_page_key||(a.last_active_page_key=nt())),t===Vn&&function(){var e=xn.currentPage;e&&e.hidden_paused&&(e.resume_at_ms=Date.now(),e.hidden_paused=!1)}(),$e(e,{event:"app_lifecycle",payload:a},r)}function it(e,t,n,r){return function(e,t,n,r){var o=We({},n||{});return Z().then(function(){return Qe(e,t,o,r,null)})}(e,r||z(),{event:"app_session_summary",summaryFields:t||{}},n)}function at(e){var t,n,r,o,i=e||{};for(t=1;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)i[r[o]]=n[r[o]];return i}function st(e){var t,n;return!e||2>e.length?0:Math.sqrt((t=e[0].clientX-e[1].clientX)*t+(n=e[0].clientY-e[1].clientY)*n)}function ct(){Gn&&(clearTimeout(Gn),Gn=null)}function ut(t,n,r){var o,i,a,s,c,u;if(r=r||{},!t)return Promise.resolve({sent:!1,ok:!1,status:0,reason:"no_endpoint"});if(!Array.isArray(n)||0===n.length)return Promise.resolve({sent:!0,ok:!0,status:200,reason:"empty_batch"});if(o=JSON.stringify(n),i=!0===r.useBeacon,a=!1!==r.requireHttps,c=isFinite(s=+r.timeoutMs)&&s>0?Math.floor(s):Qn,!function(e,t){if(!t)return!0;var n=((e||"")+"").match(/^(https?):\/\/([^/:?#]+)/i);return!(!n||"https"!==n[1].toLowerCase()&&("http"!==n[1].toLowerCase()||!function(e){var t=((e||"")+"").toLowerCase();return"localhost"===t||"127.0.0.1"===t||"::1"===t}(n[2])))}(t,a))return void 0!==console&&console.warn&&console.warn("[sdk][web] 已拒绝非 HTTPS 上报地址(localhost HTTP 除外)"),Promise.resolve({sent:!1,ok:!1,status:0,reason:"insecure_endpoint"});if(i&&"undefined"!=typeof navigator&&"function"==typeof navigator.sendBeacon)try{if(u=new Blob([o],{type:"application/json"}),navigator.sendBeacon(t,u))return Promise.resolve({sent:!0,ok:!0,status:200,transport:"beacon",uncertain:!0})}catch(e){}return function(e,t,n){var r,o;return"function"!=typeof fetch?Promise.resolve(null):(r="undefined"!=typeof AbortController?new AbortController:null,o=r&&n>0?setTimeout(function(){r.abort()},n):null,fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:t,keepalive:!0,signal:r?r.signal:void 0}).then(function(e){return o&&clearTimeout(o),{sent:!0,ok:e.ok,status:+(e.status||0),transport:"fetch"}}).catch(function(e){return o&&clearTimeout(o),e&&"object"==typeof e&&"AbortError"===e.name?{sent:!1,ok:!1,status:0,reason:"timeout",transport:"fetch"}:null}))}(t,o,c).then(function(n){return null!==n?n:function(t,n,r){return new Promise(function(o){try{if("undefined"==typeof XMLHttpRequest)return o({sent:!1,ok:!1,status:0,reason:"no_xhr"});var i=new XMLHttpRequest;i.open("POST",t,!0),i.timeout=r,i.setRequestHeader("Content-Type","application/json"),i.onreadystatechange=function(){4===i.readyState&&o({sent:!0,ok:i.status>=200&&300>i.status,status:i.status,transport:"xhr"})},i.onerror=function(){o({sent:!1,ok:!1,status:0,reason:"xhr_error"})},i.ontimeout=function(){o({sent:!1,ok:!1,status:0,reason:"timeout",transport:"xhr"})},i.send(n)}catch(e){o({sent:!1,ok:!1,status:0,reason:"xhr_exception"})}})}(t,o,c)})}function lt(e){e=e||{},this.selectedBatchEndpoint="",this.remoteEndpoints=[],this.remoteProbePromise=null,this.remoteProbeKey="",this.shouldPersistOnFail=!1,this.hasBatchProbeStarted=!1,this.initialized=!1,this.initPromise=null,this.endpointReadyListeners=[],this.timeoutMs=+e.pingTimeoutMs||1500}function dt(e){return 255&(e<<1^(128&e?27:0))}function _t(e){return(Mt[e>>>24&255]<<24|Mt[e>>>16&255]<<16|Mt[e>>>8&255]<<8|Mt[255&e])>>>0}function pt(e,t){var n,r,o,i,a,s,c,u,l,d,_,p,f=e.slice(0);for(o=[],i=0;16>i;i++)f[i]=255&(f[i]^t[i>>2]>>>24-8*(3&i));for(a=1;14>=a;a++){for(n=[],i=0;16>i;i++)n[i]=Mt[f[i]];for(r=[],c=0;4>c;c++)for(u=0;4>u;u++)r[c+4*u]=n[c+4*(u+c&3)];if(14>a){for(o=[],s=0;4>s;s++)d=r[4*s+1],_=r[4*s+2],p=r[4*s+3],o[4*s]=255&(dt(l=r[4*s])^dt(d)^d^_^p),o[4*s+1]=255&(l^dt(d)^dt(_)^_^p),o[4*s+2]=255&(l^d^dt(_)^dt(p)^p),o[4*s+3]=255&(dt(l)^l^d^_^dt(p));for(f=o,o=[],i=0;16>i;i++)f[i]=255&(f[i]^t[4*a+(i>>2)]>>>24-8*(3&i))}else for(f=r,i=0;16>i;i++)f[i]=255&(f[i]^t[4*a+(i>>2)]>>>24-8*(3&i))}return f}function ft(e,t){var n,r,o,i=[],a=t.slice(0);for(n=0;16>n;n++)i[n]=0;for(n=0;128>n;n++){if(e[n>>3]&128>>(7&n))for(r=0;16>r;r++)i[r]^=a[r];for(o=1&a[15],r=15;r>0;r--)a[r]=255&(a[r]>>>1|a[r-1]<<7);a[0]=a[0]>>>1&255,o&&(a[0]^=225)}return i}function ht(e){var t,n=e.slice(0);for(t=15;t>=12&&(n[t]=n[t]+1&255,!n[t]);t--);return n}function gt(e){this.encryptedConfigText=(e=e||{}).encryptedConfig||e.configCipher||"",this.cachedConfigPromise=null,this._secondGcmKeyBase64="en1BNo0VBrN/zi+mI2LO7E9W40ehCBYwC+frBn8s3rQ="}function mt(e,t){var n=((e&&e.event_id||"")+"").trim();return n?n+"|||"+(t||""):null}function yt(e){var t,n={},r=Object.keys(e);for(t=0;r.length>t;t++)n[r[t]]=e[r[t]];return n}function vt(){this.maxSize=tr}function wt(e){var t,n,r=e||{},o=r.im_behavior_key,i=qt[o];if(!i)throw(t=Error("[sdk][web] 未知 im_behavior_key:"+o)).name="EventValidationError",t.eventMethod="imEvent",t.typeErrorFields=["im_behavior_key(unknown)"],t;return n={},Object.keys(r).forEach(function(e){"im_behavior_name"!==e&&(n[e]=r[e])}),n.im_behavior_name=i,n}function kt(e,t){var n,r,o,i,a,s=Lt[e];if(!s)throw Error("[sdk][web] 未知事件方法:"+e);if(n=t||{},r={},o=[],i=[],Object.keys(s).forEach(function(e){var t,a=s[e],c=1&a,u=a>>1&3,l=!!(8&a),d=n[e];l&&null===d&&(d=""),!c||l||null!=d&&""!==d?c&&l&&void 0===d?o.push(e):(c||null!=d)&&(t=1===u?function(e){return"number"==typeof e&&isFinite(e)&&e===Math.floor(e)}(d):2===u?function(e){return"number"==typeof e&&isFinite(e)}(d):function(e){return"string"==typeof e}(d),t?r[e]=d:i.push(e+"(expected "+(1===u?"int":2===u?"number":"string")+")")):o.push(e)}),o.length||i.length)throw(a=Error("[sdk][web] 事件字段校验失败:"+e)).name="EventValidationError",a.eventMethod=e,a.missingFields=o.slice(),a.typeErrorFields=i.slice(),a;return r}function bt(){var e,t,n,r,o={};for(e=0;arguments.length>e;e++)if(t=arguments[e])for(n=Object.keys(t),r=0;n.length>r;r++)o[n[r]]=t[n[r]];return o}function Ct(e){var t,n,r={};if(!e||!Array.isArray(e))return r;for(t=0;e.length>t;t++)(n=((null==e[t]?"":e[t])+"").trim())&&(Vt[n]?r[n]=1:Kt('[sdk][web] disableAutoEvents 含未知事件 "'+n+'",已忽略'));return r}function St(e){var t,n=[],r=Object.keys(e||{});for(t=0;r.length>t;t++)n.push(r[t]);return n}function Pt(e,t){this.opts=e,this.resolver=t,this.queue=[],this.timer=null,this.flushTail=Promise.resolve(),this.replaying=!1,this.replayTimer=null,this.hasPersistentItems=!0,this.persistentQueue=new vt,this._resolveEndpoint=null,this._getEventCodes=null,this.startReplayTimer()}function Et(e){var t,n;if("undefined"==typeof window)return Kt("[web-sdk] 非浏览器环境(SSR/Node.js),SDK 仅支持浏览器,初始化已跳过。"),this._ssrSkipped=!0,this.options=e,this.configMgr=null,this.resolver=null,void(this.batcher=null);if(!Object.prototype.hasOwnProperty.call(e,"channel"))throw Error('channel 为必传字段(可传空字符串 "")');this._ssrSkipped=!1,(t=bt(e)).channel=null==e.channel?"":e.channel+"",this._disableAutoEvents=Ct(t.disableAutoEvents),t.disableAutoEvents=St(this._disableAutoEvents),this.options=t,this.configMgr=new gt(e),this.resolver=new lt({pingTimeoutMs:nr}),this.batcher=new Pt(this.options,this.resolver),n=this,this.batcher._resolveEndpoint=function(e){return n.resolver.resolveEndpoint(function(){return n.configMgr.getBatchEndpoints()},e)},this.batcher._getEventCodes=function(){return n.configMgr.getEventCodes()},this.batcher._getSigningConfig=function(){return n.configMgr.getSigningConfig()},this._unsubReady=null,this._refreshTimer=null,this._autoLifecycleTeardown=null,this._webLoadTeardown=null,this._autoNetworkTeardown=null,this._pageTrackerTeardown=null,this._webInteractionTeardown=null,this._sessionPollTimer=null,this._autoEventsStarted=!1,this._init({}),this._startAutoEvents()}function Tt(e){if(!Ht)throw Error("请先调用 init() 初始化(参数须包含 channel)");return Ht}function xt(e,t,n){var r,o,i;if(n>=e.length)return Promise.resolve();if(!Ht||Ht!==t){for(o=(r=e.slice(n)).length-1;o>=0;o--)Ut.unshift(r[o]);return Promise.resolve()}return t.track((i=e[n]).event,i.endpoint).catch(function(){}).then(function(){return xt(e,t,n+1)})}function Dt(e,t){var n;return Ht?(n=Ht,(Xt||Promise.resolve()).then(function(){return n.track(e,t)})):(Ut.push({event:e,endpoint:t}),Ut.length>500&&Ut.shift(),Promise.resolve({sent:!1,ok:!0,status:0,reason:"pre_init_cached"}))}var Mt,It,At,Ot,Lt,Rt,qt,Bt,Kt,Vt,jt,Nt,Ft,Ht,Ut,Xt,Yt,Gt,zt=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],Wt="3.0.1",Jt="sig_v2",Qt=["os_family","browser_kernel","browser_platform","screen_width","screen_height","screen_dpi","device_pixel_ratio","color_depth","max_touch_points","cpu_cores","cookie_enabled","local_storage_enabled","session_storage_enabled","indexeddb_enabled","service_worker_enabled","canvas_fp","webgl_fp","audio_fp","fonts_hash","plugins_hash","mimes_hash"],$t=["Arial","Arial Black","Comic Sans MS","Courier New","Georgia","Impact","Times New Roman","Trebuchet MS","Verdana","Microsoft YaHei","SimHei","SimSun","PingFang SC","Helvetica","Roboto","Monaco","Menlo","Consolas","Palatino Linotype","Tahoma","Lucida Console","Candara","Futura","Geneva"],Zt=[{kind:"video",type:'video/mp4; codecs="avc1.42E01E"'},{kind:"video",type:'video/webm; codecs="vp8, vorbis"'},{kind:"video",type:'video/ogg; codecs="theora"'},{kind:"video",type:"video/mp4"},{kind:"video",type:"video/webm"},{kind:"audio",type:"audio/mpeg"},{kind:"audio",type:'audio/ogg; codecs="vorbis"'},{kind:"audio",type:'audio/wav; codecs="1"'},{kind:"audio",type:"audio/mp4"},{kind:"audio",type:"audio/aac"}],en={cold_start:1,web_load:1,warm_start:1,foreground:1,background:1,web_unload:1,terminate:1,unknown:1},tn="__web_sdk_sid__",nn="__web_sdk_sid_meta__",rn="__web_sdk_sid__",on="__web_sdk_sid_v1",an=18e5,sn=216e5,cn=null,un=!1,ln="__web_sdk_device_id__",dn="__web_sdk_ping__",_n=null,pn="__web_sdk_device_fp__",fn=null,hn="",gn=null,mn=function(){var e="";return{append:function(t,n){var r=((t||"")+"").trim();r&&(e+=r+"|"+(null!=n?n+"":"")+";")},digest:function(){return d(e)},reset:function(){e=""}}}(),yn=50,vn=16384,wn="__web_sdk_pending_session_summaries__",kn="__web_sdk_session_metrics_v1__",bn="__web_sdk_summary_claimed__:",Cn=!1,Sn="",Pn={},En={},Tn={sdk_init:1,app_lifecycle:1,app_session_summary:1,network_state_change:1},xn={eventCountTotal:0,businessEventCount:0,pageViewCount:0,pageClickCount:0,networkChangeCount:0,backgroundCount:0,pageKeys:{},pageStayList:[],pageSeqCounter:0,currentPageSeq:0,lastEventName:"",scrollTotalCount:0,scrollTotalDistance:0,scrollUpCount:0,scrollDownCount:0,scrollDirectionChangeCount:0,lastScrollDirection:0,firstScrollDelayMs:-1,hasScrolled:!1,maxScrollDepthPercent:0,isReachBottom:0,keydownTotalCount:0,inputCount:0,focusTotalCount:0,firstInteractionDelayMs:-1,firstInteractionRecorded:!1,lastOrientation:"unknown",touchTotalCount:0,swipeTotalCount:0,longPressTotalCount:0,pinchTotalCount:0,currentPage:null},Dn=Date.now(),Mn=0,In=0,An=[],On=!1,Ln="",Rn="",qn=null,Bn={app_page_view:1,page_click:1},Kn="web_load",Vn="foreground",jn="background",Nn="web_unload",Fn=null,Hn=0,Un=0,Xn=0,Yn=0,Gn=null,zn=!1,Wn=null,Jn=!1,Qn=1e4,$n="1.2.0",Zn=5e3,er=3e4,tr=2e3,nr=1500,rr=!1;if(lt.prototype.normalizeBatchEndpoints=function(e){return(Array.isArray(e)?e:[]).map(function(e){return((e||"")+"").trim()}).filter(Boolean).filter(function(e){return/^https:\/\//i.test(e)}).map(function(e){var t=e.match(/^(https?:\/\/[^/?#]+)/i);return(t?t[1]:e.replace(/\/+$/,""))+"/api/eventTracking/batchReport.json"})},lt.prototype._withTimeout=function(e,t){return new Promise(function(n,r){var o=setTimeout(function(){r(Error("timeout"))},t);e.then(function(e){clearTimeout(o),n(e)}).catch(function(e){clearTimeout(o),r(e)})})},lt.prototype._xhrRace=function(t,n){return t.length?new Promise(function(r){function o(){for(var t=0;c.length>t;t++)try{c[t].abort()}catch(e){}}var i,a=t.length,s=!1,c=[];for(i=0;t.length>i;i++)(function(t){var i,u,l;try{function d(e){l||(l=!0,u&&(clearTimeout(u),u=null),e&&!s?(s=!0,o(),r(t)):e||(a--,s||0!==a||r("")))}i=new XMLHttpRequest,u=null,l=!1,c.push(i),u=setTimeout(function(){d(!1)},n),i.open("POST",t,!0),i.setRequestHeader("Content-Type","application/json"),i.onreadystatechange=function(){4===i.readyState&&d(i.status>0)},i.onerror=function(){d(!1)},i.send("[]")}catch(e){a--,s||0!==a||r("")}})(t[i])}):Promise.resolve("")},lt.prototype._pickFastest=function(t,n){function r(){if(o)for(var t=0;i.length>t;t++)try{i[t].abort()}catch(e){}}var o,i,a=this;return"undefined"==typeof fetch?a._xhrRace(t,n):t.length?(o="undefined"!=typeof AbortController,i=[],new Promise(function(e){var s,c=t.length,u=!1;for(s=0;t.length>s;s++)(function(t){var s,l=o?new AbortController:null;l&&i.push(l),s={method:"POST",mode:"cors",headers:{"Content-Type":"application/json"},body:"[]",cache:"no-store"},l&&(s.signal=l.signal),a._withTimeout(fetch(t,s),n).then(function(n){n&&n.status>0?u||(u=!0,r(),e(t)):(c--,u||0!==c||e(""))}).catch(function(){c--,u||0!==c||e("")})})(t[s])})):Promise.resolve("")},lt.prototype._notifyReady=function(){var t=this.selectedBatchEndpoint;this.endpointReadyListeners.forEach(function(n){try{n(t)}catch(e){}})},lt.prototype._setSelectedEndpoint=function(e){var t=(e||"")+"";t&&t!==this.selectedBatchEndpoint&&(this.selectedBatchEndpoint=t,this._notifyReady())},lt.prototype.startRemoteProbe=function(e){var t,n,r=e.join("|");return e.length?this.remoteProbePromise&&this.remoteProbeKey===r?this.remoteProbePromise:(this.remoteProbeKey=r,this.shouldPersistOnFail=!1,t=this,n=this._pickFastest(e,this.timeoutMs).then(function(r){if(t.remoteProbePromise!==n)return t.selectedBatchEndpoint;if(t.remoteProbePromise=null,r)t._setSelectedEndpoint(r);else if(!t.selectedBatchEndpoint&&e.length){t.shouldPersistOnFail=!0;var o=e[Math.floor(Math.random()*e.length)];t._setSelectedEndpoint(o)}return t.selectedBatchEndpoint}).catch(function(){return t.remoteProbePromise===n&&(t.remoteProbePromise=null),t.selectedBatchEndpoint}),this.remoteProbePromise=n,n):Promise.resolve("")},lt.prototype.refreshDomainConfig=function(e){var t=this;return e().then(function(e){var n=t.normalizeBatchEndpoints(e);return t.remoteEndpoints=n,n.length?(t.hasBatchProbeStarted=!0,t.startRemoteProbe(n)):t.selectedBatchEndpoint})},lt.prototype.init=function(e){return this.initialized?Promise.resolve():(this.initPromise||(this.initialized=!0,this.initPromise=this.refreshDomainConfig(e).catch(function(){})),this.initPromise)},lt.prototype.resolveEndpoint=function(e,t){if(t)return Promise.resolve(t);var n=this;return this.init(e).then(function(){return n.selectedBatchEndpoint||""})},lt.prototype.onEndpointReady=function(e){if("function"!=typeof e)return function(){};this.endpointReadyListeners.push(e);var t=this;return function(){var n=t.endpointReadyListeners.indexOf(e);0>n||t.endpointReadyListeners.splice(n,1)}},lt.prototype.consumeProbeFailurePersistSignal=function(){return!!this.shouldPersistOnFail&&(this.shouldPersistOnFail=!1,!0)},lt.prototype.getEndpointState=function(){return this.selectedBatchEndpoint?"ready":this.remoteEndpoints.length>0?"probing":"unavailable"},lt.prototype.getSelectedBatchEndpoint=function(){return this.selectedBatchEndpoint},Mt=function(){var e,t=[];for(e=0;1272>e;e+=2)t.push(parseInt("637c777bf26b6fc53001672bed2a28a248888da76f3248a3043813899432c6b326fb8d0d37e84760dad3574759e0b764532a380602b1fc71a9e7f97d3b6415c21d02b5612440a4500bf9c043d33e085f45c20e0b65124603dfa8cd7a76b65c11b829e69a4e7f7872c6c7150b6a0f0801d163ae27d4d6cd5d19e77a9b1b48aaf2428594d3752f1d67250bab67676203b9b40df3d7cdf7eebf4b1bd5b6e16416be39a4c4a56e644726087d013b8ea78a6750d8b0339b871ee1b6a9ab66c8032cc1ac2462c695459dc034deae0678ae1779d9e7abce45d167e719b7776c73222f3c7e3cc11ae6da5cd659b79ce60ca82ec97f6059f674c029a68db0853dc22bb6518048ed4f66256d430b74de2f1a95f0589c341befce783e7c6d9dc7dd8e21cb643ee710b3c11329260195e0c0a956f14f970a9e61e594c663f68266a11045baf84a11e051ea5b6a42067253b2d37392c3516e1fc226a5cd4ab8990c475651a4698910219b94e2eb7d2b7129f38c94e3b1246a3298fef32dcae306137c39e1f0e32fe97d11b986c41a55bd5351319f7f078010fa81eb86b8a0556f880000e3630c6a0b136307027bf70e1b5722632937a93b53bf2d240e3949fa5a6e4e6a2a6f4d5b43a39d051df60bc98f25ff3130c31fd85b6e4167b276aed3276b70c3e527f2f2963b60554543e32e1f0c1754766b610c20b0ffe75d3fb88f6fa49605d6a3d6ca3022ffc62f5c8d297b65c778da96c8941ac9c55845e094f1c4a020bff5bb01a0cb04f5a8ac42b1cb41c38d27c1e1358256f3e99e3295a6e3b8c9c0481ed49af7d43f580a8571a0b13162171ebe8666ee5365e9ce6bef0a682080268b3bb67a1076a5776a996c26c2c8c12c7490c356348d10351b5279fa09851ebf".substr(e,2),16));return t}(),It=[1,2,4,8,16,32,64,128,27,54],At="__web_sdk_remote_config_cache__",gt.prototype.setEncryptedConfig=function(e){this.encryptedConfigText!==e&&(this.encryptedConfigText=e,this.cachedConfigPromise=null)},gt.prototype._tryParse=function(t){try{return JSON.parse(t)}catch(e){return t}},gt.prototype._unwrap=function(e,t){var n,r,o;for(t=t||3,n=e,r=0;t>r&&"string"==typeof n&&(o=this._tryParse(n))!==n;r++)n=o;return n},gt.prototype._toStringArray=function(e){var t,n=this._unwrap(e);return Array.isArray(n)?n.map(function(e){return((e||"")+"").trim()}).filter(Boolean):"string"==typeof n&&n.trim()?(t=this._tryParse(n),Array.isArray(t)?t.map(function(e){return((e||"")+"").trim()}).filter(Boolean):[n.trim()]):[]},gt.prototype._getStorage=function(){try{return"undefined"!=typeof window&&window.localStorage?window.localStorage:null}catch(e){return null}},gt.prototype._readPersisted=function(){var t,n,r;try{return(t=this._getStorage())&&(n=t.getItem(At))?(r=JSON.parse(n),Date.now()-(r.updatedAt||0)>24e5?{}:this._mapPayload(r)):{}}catch(e){return{}}},gt.prototype._mapPayload=function(e){if(!e||"object"!=typeof e)return{};var t=e.data&&"object"==typeof e.data?e.data:e;return{batchUrls:this._toStringArray(null!=t.batchUrls?t.batchUrls:t.domainList),eventCodes:this._toStringArray(null!=t.eventCodes?t.eventCodes:t.eventList),signKey:(t.signKey||t.hmacKey||t.eventSignKey||"")+"",securityKeyVersion:(t.securityKeyVersion||t.signKeyVersion||"")+""}},gt.prototype._persist=function(t){try{var n=this._getStorage();if(!n)return;n.setItem(At,JSON.stringify({batchUrls:this._toStringArray(t.batchUrls),eventCodes:this._toStringArray(t.eventCodes),signKey:(t.signKey||"")+"",securityKeyVersion:(t.securityKeyVersion||"")+"",updatedAt:Date.now()}))}catch(e){}},gt.prototype._base64ToBytes=function(e){var t,n,r,o,i=((e||"")+"").trim().replace(/-/g,"+").replace(/_/g,"/"),a=i.length%4?4-i.length%4:0;for(t=0;a>t;t++)i+="=";if("undefined"!=typeof atob&&"undefined"!=typeof Uint8Array){for(n=atob(i),r=new Uint8Array(n.length),o=0;n.length>o;o++)r[o]=n.charCodeAt(o);return r}return new Uint8Array(0)},gt.prototype._decryptAes256Gcm=function(t,n){var r,o,i,a,s,c,u;try{if(28>(r=this._base64ToBytes(t)).length)return Promise.reject(Error("密文过短"));if(o=this._base64ToBytes(n),i=r.slice(0,12),a=r.slice(12),"undefined"!=typeof crypto&&crypto.subtle)return crypto.subtle.importKey("raw",o,{name:"AES-GCM"},!1,["decrypt"]).then(function(e){return crypto.subtle.decrypt({name:"AES-GCM",iv:i},e,a)}).then(function(e){return new Uint8Array(e)});if(!(s=function(e,t,n){var r,o,i,a,s,c,u,l,d,_,p,f,h,g,m=[],y=[],v=[],w=[];for(r=0;32>r;r++)m[r]=e[r]||0;for(r=0;12>r;r++)y[r]=t[r]||0;if(0>(i=n.length-16))return null;for(r=0;i>r;r++)v[r]=n[r];for(r=0;16>r;r++)w[r]=n[i+r];for(a=function(e){var t,n,r=[];for(t=0;8>t;t++)r[t]=(e[4*t]<<24|e[4*t+1]<<16|e[4*t+2]<<8|e[4*t+3])>>>0;for(t=8;60>t;t++)n=r[t-1],t%8?t%8==4&&(n=_t(n)):n=(_t((n<<8|n>>>24)>>>0)^It[t/8-1]<<24)>>>0,r[t]=(r[t-8]^n)>>>0;return r}(m),s=[],r=0;16>r;r++)s[r]=0;for(c=pt(s,a),u=y.slice(0),r=12;15>r;r++)u[r]=0;for(u[15]=1,l=pt(u,a),d=ht(u),_=[],r=0;i>r;r+=16)for(p=pt(d,a),d=ht(d),o=0;16>o&&i>r+o;o++)_[r+o]=v[r+o]^p[o];for(f=v.slice(0);15&f.length;)f.push(0);for(r=0;8>r;r++)f.push(0);for(h=8*i,f.push(0,0,0,0),f.push(h>>>24&255,h>>>16&255,h>>>8&255,255&h),g=function(e,t){var n,r,o=[];for(n=0;16>n;n++)o[n]=0;for(n=0;t.length>n;n+=16){for(r=0;16>r;r++)o[r]^=t[n+r]||0;o=ft(o,e)}return o}(c,f),r=0;16>r;r++)if((l[r]^g[r])!==w[r])return null;return _}(o,i,a)))return Promise.reject(Error("AES-GCM 认证标签校验失败"));if("undefined"!=typeof Uint8Array)for(c=new Uint8Array(s.length),u=0;s.length>u;u++)c[u]=s[u];else c=s;return Promise.resolve(c)}catch(e){return Promise.reject(e)}},gt.prototype.fetchAndDecryptConfig=function(){var e,t,n;return this.cachedConfigPromise?this.cachedConfigPromise:(e=this,t=this._readPersisted(),this.encryptedConfigText?(n=this._decryptAes256Gcm(this.encryptedConfigText,this._secondGcmKeyBase64).then(function(t){var n,r,o,i;if("undefined"!=typeof TextDecoder)n=(new TextDecoder).decode(t);else{for(r=[],o=0;t.length>o;o++)r[o]=t[o];n=String.fromCharCode.apply(null,r)}return i=e._mapPayload(JSON.parse(n)),e._persist(i),i}).catch(function(e){return void 0!==console&&console.warn&&console.warn("[sdk][config] 解密失败:",e),t}),this.cachedConfigPromise=n,n):(this.cachedConfigPromise=Promise.resolve(t),this.cachedConfigPromise))},gt.prototype.getBatchEndpoints=function(){return this.fetchAndDecryptConfig().then(function(e){return e.batchUrls||[]})},gt.prototype.getEventCodes=function(){return this.fetchAndDecryptConfig().then(function(e){return Array.isArray(e.eventCodes)&&e.eventCodes.length>0?e.eventCodes:[]})},gt.prototype.getSigningConfig=function(){return this.fetchAndDecryptConfig().then(function(e){return{hmacKey:(e.signKey||"")+"",securityKeyVersion:(e.securityKeyVersion||"")+""}})},gt.prototype.prewarm=function(){this.fetchAndDecryptConfig().catch(function(){})},gt.prototype.forceRefresh=function(){this.cachedConfigPromise=null},Ot="events",vt.prototype.isAvailable=function(){return"undefined"!=typeof indexedDB&&"undefined"!=typeof window&&null!=window.indexedDB},vt.prototype.openDb=function(){var t=this;return new Promise(function(n,r){if(!t.isAvailable())return n(null);var o=indexedDB.open("web_sdk_event_queue",3);o.onerror=function(){r(o.error||Error("open indexedDB failed"))},o.onupgradeneeded=function(t){var n,r,i,a,s=o.result,c=t.oldVersion,u=t.target.transaction;if(!s.objectStoreNames.contains(Ot))return(n=s.createObjectStore(Ot,{keyPath:"id",autoIncrement:!0})).createIndex("createdAt","createdAt",{unique:!1}),void n.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0});2>c&&u&&((r=u.objectStore(Ot)).indexNames.contains("pendingDedupeKey")||r.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!1})),3>c&&u&&((i=u.objectStore(Ot)).indexNames.contains("pendingDedupeKey")&&i.deleteIndex("pendingDedupeKey"),(a=i.getAll()).onsuccess=function(){var t,n,r,o,s,c,u,l=a.result||[],d={};for(t=0;l.length>t;t++)(r=(n=l[t]).pendingDedupeKey||mt(n.record,n.endpointOverride))&&(d[r]||(d[r]=[]),d[r].push(n.id));for(o=Object.keys(d),s=0;o.length>s;s++)if((c=d[o[s]]).length>=2)for(c.sort(function(e,t){return e-t}),u=1;c.length>u;u++)i.delete(c[u]);try{i.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0})}catch(e){}},a.onerror=function(){try{i.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0})}catch(e){}})},o.onsuccess=function(){n(o.result)}})},vt.prototype._txDone=function(e){return new Promise(function(t,n){e.oncomplete=function(){t()},e.onerror=function(){n(e.error||Error("tx failed"))},e.onabort=function(){n(e.error||Error("tx aborted"))}})},vt.prototype._trimOverflow=function(e){var t=this,n=Ot,r=e.transaction(n,"readonly");return new Promise(function(e,t){var o=r.objectStore(n).count();o.onsuccess=function(){e(+(o.result||0))},o.onerror=function(){t(o.error)}}).then(function(e){return t._txDone(r).then(function(){return e})}).then(function(r){var o,i,a,s;if(r>t.maxSize)return o=r-t.maxSize,i=e.transaction(n,"readwrite"),a=i.objectStore(n).index("createdAt"),s=0,new Promise(function(e,t){var r=a.openCursor();r.onerror=function(){t(r.error)},r.onsuccess=function(){var t=r.result;if(!t||s>=o)return e();i.objectStore(n).delete(t.primaryKey),s++,t.continue()}}).then(function(){return t._txDone(i)})})},vt.prototype.enqueueMany=function(e){var t=this;return e&&e.length?this.openDb().then(function(n){var r,o;if(n)return r=Date.now(),o=e.map(function(e,t){var n=e&&"object"==typeof e&&"record"in e?e:{record:e},o=mt(n.record,n.endpointOverride||""),i={createdAt:r+t,record:n.record,endpointOverride:n.endpointOverride||"",retryCount:0,nextRetryAt:0};return o&&(i.pendingDedupeKey=o),i}),new Promise(function(e,t){var r,i=n.transaction(Ot,"readwrite"),a=i.objectStore(Ot);i.oncomplete=function(){e()},i.onerror=function(){t(i.error)},i.onabort=function(){t(i.error)},r=0,function e(){var n,i;o.length>r&&(n=o[r++],(i=a.add(n)).onsuccess=function(){e()},i.onerror=function(n){var r=i.error;if(r&&"ConstraintError"===r.name)return n.preventDefault(),void e();t(r||Error("add failed"))})}()}).then(function(){return t._trimOverflow(n)}).then(function(){n.close()}).catch(function(e){throw n.close(),e})}):Promise.resolve()},vt.prototype.readOldest=function(e){return e=e||20,this.openDb().then(function(t){var n,r;return t?(n=Date.now(),r=[],new Promise(function(o,i){var a=t.transaction(Ot,"readonly").objectStore(Ot).index("createdAt").openCursor();a.onerror=function(){i(a.error)},a.onsuccess=function(){var t,i,s,c,u,l=a.result;if(!l||r.length>=e)return o();(s=isFinite(i=+(t=l.value).nextRetryAt)?Math.floor(i):0)>n||((c=yt(t)).endpointOverride=t.endpointOverride||"",c.retryCount=isFinite(u=+t.retryCount)?Math.floor(u):0,c.nextRetryAt=s,r.push(c)),l.continue()}}).then(function(){return t.close(),r}).catch(function(e){throw t.close(),e})):[]})},vt.prototype.removeByIds=function(e){var t=this;return e&&e.length?this.openDb().then(function(n){var r,o,i;if(n){for(o=(r=n.transaction(Ot,"readwrite")).objectStore(Ot),i=0;e.length>i;i++)o.delete(e[i]);return t._txDone(r).then(function(){n.close()}).catch(function(e){throw n.close(),e})}}):Promise.resolve()},vt.prototype.updateRetryMeta=function(e,t,n){var r=this;return e&&e.length?this.openDb().then(function(o){var i,a,s,c,u,l,d,_,p,f,h,g,m,y,v;if(!o)return{dropped:0,kept:0};for(i=Date.now(),s=isFinite(a=+t)?Math.max(0,Math.floor(a)):0,u=isFinite(c=+n)?Math.max(0,Math.floor(c)):0,l=0,d=0,p=(_=o.transaction(Ot,"readwrite")).objectStore(Ot),f=0;e.length>f;f++)u<(m=(isFinite(g=+(h=e[f]).retryCount)?Math.floor(g):0)+1)?(l++,p.delete(h.id)):(d++,y=h.pendingDedupeKey||mt(h.record,h.endpointOverride||""),v=yt(h),y&&(v.pendingDedupeKey=y),v.retryCount=m,v.nextRetryAt=i+s,p.put(v));return r._txDone(_).then(function(){return o.close(),{dropped:l,kept:d}}).catch(function(e){throw o.close(),e})}):Promise.resolve({dropped:0,kept:0})},vt.prototype.count=function(){var e=this;return this.openDb().then(function(t){if(!t)return 0;var n=t.transaction(Ot,"readonly");return new Promise(function(e,t){var r=n.objectStore(Ot).count();r.onsuccess=function(){e(+(r.result||0))},r.onerror=function(){t(r.error)}}).then(function(r){return e._txDone(n).then(function(){return t.close(),r})}).catch(function(e){throw t.close(),e})})},Lt={navigation:{navigation_key:1,navigation_name:1},appPageView:{user_type:1,page_key:1,page_name:1,referrer_page_key:9,referrer_page_name:9,current_page_key:1,current_page_name:1,page_load_time:3,recommend_trace_id:0},appInstall:{trace_id:1,attribution_flag:2},pageClick:{page_key:1,page_name:1,click_page_x:3,click_page_y:3,click_x_percent:3,click_y_percent:3,screen_width:3,screen_height:3,recommend_trace_id:0},recommendListView:{page_key:1,page_name:1,recommend_content_type:1,recommend_trace_id:0,recommend_id:1,recommend_trace_info:0,client_version:0},recommendListClick:{page_key:1,page_name:1,recommend_content_type:1,recommend_trace_id:0,recommend_id:1,recommend_trace_info:0,client_version:0},advertising:{event_type:1,advertising_key:1,advertising_name:1,advertising_id:1},videoEvent:{media_id:0,video_id:1,video_title:1,video_type_id:1,video_type_name:1,video_content_type:9,recommend_trace_id:0,video_tag_key:9,video_tag_name:9,video_duration:3,play_duration:3,play_progress:3,play_speed:3,video_behavior_key:1,video_behavior_name:1,post_id:0},novelEvent:{media_id:0,novel_id:1,novel_title:1,chapter_id:0,chapter_name:0,novel_type_id:1,novel_type_name:1,recommend_trace_id:0,novel_tag_key:9,novel_tag_name:9,read_progress:3,page_no:3,novel_behavior_key:1,novel_behavior_name:1},comicEvent:{media_id:0,comic_id:1,comic_title:1,chapter_id:0,chapter_name:0,comic_type_id:1,comic_type_name:1,recommend_trace_id:0,comic_tag_key:9,comic_tag_name:9,read_progress:3,page_no:3,comic_behavior_key:1,comic_behavior_name:1},keywordSearch:{keyword:1,search_result_count:3,search_content_type:0,search_trace_id:0,search_id:0},keywordClick:{keyword:1,click_item_id:1,click_item_type_key:1,click_item_type_name:1,click_position:3,search_trace_id:0},adImpression:{page_key:1,page_name:1,ad_slot_key:1,ad_slot_name:1,ad_id:1,creative_id:0,ad_type:0},adClick:{page_key:1,page_name:1,ad_slot_key:1,ad_slot_name:1,ad_id:1,creative_id:0,ad_type:0},appPageitemExposure:{item_id:1,item_type:1,item_name:1,parent_item_id:1,page_key:1,page_name:1,referrer_page_key:9,referrer_page_name:9,recommend_trace_id:9,recommend_id:9,recommend_trace_info:9},appPageitemClick:{item_id:1,item_type:1,item_name:1,parent_item_id:1,position:3,page_key:1,page_name:1,recommend_trace_id:9,recommend_id:9,recommend_trace_info:9},adRequest:{request_id:1,ad_slot_key:1},adFill:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1,fill_count:3,is_skippable:3,is_closable:3},adNoFill:{request_id:1,ad_slot_key:1,ad_type:1,reason:1},adLoadSuccess:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1,load_duration:3},adImpressionSdk:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1,material_size:1,is_skippable:3,is_closable:3},adClickSdk:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1},adClose:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1,close_type:1},adError:{trace_id:1,request_id:0,ad_slot_key:1,ad_type:1,ad_id:9,error_code:1,error_msg:1,error_stage:1},imEvent:{conversation_id:1,conversation_type:1,current_uid:1,target_uid:1,target_role_key:1,target_role_name:1,message_id:9,message_type:9,im_behavior_key:1,im_behavior_name:1},imConversationView:{current_uid:1,conversation_count:3,total_unread_count:3,normal_conversation_count:3,archived_count:3,pinned_count:3},imConnectionSuccess:{current_uid:9,im_sdk_version:1},imMessageContent:{conversation_id:1,conversation_type:1,current_uid:1,target_uid:1,sender_uid:1,message_id:1,message_type:1,message:9,message_content_length:3,content_hash:9},postEvent:{media_id:0,post_id:1,post_title:1,post_create_time:3,post_user_id:1,post_user_nick:1,post_type_id:1,post_type_name:1,post_content_type:1,recommend_trace_id:9,post_tag_key:9,post_tag_name:9,post_behavior_key:1,post_behavior_name:1},dramaEvent:{media_id:0,drama_id:1,drama_title:1,episode:3,total_episode:3,video_id:1,drama_type_id:1,drama_type_name:1,drama_content_type:1,recommend_trace_id:9,drama_tag_key:9,drama_tag_name:9,drama_duration:3,play_duration:3,play_progress:3,play_speed:3,play_behavior_key:1,play_behavior_name:1},dramaStatusChange:{media_id:0,drama_id:1,drama_title:1,episode:3,video_id:1,pre_episode:3,drama_behavior_key:1,drama_behavior_name:1}},qt={message_send:"发送消息",message_receive:"收到消息",message_read:"消息已读",message_recall:"消息撤回",conversation_pin:"置顶会话",conversation_unpin:"取消置顶",conversation_archive:"归档会话",conversation_unarchive:"取消归档"},Bt=Object.keys(Rt={navigation:"navigation",appPageView:"app_page_view",appInstall:"app_install",pageClick:"page_click",recommendListView:"recommend_list_view",recommendListClick:"recommend_list_click",advertising:"advertising",videoEvent:"video_event",novelEvent:"novel_event",comicEvent:"comic_event",keywordSearch:"keyword_search",keywordClick:"keyword_click",adImpression:"ad_impression",adClick:"ad_click",appPageitemExposure:"app_pageitem_exposure",appPageitemClick:"app_pageitem_click",adRequest:"ad_request",adFill:"ad_fill",adNoFill:"ad_no_fill",adLoadSuccess:"ad_load_success",adImpressionSdk:"ad_impression_sdk",adClickSdk:"ad_click_sdk",adClose:"ad_close",adError:"ad_error",imEvent:"im_event",imConversationView:"im_conversation_view",imConnectionSuccess:"im_connection_success",imMessageContent:"im_message_content",postEvent:"post_event",dramaEvent:"drama_event",dramaStatusChange:"drama_status_change"}).reduce(function(e,t){var n=Rt[t];return e[t]="imEvent"===t?function(e){return{event:n,payload:kt(t,wt(e))}}:function(e){return{event:n,payload:kt(t,e||{})}},e},{}),Kt=void 0!==console&&console.warn?function(e){console.warn(e)}:function(){},Vt={sdk_init:1,app_lifecycle:1,network_state_change:1,app_session_summary:1,app_page_view:1,page_click:1},jt=6e4,Nt="__web_sdk_recent_ok_event_ids__",Ft=function(){function t(e){var t,n=Object.keys(r);for(t=0;n.length>t;t++)e-r[n[t]]>jt&&delete r[n[t]]}function n(){try{"undefined"!=typeof window&&window.localStorage&&window.localStorage.setItem(Nt,JSON.stringify(r))}catch(e){}}var r={};if(function(){var t,n,o,i,a;try{if("undefined"!=typeof window&&window.localStorage&&(t=window.localStorage.getItem(Nt)))for(n=JSON.parse(t),o=Date.now(),i=Object.keys(n),a=0;i.length>a;a++)/^[0-9a-f]{32}$/.test(i[a])&&(o-n[i[a]]>jt||(r[i[a]]=n[i[a]]))}catch(e){}}(),"undefined"!=typeof window)try{window.addEventListener("storage",function(e){var t,n,o,i,a;try{if(e.key!==Nt||!e.newValue)return;for(t=JSON.parse(e.newValue),n=Date.now(),o=Object.keys(t),i=0;o.length>i;i++)a=t[o[i]],/^[0-9a-f]{32}$/.test(o[i])&&(n-a>jt||(r[o[i]]=a))}catch(s){}})}catch(e){}return{has:function(e){var n,o;return!!e&&(t(n=Date.now()),void 0!==(o=r[e])&&jt>=n-o)},set:function(e){var o,i,a,s,c,u=Date.now();for(t(u),o=0;e.length>o;o++)e[o]&&(r[e[o]]=u);if((i=Object.keys(r)).length>500){for(a="",s=1/0,c=0;i.length>c;c++)s>r[i[c]]&&(s=r[i[c]],a=i[c]);a&&delete r[a]}n()},reset:function(){r={},n()}}}(),Pt.prototype.startReplayTimer=function(){var e=this;this.replayTimer=setInterval(function(){e.replayPersistent()},Zn)},Pt.prototype._stopTimer=function(){this.timer&&(clearTimeout(this.timer),this.timer=null)},Pt.prototype._settleItem=function(e,t){e.settled||(e.settled=!0,e.timeoutTimer&&(clearTimeout(e.timeoutTimer),e.timeoutTimer=null),e.resolve(t))},Pt.prototype._purgeSettled=function(){this.queue.length&&(this.queue=this.queue.filter(function(e){return!e.settled}))},Pt.prototype._toPersistentEntries=function(e){return e.map(function(e){return{record:e.record,endpointOverride:e.endpointOverride||"",retryCount:0,nextRetryAt:0}})},Pt.prototype._getRetryDelay=function(e){return Math.min(6e4,Math.max(Zn,Math.floor(Zn*Math.pow(2,Math.min(10,Math.max(0,e))))))},Pt.prototype.schedule=function(){var e=this;this._purgeSettled(),!this.timer&&this.queue.length&&(this.timer=setTimeout(function(){e.timer=null,e.flush().catch(function(){})},5e3))},Pt.prototype.flush=function(){var e=this,t=this.flushTail.then(function(){return e.flushOnce()});return this.flushTail=t.catch(function(){}),t},Pt.prototype._processFlushGroup=function(e,t,n){var r,o,i,a,s,c,u,l,d,_,p,f=this,h="__default__"===e?"":e,g=[],m=[];for(r=0;t.length>r;r++)((i=(((o=t[r]).record&&o.record.event_id||"")+"").trim())&&Ft.has(i)?m:g).push(o);for(a=0;m.length>a;a++)f._settleItem(m[a],{sent:!1,ok:!1,status:0,reason:"cross_batch_recent_success_deduped"});if(!g.length)return Promise.resolve();if(!f._resolveEndpoint){for(Kt("[sdk][web] _resolveEndpoint 未注入,事件无法发送"),s=0;g.length>s;s++)f._settleItem(g[s],{sent:!1,ok:!1,status:0,reason:"no_endpoint_resolver"});return Promise.resolve()}for(c={},u=[],l={},d=0;g.length>d;d++)c[p=(((_=g[d]).record&&_.record.event_id||"")+"").trim()||"__empty__:"+d]||(c[p]=!0,u.push(_.record),l[d]=!0);return f.resolver.consumeProbeFailurePersistSignal()?f.persistentQueue.enqueueMany(f._toPersistentEntries(g)).then(function(){f.hasPersistentItems=!0;for(var e=0;g.length>e;e++)f._settleItem(g[e],{sent:!1,ok:!1,status:0,reason:"probe_all_failed"})}).catch(function(){}):f._resolveEndpoint(h).then(function(e){var t,r,o,i,a,s;if(e){for(r=[],o=0;u.length>o;o++)if((i=((u[o]&&u[o].event_id||"")+"").trim())&&Ft.has(i)){for(a=0;g.length>a;a++)if(l[a]&&((g[a].record&&g[a].record.event_id||"")+"").trim()===i){f._settleItem(g[a],{sent:!1,ok:!1,status:0,reason:"cross_batch_recent_success_deduped"}),l[a]=!1;break}}else r.push(u[o]);if(!r.length){for(s=0;g.length>s;s++)l[s]||f._settleItem(g[s],{sent:!1,ok:!1,status:0,reason:"batch_deduped_duplicate"});return Promise.resolve()}return ut(e,r,{useBeacon:rr}).then(function(e){var t,n,o,i,a,s=Promise.resolve();if(e&&e.ok){for(t=[],n=0;r.length>n;n++)(o=((r[n]&&r[n].event_id||"")+"").trim())&&t.push(o);Ft.set(t)}else{for(i=[],a=0;g.length>a;a++)l[a]&&i.push(g[a]);s=f.persistentQueue.enqueueMany(f._toPersistentEntries(i)).then(function(){f.hasPersistentItems=!0}).catch(function(){})}return s.then(function(){for(var t=0;g.length>t;t++)f._settleItem(g[t],l[t]?e:{sent:!1,ok:!1,status:0,reason:"batch_deduped_duplicate"})})})}if("unavailable"===f.resolver.getEndpointState())return f.persistentQueue.enqueueMany(f._toPersistentEntries(g)).then(function(){f.hasPersistentItems=!0;for(var e=0;g.length>e;e++)f._settleItem(g[e],{sent:!1,ok:!1,status:0,reason:"endpoint_unavailable"})}).catch(function(){});for(t=0;g.length>t;t++)n.push(g[t])})},Pt.prototype.flushOnce=function(){var e,t,n,r,o,i,a,s,c,u=this;if(this._purgeSettled(),!this.queue.length)return Promise.resolve();for(this._stopTimer(),e=this.queue.splice(0,10),t={},n=0;e.length>n;n++)(r=e[n]).settled||(t[o=r.endpointOverride||"__default__"]||(t[o]=[]),t[o].push(r));for(i=[],a=Object.keys(t),s=Promise.resolve(),c=0;a.length>c;c++)(function(e){s=s.then(function(){return u._processFlushGroup(e,t[e],i)})})(a[c]);return s.then(function(){var e,t,n,r,o,a,s;if(i.length){for(e=Date.now(),t=[],n=[],r=0;i.length>r;r++)(i[r].timeoutAt>e?n:t).push(i[r]);for(o=0;n.length>o;o++)u.queue.push(n[o]);for(a=Promise.resolve(),s=0;t.length>s;s++)(function(e){a=a.then(function(){return u.persistentQueue.enqueueMany(u._toPersistentEntries([e])).catch(function(){}).then(function(){u.hasPersistentItems=!0,u._settleItem(e,{sent:!1,ok:!1,status:0,reason:"track_timeout",transport:"queue"})})})})(t[s]);return a}}).then(function(){u.queue.length&&u.schedule()},function(e){throw u.queue.length&&u.schedule(),e})},Pt.prototype._processReplayGroup=function(e,t){var n=this;return n._resolveEndpoint?n._resolveEndpoint("__default__"===e?"":e).then(function(e){var r,o,i,a,s,c,u,l,d;if(e){for(r={},o=[],i=[],a=[],s=0;t.length>s;s++)u=(((c=t[s]).record&&c.record.event_id||"")+"").trim()||"__empty__:"+c.id,(l=((c.record&&c.record.event_id||"")+"").trim())&&Ft.has(l)||r[u]?a.push(c.id):(r[u]=!0,o.push(c.record),i.push(c.id));return d=Promise.resolve(),a.length&&(d=d.then(function(){return n.persistentQueue.removeByIds(a)})),o.length?d.then(function(){return ut(e,o,{useBeacon:rr})}).then(function(e){var r,s,c,u,l,d,_,p,f,h;if(e&&e.ok){for(r=[],s=0;o.length>s;s++)(c=((o[s]&&o[s].event_id||"")+"").trim())&&r.push(c);for(Ft.set(r),u=[],l=0;i.length>l;l++)u.push(i[l]);for(d=0;a.length>d;d++)u.push(a[d]);return n.persistentQueue.removeByIds(u)}for(_=0,p=0;t.length>p;p++)(f=t[p].retryCount||0)>_&&(_=f);return h=n._getRetryDelay(_),n.persistentQueue.updateRetryMeta(t,h,5)}):d}}):(Kt("[sdk][web] _resolveEndpoint 未注入,持久队列重试跳过"),Promise.resolve())},Pt.prototype.replayPersistent=function(){var e=this;return!this.hasPersistentItems||this.replaying?Promise.resolve():(this.replaying=!0,this.persistentQueue.readOldest(10).then(function(t){var n,r,o,i,a,s,c;if(!t.length)return e.persistentQueue.count().then(function(t){0===t&&(e.hasPersistentItems=!1)});for(n={},r=0;t.length>r;r++)n[i=(o=t[r]).endpointOverride||"__default__"]||(n[i]=[]),n[i].push(o);for(a=Object.keys(n),s=Promise.resolve(),c=0;a.length>c;c++)(function(t){s=s.then(function(){return e._processReplayGroup(t,n[t])})})(a[c]);return s}).catch(function(){}).then(function(){e.replaying=!1},function(){e.replaying=!1}))},Pt.prototype.enqueueAssembled=function(e,t){var n=this;return(n._getEventCodes?n._getEventCodes():Promise.resolve([])).then(function(r){return r&&0===r.length||r&&-1===r.indexOf(e.event)?{sent:!1,ok:!1,status:0,reason:"event_code_not_allowed",event:e.event}:new Promise(function(r){var o={record:e,endpointOverride:t||"",resolve:r,reject:r,settled:!1,timeoutAt:Date.now()+er,timeoutTimer:null};o.timeoutTimer=setTimeout(function(){o.settled||n.persistentQueue.enqueueMany(n._toPersistentEntries([o])).catch(function(){}).then(function(){n.hasPersistentItems=!0,n._settleItem(o,{sent:!1,ok:!1,status:0,reason:"track_timeout",transport:"queue"})})},er),n.queue.push(o),10>n.queue.length?n.schedule():n.flush().catch(function(){})})})},Pt.prototype.track=function(e,t){var n=this;return Q().then(function(r){var o={appId:n.opts.appId,channel:n.opts.channel,uid:n.opts.uid||"",deviceId:n.opts.deviceId||r,deviceType:y(),sdkVersion:$n,appVersion:v(n.opts.appVersion)};return(n._getSigningConfig?n._getSigningConfig():Promise.resolve({hmacKey:"",securityKeyVersion:""})).then(function(t){return $e(o,e,t)}).then(function(e){return delete e._sessionInfo,(n._getEventCodes?n._getEventCodes():Promise.resolve([])).then(function(r){return r&&0===r.length?(Kt("[sdk][web] 当前没有事件允许上报,请检查服务端配置(eventCodes 为空)"),{sent:!1,ok:!1,status:0,reason:"event_code_not_allowed",event:e.event}):r&&-1===r.indexOf(e.event)?(Kt('[sdk][web] 事件 "'+e.event+'" 不在服务端允许上报的列表中,已忽略'),{sent:!1,ok:!1,status:0,reason:"event_code_not_allowed",event:e.event}):new Promise(function(r,o){var i={record:e,endpointOverride:t||"",resolve:r,reject:o,settled:!1,timeoutAt:Date.now()+er,timeoutTimer:null};i.timeoutTimer=setTimeout(function(){i.settled||n.persistentQueue.enqueueMany(n._toPersistentEntries([i])).catch(function(){}).then(function(){n.hasPersistentItems=!0,n._settleItem(i,{sent:!1,ok:!1,status:0,reason:"track_timeout",transport:"queue"})})},er),n.queue.push(i),10>n.queue.length?n.schedule():n.flush().catch(function(){})})})})})},Pt.prototype.stop=function(){var e,t;this._stopTimer(),this.replayTimer&&(clearInterval(this.replayTimer),this.replayTimer=null),e={sent:!1,ok:!1,status:0,reason:"stopped"},t=this,this.queue.splice(0).forEach(function(n){t._settleItem(n,e)})},Pt.prototype.getConfig=function(){return{batchMaxSize:10,batchIntervalMs:5e3,replayBatchSize:10,replayIntervalMs:Zn}},Et.prototype._sdkOpts=function(){return{appId:this.options.appId,channel:this.options.channel,uid:this.options.uid||"",deviceId:this.options.deviceId||"",deviceType:y(),sdkVersion:$n,appVersion:v(this.options.appVersion)}},Et.prototype._isAutoDisabled=function(e){return!(!this._disableAutoEvents||!this._disableAutoEvents[e])},Et.prototype._handleEndedSession=function(t){var n,r,o,i,a,s,c;return t&&t.sid&&!this._ssrSkipped?this._isAutoDisabled("app_session_summary")?Promise.resolve():function(t){if(!t)return!1;if(Pn[t]||En[t])return!1;if(se())try{if(window.sessionStorage.getItem(bn+(t+"")))return Pn[t]=1,!1}catch(e){}return En[t]=1,!0}(t.sid)?(n=this,o=t.sid,a=(null!=t.sessionEndTs?1e12>t.sessionEndTs?1e3*t.sessionEndTs:t.sessionEndTs:Date.now())||Date.now(),s=fe(o),c=!Sn||Sn===o||!s&&(xn.eventCountTotal>0||xn.currentPage||(xn.pageStayList||[]).length>0),s&&Sn&&Sn!==o&&(c=!1),s&&c&&(s.eventCountTotal||0)>(xn.eventCountTotal||0)&&(c=!1),c?(xe(),i=ce(xn)):i=s?function(e,t){var n,r,o,i=e&&e.currentPage;return i?(n=t||Date.now(),r=i.accumulated_ms||0,null!=i.resume_at_ms&&(r+=Math.max(0,n-i.resume_at_ms)),o={page_key:i.page_key,page_name:i.page_name||"",stay_duration_ms:r,enter_time:Math.floor((i.enter_time||n)/1e3),has_interaction:i.has_interaction?1:0},i.page_seq_in_sid&&(o.page_seq_in_sid=i.page_seq_in_sid),e.pageStayList=be((e.pageStayList||[]).concat([o])),e.currentPage=null,e):e}(ce(s),a):{eventCountTotal:0,businessEventCount:0,pageViewCount:0,pageClickCount:0,networkChangeCount:0,backgroundCount:0,pageKeys:{},pageStayList:[],pageSeqCounter:0,currentPageSeq:0,lastEventName:"",scrollTotalCount:0,scrollTotalDistance:0,scrollUpCount:0,scrollDownCount:0,scrollDirectionChangeCount:0,lastScrollDirection:0,firstScrollDelayMs:-1,hasScrolled:!1,maxScrollDepthPercent:0,isReachBottom:0,keydownTotalCount:0,inputCount:0,focusTotalCount:0,firstInteractionDelayMs:-1,firstInteractionRecorded:!1,lastOrientation:"unknown",touchTotalCount:0,swipeTotalCount:0,longPressTotalCount:0,pinchTotalCount:0,currentPage:null},pe(o,{sid:o,savedAt:Date.now(),sessionStartedAt:Dn,behaviorHash:ne(),metrics:i}),r=i,Q().then(function(e){var o=bt(n._sdkOpts(),{deviceId:e});return n.configMgr.getSigningConfig().then(function(e){var n=Me(t,t.endReason,r);return it(o,n,e,t.sid)})}).then(function(e){return e}).then(function(e){return delete e._sessionInfo,n.batcher.enqueueAssembled(e,"")}).then(function(){var n,r;!function(t){if(t&&(delete En[t],Pn[t]=1,se()))try{window.sessionStorage.setItem(bn+(t+""),"1")}catch(e){}}(t.sid),ke(t.sid),n={pageKey:Ln||"",pageName:Rn||""},r=function(e){if(!e||null==e.sessionStartTs)return Date.now();var t=e.sessionStartTs;return 1e12>t?1e3*t:t}(F()),function(t,n){var r,o;t&&function(t){if(t){if(se())try{window.sessionStorage.removeItem(de(t))}catch(e){}if(ye())try{window.localStorage.removeItem(de(t))}catch(e){}}}(t),xn={eventCountTotal:0,businessEventCount:0,pageViewCount:0,pageClickCount:0,networkChangeCount:0,backgroundCount:0,pageKeys:{},pageStayList:[],pageSeqCounter:0,currentPageSeq:0,lastEventName:"",scrollTotalCount:0,scrollTotalDistance:0,scrollUpCount:0,scrollDownCount:0,scrollDirectionChangeCount:0,lastScrollDirection:0,firstScrollDelayMs:-1,hasScrolled:!1,maxScrollDepthPercent:0,isReachBottom:0,keydownTotalCount:0,inputCount:0,focusTotalCount:0,firstInteractionDelayMs:-1,firstInteractionRecorded:!1,lastOrientation:"unknown",touchTotalCount:0,swipeTotalCount:0,longPressTotalCount:0,pinchTotalCount:0,currentPage:null},Dn=r=n&&"number"==typeof n.sessionStartMs&&isFinite(n.sessionStartMs)?n.sessionStartMs:Date.now(),xn.lastOrientation=re(),mn.reset(),Cn=!0,Sn=z()||"",(o=n&&n.carryOver)&&o.pageKey?function(t,n,r){var o,i,a,s,c=((t||"")+"").trim();if(!c)return!1;o="number"==typeof r&&isFinite(r)?r:Date.now(),i=Date.now(),a=!1;try{a="undefined"!=typeof document&&"hidden"===document.visibilityState}catch(e){}xn.pageKeys[c]=1,s=ae(),xn.currentPage={page_key:c,page_name:(n||"")+"",enter_time:o,enter_time_ms:o,page_seq_in_sid:s,has_interaction:!1,accumulated_ms:0,resume_at_ms:a?null:i,hidden_paused:a},me()}(o.pageKey,o.pageName,r):me()}(t.sid,{sessionStartMs:r,carryOver:n.pageKey?{pageKey:n.pageKey,pageName:n.pageName}:null})}).catch(function(){var e;(e=t.sid)&&delete En[e],function(e){if(e&&e.sid){var t=ve().filter(function(t){return t&&t.sid!==e.sid});t.push(e),we(t)}}({sid:t.sid,sessionStartTs:t.sessionStartTs,sessionEndTs:t.sessionEndTs,endReason:t.endReason,fields:Me(t,t.endReason,r)})})):Promise.resolve():Promise.resolve()},Et.prototype._replayPendingSummaries=function(e,t,n){var r,o,i=ve();if(!i.length)return Promise.resolve();for(r=Promise.resolve(),o=0;i.length>o;o++)(function(o){r=r.then(function(){return n(it(e,o.fields,t,o.sid)).then(function(){return ke(o.sid),null}).catch(function(){})})})(i[o]);return r},Et.prototype._startAutoEvents=function(){if(!this._ssrSkipped&&!this._autoEventsStarted){var t=this;this._autoEventsStarted=!0,qn=function(e){t._handleEndedSession(e).catch(function(){})},Z().then(function(){return Q()}).then(function(n){var r=bt(t._sdkOpts(),{deviceId:n});return t.configMgr.getSigningConfig().then(function(n){return t.configMgr.fetchAndDecryptConfig().then(function(o){function i(e){return!s||-1!==s.indexOf(e)}function a(e){return i(e)&&!t._isAutoDisabled(e)}var s=o.eventCodes&&o.eventCodes.length>0?o.eventCodes:null,c=function(e){return e.then(function(e){return delete e._sessionInfo,t.batcher.enqueueAssembled(e,"")})};return t._replayPendingSummaries(r,n,c).then(function(){function o(e){if(!s&&(s=!0,a("app_session_summary"))){var n=function(e){var t,n,r=Date.now()||Date.now(),o=F();return o?(t=Y(o,e||"web_visibility_hidden",r),"web_visibility_hidden"!==e&&"app_exit"!==e||(t.sessionEndTs=r),N(n=H(r)),{endedSession:t,sid:n.sid}):null}(e);n&&n.endedSession&&t._handleEndedSession(n.endedSession).catch(function(){})}}var s,u,l,d,_,p,f,h,g,m,y,v,w,k,b;a("sdk_init")&&c(function(e,t){var n,r=fn||{},o={},i=Object.keys(r);for(n=0;i.length>n;n++)o[i[n]]=r[i[n]];return $e(e,{event:"sdk_init",payload:o},t)}(r,n)).catch(function(){}),a("app_lifecycle")&&(t._webLoadTeardown=function(t,n,r){function o(){a||(a=!0,s&&(window.removeEventListener("load",s,!1),s=null),t(ot(n,Kn,{},r)).catch(function(){}))}function i(){(function(){var t,n,r;try{if("undefined"!=typeof performance&&performance.timing&&(t=performance.timing).loadEventEnd&&t.navigationStart&&t.loadEventEnd>0)return!0;if("undefined"!=typeof performance&&"function"==typeof performance.getEntriesByType&&(n=performance.getEntriesByType("navigation"))&&n.length>0&&(r=n[0]).loadEventEnd>0&&r.fetchStart>=0)return!0}catch(e){}return!1})()&&o()}var a,s;return"undefined"==typeof window?(t(ot(n,Kn,{},r)).catch(function(){}),function(){}):(a=!1,s=null,"complete"===document.readyState?(i(),a||o(),function(){}):(s=function(){i(),a||o()},window.addEventListener("load",s,!1),setTimeout(function(){a||o()},1e4),function(){a=!0,s&&(window.removeEventListener("load",s,!1),s=null)}))}(c,r,n)),s=!1,t._autoLifecycleTeardown=function(e,t){var n,r,o,i,a;return"undefined"==typeof window||"undefined"==typeof document?function(){}:(r=!0===(n=t||{}).webMode,o=function(){"visible"===document.visibilityState?r?n.onVisible&&n.onVisible():e(Vn,{}):"hidden"===document.visibilityState&&(r?n.onVisibleHidden&&n.onVisibleHidden():e(jn,{}),n.onHidden&&n.onHidden())},i=function(){e(Nn,rt()),n.onPageUnload&&n.onPageUnload()},a=function(e){e&&e.persisted||n.onPageHide&&n.onPageHide()},window.addEventListener("visibilitychange",o,!1),window.addEventListener("beforeunload",i,!1),window.addEventListener("pagehide",a,!1),function(){window.removeEventListener("visibilitychange",o,!1),window.removeEventListener("beforeunload",i,!1),window.removeEventListener("pagehide",a,!1)})}(function(e,t){return a("app_lifecycle")?c(ot(r,e,t||{},n)):Promise.resolve()},{onHidden:function(){t.batcher.flush().catch(function(){})},onPageUnload:function(){t.batcher.flush().catch(function(){}),o("app_exit")},onPageHide:function(){t.batcher.flush().catch(function(){}),o("web_visibility_hidden")}}),t._autoNetworkTeardown=(l=function(e){return a("network_state_change")?c(function(e,t,n){return $e(e,{event:"network_state_change",payload:t||{}},n)}(r,e,n)):Promise.resolve()},"undefined"==typeof navigator?function(){}:(d=navigator.connection||navigator.mozConnection||navigator.webkitConnection)?(_="unknown",p=d.type?d.type+"":"",f="boolean"==typeof navigator.onLine?navigator.onLine:null,d.onchange=function(){var e,t,n,r,o,i,a=d.type?d.type+"":"",s="boolean"==typeof navigator.onLine?navigator.onLine:null;(a!==p||s!==f)&&(p=a,f=s,r="boolean"==typeof(t=s)?t?1:0:-1,"none"==(n=(a||"")+"")&&1===r?n="unknown":n&&"none"!==n&&"unknown"!==n&&0===r&&(r=1),l(at({network_type:_,from_network_type:_,to_network_type:_,is_online:(e={connection_type:n,is_online:r}).is_online,change_count_in_sid:(xn.networkChangeCount||0)+1},(i={},(o=e.connection_type)?i.connection_type=o+"":d.type&&(i.connection_type=d.type+""),d.effectiveType&&(i.effective_type=d.effectiveType+""),null!=d.downlink&&(i.downlink=Math.round(1e3*d.downlink)),null!=d.rtt&&(i.rtt=d.rtt),i))))},function(){d.onchange=null}):function(){}),t._webInteractionTeardown="undefined"==typeof window||"undefined"==typeof document?function(){}:(h=function(e){!e||e.repeat||e.isComposing||(Se(),he(),xn.keydownTotalCount++,me())},g=function(e){var t,n=e&&e.target;(function(t){if(!t||1!==t.nodeType)return!1;var n=((t.tagName||"")+"").toLowerCase();if("input"===n||"textarea"===n)return!0;try{if(t.isContentEditable)return!0}catch(e){}return!1})(n)&&(t=Date.now(),Fn===n&&50>t-Hn||(Fn=n,Hn=t,Se(),he(),xn.inputCount++,me()))},m=function(e){(function(t){if(!t||1!==t.nodeType)return!1;var n=((t.tagName||"")+"").toLowerCase();if("input"===n||"textarea"===n||"select"===n)return!0;try{if(t.isContentEditable)return!0}catch(e){}return!1})(e&&e.target)&&(Se(),he(),xn.focusTotalCount++,me())},y=function(){Pe()},v=function(e){e&&e.changedTouches&&e.changedTouches.length&&(Se(),he(),xn.touchTotalCount++,me(),e.touches&&1===e.touches.length?(Un=e.touches[0].clientX,Xn=e.touches[0].clientY,Yn=Date.now(),ct(),zn=!1,Gn=setTimeout(function(){zn=!0,he(),xn.longPressTotalCount++,me()},500)):(Yn=0,ct()),e.touches&&2===e.touches.length&&(Wn=st(e.touches),Jn=!1))},w=function(e){var t,n,r;e&&e.touches&&(1===e.touches.length&&Yn>0&&Math.sqrt((t=e.touches[0].clientX-Un)*t+(n=e.touches[0].clientY-Xn)*n)>8&&ct(),2===e.touches.length&&(r=st(e.touches),null==Wn||Jn||12>Math.abs(r-Wn)||(he(),xn.pinchTotalCount++,me(),Jn=!0),Wn=r))},k=function(e){var t,n;ct(),Yn>0&&e.changedTouches&&e.changedTouches.length&&!zn&&(30>Math.sqrt((t=e.changedTouches[0].clientX-Un)*t+(n=e.changedTouches[0].clientY-Xn)*n)||(he(),xn.swipeTotalCount++,me())),Yn=0,zn=!1,e.touches&&e.touches.length>=2||(Wn=null,Jn=!1)},b=function(){ct(),Yn=0,zn=!1,Wn=null,Jn=!1},document.addEventListener("keydown",h,!1),document.addEventListener("input",g,!1),document.addEventListener("focusin",m,!1),window.addEventListener("orientationchange",y,!1),document.addEventListener("touchstart",v,!1),document.addEventListener("touchmove",w,!1),document.addEventListener("touchend",k,!1),document.addEventListener("touchcancel",b,!1),Pe(),function(){document.removeEventListener("keydown",h,!1),document.removeEventListener("input",g,!1),document.removeEventListener("focusin",m,!1),window.removeEventListener("orientationchange",y,!1),document.removeEventListener("touchstart",v,!1),document.removeEventListener("touchmove",w,!1),document.removeEventListener("touchend",k,!1),document.removeEventListener("touchcancel",b,!1),ct()}),(i("app_page_view")||i("page_click"))&&(u=a("app_page_view"),t._pageTrackerTeardown=function(t){function n(){if(u.resolvePageKey)try{var t=u.resolvePageKey();if(t)return qe(t+"")}catch(e){}return Be()}function r(){var e,t=n(),r=ze({previousPageKey:d,previousPageName:_,pageKey:t});d=t,_=r.page_name,Ln=t,Rn=r.page_name||"",l&&(e=ae(),function(e,t,n,r){he(),xn.eventCountTotal++,xn.lastEventName="app_page_view",xn.pageViewCount++;var o=(e||"")+"";o&&(xn.pageKeys[o]=1),Te(o,t,n,r),me()}(t,r.page_name,Math.floor(Date.now()/1e3),e),r._pageSeqPrealloc=e,r._metricsRecorded=1,u.onPageView&&u.onPageView(r))}function o(){var t,n,r,o,i=Date.now();200>i-Mn||(Mn=i,(t=Ke())&&(r=(n=t.scrollTop||0)-In,In=n,o=function(){var t,n,r;try{return(t=Ke())?(n=Math.max(0,(t.scrollHeight||0)-(t.clientHeight||0)))>0?{depthPercent:Math.min(100,Math.max(0,Math.round((r=t.scrollTop||0)/n*100))),atBottom:r>=n-8}:{depthPercent:0,atBottom:!0}:{depthPercent:0,atBottom:!1}}catch(e){return{depthPercent:0,atBottom:!1}}}(),u.onScroll&&u.onScroll(r,o.depthPercent,o.atBottom)))}function i(e){var t,r,o,i;e&&0>=e.button&&((t=e.target)&&"function"==typeof t.closest&&t.closest("[data-sdk-no-auto-click]")||(r=null!=e.clientX?e.clientX:0,o=null!=e.clientY?e.clientY:0,i=Ye(e),setTimeout(function(){if(!function(e,t){var n,r;for(function(e){for(var t=An.length-1;t>=0;t--)e-An[t].t>300&&An.splice(t,1)}(Date.now()),n=0;An.length>n;n++)if(10>=Math.abs((r=An[n]).x-e)&&10>=Math.abs(r.y-t))return!0;return!1}(r,o)){Se();var e=function(e,t){var n=e||{},r=qe(n.pageKey||Be()),o=Ye(t);return Ae({page_key:r,page_name:n.pageName||"undefined"!=typeof document&&document.title||r,recommend_trace_id:"",referrer_page_key:n.previousPageKey||"",is_sdk_auto_collected:1,_clickSnap:o},Ge(o))}({pageKey:d||n(),pageName:_,previousPageKey:d},i);u.onPageClick&&u.onPageClick(e)}},50)))}function a(){Ie(),r()}function s(e){e&&e.persisted&&r()}function c(){var e,t,n=Ke();In=n&&n.scrollTop||0,r(),!p&&window.history&&(p=!0,e=history.pushState,t=history.replaceState,history.pushState=function(){var t=e.apply(history,arguments);return Ie(),setTimeout(r,0),t},history.replaceState=function(){var e=t.apply(history,arguments);return Ie(),setTimeout(r,0),e},window.addEventListener("popstate",a,!1))}var u,l,d,_,p;return"undefined"==typeof window||"undefined"==typeof document?function(){}:(l=!1!==(u=t||{}).enableAutoPageView,d="",_="",p=!1,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",c,!1):c(),window.addEventListener("pageshow",s,!1),window.addEventListener("scroll",o,!1),document.addEventListener("click",i,!0),function(){document.removeEventListener("click",i,!0),window.removeEventListener("scroll",o,!1),window.removeEventListener("pageshow",s,!1),window.removeEventListener("popstate",a,!1),document.removeEventListener("DOMContentLoaded",c,!1)})}({resolvePageKey:t.options.pageKeyResolver,enableAutoPageView:u,onPageView:function(e){u&&t.batcher.track({event:"app_page_view",payload:e},"").catch(function(){})},onPageClick:function(e){a("page_click")&&t.batcher.track({event:"page_click",payload:e},"").catch(function(){})},onScroll:function(e,t,n){!function(e,t,n){var r,o;he(),8>(r=Math.abs(e))||(xn.hasScrolled||(xn.hasScrolled=!0,xn.firstScrollDelayMs=Math.max(0,Date.now()-Dn)),xn.scrollTotalCount++,xn.scrollTotalDistance+=r,o=e>0?1:-1,0!==xn.lastScrollDirection&&o!==xn.lastScrollDirection&&xn.scrollDirectionChangeCount++,xn.lastScrollDirection=o,0>e?xn.scrollUpCount++:e>0&&xn.scrollDownCount++,null!=t&&(xn.maxScrollDepthPercent=Math.max(xn.maxScrollDepthPercent,Math.min(100,Math.max(0,Math.round(t))))),n&&(xn.isReachBottom=1),me())}(e,t,n)}})),t._sessionPollTimer&&clearInterval(t._sessionPollTimer),t._sessionPollTimer=setInterval(function(){var e=G();e.endedSession&&t._handleEndedSession(e.endedSession).catch(function(){})},6e4)})})})}).catch(function(){})}},Et.prototype._init=function(e){var t,n,r;if(this._ssrSkipped)return this._buildInitReturn();for(t=Object.keys(e||{}),n=0;t.length>n;n++)this.options[t[n]]=e[t[n]];return null==this.options.channel&&(this.options.channel=""),this._disableAutoEvents=Ct(this.options.disableAutoEvents),this.options.disableAutoEvents=St(this._disableAutoEvents),this.configMgr.setEncryptedConfig(this.options.encryptedConfig||this.options.configCipher||""),this.configMgr.prewarm(),r=this,this.resolver.init(function(){return r.configMgr.getBatchEndpoints()}),this._refreshTimer&&clearInterval(this._refreshTimer),this._refreshTimer=setInterval(function(){r.refreshConfig()},12e5),this._unsubReady&&this._unsubReady(),this._unsubReady=this.resolver.onEndpointReady(function(){r.batcher.flush().catch(function(){})}),this._buildInitReturn()},Et.prototype.refreshConfig=function(){var e=this;return this.configMgr.forceRefresh(),this.resolver.refreshDomainConfig(function(){return e.configMgr.getBatchEndpoints()}).catch(function(){})},Et.prototype.track=function(e,t){return this._ssrSkipped?Promise.resolve({sent:!1,ok:!0,status:0,reason:"ssr_skipped"}):this.batcher.track(e,t)},Et.prototype.setContext=function(e){if(this._ssrSkipped)return this.getConfig();var t=e&&"object"==typeof e?e:{};return Object.prototype.hasOwnProperty.call(t,"uid")&&(this.options.uid=null==t.uid?"":t.uid+""),Object.prototype.hasOwnProperty.call(t,"channel")&&(this.options.channel=null==t.channel?"":t.channel+""),this.batcher.opts=bt(this.options),this.getConfig()},Et.prototype.setUid=function(e){return this.setContext({uid:e})},Et.prototype.getSelectedHost=function(){var e,t;return this._ssrSkipped||!this.resolver?"":(e=this.resolver.getSelectedBatchEndpoint())&&(t=/^(https?:\/\/[^/]+)/.exec(e))?t[1]:""},Et.prototype._buildInitReturn=function(){return this._ssrSkipped||!this.batcher?{selectedHost:"",version:$n}:bt(this.batcher.getConfig(),{selectedHost:this.getSelectedHost(),version:$n})},Et.prototype.getConfig=function(){if(this._ssrSkipped||!this.batcher)return bt(this.options,{selectedHost:"",selectedBatchEndpoint:"",version:$n});var e=bt(this.options,this.batcher.getConfig(),{selectedHost:this.getSelectedHost(),selectedBatchEndpoint:this.resolver.getSelectedBatchEndpoint(),version:$n});return delete e.encryptedConfig,delete e.configCipher,e},Et.prototype.flush=function(){return this._ssrSkipped?Promise.resolve():this.batcher.flush()},Et.prototype.refreshDomainConfig=function(){if(this._ssrSkipped)return Promise.resolve({selectedBatchEndpoint:"",selectedHost:"",version:$n});var e=this;return this.resolver.refreshDomainConfig(function(){return e.configMgr.getBatchEndpoints()}).then(function(t){return{selectedBatchEndpoint:t,selectedHost:e.getSelectedHost(),version:$n}})},Et.prototype.stop=function(){this._ssrSkipped||(this._sessionPollTimer&&(clearInterval(this._sessionPollTimer),this._sessionPollTimer=null),this._pageTrackerTeardown&&(this._pageTrackerTeardown(),this._pageTrackerTeardown=null),this._webInteractionTeardown&&(this._webInteractionTeardown(),this._webInteractionTeardown=null),this._webLoadTeardown&&(this._webLoadTeardown(),this._webLoadTeardown=null),this._autoLifecycleTeardown&&(this._autoLifecycleTeardown(),this._autoLifecycleTeardown=null),this._autoNetworkTeardown&&(this._autoNetworkTeardown(),this._autoNetworkTeardown=null),this._unsubReady&&(this._unsubReady(),this._unsubReady=null),this._refreshTimer&&(clearInterval(this._refreshTimer),this._refreshTimer=null),this.batcher.stop())},Ht=null,Ut=[],Xt=null,(Yt=Dt).setContext=function(e){return Tt().setContext(e)},Yt.setUid=function(e){return Tt().setUid(e)},Yt.setChannel=function(e){return Tt().setContext({channel:e})},Yt.notifyPageLoad=function(){!function(){if(cn=null,V())try{window.sessionStorage.removeItem(tn),window.sessionStorage.removeItem(nn)}catch(e){}}(),G(Date.now())},Yt.debugInvalidateSessionCache=function(){W()},Yt.debugForceEnsureSession=function(){var e,t;return W(),e=G(),t=Tt(),e.endedSession&&t._handleEndedSession(e.endedSession).catch(function(){}),e},Yt.getConfig=function(){return Tt().getConfig()},Yt.flush=function(){return Tt().flush()},Yt.refreshDomainConfig=function(){return Tt().refreshDomainConfig()},Yt.stop=function(){Ht?(Ht.stop(),Ht=null):Ut.length=0},Yt.version=$n,Yt.getCommonFields=function(e){var t=Ht?Ht.getConfig():{};return Z().then(function(){return Q().then(function(n){var r,o,i,a,s=B({appId:t.appId,channel:t.channel,uid:t.uid,deviceId:t.deviceId||n,sid:(a=z(),a||G().sid),deviceType:y(),deviceBrand:"",deviceModel:"",deviceFingerprint:ee(),fpVersion:te(),sdkVersion:$n,appVersion:v(t.appVersion)});if(!Array.isArray(e)||!e.length)return s;for(r={},o=0;e.length>o;o++)(i=((e[o]||"")+"").trim())&&(r[i]=Object.prototype.hasOwnProperty.call(s,i)?s[i]:"");return r})})},Yt.getCommonField=function(e){var t=((e||"")+"").trim();return t?Yt.getCommonFields([t]).then(function(e){return e[t]}):Promise.resolve("")},Yt.init=function(e){var t,n,r,o;return Ht&&(t=Ht,Ht=null,t.flush().catch(function(){}).then(function(){t.stop()})),n=new Et(e),Ht=n,o=xt(Ut.splice(0),n,0).then(function(){Xt===r&&(Xt=null)},function(){Xt===r&&(Xt=null)}),Xt=r=o,bt(n._buildInitReturn(),{track:Yt.track,setContext:Yt.setContext,setUid:Yt.setUid,setChannel:Yt.setChannel,notifyPageLoad:Yt.notifyPageLoad,getCommonFields:Yt.getCommonFields,getCommonField:Yt.getCommonField})},Gt=function(){return Kt("[sdk][web] 非法上报:禁止直接调用 WebSDK.track({...}),请使用 WebSDK.track.videoEvent({...}) 等具名方法"),Promise.resolve({sent:!1,ok:!1,status:0,reason:"direct_track_forbidden"})},Object.keys(Bt).forEach(function(t){var n=Bt[t];Gt[t]=function(r,o){try{return Dt(n(r),o)}catch(e){if(e&&"EventValidationError"===e.name)return Promise.resolve({sent:!1,ok:!1,status:0,reason:"event_validation_failed",event:e.eventMethod||t,missingFields:e.missingFields||[],typeErrorFields:e.typeErrorFields||[]});throw e}}}),Yt.track="undefined"!=typeof Proxy?new Proxy(Gt,{get:function(e,t){return"string"!=typeof t||t in e||!/^[a-z]/.test(t)?e[t]:function(){return Kt('[sdk][web] 未知上报事件 "'+t+'",该方法不存在,事件未上报'),Promise.resolve({sent:!1,ok:!1,status:0,reason:"unknown_event_method",method:t})}}}):Gt,"undefined"!=typeof globalThis)try{globalThis.WebSDK=Yt}catch(e){}else"undefined"!=typeof window&&(window.WebSDK=Yt);return Yt}); diff --git a/app/static/css/account-empty.css b/app/static/css/account-empty.css deleted file mode 100644 index 10834e5..0000000 --- a/app/static/css/account-empty.css +++ /dev/null @@ -1,72 +0,0 @@ -/* 与「我的收藏」一致的账户列表空状态。 */ -/* 空态时仍保持与个人中心同宽的主栏,避免筛选条贴到视口左缘。 */ - -.account-empty { - display: flex; - flex-direction: column; - align-items: center; - gap: 16px; - width: 100%; - padding: 96px 16px; - font-size: 14px; - line-height: 22px; - text-align: center; -} - -.account-empty[hidden] { display: none !important; } - -.account-empty__icon { - display: flex; - align-items: center; - justify-content: center; - width: 96px; - height: 96px; - flex: 0 0 96px; - border: 1px solid var(--line); - border-radius: 50%; - background: #1b1b1f; -} - -.account-empty__icon img { width: 40px; height: 40px; } - -.account-empty__title { - margin: 0; - color: var(--text); - font-size: 20px; - font-weight: 600; - line-height: 28px; -} - -.account-empty__description { - margin: 0; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.account-empty__description-mobile { display: none; } - -.account-empty__action { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; - width: 147px; - height: 48px; - border-radius: 999px; - background: linear-gradient(105deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); - color: #fff; - font-size: 14px; - font-weight: 600; - line-height: 20px; - white-space: nowrap; -} - -.account-empty__action img { width: 20px; height: 20px; } -.account-empty__action:focus-visible { outline: 2px solid var(--accent-hot); outline-offset: 3px; } - -@media (max-width: 768px) { - .account-empty { padding: 48px 16px; } - .account-empty__description-desktop { display: none; } - .account-empty__description-mobile { display: inline; } -} diff --git a/app/static/css/base.css b/app/static/css/base.css index b4757b8..7a416ce 100644 --- a/app/static/css/base.css +++ b/app/static/css/base.css @@ -770,3 +770,35 @@ img { white-space: nowrap !important; border: 0 !important; } + +/* 卡片信息行:集数/状态前置为金色小标,标签随后(首页、列表、排行榜共用) */ +.card__meta--ep { + display: flex; + align-items: center; + gap: 6px; +} + +.card__meta-ep { + flex-shrink: 0; + padding: 0 6px; + border-radius: 4px; + color: var(--gold); + font-size: 12px; + line-height: 18px; + background: rgba(233, 201, 119, 0.12); +} + +.card__meta-tags { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@media (max-width: 768px) { + .card__meta-ep { + padding: 0 4px; + font-size: 10px; + line-height: 16px; + } +} diff --git a/app/static/css/favorites-mobile.css b/app/static/css/favorites-mobile.css deleted file mode 100644 index d262fe8..0000000 --- a/app/static/css/favorites-mobile.css +++ /dev/null @@ -1,266 +0,0 @@ -/* 我的收藏 H5:以 Figma 375px 画板为基准。 */ -@media (max-width: 768px) { - .favorites-main { - display: flex; - flex-direction: column; - gap: 12px; - width: 100%; - max-width: none; - min-width: 0; - margin: 0; - padding: 12px var(--profile-gutter, 16px) 20px; - } - - .favorites-main.is-empty { - width: 100%; - gap: 0; - padding: 12px 0 20px; - } - - .favorites-main.is-empty .favorites-toolbar { - width: 100%; - padding-inline: var(--profile-gutter, 16px); - } - - .favorites-main.is-empty .favorites-recommendations { - width: auto; - margin-top: 8px; - padding: 0 var(--profile-gutter, 16px) 24px; - } - - .favorites-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - width: 100%; - min-width: 0; - min-height: 30px; - } - - .favorites-filters { - display: flex; - flex: 1 1 auto; - flex-wrap: nowrap; - align-items: center; - gap: clamp(6px, 2.666667vw, 10px); - min-width: 0; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - - .favorites-filters::-webkit-scrollbar { - display: none; - } - - .favorites-filter { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - justify-content: center; - height: 30px; - padding: 0 clamp(11px, 4.266667vw, 16px); - border: 1px solid rgba(255, 255, 255, .12); - border-radius: 999px; - background: transparent; - color: #a1a1aa; - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; - } - - .favorites-filter.is-active { - border-color: rgba(236, 35, 127, .3); - background: rgba(236, 35, 127, .1); - color: #ff4a7b; - } - - .favorites-manage { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - justify-content: center; - height: 30px; - padding: 0; - border: 0; - background: none; - color: #a1a1aa; - font-size: 12px; - font-weight: 400; - line-height: 18px; - white-space: nowrap; - } - - .favorites-manage svg, - .favorites-manage img { - display: none; - } - - .favorites-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px var(--profile-card-gap, 8px); - width: 100%; - min-width: 0; - } - - .favorites-card { - position: relative; - display: flex; - flex-direction: column; - gap: 6px; - min-width: 0; - } - - .favorites-card .profile-card__cover { - width: 100%; - aspect-ratio: 109 / 145; - border-radius: 8px; - } - - .favorites-card .profile-card__title { - overflow: hidden; - font-size: 14px; - font-weight: 600; - line-height: 20px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .favorites-card .profile-card__meta { - overflow: hidden; - font-size: 11px; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .favorites-card__desktop-meta { - display: none; - } - - .favorites-card__mobile-meta { - display: inline; - } - - .favorites-card__remove { - display: none; - } - - .favorites-page.is-managing .favorites-card__remove { - position: absolute; - top: 6px; - left: 6px; - z-index: 3; - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - padding: 0; - border: 1px solid rgba(255, 255, 255, .5); - border-radius: 50%; - background: rgba(11, 11, 13, .8); - color: #f2f2f4; - } - - .favorites-card .profile-card__episodes { - bottom: 10px; - left: 10px; - font-size: 11px; - line-height: 14px; - } - - .favorites-card .profile-card__badge { - top: 4px; - right: 4px; - min-width: 30px; - height: 20px; - padding: 0 5px; - } - - .favorites-load-more { - display: inline-flex; - align-items: center; - justify-content: center; - width: 100%; - height: 40px; - padding: 0 16px; - border: 1px solid rgba(255, 255, 255, .24); - border-radius: 999px; - background: transparent; - color: #f2f2f4; - font-size: 14px; - font-weight: 500; - line-height: 20px; - } - - .favorites-pagination { - display: none; - } - - .favorites-empty { - gap: 16px; - padding: 48px 16px; - } - - .favorites-empty__description-desktop, - .favorites-recommendations__more-desktop { - display: none; - } - - .favorites-empty__description-mobile, - .favorites-recommendations__more-mobile { - display: inline; - } - - .favorites-recommendations { - width: 100%; - padding: 0 var(--profile-gutter, 16px) 20px; - } - - .favorites-recommendations__head { - height: 26px; - margin-bottom: 14px; - } - - .favorites-recommendations__head h2 { - font-size: 18px; - line-height: 26px; - } - - .favorites-recommendations__head a { - font-size: 12px; - line-height: 18px; - } - - .favorites-recommendations__head a img { - width: 14px; - height: 14px; - } - - .favorites-recommendations__grid { - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px var(--profile-card-gap, 8px); - } - - .favorites-card[hidden], - .favorites-empty[hidden], - .favorites-load-more[hidden] { - display: none !important; - } -} - -@media (max-width: 359px) { - .favorites-filters { - flex-wrap: nowrap; - gap: 6px; - } - - .favorites-filter { - padding-inline: 8px; - font-size: 12px; - } -} diff --git a/app/static/css/favorites.css b/app/static/css/favorites.css deleted file mode 100644 index c2230ab..0000000 --- a/app/static/css/favorites.css +++ /dev/null @@ -1,352 +0,0 @@ -/* 我的收藏:1440px 桌面稿,公共个人页头与封面卡复用 profile.css。 */ -.favorites-page [hidden] { - display: none !important; -} - -.favorites-main { - display: flex; - flex-direction: column; - gap: 24px; - width: min(calc(100% - 48px), var(--page-width)); - margin-inline: auto; - padding: 24px 0 40px; -} - -.favorites-main.is-empty { - width: 100%; - max-width: none; - gap: 24px; - margin: 0; - padding: 24px 0 40px; -} - -.favorites-main.is-empty .favorites-toolbar { - width: min(calc(100% - 48px), var(--page-width)); - margin-inline: auto; -} - -.favorites-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - min-height: 36px; -} - -.favorites-filters { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 10px; - min-width: 0; -} - -.favorites-filter, -.favorites-manage { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - justify-content: center; - height: 36px; - border: 1px solid var(--line); - border-radius: 999px; - background: transparent; - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; -} - -.favorites-filter { - padding-inline: 16px; - color: var(--text-muted); -} - -.favorites-filter.is-active { - border-color: rgba(236, 35, 127, .3); - background: rgba(236, 35, 127, .1); - color: var(--accent-hot); -} - -.favorites-manage { - gap: 6px; - padding-inline: 12px; - border-color: rgba(255, 255, 255, .24); - color: var(--text); - cursor: pointer; -} - -.favorites-manage img, -.favorites-manage svg { - display: block; - width: 16px; - height: 16px; - flex: 0 0 16px; -} - -.favorites-filter:hover, -.favorites-manage:hover { - border-color: rgba(255, 255, 255, .36); - color: var(--text); -} - -.favorites-filter:focus-visible, -.favorites-manage:focus-visible, -.favorites-pagination button:focus-visible { - outline: 2px solid var(--accent-hot); - outline-offset: 3px; -} - -.favorites-grid { - display: grid; - grid-template-columns: repeat(6, minmax(0, 1fr)); - gap: 32px 24px; - min-width: 0; -} - -.favorites-grid .favorites-card { - min-width: 0; -} - -.favorites-card__mobile-meta, -.favorites-card__remove { - display: none; -} - -.favorites-card__media { - position: relative; - min-width: 0; -} - -.favorites-page.is-managing .favorites-card__remove { - position: absolute; - top: 6px; - left: 6px; - z-index: 3; - display: inline-flex; - align-items: center; - justify-content: center; - width: 26px; - height: 26px; - border: 1px solid rgba(255, 255, 255, .4); - border-radius: 50%; - background: rgba(11, 11, 13, .78); - color: #fff; - font-size: 18px; - line-height: 1; -} - -.favorites-card__remove img { - display: block; - width: 14px; - height: 14px; -} - -.favorites-page.is-managing .favorites-manage { - border-color: rgba(236, 35, 127, .3); - background: rgba(236, 35, 127, .1); - color: var(--accent-hot); -} - -.favorites-load-more { - display: none; -} - -.favorites-pagination, -.favorites-pagination__pages { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; -} - -.favorites-pagination { - min-height: 36px; -} - -.favorites-pagination button { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 36px; - width: 36px; - height: 36px; - border: 1px solid var(--line-soft); - border-radius: 8px; - background: var(--bg-elevated); - color: var(--text-muted); - font-family: Inter, var(--font); - font-size: 16px; - font-weight: 600; - line-height: 20px; -} - -.favorites-pagination button.is-active { - border-color: transparent; - background: linear-gradient(135deg, #ff4a7b 15%, #ec237f 51%, #93226f 85%); - color: #fff; -} - -.favorites-pagination button:not(:disabled):not(.is-active):hover { - border-color: rgba(255, 255, 255, .24); - color: var(--text); -} - -.favorites-pagination button:disabled { - cursor: default; - opacity: .45; -} - -.favorites-pagination button img, -.favorites-pagination button svg { - display: block; - width: 16px; - height: 16px; -} - -.favorites-pagination > .favorites-pagination__button:first-child img { - transform: rotate(180deg); -} - -.favorites-pagination__total { - color: var(--text-dim); - font-size: 12px; - line-height: 18px; - white-space: nowrap; -} - -.favorites-empty { - display: flex; - flex-direction: column; - align-items: center; - gap: 16px; - width: 100%; - padding: 96px 16px; - font-size: 14px; - line-height: 22px; - text-align: center; -} - -.favorites-main.is-empty:has(.favorites-recommendations) .favorites-empty { - padding-bottom: 40px; -} - -.favorites-main.is-empty .favorites-recommendations { - padding-top: 8px; -} - -.favorites-filter-empty { - margin: 0; - padding: 56px 16px; - color: var(--text-dim); - font-size: 14px; - line-height: 22px; - text-align: center; -} - -.favorites-empty__icon { - display: flex; - align-items: center; - justify-content: center; - width: 96px; - height: 96px; - flex: 0 0 96px; - border: 1px solid var(--line); - border-radius: 50%; - background: #1b1b1f; -} - -.favorites-empty__icon img { - width: 40px; - height: 40px; -} - -.favorites-empty__title { - margin: 0; - color: var(--text); - font-size: 20px; - font-weight: 600; - line-height: 28px; -} - -.favorites-empty__description { - margin: 0; - color: var(--text-muted); - font-size: 14px; - font-weight: 400; - line-height: 22px; -} - -.favorites-empty__description-mobile, -.favorites-recommendations__more-mobile { - display: none; -} - -.favorites-empty__action { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; - width: 147px; - height: 48px; - border-radius: 999px; - background: linear-gradient(105deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); - color: #fff; - font-size: 14px; - font-weight: 600; - line-height: 20px; - white-space: nowrap; -} - -.favorites-empty__action img { - width: 20px; - height: 20px; -} - -.favorites-empty__action:focus-visible, -.favorites-recommendations__head a:focus-visible { - outline: 2px solid var(--accent-hot); - outline-offset: 3px; -} - -.favorites-recommendations { - width: min(calc(100% - 48px), var(--page-width)); - margin-inline: auto; - padding-bottom: 40px; -} - -.favorites-recommendations__head { - display: flex; - align-items: center; - justify-content: space-between; - height: 32px; - margin-bottom: 18px; -} - -.favorites-recommendations__head h2 { - margin: 0; - color: var(--text); - font-size: 20px; - font-weight: 600; - line-height: 28px; -} - -.favorites-recommendations__head a { - display: inline-flex; - align-items: center; - gap: 4px; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.favorites-recommendations__head a img { - width: 16px; - height: 16px; -} - -.favorites-recommendations__grid { - display: grid; - grid-template-columns: repeat(6, minmax(0, 1fr)); - gap: 24px; -} diff --git a/app/static/css/footer.css b/app/static/css/footer.css index e3c3cda..d017a09 100644 --- a/app/static/css/footer.css +++ b/app/static/css/footer.css @@ -255,3 +255,99 @@ text-align: center; } } + +/* 访问地址:横向地址卡片,窄屏改为纵向堆叠 */ +.footer-addrs { + display: flex; + flex-direction: column; + gap: 14px; +} + +.footer-addrs__head { + display: flex; + align-items: baseline; + gap: 12px; +} + +.footer-addrs__head h3 { + margin: 0; + font-size: 14px; + font-weight: 500; + line-height: 20px; +} + +.footer-addrs__head p { + margin: 0; + color: var(--text-dim); + font-size: 12px; + line-height: 18px; +} + +.footer-addrs__list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); + gap: 10px; +} + +.footer-addr { + display: flex; + align-items: center; + gap: 10px; + min-width: 0; + padding: 10px 14px; + border: 1px solid var(--line-soft); + border-left: 3px solid var(--accent); + border-radius: var(--radius-sm); + background: var(--bg-panel); + text-decoration: none; +} + +.footer-addr:hover { + border-color: var(--line); + border-left-color: var(--accent-hot); + background: rgba(236, 35, 127, 0.08); +} + +.footer-addr__label { + flex-shrink: 0; + color: var(--text-muted); + font-size: 12px; + line-height: 18px; +} + +.footer-addr__url { + flex: 1; + min-width: 0; + color: var(--text); + font-size: 13px; + line-height: 18px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.footer-addr__go { + flex-shrink: 0; + opacity: 0.6; +} + +@media (max-width: 768px) { + .footer-addrs { + width: 100%; + gap: 10px; + text-align: left; + } + + .footer-addrs__head { + justify-content: space-between; + } + + .footer-addrs__list { + grid-template-columns: 1fr; + gap: 8px; + } + + .footer-addr { + padding: 9px 12px; + } +} diff --git a/app/static/css/header.css b/app/static/css/header.css index b49431e..2caeac7 100644 --- a/app/static/css/header.css +++ b/app/static/css/header.css @@ -1194,3 +1194,13 @@ body.drawer-open { overflow: hidden; } + +/* 手机页头:商务合作 Telegram 入口,与搜索按钮同尺寸,带品牌色底 */ +.site-header__tg { + background: rgba(42, 171, 238, 0.14); +} + +.site-header__tg .icon { + width: 18px; + height: 18px; +} diff --git a/app/static/css/history-mobile.css b/app/static/css/history-mobile.css deleted file mode 100644 index 0d6a0c2..0000000 --- a/app/static/css/history-mobile.css +++ /dev/null @@ -1,256 +0,0 @@ -/* 观看历史 H5:对齐 Figma 375px 画板。 */ -@media (max-width: 768px) { - .history-main { - display: flex; - flex-direction: column; - gap: 12px; - width: 100%; - max-width: none; - margin: 0; - padding: 12px 16px 20px; - } - - .history-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - min-width: 0; - min-height: 30px; - } - - .history-filters { - display: flex; - flex: 1 1 auto; - align-items: center; - gap: 10px; - min-width: 0; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - - .history-filters::-webkit-scrollbar { display: none; } - - .history-filter { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - justify-content: center; - height: 30px; - padding: 0 16px; - border: 1px solid rgba(255, 255, 255, .12); - border-radius: 999px; - background: transparent; - color: #a1a1aa; - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; - cursor: pointer; - } - - .history-filter.is-active { - border-color: rgba(236, 35, 127, .3); - background: rgba(236, 35, 127, .1); - color: #ff4a7b; - } - - .history-clear { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - justify-content: center; - height: auto; - padding: 0; - border: 0; - background: transparent; - color: #a1a1aa; - font-size: 12px; - font-weight: 400; - line-height: 18px; - white-space: nowrap; - cursor: pointer; - } - - .history-clear img, - .history-clear__desktop { display: none; } - .history-clear__mobile { display: inline; } - - .history-group, - .history-list { - display: flex; - flex-direction: column; - width: 100%; - min-width: 0; - } - - .history-group__title { - margin: 0; - color: #7c7c86; - font-size: 12px; - font-weight: 500; - line-height: 16px; - } - - .history-item { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - min-width: 0; - padding: 12px 0; - border-bottom: 1px solid rgba(255, 255, 255, .07); - } - - .history-item__cover { - display: block; - flex: 0 0 48px; - width: 48px; - height: 64px; - overflow: hidden; - border-radius: 6px; - background: #141417; - } - - .history-item__image { - display: block; - width: 100%; - height: 100%; - object-fit: cover; - } - - .history-item__body { - display: flex; - flex: 1 1 0; - flex-direction: column; - align-items: flex-start; - gap: 5px; - min-width: 0; - } - - .history-item__title { - display: block; - align-self: stretch; - overflow: hidden; - margin: 0; - color: #f2f2f4; - font-size: 15px; - font-weight: 600; - line-height: 21px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .history-item__title a { - color: inherit; - text-decoration: none; - } - - .history-item__details { - display: block; - align-self: stretch; - overflow: hidden; - color: #7c7c86; - font-size: 11px; - font-weight: 400; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .history-item__episodes, - .history-item__mobile-time { - display: inline; - } - - .history-item__mobile-time::before { - content: " "; - white-space: pre; - } - - .history-item__genre, - .history-item__time, - .history-item__resume-desktop { - display: none; - } - - .history-item__progress { - display: block; - width: 140px; - max-width: 100%; - height: 3px; - overflow: hidden; - border-radius: 999px; - background: rgba(255, 255, 255, .12); - } - - .history-item__progress-fill { - display: block; - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); - } - - .history-item__resume { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - justify-content: center; - gap: 8px; - width: auto; - min-width: 0; - height: 32px; - padding: 0 12px; - border: 1px solid rgba(255, 255, 255, .24); - border-radius: 999px; - background: transparent; - color: #f2f2f4; - font-size: 14px; - font-weight: 500; - line-height: 20px; - text-decoration: none; - white-space: nowrap; - } - - .history-item__resume svg, - .history-item__resume img { - flex: 0 0 16px; - width: 16px; - height: 16px; - } - - .history-item__resume-mobile { - display: inline; - } - - .history-item__remove { - display: inline-flex; - flex: 0 0 14px; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - padding: 0; - border: 0; - background: transparent; - color: #7c7c86; - cursor: pointer; - } - - .history-item__remove svg, - .history-item__remove img { - display: block; - width: 14px; - height: 14px; - } - - .history-main .account-pagination { - margin-top: 4px; - } - - .history-empty, - .history-main .account-empty { - padding: 48px 0; - } -} diff --git a/app/static/css/history.css b/app/static/css/history.css deleted file mode 100644 index a8ad0c2..0000000 --- a/app/static/css/history.css +++ /dev/null @@ -1,256 +0,0 @@ -/* 观看历史:1440px Figma 桌面画板,沿用个人中心公共页头与页签。 */ -.history-page [hidden] { display: none !important; } - -.history-main { - display: flex; - flex-direction: column; - gap: 24px; - width: min(calc(100% - 48px), var(--page-width)); - margin-inline: auto; - padding: 24px 0 40px; -} - -.history-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - min-height: 36px; -} - -.history-filters { - display: flex; - align-items: center; - gap: 10px; -} - -.history-filter, -.history-clear, -.history-item__resume { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - justify-content: center; - height: 36px; - padding: 0; - border: 1px solid var(--line); - border-radius: 999px; - background: transparent; - color: var(--text-muted); - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; - cursor: pointer; -} - -.history-filter { - padding-inline: 16px; -} - -.history-filter.is-active { - border-color: rgba(236, 35, 127, .3); - background: rgba(236, 35, 127, .1); - color: var(--accent-hot); -} - -.history-clear { - gap: 6px; - padding-inline: 12px; - border-color: rgba(255, 255, 255, .24); - color: var(--text); -} - -.history-clear__mobile { display: none; } - -.history-clear svg, -.history-clear img, -.history-item__resume svg, -.history-item__resume img, -.history-item__remove svg, -.history-item__remove img { - display: block; - width: 16px; - height: 16px; - flex: 0 0 16px; -} - -.history-filter:hover, -.history-clear:hover, -.history-item__resume:hover, -.history-item__remove:hover { - border-color: rgba(255, 255, 255, .36); - color: var(--text); -} - -.history-filter:focus-visible, -.history-clear:focus-visible, -.history-item__resume:focus-visible, -.history-item__remove:focus-visible { - outline: 2px solid var(--accent-hot); - outline-offset: 3px; -} - -.history-group { - min-width: 0; -} - -.history-group__title { - margin: 0; - color: var(--text-dim); - font-size: 12px; - font-weight: 500; - line-height: 16px; -} - -.history-list { - margin: 0; - padding: 0; - list-style: none; -} - -.history-item { - display: grid; - grid-template-columns: 56px minmax(0, 1fr) 120px 106px 16px; - align-items: center; - gap: 16px; - min-width: 0; - min-height: 104px; - padding-block: 14px; - border-bottom: 1px solid var(--line-soft); -} - -.history-item__cover { - display: block; - width: 56px; - height: 75px; - overflow: hidden; - border-radius: 6px; - background: var(--bg-panel); -} - -.history-item__image { - display: block; - width: 100%; - height: 100%; - object-fit: cover; -} - -.history-item__body { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - min-width: 0; -} - -.history-item__title { - display: block; - max-width: 100%; - margin: 0; - overflow: hidden; - color: var(--text); - font-size: 16px; - font-weight: 600; - line-height: 22px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.history-item__title a { - color: inherit; - text-decoration: none; -} - -.history-item__title a:hover { - color: var(--accent-hot); -} - -.history-item__details { - display: flex; - align-items: center; - gap: 8px; - max-width: 100%; - overflow: hidden; - color: #a1a1aa; - font-size: 13px; - font-weight: 400; - line-height: 20px; - white-space: nowrap; -} - -.history-item__episodes, -.history-item__genre { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.history-item__episodes { - flex: 0 0 auto; -} - -.history-item__progress { - display: block; - width: 240px; - max-width: 100%; - height: 3px; - overflow: hidden; - border-radius: 999px; - background: var(--line); -} - -.history-item__progress-fill { - display: block; - width: var(--progress, 0%); - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); -} - -.history-item__time { - color: var(--text-dim); - font-size: 12px; - line-height: 18px; - white-space: nowrap; -} - -.history-item__mobile-time, -.history-item__resume-mobile { - display: none; -} - -.history-item__resume { - gap: 8px; - width: 106px; - padding-inline: 12px; - border-color: rgba(255, 255, 255, .24); - color: var(--text); - text-decoration: none; -} - -.history-item__remove { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 36px; - padding: 0; - border: 0; - background: transparent; - color: var(--text-dim); - cursor: pointer; -} - -.history-group + .history-group { - margin-top: 0; -} - -.history-main.is-empty { - gap: 24px; - padding: 24px 0 40px; -} - -.history-main.is-empty .account-empty { - padding-block: 72px; -} diff --git a/app/static/css/index.css b/app/static/css/index.css index c1b5a73..4b0b64f 100644 --- a/app/static/css/index.css +++ b/app/static/css/index.css @@ -1364,3 +1364,78 @@ width: 100%; } } + +/* 公告跑马灯:官方访问地址以金色描边胶囊展示 */ +.announce__addrs { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.announce__addrs > span { + color: var(--gold); +} + +.announce__addr { + display: inline-flex; + align-items: center; + height: 22px; + padding: 0 10px; + border: 1px solid rgba(233, 201, 119, 0.45); + border-radius: 999px; + color: var(--text); + font-size: 12px; + line-height: 20px; + text-decoration: none; + background: rgba(233, 201, 119, 0.08); +} + +.announce__addr:hover { + border-color: var(--gold); + background: rgba(233, 201, 119, 0.16); +} + +@media (max-width: 768px) { + .announce__addr { + height: 20px; + padding: 0 8px; + font-size: 11px; + line-height: 18px; + } +} + +/* Banner 可读性(桌面端):背景海报保持原样,只在左侧文字区域加一道渐变 */ +@media (min-width: 769px) { + .hero__shade--side { + background: + linear-gradient(90deg, #0b0b0d 0%, rgba(11, 11, 13, 0.78) 16%, rgba(11, 11, 13, 0.45) 36%, rgba(11, 11, 13, 0) 52%), + linear-gradient(270deg, #0b0b0d 0%, rgba(11, 11, 13, 0.72) 8%, rgba(11, 11, 13, 0) 24%); + } + + .hero__title, + .hero__desc { + text-shadow: 0 2px 12px rgba(0, 0, 0, 0.6); + } +} + +.hero__attrs { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px 12px; +} + +@media (max-width: 768px) { + /* 手机端背景即主画面,不模糊;拉高底部渐变保证文字可读 */ + .hero__shade--bottom { + background: linear-gradient(180deg, rgba(11, 11, 13, 0) 0%, rgba(11, 11, 13, 0.25) 28%, rgba(11, 11, 13, 0.82) 56%, #0b0b0d 78%); + } + + .hero__desc { + -webkit-line-clamp: 2; + } + + .hero__attrs { + gap: 4px 10px; + } +} diff --git a/app/static/css/login.css b/app/static/css/login.css deleted file mode 100644 index 81a3e34..0000000 --- a/app/static/css/login.css +++ /dev/null @@ -1,283 +0,0 @@ -/* H5 登录页 */ - -body.page-login { - min-height: 100vh; - background: var(--bg); -} - -.page-login .page-subheader { - display: flex; - border-bottom: 0; -} - -.login-page { - min-height: 100vh; - padding: 56px 0 32px; - box-sizing: border-box; -} - -.login-page__inner { - width: 100%; - max-width: 440px; - margin: 0 auto; - padding: 24px var(--h5-gutter); - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; - box-sizing: border-box; -} - -.login-page__brand { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.login-page__logo { - width: 64px; - height: 64px; - border-radius: 12px; - object-fit: cover; - box-shadow: 0 8px 24px rgba(236, 35, 127, 0.4); -} - -.login-page__content { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 18px; -} - -.login-page__intro { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; -} - -.login-page__title { - margin: 0; - color: var(--text); - font-size: 28px; - font-weight: 600; - line-height: 38px; -} - -.login-page__desc { - margin: 0; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.login-page__form { - align-self: stretch; - display: flex; - flex-direction: column; - gap: 16px; -} - -.login-page__field { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; -} - -.login-page__label { - color: var(--text-muted); - font-size: 14px; - font-weight: 500; - line-height: 20px; -} - -.login-page__control { - align-self: stretch; - height: 48px; - padding: 0 16px; - display: flex; - align-items: center; - gap: 10px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid transparent; - border-radius: 12px; - outline: 1px solid transparent; - outline-offset: -1px; -} - -.login-page__control:focus-within { - outline-color: rgba(236, 35, 127, 0.45); -} - -.login-page__field.is-error .login-page__control { - outline-color: #ff5a5f; -} - -.login-page__error { - display: inline-flex; - align-items: center; - gap: 6px; - color: #ff5a5f; - font-size: 12px; - font-weight: 400; - line-height: 18px; -} - -.login-page__error[hidden] { - display: none !important; -} - -.login-page__error-icon { - width: 14px; - height: 14px; - flex-shrink: 0; -} - -.login-page__control > .icon { - width: 18px; - height: 18px; - flex-shrink: 0; -} - -.login-page__input { - flex: 1 1 0; - min-width: 0; - height: 100%; - padding: 0; - border: 0; - outline: none; - background: transparent; - color: var(--text); - font: inherit; - font-size: 15px; - line-height: 24px; -} - -.login-page__input::placeholder { - color: var(--text-dim); -} - -.login-page__toggle { - width: 18px; - height: 18px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--text-dim); -} - -.login-page__toggle .icon { - width: 18px; - height: 18px; -} - -.login-page__meta { - align-self: stretch; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.login-page__remember { - display: inline-flex; - align-items: center; - gap: 8px; - color: var(--text-muted); - font-size: 13px; - line-height: 20px; - cursor: pointer; -} - -.login-page__checkbox { - position: absolute; - opacity: 0; - width: 1px; - height: 1px; - pointer-events: none; -} - -.login-page__check { - width: 18px; - height: 18px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid var(--line); -} - -.login-page__checkbox:checked + .login-page__check { - border-color: transparent; - background: linear-gradient(135deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); -} - -.login-page__checkbox:checked + .login-page__check::after { - content: ""; - width: 10px; - height: 6px; - border-left: 1.5px solid #fff; - border-bottom: 1.5px solid #fff; - transform: translateY(-1px) rotate(-45deg); -} - -.login-page__forgot { - color: #ff4a7b; - font-size: 13px; - line-height: 20px; -} - -.login-page__submit { - align-self: stretch; - height: 52px; - padding: 0 28px; - font-size: 16px; - line-height: 24px; -} - -.login-page__switch { - margin: 0; - align-self: stretch; - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.login-page__switch a { - color: #ff4a7b; - font-size: 14px; - font-weight: 500; - line-height: 20px; -} - -.login-page__rule { - align-self: stretch; - height: 1px; - background: var(--line-soft); -} - -.login-page__legal { - margin: 0; - align-self: stretch; - text-align: center; - color: var(--text-dim); - font-size: 12px; - line-height: 18px; -} - -.login-page__legal a { - color: inherit; - text-decoration: underline; - text-underline-offset: 2px; -} diff --git a/app/static/css/loginPop.css b/app/static/css/loginPop.css deleted file mode 100644 index 3269103..0000000 --- a/app/static/css/loginPop.css +++ /dev/null @@ -1,308 +0,0 @@ -/* PC 登录弹窗 */ - -.login-pop { - position: fixed; - inset: 0; - z-index: 290; - display: none; - align-items: center; - justify-content: center; - padding: 24px; -} - -.login-pop.is-open { - display: flex; -} - -.login-pop__mask { - position: absolute; - inset: 0; - background: rgba(0, 0, 0, 0.7); -} - -.login-pop__panel { - position: relative; - z-index: 1; - width: 440px; - max-width: 100%; - padding: 32px; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 20px; - background: #141417; - border: 1px solid var(--line); - border-radius: var(--radius-xl); - box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55); - overflow: hidden; -} - -.login-pop__close { - position: absolute; - top: 16px; - right: 16px; - width: 32px; - height: 32px; - display: inline-flex; - align-items: center; - justify-content: center; - background: rgba(255, 255, 255, 0.04); - border-radius: 999px; -} - -.login-pop__close .icon { - width: 18px; - height: 18px; -} - -.login-pop__intro { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - padding-right: 40px; -} - -.login-pop__title { - margin: 0; - color: var(--text); - font-size: 28px; - font-weight: 600; - line-height: 38px; -} - -.login-pop__desc { - margin: 0; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.login-pop__form { - align-self: stretch; - display: flex; - flex-direction: column; - gap: 16px; -} - -.login-pop__field { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; -} - -.login-pop__label { - color: var(--text-muted); - font-size: 14px; - font-weight: 500; - line-height: 20px; -} - -.login-pop__control { - align-self: stretch; - height: 48px; - padding: 0 16px; - display: flex; - align-items: center; - gap: 10px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid transparent; - border-radius: 12px; - outline: 1px solid transparent; - outline-offset: -1px; -} - -.login-pop__control:focus-within { - outline-color: rgba(236, 35, 127, 0.45); -} - -.login-pop__field.is-error .login-pop__control { - outline-color: #ff5a5f; -} - -.login-pop__error { - display: inline-flex; - align-items: center; - gap: 6px; - color: #ff5a5f; - font-size: 12px; - font-weight: 400; - line-height: 18px; -} - -.login-pop__error[hidden] { - display: none !important; -} - -.login-pop__error-icon { - width: 14px; - height: 14px; - flex-shrink: 0; -} - -.login-pop__control > .icon { - width: 18px; - height: 18px; - flex-shrink: 0; - color: var(--text-dim); -} - -.login-pop__input { - flex: 1 1 0; - min-width: 0; - height: 100%; - padding: 0; - border: 0; - outline: none; - background: transparent; - color: var(--text); - font: inherit; - font-size: 15px; - line-height: 24px; -} - -.login-pop__input::placeholder { - color: var(--text-dim); -} - -.login-pop__toggle { - width: 18px; - height: 18px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--text-dim); -} - -.login-pop__toggle .icon { - width: 18px; - height: 18px; -} - -.login-pop__meta { - align-self: stretch; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.login-pop__remember { - display: inline-flex; - align-items: center; - gap: 8px; - color: var(--text-muted); - font-size: 13px; - line-height: 20px; - cursor: pointer; -} - -.login-pop__checkbox { - position: absolute; - opacity: 0; - width: 1px; - height: 1px; - pointer-events: none; -} - -.login-pop__check { - width: 18px; - height: 18px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid var(--line); -} - -.login-pop__checkbox:checked + .login-pop__check { - border-color: transparent; - background: linear-gradient(135deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); -} - -.login-pop__checkbox:checked + .login-pop__check::after { - content: ""; - width: 10px; - height: 6px; - border-left: 1.5px solid #fff; - border-bottom: 1.5px solid #fff; - transform: translateY(-1px) rotate(-45deg); -} - -.login-pop__submit { - align-self: stretch; - height: 52px; - padding: 0 28px; - font-size: 16px; - line-height: 24px; -} - -.login-pop__submit.is-loading, -.login-pop__submit:disabled { - opacity: 0.72; - cursor: wait; -} - -.login-pop__tip { - margin: 0; - align-self: stretch; - padding: 8px 12px; - border-radius: 8px; - font-size: 13px; - line-height: 20px; - background: rgba(255, 74, 123, 0.1); - color: #ff4a7b; -} - -.login-pop__tip.is-success { - background: rgba(30, 120, 70, 0.12); - color: #1e7846; -} - -.login-pop__tip[hidden] { - display: none !important; -} - -.login-pop__rule { - align-self: stretch; - height: 1px; - background: var(--line-soft); -} - -.login-pop__legal { - margin: 0; - align-self: stretch; - text-align: center; - color: var(--text-dim); - font-size: 12px; - line-height: 18px; -} - -.login-pop__legal a { - color: inherit; - text-decoration: underline; - text-underline-offset: 2px; -} - -@media (max-width: 768px) { - .login-pop { - padding: 16px; - } - - .login-pop__panel { - max-height: calc(100vh - 32px); - max-height: calc(100dvh - 32px); - padding: 24px 20px; - border-radius: 18px; - overflow-y: auto; - } - - .login-pop__title { - font-size: 24px; - line-height: 32px; - } -} diff --git a/app/static/css/play.css b/app/static/css/play.css index 2255f6d..bffa0b1 100644 --- a/app/static/css/play.css +++ b/app/static/css/play.css @@ -980,10 +980,8 @@ a.play-meta__tag:focus-visible { width: 12px; height: 12px; border-radius: 999px; - background: - url("/static/images/coin.png") center / 8px 8px no-repeat, - linear-gradient(135deg, #e9c977 0%, #d9a94a 100%); - border: 1px solid #e9c977; + background: url("/static/images/lock.svg") center / 8px 8px no-repeat, var(--accent-deep); + border: 1px solid var(--accent); box-sizing: border-box; } @@ -1817,3 +1815,45 @@ a.play-meta__tag:focus-visible { line-height: 18px; } } + +/* 简介下方标签行 */ +.play-synopsis__tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; +} + +.play-synopsis__tag { + padding: 3px 10px; + border-radius: var(--radius-sm); + color: var(--text-muted); + font-size: 12px; + line-height: 18px; + text-decoration: none; + background: rgba(255, 255, 255, 0.06); +} + +.play-synopsis__tag::before { + content: "#"; + margin-right: 2px; + color: var(--accent); +} + +a.play-synopsis__tag:hover { + color: var(--text); + background: rgba(236, 35, 127, 0.12); +} + +@media (max-width: 768px) { + .play-synopsis__tags { + gap: 6px; + margin-top: 10px; + } + + .play-synopsis__tag { + padding: 2px 8px; + font-size: 11px; + line-height: 16px; + } +} diff --git a/app/static/css/profile-mobile.css b/app/static/css/profile-mobile.css deleted file mode 100644 index 41fe34f..0000000 --- a/app/static/css/profile-mobile.css +++ /dev/null @@ -1,590 +0,0 @@ -/* 个人中心 H5:以 Figma 375px 画板为基准。 */ -@media (max-width: 768px) { - .profile-page { - --profile-gutter: clamp(14px, 4.266667vw, 18px); - --profile-card-gap: clamp(7px, 2.133333vw, 9px); - width: 100%; - min-width: 0; - padding-top: 0; - background: #0b0b0d; - } - - .profile-hero { - width: 100%; - padding-top: 56px; /* 公共导航固定在视口顶部 */ - background: linear-gradient(180deg, #2a0d22 0%, #150a17 60%, #0b0b0d 100%); - } - - .profile-hero::before { - top: -90px; - left: -60px; - width: 360px; - height: 220px; - filter: blur(55px); - opacity: 0.22; - } - - .profile-hero__inner { - display: block; - grid-template-columns: none; - width: 100%; - max-width: none; - margin: 0; - padding: 0; - } - - .profile-identity { - display: flex; - align-items: center; - gap: clamp(12px, 3.733333vw, 16px); - min-width: 0; - margin: 0; - padding: 12px var(--profile-gutter) 16px; - } - - .profile-identity__avatar { - display: block; - flex: 0 0 clamp(55px, 17.066667vw, 73px); - width: clamp(55px, 17.066667vw, 73px); - height: clamp(55px, 17.066667vw, 73px); - border: 2px solid rgba(255, 255, 255, 0.24); - border-radius: 50%; - object-fit: cover; - } - - .profile-identity__copy { - display: grid; - flex: 1 1 0; - grid-template-columns: minmax(0, 1fr) auto; - grid-template-rows: 26px 16px; - column-gap: 12px; - row-gap: 6px; - min-width: 0; - } - - .profile-identity__name { - grid-column: 1; - grid-row: 1; - overflow: hidden; - color: #f2f2f4; - font-size: 18px; - font-weight: 600; - line-height: 26px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .profile-identity__meta { - grid-column: 1; - grid-row: 2; - overflow: hidden; - color: #a1a1aa; - font-size: 11px; - font-weight: 400; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .profile-identity__edit { - display: inline-flex; - grid-column: 2; - grid-row: 1 / 3; - align-self: center; - flex: 0 0 auto; - align-items: center; - justify-content: center; - gap: 8px; - height: 32px; - padding: 0 8px; - color: #f2f2f4; - border: 1px solid rgba(255, 255, 255, 0.24); - border-radius: 999px; - font-size: 0; - font-weight: 500; - line-height: 0; - white-space: nowrap; - } - - .profile-identity__edit::after { - content: "编辑"; - font-size: 14px; - line-height: 20px; - } - - .profile-identity__edit .icon, - .profile-identity__edit img, - .profile-identity__edit svg { - width: 16px; - height: 16px; - } - - .profile-wallet { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - min-height: 101px; - margin: 0 var(--profile-gutter) 16px; - padding: 14px; - background: #141417; - border: 1px solid rgba(255, 255, 255, 0.07); - border-radius: 12px; - } - - .profile-wallet__label { - display: flex; - align-items: center; - gap: 6px; - color: #f2f2f4; - font-size: 15px; - font-weight: 600; - line-height: 21px; - } - - .profile-wallet__amount { - display: block; - margin-top: 4px; - color: #e9c977; - font-family: Inter, var(--font); - font-size: 20px; - font-weight: 600; - line-height: 26px; - } - - .profile-wallet__hint { - margin: 4px 0 0; - color: #7c7c86; - font-size: 11px; - line-height: 16px; - white-space: nowrap; - } - - .profile-wallet__actions { - display: flex; - flex: 0 0 auto; - align-items: center; - } - - .profile-wallet__recharge { - display: inline-flex; - align-items: center; - justify-content: center; - height: 32px; - padding: 0 12px; - color: #fff; - background: linear-gradient(135deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); - border: 0; - border-radius: 999px; - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; - } - - .profile-wallet__details { - display: none; - } - - .profile-stats { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - margin: 0; - min-height: 64px; - padding: 12px var(--profile-gutter) 14px; - border-top: 1px solid rgba(255, 255, 255, 0.07); - } - - .profile-stat { - display: flex; - flex: 0 0 auto; - flex-direction: column; - align-items: center; - gap: 2px; - min-width: 0; - text-align: center; - } - - .profile-stat__value { - color: #f2f2f4; - font-family: Inter, var(--font); - font-size: 16px; - font-weight: 600; - line-height: 20px; - } - - .profile-stat__value span { display: none; } - - .profile-stat__label { - color: #7c7c86; - font-size: 11px; - font-weight: 400; - line-height: 16px; - white-space: nowrap; - } - - .profile-tabs { - width: 100%; - margin: 0; - padding-top: 0; - border-bottom: 1px solid rgba(255, 255, 255, 0.07); - } - - .profile-tabs__inner { - display: flex; - align-items: flex-end; - gap: 24px; - width: 100%; - max-width: none; - min-height: 38px; - height: 38px; - margin: 0; - padding: 6px var(--profile-gutter) 0; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: none; - -webkit-overflow-scrolling: touch; - } - - .profile-tabs__inner::-webkit-scrollbar { display: none; } - - .profile-tabs__link { - position: relative; - display: inline-flex; - flex: 0 0 auto; - align-items: flex-start; - justify-content: center; - min-height: 31px; - padding: 0 0 10px; - color: #a1a1aa; - font-size: 15px; - font-weight: 600; - line-height: 21px; - white-space: nowrap; - } - - .profile-tabs__link.is-active { color: #f2f2f4; } - - .profile-tabs__link.is-active::after { - content: ""; - position: absolute; - bottom: 0; - left: 50%; - width: 20px; - height: 2px; - background: linear-gradient(90deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); - transform: translateX(-50%); - } - - .profile-main { - display: block; - width: 100%; - max-width: none; - min-width: 0; - margin: 0; - padding: 0; - } - - .profile-section { - display: flex; - flex-direction: column; - gap: 14px; - width: 100%; - max-width: none; - margin: 0; - padding-right: var(--profile-gutter); - padding-left: var(--profile-gutter); - } - - .profile-section--recent { padding-top: 16px; padding-bottom: 8px; } - .profile-section--favorites { padding-top: 12px; padding-bottom: 8px; } - - .profile-section__head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - min-height: 26px; - } - - .profile-section__heading { - display: flex; - align-items: baseline; - gap: 10px; - min-width: 0; - } - - .profile-section__title { - margin: 0; - color: #f2f2f4; - font-size: 18px; - font-weight: 600; - line-height: 26px; - white-space: nowrap; - } - - .profile-section__subtitle { - color: #7c7c86; - font-size: 12px; - line-height: 18px; - white-space: nowrap; - } - - .profile-section--recent .profile-section__subtitle { display: none; } - - .profile-section__more { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - gap: 2px; - color: #a1a1aa; - font-size: 13px; - line-height: 20px; - white-space: nowrap; - } - - .profile-section__more .icon, - .profile-section__more img, - .profile-section__more svg { width: 14px; height: 14px; } - - .profile-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px var(--profile-card-gap); - width: 100%; - min-width: 0; - } - - .profile-section--recent .profile-card:nth-child(n + 4) { display: none; } - - .profile-card { - display: flex; - flex-direction: column; - gap: 6px; - min-width: 0; - } - - .profile-card__cover { - position: relative; - display: block; - width: 100%; - aspect-ratio: 109 / 145; - overflow: hidden; - background: #1b1b1f; - border: 1px solid rgba(255, 255, 255, 0.07); - border-radius: 8px; - } - - .profile-card__cover::after { - content: ""; - position: absolute; - inset: 0; - z-index: 1; - background: linear-gradient(180deg, transparent 58%, rgba(0, 0, 0, 0.7) 100%); - pointer-events: none; - } - - .profile-card__image { - display: block; - width: 100%; - height: 100%; - object-fit: cover; - } - - .profile-card__progress { - position: absolute; - right: 10px; - bottom: 6px; - left: 10px; - z-index: 2; - height: 3px; - overflow: hidden; - background: rgba(255, 255, 255, 0.12); - border-radius: 999px; - } - - .profile-card__progress-fill { - display: block; - width: 60%; - height: 100%; - background: linear-gradient(90deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); - } - - .profile-section--recent .profile-card:nth-child(1) .profile-card__progress-fill { width: 59.6%; } - .profile-section--recent .profile-card:nth-child(2) .profile-card__progress-fill { width: 24.7%; } - .profile-section--recent .profile-card:nth-child(3) .profile-card__progress-fill { width: 79.8%; } - - .profile-card__episodes { - position: absolute; - bottom: 10px; - left: 10px; - z-index: 2; - color: #f2f2f4; - font-size: 11px; - font-weight: 500; - line-height: 14px; - white-space: nowrap; - } - - .profile-card__badge { - position: absolute; - top: 4px; - right: 4px; - z-index: 2; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 30px; - height: 20px; - padding: 0 5px; - color: #141414; - background: linear-gradient(135deg, #fadc44 15%, #f9c24e 85%); - border-radius: 6px; - font-size: 11px; - font-weight: 500; - line-height: 14px; - white-space: nowrap; - } - - .profile-card__badge:not(.profile-card__badge--hot) { - color: #141414; - background: linear-gradient(135deg, #fadc44 15%, #f9c24e 85%); - } - - .profile-card__badge--hot, - .profile-card__badge[data-badge="hot"] { - color: #fff; - background: linear-gradient(135deg, #f04050 15%, #d01030 85%); - } - - .profile-card__title { - display: block; - overflow: hidden; - color: #f2f2f4; - font-size: 14px; - font-weight: 600; - line-height: 20px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .profile-card__meta { - display: block; - overflow: hidden; - color: #a1a1aa; - font-size: 11px; - font-weight: 400; - line-height: 16px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .profile-card__remaining { display: none; } - - .profile-promo { - display: flex; - flex-direction: column; - gap: 8px; - width: 100%; - max-width: none; - margin: 0; - padding: 8px var(--profile-gutter); - } - - .profile-ad { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 4px; - width: 100%; - aspect-ratio: 343 / 98; - min-height: 0; - background: #232328; - border: 1px solid rgba(255, 255, 255, 0.07); - border-radius: 12px; - text-align: center; - } - - .profile-ad__icon { width: 20px; height: 20px; object-fit: contain; } - - .profile-ad__title { - color: #a1a1aa; - font-size: 15px; - font-weight: 600; - line-height: 21px; - } - - .profile-ad__mobile-size { display: inline; } - .profile-ad__desktop-size { display: none; } - - .profile-ad__meta { - color: #7c7c86; - font-size: 11px; - line-height: 16px; - } - - .profile-ad__badge { - position: absolute; - top: 8px; - right: 8px; - display: inline-flex; - align-items: center; - justify-content: center; - height: 20px; - padding: 0 6px; - color: #f2f2f4; - background: rgba(0, 0, 0, 0.6); - border-radius: 999px; - font-size: 11px; - font-weight: 500; - line-height: 14px; - } - - /* The shared footer's compact profile arrangement in the mobile design. */ - .profile-page .site-footer__brand { gap: 18px; } - .profile-page .site-footer__links { - flex-wrap: wrap; - gap: 12px 16px; - } - .profile-page .footer-col { flex-wrap: nowrap; } - .profile-page .footer-col:first-child, - .profile-page .footer-col:nth-child(2) a:last-child, - .profile-page .footer-col:nth-child(3) a:last-child { display: none; } - .profile-page .footer-download { - display: flex; - flex-basis: 100%; - justify-content: center; - padding: 0; - border: 0; - background: none; - } - .profile-page .footer-download .qr-box, - .profile-page .footer-download__copy strong, - .profile-page .footer-download__copy p, - .profile-page .footer-download__copy .icon { display: none; } - .profile-page .footer-download .btn { - height: 20px; - padding: 0; - color: var(--text-muted); - background: none; - font-size: 13px; - line-height: 20px; - } - .profile-page .site-footer__rule { display: none; } - .profile-page .site-footer__legal p:first-child { - order: 2; - width: 100%; - padding-top: 16px; - margin-top: 16px; - border-top: 1px solid var(--line-soft); - } - .profile-page .site-footer__legal p:last-child { order: 1; } - .profile-page .site-footer__adult { display: none; } - .profile-page .site-footer__rights { display: none; } - .profile-page .site-footer__mobile-adult { display: inline; } -} diff --git a/app/static/css/profile.css b/app/static/css/profile.css deleted file mode 100644 index a4d8a8b..0000000 --- a/app/static/css/profile.css +++ /dev/null @@ -1,557 +0,0 @@ -/* Personal center: desktop layout based on the 1440px design frame. */ -.profile-page { - min-height: 100vh; - padding-top: var(--site-header-height, 72px); - background: var(--bg); -} - -.profile-hero { - position: relative; - overflow: hidden; - background: linear-gradient(180deg, #2a0d22 0%, #150a17 60%, #0b0b0d 100%); -} - -.profile-hero::before { - position: absolute; - top: -140px; - left: -120px; - width: 620px; - height: 360px; - border-radius: 50%; - background: var(--accent); - filter: blur(80px); - opacity: .2; - content: ""; - pointer-events: none; -} - -.profile-hero__inner, -.profile-stats, -.profile-tabs, -.profile-main, -.profile-promo { - width: min(calc(100% - 48px), var(--page-width)); - margin-inline: auto; -} - -.profile-hero__inner { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - gap: 32px; - padding-block: 36px 28px; -} - -.profile-identity { - display: flex; - align-items: center; - gap: 24px; - min-width: 0; -} - -.profile-identity__avatar { - width: 96px; - height: 96px; - flex: 0 0 96px; - border: 3px solid rgba(255, 255, 255, .24); - border-radius: 50%; - object-fit: cover; -} - -.profile-identity__copy { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; - min-width: 0; -} - -.profile-identity__name { - max-width: 100%; - margin: 0; - overflow: hidden; - color: var(--text); - font-size: 32px; - font-weight: 600; - line-height: 40px; - letter-spacing: -.01em; - text-overflow: ellipsis; - white-space: nowrap; -} - -.profile-identity__meta { - margin: 0; - color: var(--text-muted); - font-size: 13px; - line-height: 18px; - letter-spacing: .06em; -} - -.profile-identity__edit { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; - height: 36px; - padding-inline: 12px; - border: 1px solid rgba(255, 255, 255, .24); - border-radius: 999px; - color: var(--text); - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; -} - -.profile-identity__edit img, -.profile-wallet__label img { - width: 16px; - height: 16px; - flex: 0 0 16px; -} - -.profile-wallet { - display: flex; - align-items: center; - gap: 28px; - min-height: 120px; - padding: 18px 24px; - border: 1px solid var(--line-soft); - border-radius: 16px; - background: var(--bg-elevated); -} - -.profile-wallet__label { - display: flex; - align-items: center; - gap: 8px; - margin: 0; - color: var(--text); - font-size: 15px; - font-weight: 600; - line-height: 21px; - white-space: nowrap; -} - -.profile-wallet__amount { - display: block; - margin: 4px 0 0; - color: var(--gold); - font-family: Inter, var(--font); - font-size: 28px; - font-weight: 600; - line-height: 34px; -} - -.profile-wallet__hint { - margin: 2px 0 0; - color: var(--text-dim); - font-size: 12px; - line-height: 18px; - white-space: nowrap; -} - -.profile-wallet__actions { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - flex: 0 0 auto; -} - -.profile-wallet__recharge { - display: inline-flex; - align-items: center; - justify-content: center; - height: 40px; - padding-inline: 16px; - border-radius: 999px; - background: linear-gradient(135deg, #ff4a7b 15%, #ec237f 51%, #93226f 85%); - color: #fff; - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; -} - -.profile-wallet__details { - color: var(--text-muted); - font-size: 12px; - line-height: 18px; - white-space: nowrap; - text-decoration: none; -} - -.profile-wallet__details:hover { - color: var(--text); -} - -.profile-stats { - position: relative; - display: flex; - align-items: center; - gap: 40px; - padding: 18px 0 22px; - border-top: 1px solid var(--line-soft); -} - -.profile-stat { - display: flex; - flex-direction: column; - gap: 2px; -} - -.profile-stat__value { - color: var(--text); - font-family: Inter, var(--font); - font-size: 20px; - font-weight: 600; - line-height: 26px; - white-space: nowrap; -} - -.profile-stat__label { - color: var(--text-dim); - font-size: 12px; - line-height: 18px; -} - -.profile-tabs { - padding-top: 4px; - border-bottom: 1px solid var(--line-soft); -} - -.profile-tabs__inner { - display: flex; - align-items: flex-start; - gap: 32px; - height: 38px; -} - -.profile-tabs__link { - position: relative; - display: inline-flex; - flex-direction: column; - align-items: center; - height: 100%; - padding: 4px 0 0; - color: var(--text-muted); - font-size: 16px; - font-weight: 600; - line-height: 22px; - white-space: nowrap; -} - -.profile-tabs__link::after { - position: absolute; - bottom: 0; - left: 50%; - width: 24px; - height: 2px; - transform: translateX(-50%); - background: linear-gradient(90deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); - content: ""; - opacity: 0; -} - -.profile-tabs__link.is-active { - color: var(--text); -} - -.profile-tabs__link.is-active::after { - opacity: 1; -} - -.profile-section { - display: flex; - flex-direction: column; - gap: 20px; -} - -.profile-section--recent { - padding-block: 28px 20px; -} - -.profile-section--favorites { - padding-block: 20px; -} - -.profile-section__head, -.profile-section__heading { - display: flex; - align-items: center; -} - -.profile-section__head { - justify-content: space-between; - gap: 20px; -} - -.profile-section__heading { - gap: 14px; - min-width: 0; -} - -.profile-section__title { - margin: 0; - color: var(--text); - font-size: 22px; - font-weight: 600; - line-height: 30px; - white-space: nowrap; -} - -.profile-section__subtitle { - color: var(--text-dim); - font-size: 14px; - line-height: 22px; - white-space: nowrap; -} - -.profile-section__more { - display: inline-flex; - align-items: center; - gap: 4px; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; - white-space: nowrap; -} - -.profile-section__more img { - width: 16px; - height: 16px; -} - -.profile-grid { - display: grid; - grid-template-columns: repeat(6, minmax(0, 1fr)); - gap: 32px 24px; -} - -.profile-section__empty { - grid-column: 1 / -1; - margin: 8px 0 32px; - color: var(--text-muted); -} - -.profile-card { - display: flex; - flex-direction: column; - gap: 10px; - min-width: 0; -} - -.profile-card__cover { - position: relative; - display: block; - width: 100%; - aspect-ratio: 3 / 4; - overflow: hidden; - border: 1px solid var(--line-soft); - border-radius: 12px; - background: var(--bg-panel); -} - -.profile-card__cover::after { - position: absolute; - inset: 58% 0 0; - z-index: 1; - background: linear-gradient(180deg, transparent, rgba(0, 0, 0, .7)); - content: ""; - pointer-events: none; -} - -.profile-card__image { - width: 100%; - height: 100%; - object-fit: cover; -} - -.profile-card__episodes { - position: absolute; - bottom: 10px; - left: 10px; - z-index: 2; - color: var(--text); - font-size: 12px; - font-weight: 500; - line-height: 16px; - text-shadow: 0 1px 4px #000; -} - -.profile-card__progress { - position: absolute; - right: 10px; - bottom: 6px; - left: 10px; - z-index: 2; - height: 3px; - overflow: hidden; - border-radius: 999px; - background: rgba(255, 255, 255, .12); -} - -.profile-card__progress-fill { - display: block; - width: var(--progress, 0%); - height: 100%; - background: linear-gradient(90deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); -} - -.profile-card__badge { - position: absolute; - top: 4px; - right: 4px; - z-index: 2; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 36px; - height: 20px; - padding-inline: 8px; - border-radius: 6px; - background: linear-gradient(135deg, #fadc44 15%, #f9c24e 85%); - color: #141414; - font-size: 11px; - font-weight: 500; - line-height: 14px; - white-space: nowrap; -} - -.profile-card__badge--hot { - background: linear-gradient(135deg, #f04050 15%, #d01030 85%); - color: #fff; -} - -.profile-card__title { - margin: 0; - overflow: hidden; - color: var(--text); - font-size: 16px; - font-weight: 600; - line-height: 22px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.profile-card__title a { - color: inherit; - text-decoration: none; -} - -.profile-card__title a:hover { - color: var(--accent); -} - -.profile-card__meta { - margin: 0; - overflow: hidden; - color: var(--text-muted); - font-size: 13px; - line-height: 20px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.profile-promo { - display: grid; - width: 100%; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 16px; - margin-inline: 0; - padding-block: 8px 40px; -} - -.profile-ad { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - width: 100%; - aspect-ratio: 592 / 169; - border: 1px solid var(--line-soft); - border-radius: 12px; - background: #232328; - text-align: center; -} - -.profile-ad__icon { - width: 24px; - height: 24px; - color: var(--text-muted); -} - -.profile-ad__title { - margin: 0; - color: var(--text-muted); - font-size: 16px; - font-weight: 600; - line-height: 22px; -} - -.profile-ad__mobile-size { - display: none; -} - -.profile-ad__meta { - margin: 0; - color: var(--text-dim); - font-size: 12px; - line-height: 18px; -} - -.profile-ad__badge { - position: absolute; - top: 8px; - right: 8px; - display: inline-flex; - align-items: center; - justify-content: center; - height: 20px; - padding-inline: 6px; - border-radius: 999px; - background: rgba(0, 0, 0, .6); - color: var(--text); - font-size: 11px; - font-weight: 500; - line-height: 14px; -} - -@media (min-width: 901px) { - .profile-page .site-footer__inner { - gap: 32px; - padding-bottom: 31px; - } - - .profile-page .site-footer__legal { - flex-direction: row; - justify-content: space-between; - gap: 24px; - } - - .profile-page .site-footer__legal p:last-child { - text-align: right; - } -} - -/* 账户列表使用上游总数进行服务端分页。 */ -.account-pagination { - display: flex; - align-items: center; - justify-content: center; - gap: 20px; - margin: 24px 0; - color: var(--text-muted); - font-size: 13px; -} - -.account-pagination a { - color: var(--text); - border: 1px solid var(--line); - border-radius: 20px; - padding: 7px 14px; -} diff --git a/app/static/css/purchased-mobile.css b/app/static/css/purchased-mobile.css deleted file mode 100644 index ca94e99..0000000 --- a/app/static/css/purchased-mobile.css +++ /dev/null @@ -1,152 +0,0 @@ -/* 已购剧集 H5:以 Figma 375px 画板为基准。 */ -@media (max-width: 768px) { - .purchased-main { - display: flex; - flex-direction: column; - gap: 8px; - width: 100%; - max-width: none; - min-width: 0; - margin: 0; - padding: 12px var(--profile-gutter, 16px) 20px; - } - - .purchased-note { - margin: 0; - color: #7c7c86; - font-size: 12px; - font-weight: 400; - line-height: 18px; - } - - .purchased-list { - display: flex; - flex-direction: column; - width: 100%; - min-width: 0; - margin: 0; - padding: 0; - list-style: none; - } - - .purchased-item { - display: flex; - align-items: center; - gap: clamp(8px, 3.2vw, 14px); - width: 100%; - min-width: 0; - min-height: 89px; - padding: 12px 0; - border-bottom: 1px solid rgba(255, 255, 255, .07); - } - - .purchased-item__cover { - display: block; - flex: 0 0 clamp(41px, 12.8vw, 56px); - width: clamp(41px, 12.8vw, 56px); - height: clamp(55px, 17.066667vw, 75px); - overflow: hidden; - border-radius: 6px; - background: #141417; - } - - .purchased-item__cover img { - display: block; - width: 100%; - height: 100%; - object-fit: cover; - } - - .purchased-item__body { - display: flex; - flex: 1 1 0; - flex-direction: column; - align-items: flex-start; - gap: 5px; - min-width: 0; - } - - .purchased-item__title { - display: block; - max-width: 100%; - overflow: hidden; - margin: 0; - color: #f2f2f4; - font-size: clamp(13px, 4vw, 15px); - font-weight: 600; - line-height: 21px; - text-overflow: ellipsis; - white-space: nowrap; - } - - .purchased-item__info { - display: block; - max-width: 100%; - color: #7c7c86; - font-size: clamp(10px, 2.933333vw, 11px); - font-weight: 400; - line-height: 16px; - white-space: normal; - } - - .purchased-item__mobile-date { - display: inline; - white-space: nowrap; - } - - .purchased-item__date { - display: none; - } - - .purchased-item__progress { - display: block; - width: min(140px, 100%); - height: 3px; - overflow: hidden; - border-radius: 999px; - background: rgba(255, 255, 255, .12); - } - - .purchased-item__progress > span { - display: block; - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); - } - - .purchased-item__resume { - display: inline-flex; - flex: 0 0 clamp(67px, 20.8vw, 88px); - align-items: center; - justify-content: center; - gap: 6px; - width: clamp(67px, 20.8vw, 88px); - height: 32px; - padding: 0 8px; - border: 1px solid rgba(255, 255, 255, .24); - border-radius: 999px; - background: transparent; - color: #f2f2f4; - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; - } - - .purchased-item__resume svg, - .purchased-item__resume img { - display: block; - flex: 0 0 16px; - width: 16px; - height: 16px; - } - - .purchased-item__resume-desktop { display: none; } - .purchased-item__resume-mobile { display: inline; } -} - -@media (max-width: 359px) { - .purchased-page .profile-tabs__inner { - gap: 16px; - } -} diff --git a/app/static/css/purchased.css b/app/static/css/purchased.css deleted file mode 100644 index 9e2a344..0000000 --- a/app/static/css/purchased.css +++ /dev/null @@ -1,148 +0,0 @@ -/* 已购剧集:1440px 桌面稿,沿用个人中心公共页头与页签。 */ -.purchased-main { - display: flex; - flex-direction: column; - gap: 16px; - width: min(calc(100% - 48px), var(--page-width)); - margin-inline: auto; - padding: 24px 0 40px; -} - -.purchased-note { - margin: 0; - color: var(--text-dim); - font-size: 13px; - font-weight: 400; - line-height: 20px; -} - -.purchased-list { - min-width: 0; -} - -.purchased-item { - display: grid; - grid-template-columns: 56px minmax(0, 1fr) 120px 106px; - align-items: center; - gap: 16px; - min-width: 0; - min-height: 104px; - padding-block: 14px; - border-bottom: 1px solid var(--line-soft); -} - -.purchased-item__cover { - display: block; - width: 56px; - height: 75px; - overflow: hidden; - border-radius: 6px; - background: var(--bg-panel); -} - -.purchased-item__cover img { - display: block; - width: 100%; - height: 100%; - object-fit: cover; -} - -.purchased-item__body { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - min-width: 0; -} - -.purchased-item__title { - display: block; - max-width: 100%; - margin: 0; - overflow: hidden; - color: var(--text); - font-size: 16px; - font-weight: 600; - line-height: 22px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.purchased-item__info { - display: block; - max-width: 100%; - margin: 0; - overflow: hidden; - color: var(--text-muted); - font-size: 13px; - font-weight: 400; - line-height: 20px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.purchased-item__progress { - display: block; - width: 240px; - max-width: 100%; - height: 3px; - overflow: hidden; - border-radius: 999px; - background: var(--line); -} - -.purchased-item__progress > span { - display: block; - width: var(--progress, 100%); - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); -} - -.purchased-item__date { - color: var(--text-dim); - font-size: 12px; - font-weight: 400; - line-height: 18px; - white-space: nowrap; -} - -.purchased-item__mobile-date, -.purchased-item__resume-mobile { - display: none; -} - -.purchased-item__resume { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; - width: 106px; - height: 36px; - padding-inline: 12px; - border: 1px solid rgba(255, 255, 255, .24); - border-radius: 999px; - color: var(--text); - font-size: 14px; - font-weight: 500; - line-height: 20px; - white-space: nowrap; -} - -.purchased-item__resume img, -.purchased-item__resume svg { - display: block; - width: 16px; - height: 16px; - flex: 0 0 16px; -} - -.purchased-item__resume:hover { - border-color: rgba(255, 255, 255, .36); - color: var(--text); -} - -.purchased-item__resume:focus-visible { - outline: 2px solid var(--accent-hot); - outline-offset: 3px; -} diff --git a/app/static/css/recharge-mobile.css b/app/static/css/recharge-mobile.css deleted file mode 100644 index f199470..0000000 --- a/app/static/css/recharge-mobile.css +++ /dev/null @@ -1,117 +0,0 @@ -@media (max-width: 768px) { - .recharge-page { - --recharge-gutter: clamp(14px, 4.266667vw, 18px); - padding-top: 0; - } - - .recharge-page .site-header { display: none; } - .subpage-header { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - height: 56px; - padding: 0 var(--recharge-gutter); - border-bottom: 1px solid var(--line-soft); - background: var(--bg); - } - .subpage-header__button { display: flex; align-items: center; justify-content: center; width: 28px; height: 36px; } - .subpage-header__button img { width: 22px; height: 22px; } - .subpage-header__title { color: var(--text); font-size: 16px; font-weight: 600; line-height: 24px; } - - .recharge-main { width: 100%; margin: 0; padding: 16px 0 0; } - .recharge-breadcrumb, - .recharge-intro, - .recharge-balance__guide { display: none; } - - .recharge-balance { - width: auto; - height: 110px; - margin: 0 var(--recharge-gutter); - padding: 18px; - border-radius: 16px; - } - .recharge-balance__main { gap: 14px; } - .recharge-balance__coin { width: 72px; height: 72px; flex-basis: 72px; } - .recharge-balance__label { font-size: 11px; line-height: 16px; } - .recharge-balance__copy strong { margin-top: 2px; font-size: 28px; line-height: 34px; } - .recharge-balance__copy small { font-size: 13px; line-height: 20px; } - .recharge-balance__copy p { margin-top: 0; font-size: 11px; line-height: 16px; } - - .recharge-packages { margin-top: 20px; padding: 0 var(--recharge-gutter); } - .recharge-section-heading { gap: 10px; height: 22px; min-height: 22px; } - .recharge-section-heading h2, - .recharge-payment > h2 { font-size: 16px; line-height: 22px; } - .recharge-section-heading span { font-size: 12px; line-height: 18px; white-space: nowrap; } - .recharge-packages__grid { grid-template-columns: repeat(2,minmax(0,1fr)); gap: 10px; margin-top: 12px; } - .recharge-package { gap: 3px; height: 99px; padding: 12px; border-radius: 12px; } - .recharge-package__title { gap: 5px; font-size: 15px; line-height: 21px; white-space: nowrap; } - .recharge-package__title img { width: 14px; height: 14px; } - .recharge-package__hint { font-size: 11px; line-height: 16px; } - .recharge-package__price { font-size: 20px; line-height: 26px; } - .recharge-package__selected { top: 12px; right: 12px; width: 16px; height: 16px; } - .recharge-package__badge { top: -2px; right: 9px; height: 21px; padding-inline: 7px; font-size: 10px; } - .recharge-packages__note { display: none; } - - .recharge-payment { margin-top: 16px; padding: 0 var(--recharge-gutter); } - .recharge-payment__grid { grid-template-columns: repeat(2,minmax(0,1fr)); gap: 12px; margin-top: 12px; } - .recharge-method { gap: 10px; padding: 0 12px; } - .recharge-payment__checkout { flex-direction: column; gap: 11px; margin-top: 11px; } - .recharge-payment__checkout button { width: 100%; height: 48px; font-size: 16px; } - .recharge-payment__checkout p { font-size: 11px; line-height: 18px; text-align: center; } - .recharge-payment__status { text-align: center; } - - .recharge-history { margin-top: 22px; padding: 0 var(--recharge-gutter) 22px; } - .recharge-history__head { height: 26px; } - .recharge-history__head h2 { font-size: 18px; line-height: 26px; } - .recharge-history__filters, - .recharge-history__export { display: none; } - .recharge-history__more { display: flex; align-items: center; gap: 2px; margin-left: auto; padding: 0; border: 0; background: none; color: var(--text-muted); font-size: 12px; line-height: 18px; } - .recharge-history__more img { width: 14px; height: 14px; } - .recharge-history__table { height: auto; margin-top: 8px; padding: 0; border: 0; border-radius: 0; background: none; } - .recharge-history__columns { display: none; } - .recharge-history__row { display: grid; grid-template-columns: minmax(0,1fr) auto; grid-template-rows: 20px 16px; align-content: center; height: 64px; border-bottom: 1px solid var(--line-soft); font-size: 14px; line-height: 20px; } - .recharge-history__kind { grid-column: 1; grid-row: 1; } - .recharge-history__detail-line { display: flex; align-items: center; gap: 8px; grid-column: 1; grid-row: 2; min-width: 0; } - .recharge-history__description, - .recharge-history__time { color: var(--text-dim); font-size: 11px; line-height: 16px; white-space: nowrap; } - .recharge-history__description { overflow: hidden; text-overflow: ellipsis; } - .recharge-history__time { flex: 0 0 auto; } - .recharge-history__amount { align-self: center; grid-column: 2; grid-row: 1 / 3; font-size: 16px; line-height: 20px; } - .only-desktop { display: none; } - .only-mobile { display: inline; } - .recharge-pagination { display: none; } - - .recharge-page .site-footer { background: #141417; } - .recharge-page .site-footer__inner { padding: 32px 16px 27px; gap: 18px; } - .recharge-page .site-footer__brand { gap: 18px; } - .recharge-page .site-footer__links { flex-wrap: wrap; gap: 12px 16px; } - .recharge-page .footer-col { flex-wrap: nowrap; } - .recharge-page .footer-col:first-child, - .recharge-page .footer-col:nth-child(2) a:last-child, - .recharge-page .footer-col:nth-child(3) a:last-child { display: none; } - .recharge-page .footer-download { display: flex; flex-basis: 100%; justify-content: center; padding: 0; border: 0; background: none; } - .recharge-page .footer-download .qr-box, - .recharge-page .footer-download__copy strong, - .recharge-page .footer-download__copy p, - .recharge-page .footer-download__copy .icon { display: none; } - .recharge-page .footer-download .btn { height: 20px; padding: 0; color: var(--text-muted); background: none; font-size: 13px; line-height: 20px; } - .recharge-page .site-footer__rule { display: none; } - .recharge-page .site-footer__legal { flex-direction: column; align-items: center; gap: 0; } - .recharge-page .site-footer__legal p:first-child { order: 2; width: 100%; padding-top: 16px; margin-top: 16px; border-top: 1px solid var(--line-soft); } - .recharge-page .site-footer__legal p:last-child { order: 1; } - .recharge-page .site-footer__adult, - .recharge-page .site-footer__rights { display: none; } - .recharge-page .site-footer__mobile-adult { display: inline; } -} - -@media (max-width: 359px) { - .recharge-balance { height: 110px; padding: 14px; } - .recharge-balance__main { gap: 10px; } - .recharge-balance__coin { width: 64px; height: 64px; flex-basis: 64px; } - .recharge-balance__copy p { line-height: 14px; } - .recharge-balance__note-full { display: none; } - .recharge-balance__note-short { display: inline; } - .recharge-package__title { font-size: 14px; } - .recharge-package__badge { top: -4px; height: 18px; padding-inline: 5px; font-size: 9px; } -} diff --git a/app/static/css/recharge-modal.css b/app/static/css/recharge-modal.css deleted file mode 100644 index ae6cabd..0000000 --- a/app/static/css/recharge-modal.css +++ /dev/null @@ -1,516 +0,0 @@ -.recharge-modal[hidden] { - display: none !important; -} - -.recharge-modal { - position: fixed; - inset: 0; - z-index: 320; - display: flex; - align-items: center; - justify-content: center; - padding: 24px; -} - -.recharge-modal__backdrop { - position: absolute; - inset: 0; - background: rgba(0, 0, 0, 0.7); -} - -.recharge-modal__panel { - position: relative; - z-index: 1; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 16px; - width: 640px; - max-width: 100%; - max-height: calc(100dvh - 48px); - padding: 32px; - overflow-y: auto; - background: #141417; - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 20px; - box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55); - scrollbar-width: thin; -} - -.recharge-modal__handle { - display: none; -} - -.recharge-modal__head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - min-height: 54px; -} - -.recharge-modal__heading { - min-width: 0; - display: flex; - flex-direction: column; - gap: 4px; -} - -.recharge-modal__heading h2, -.recharge-modal__subtitle { - margin: 0; -} - -.recharge-modal__heading h2 { - color: #f2f2f4; - font-size: 22px; - font-weight: 600; - line-height: 30px; -} - -.recharge-modal__subtitle { - color: #a1a1aa; - font-size: 13px; - font-weight: 400; - line-height: 20px; - white-space: nowrap; -} - -.recharge-modal__subtitle--mobile { - display: none; -} - -.recharge-modal__balance { - display: inline-flex; - align-items: center; - gap: 6px; - flex: none; - padding: 6px 12px; - color: #e9c977; - background: rgba(217, 169, 74, 0.14); - border-radius: 999px; -} - -.recharge-modal__balance strong { - font-family: Inter, var(--font); - font-size: 16px; - font-weight: 600; - line-height: 20px; -} - -.recharge-modal__coin { - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - width: 14px; - height: 14px; - background: linear-gradient(135deg, #e9c977 15%, #d9a94a 85%); - border: 1px solid #e9c977; - border-radius: 50%; -} - -.recharge-modal__coin::before { - content: ""; - position: absolute; - inset: 2px; - border: 1px solid rgba(58, 42, 10, 0.45); - border-radius: 50%; -} - -.recharge-modal__coin img { - position: relative; - width: 8px; - height: 8px; -} - -.recharge-modal__coin--balance { - flex-basis: 16px; - width: 16px; - height: 16px; -} - -.recharge-modal__close { - position: absolute; - top: 16px; - right: 16px; - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: rgba(255, 255, 255, 0.04); - border-radius: 50%; -} - -.recharge-modal__close img { - width: 18px; - height: 18px; -} - -.recharge-modal__packages { - display: grid; - grid-template-columns: repeat(3, 181px); - gap: 12px; -} - -.recharge-modal__package { - position: relative; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - width: 181px; - height: 99px; - padding: 12px; - text-align: left; - background: #141417; - border: 1px solid rgba(255, 255, 255, 0.07); - border-radius: 12px; -} - -.recharge-modal__package.is-selected { - background: rgba(217, 169, 74, 0.14); - border: 1.5px solid #d9a94a; -} - -.recharge-modal__package-top { - display: flex; - align-items: center; - justify-content: space-between; - gap: 4px; - width: 100%; - min-height: 21px; -} - -.recharge-modal__package-name { - display: inline-flex; - align-items: center; - gap: 6px; - white-space: nowrap; - color: #f2f2f4; - font-size: 15px; - font-weight: 600; - line-height: 21px; -} - -.recharge-modal__package.is-selected .recharge-modal__package-name, -.recharge-modal__package.is-selected .recharge-modal__package-price { - color: #e9c977; -} - -.recharge-modal__selected-icon { - display: none; - flex: none; - width: 14px; - height: 14px; -} - -.recharge-modal__package.is-selected .recharge-modal__selected-icon { - display: block; -} - -.recharge-modal__package-detail { - color: #7c7c86; - font-size: 12px; - font-weight: 400; - line-height: 18px; - white-space: nowrap; -} - -.recharge-modal__package-price { - color: #f2f2f4; - font-family: Inter, var(--font); - font-size: 20px; - font-weight: 600; - line-height: 26px; -} - -.recharge-modal__package-badge { - position: absolute; - top: -1px; - right: 10px; - display: inline-flex; - align-items: center; - justify-content: center; - height: 20px; - padding: 0 8px; - color: #141414; - background: linear-gradient(135deg, #e9c977 15%, #d9a94a 85%); - border-radius: 6px; - font-size: 11px; - font-weight: 500; - line-height: 14px; -} - -.recharge-modal__methods { - display: grid; - grid-template-columns: repeat(2, 278px); - gap: 12px; -} - -.recharge-modal__method { - display: flex; - align-items: center; - gap: 10px; - width: 278px; - height: 52px; - padding: 0 16px; - text-align: left; - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 12px; -} - -.recharge-modal__method.is-selected { - background: rgba(236, 35, 127, 0.1); - border-color: rgba(236, 35, 127, 0.3); -} - -.recharge-modal__method-check { - display: inline-flex; - align-items: center; - justify-content: center; - flex: none; - width: 18px; - height: 18px; - border: 1px solid rgba(255, 255, 255, 0.24); - border-radius: 50%; -} - -.recharge-modal__method-check img { - display: none; - width: 12px; - height: 12px; -} - -.recharge-modal__method.is-selected .recharge-modal__method-check { - border: 0; - background: linear-gradient(135deg, #ff4a7b 15%, #ec237f 51%, #93226f 85%); -} - -.recharge-modal__method.is-selected .recharge-modal__method-check img { - display: block; -} - -.recharge-modal__method-copy { - display: flex; - flex-direction: column; - gap: 1px; - min-width: 0; - color: #f2f2f4; - font-size: 14px; - font-weight: 500; - line-height: 20px; -} - -.recharge-modal__method-copy small { - color: #7c7c86; - font-size: 12px; - font-weight: 400; - line-height: 18px; -} - -.recharge-modal__pay { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 52px; - padding: 0 28px; - color: #fff; - background: linear-gradient(135deg, #ff4a7b 15%, #ec237f 51%, #93226f 85%); - border-radius: 999px; - font-size: 16px; - font-weight: 500; - line-height: 24px; -} - -.recharge-modal__status { - margin: -8px 0 0; - color: #a1a1aa; - font-size: 12px; - line-height: 18px; - text-align: center; -} - -.recharge-modal__status.is-ok { - color: #6ee7a8; -} - -.recharge-modal__empty { - margin: 0; - grid-column: 1 / -1; - color: rgba(255, 255, 255, 0.56); - font-size: 13px; - line-height: 20px; -} - -.recharge-modal__foot { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - color: #7c7c86; - font-size: 12px; - font-weight: 400; - line-height: 18px; - white-space: nowrap; -} - -.recharge-modal__foot a { - display: inline-flex; - align-items: center; - gap: 5px; - color: #a1a1aa; -} - -.recharge-modal__foot img { - width: 12px; - height: 12px; -} - -.recharge-modal__close:focus-visible, -.recharge-modal__package:focus-visible, -.recharge-modal__method:focus-visible, -.recharge-modal__pay:focus-visible, -.recharge-modal__foot a:focus-visible { - outline: 2px solid #ff4a7b; - outline-offset: 2px; -} - -@media (max-width: 768px) { - .recharge-modal { - align-items: flex-end; - justify-content: stretch; - padding: 0; - } - - .recharge-modal__panel { - width: 100%; - max-width: none; - max-height: 100dvh; - padding: 12px 20px 24px; - gap: 14px; - border: 0; - border-radius: 20px 20px 0 0; - box-shadow: none; - } - - .recharge-modal__handle { - display: flex; - justify-content: center; - align-items: center; - flex: none; - height: 4px; - } - - .recharge-modal__handle::before { - content: ""; - width: 36px; - height: 4px; - background: rgba(255, 255, 255, 0.24); - border-radius: 2px; - } - - .recharge-modal__head { - min-height: 46px; - } - - .recharge-modal__heading { - gap: 2px; - } - - .recharge-modal__heading h2 { - font-size: 18px; - line-height: 26px; - } - - .recharge-modal__subtitle { - font-size: 12px; - line-height: 18px; - } - - .recharge-modal__subtitle--desktop, - .recharge-modal__balance { - display: none; - } - - .recharge-modal__subtitle--mobile { - display: block; - } - - .recharge-modal__close { - position: static; - flex: none; - } - - .recharge-modal__packages { - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 10px; - } - - .recharge-modal__package { - width: 100%; - } - - .recharge-modal__package-detail { - font-size: clamp(10px, 3.2vw, 12px); - } - - .recharge-modal__methods { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .recharge-modal__method { - width: 100%; - padding: 0 12px; - } - - .recharge-modal__method-copy small { - font-size: 11px; - line-height: 16px; - } - - .recharge-modal__pay { - height: 48px; - padding: 0 22px; - font-size: 15px; - line-height: 22px; - } - - .recharge-modal__foot { - font-size: 11px; - line-height: 16px; - } - - .recharge-modal__foot img { - width: 11px; - height: 11px; - } -} - -@media (max-width: 340px) { - .recharge-modal__panel { - padding-inline: 16px; - } - - .recharge-modal__package { - padding-inline: 9px; - } - - .recharge-modal__package-name { - font-size: 13px; - } - - .recharge-modal__package-badge { - top: -8px; - right: 4px; - } - - .recharge-modal__method { - gap: 7px; - padding-inline: 9px; - } -} diff --git a/app/static/css/recharge.css b/app/static/css/recharge.css deleted file mode 100644 index 20d52ec..0000000 --- a/app/static/css/recharge.css +++ /dev/null @@ -1,271 +0,0 @@ -.recharge-page { - min-height: 100vh; - padding-top: 72px; - background: var(--bg); -} - -.recharge-page [hidden] { display: none !important; } -.subpage-header { display: none; } - -.recharge-main { - width: min(calc(100% - 48px), var(--page-width)); - margin-inline: auto; - padding: 24px 0 41px; -} - -.recharge-breadcrumb { - display: flex; - align-items: center; - gap: 8px; - height: 20px; - color: var(--text-dim); - font-size: 13px; - line-height: 20px; -} - -.recharge-breadcrumb span:last-child { color: var(--text-muted); } -.recharge-breadcrumb a:hover { color: var(--text); } - -.recharge-intro { - display: flex; - flex-direction: column; - align-items: flex-start; - margin-top: 16px; -} - -.recharge-intro__eyebrow { - color: var(--accent-hot); - font-size: 13px; - font-weight: 600; - line-height: 18px; - letter-spacing: .14em; -} - -.recharge-intro__eyebrow::before { - display: inline-block; - width: 18px; - height: 2px; - margin: 0 10px 4px 0; - background: var(--accent); - content: ""; -} - -.recharge-intro h1 { - margin: 12px 0 0; - color: var(--text); - font-size: 28px; - font-weight: 600; - line-height: 36px; -} - -.recharge-intro p { - margin: 12px 0 0; - color: var(--text-muted); - font-size: 15px; - line-height: 24px; -} - -.recharge-balance { - display: flex; - align-items: center; - justify-content: space-between; - gap: 32px; - width: 100%; - height: 216px; - margin-top: 24px; - padding: 24px 32px; - border: 1px solid var(--line-soft); - border-radius: 20px; - background: linear-gradient(90deg, #2a0d22 0%, #150a17 55%, #141417 100%); -} - -.recharge-balance__main { - display: flex; - align-items: center; - gap: 24px; - min-width: 0; -} - -.recharge-balance__coin { - width: 120px; - height: 120px; - flex: 0 0 120px; - object-fit: contain; -} - -.recharge-balance__copy { min-width: 0; } -.recharge-balance__label, -.recharge-balance__guide h2 { - color: var(--text-dim); - font-size: 12px; - font-weight: 500; - line-height: 18px; -} - -.recharge-balance__copy strong { - display: block; - margin-top: 4px; - color: var(--gold); - font-family: Inter, var(--font); - font-size: 40px; - font-weight: 600; - line-height: 46px; -} - -.recharge-balance__copy small { - color: var(--text); - font-family: var(--font); - font-size: 15px; - font-weight: 600; - line-height: 22px; -} - -.recharge-balance__copy p { - margin: 3px 0 0; - color: var(--text-dim); - font-size: 12px; - line-height: 18px; - white-space: nowrap; -} - -.recharge-balance__note-short { display: none; } - -.recharge-balance__guide { - display: flex; - flex: 0 0 360px; - flex-direction: column; - gap: 9px; -} - -.recharge-balance__guide h2 { margin: 0; } -.recharge-balance__tip { display: flex; align-items: flex-start; gap: 8px; } -.recharge-balance__tip > img { width: 16px; height: 16px; flex: 0 0 16px; } -.recharge-balance__tip > div { display: flex; flex-direction: column; gap: 2px; } -.recharge-balance__tip strong { color: var(--text); font-size: 13px; font-weight: 600; line-height: 18px; } -.recharge-balance__tip span { color: var(--text-dim); font-size: 12px; line-height: 18px; } - -.recharge-packages { margin-top: 32px; } -.recharge-section-heading { display: flex; align-items: center; gap: 14px; min-height: 30px; } -.recharge-section-heading h2, -.recharge-payment > h2, -.recharge-history__head h2 { - margin: 0; - color: var(--text); - font-size: 20px; - font-weight: 600; - line-height: 26px; -} -.recharge-section-heading span { color: var(--text-dim); font-size: 13px; line-height: 20px; } - -.recharge-packages__grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; - margin-top: 20px; -} - -.recharge-package { - position: relative; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 5px; - min-width: 0; - height: 124px; - padding: 18px 20px; - border: 1px solid var(--line-soft); - border-radius: 16px; - background: var(--bg-elevated); - color: var(--text); - text-align: left; - cursor: pointer; -} - -.recharge-package.is-selected { - border: 1.5px solid #d9a94a; - background: rgba(217, 169, 74, .14); -} - -.recharge-package__title { display: flex; align-items: center; gap: 6px; font-size: 18px; font-weight: 600; line-height: 26px; } -.recharge-package__title img { width: 16px; height: 16px; } -.recharge-package__hint { color: var(--text-dim); font-size: 12px; line-height: 18px; white-space: nowrap; } -.recharge-package__price { margin-top: auto; color: var(--text); font-family: Inter, var(--font); font-size: 24px; font-weight: 600; line-height: 30px; } -.recharge-package.is-selected .recharge-package__title, -.recharge-package.is-selected .recharge-package__price { color: var(--gold); } -.recharge-package__selected { position: absolute; top: 18px; right: 20px; width: 18px; height: 18px; display: none; } -.recharge-package.is-selected .recharge-package__selected { display: block; } -.recharge-package__badge { position: absolute; top: -2px; right: 10px; display: flex; align-items: center; justify-content: center; height: 21px; padding: 0 8px; border-radius: 5px; background: linear-gradient(135deg, #ffe7a0, #e9c977); color: #382506; font-size: 11px; font-weight: 600; line-height: 16px; } - -.recharge-packages__note { display: flex; align-items: center; gap: 8px; min-height: 24px; margin-top: 20px; } -.recharge-packages__note span { display: inline-flex; align-items: center; height: 24px; padding: 0 10px; border: 1px solid rgba(236,35,127,.34); border-radius: 999px; background: rgba(236,35,127,.08); color: var(--accent-hot); font-size: 12px; font-weight: 500; white-space: nowrap; } -.recharge-packages__note p { margin: 0; color: var(--text-dim); font-size: 12px; line-height: 18px; } -.recharge-data-empty { margin: 16px 0 0; color: var(--text-dim); font-size: 13px; line-height: 20px; } - -.recharge-payment { margin-top: 28px; } -.recharge-payment__grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; margin-top: 20px; } -.recharge-method { display: flex; align-items: center; gap: 10px; height: 52px; min-width: 0; padding: 0 14px; border: 1px solid var(--line); border-radius: 12px; background: #242427; color: var(--text); text-align: left; cursor: pointer; } -.recharge-method.is-selected { border-color: rgba(236,35,127,.45); background: rgba(236,35,127,.12); } -.recharge-method__radio { display: flex; align-items: center; justify-content: center; width: 18px; height: 18px; flex: 0 0 18px; border: 1px solid #68686d; border-radius: 50%; } -.recharge-method__radio img { display: none; width: 12px; height: 12px; } -.recharge-method.is-selected .recharge-method__radio { border-color: transparent; background: linear-gradient(135deg,#ff4a7b,#ec237f); } -.recharge-method.is-selected .recharge-method__radio img { display: block; } -.recharge-method > span:last-child { display: flex; flex-direction: column; } -.recharge-method strong { font-size: 14px; font-weight: 500; line-height: 20px; } -.recharge-method small { color: var(--text-dim); font-size: 12px; line-height: 18px; } - -.recharge-payment__checkout { display: flex; align-items: center; gap: 16px; margin-top: 28px; } -.recharge-payment__checkout button { display: flex; align-items: center; justify-content: center; width: 320px; height: 56px; border: 0; border-radius: 999px; background: linear-gradient(105deg,#ff4a7b 0%,#ec237f 52%,#93226f 100%); color: #fff; font-size: 16px; font-weight: 600; line-height: 24px; cursor: pointer; } -.recharge-payment__checkout button:disabled { cursor: not-allowed; opacity: .5; } -.recharge-payment__checkout p { margin: 0; color: var(--text-dim); font-size: 12px; line-height: 18px; } -.recharge-payment__status { margin: 8px 0 0; color: var(--text-muted); font-size: 12px; line-height: 18px; } -.recharge-payment__status.is-success { color: #39c98a; } - -.recharge-history { margin-top: 40px; scroll-margin-top: calc(var(--site-header-height, 72px) + 16px); } -.recharge-history__head { display: flex; align-items: center; gap: 14px; height: 30px; } -.recharge-history__filters { display: flex; align-items: center; gap: 18px; align-self: stretch; } -.recharge-history__filters button { position: relative; padding: 0; border: 0; background: none; color: var(--text-dim); font-size: 13px; font-weight: 500; cursor: pointer; } -.recharge-history__filters button.is-active { color: var(--text); } -.recharge-history__filters button.is-active::after { position: absolute; right: 0; bottom: -7px; left: 0; height: 2px; background: var(--accent); content: ""; } -.recharge-history__export { display: flex; align-items: center; gap: 3px; margin-left: auto; padding: 0; border: 0; background: none; color: var(--text-muted); font-size: 13px; cursor: pointer; } -.recharge-history__export img { width: 16px; height: 16px; } -.recharge-history__more { display: none; } -.recharge-history__table { height: 310px; margin-top: 16px; padding: 4px 24px; border: 1px solid var(--line-soft); border-radius: 16px; background: var(--bg-elevated); } -.recharge-history__columns, -.recharge-history__row { display: grid; grid-template-columns: 200px minmax(0,1fr) 200px 120px; align-items: center; } -.recharge-history__columns { height: 45px; border-bottom: 1px solid var(--line-soft); color: var(--text-dim); font-size: 12px; line-height: 18px; } -.recharge-history__columns span:last-child, -.recharge-history__amount { text-align: right; } -.recharge-history__row { height: 51px; border-bottom: 1px solid var(--line-soft); color: var(--text-muted); font-size: 13px; line-height: 20px; } -.recharge-history__row:last-child { border-bottom: 0; } -.recharge-history__kind { color: var(--text); } -.recharge-history__detail-line { display: contents; } -.recharge-history__time { color: var(--text-dim); } -.recharge-history__amount { color: #f35b69; font-family: Inter, var(--font); font-size: 15px; font-weight: 600; } -.recharge-history__amount.is-credit { color: #39c98a; } -.recharge-history__empty { margin: 0; padding: 36px 0; color: var(--text-dim); font-size: 13px; text-align: center; } -.only-mobile { display: none; } - -.recharge-pagination, -.recharge-pagination [data-recharge-pages] { display: flex; align-items: center; justify-content: center; gap: 8px; } -.recharge-pagination { margin-top: 16px; } -.recharge-pagination button { display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; border: 1px solid var(--line); border-radius: 8px; background: var(--bg-elevated); color: var(--text-muted); font-family: Inter, var(--font); font-size: 16px; font-weight: 600; cursor: pointer; } -.recharge-pagination button.is-active { border-color: transparent; background: linear-gradient(135deg,#ff4a7b,#ec237f 52%,#93226f); color: #fff; } -.recharge-pagination button:disabled { opacity: .45; cursor: default; } -.recharge-pagination button img { width: 16px; height: 16px; } -.recharge-pagination > button:first-child img { transform: rotate(180deg); } -.recharge-pagination__total { color: var(--text-dim); font-size: 12px; line-height: 18px; } - -.recharge-page button:focus-visible, -.recharge-page a:focus-visible { outline: 2px solid var(--accent-hot); outline-offset: 3px; } - -@media (max-width: 1080px) and (min-width: 769px) { - .recharge-balance__guide { flex-basis: 300px; } - .recharge-history__columns, - .recharge-history__row { grid-template-columns: 140px minmax(0,1fr) 175px 90px; } -} - -@media (min-width: 901px) { - .recharge-page .site-footer__inner { gap: 32px; padding-bottom: 32px; } - .recharge-page .site-footer__legal { flex-direction: row; justify-content: space-between; gap: 24px; } - .recharge-page .site-footer__legal p:last-child { text-align: right; } -} diff --git a/app/static/css/register.css b/app/static/css/register.css deleted file mode 100644 index 93f04f1..0000000 --- a/app/static/css/register.css +++ /dev/null @@ -1,255 +0,0 @@ -/* H5 注册页 */ - -body.page-register { - min-height: 100vh; - background: var(--bg); -} - -.page-register .page-subheader { - display: flex; - border-bottom: 0; -} - -.register-page { - min-height: 100vh; - padding: 56px 0 32px; - box-sizing: border-box; -} - -.register-page__inner { - width: 100%; - max-width: 440px; - margin: 0 auto; - padding: 24px var(--h5-gutter); - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; - box-sizing: border-box; -} - -.register-page__brand { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.register-page__logo { - width: 64px; - height: 64px; - border-radius: 12px; - object-fit: cover; - box-shadow: 0 8px 24px rgba(236, 35, 127, 0.4); -} - -.register-page__content { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 18px; -} - -.register-page__intro { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; -} - -.register-page__title { - margin: 0; - color: var(--text); - font-size: 28px; - font-weight: 600; - line-height: 38px; -} - -.register-page__desc { - margin: 0; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.register-page__form { - align-self: stretch; - display: flex; - flex-direction: column; - gap: 16px; -} - -.register-page__field { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; -} - -.register-page__label { - color: var(--text-muted); - font-size: 14px; - font-weight: 500; - line-height: 20px; -} - -.register-page__control { - align-self: stretch; - height: 48px; - padding: 0 16px; - display: flex; - align-items: center; - gap: 10px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid transparent; - border-radius: 12px; - outline: 1px solid transparent; - outline-offset: -1px; -} - -.register-page__control:focus-within { - outline-color: rgba(236, 35, 127, 0.45); -} - -.register-page__field.is-error .register-page__control { - outline-color: #ff5a5f; -} - -.register-page__error { - display: inline-flex; - align-items: center; - gap: 6px; - color: #ff5a5f; - font-size: 12px; - font-weight: 400; - line-height: 18px; -} - -.register-page__error[hidden] { - display: none !important; -} - -.register-page__error-icon { - width: 14px; - height: 14px; - flex-shrink: 0; -} - -.register-page__control > .icon { - width: 18px; - height: 18px; - flex-shrink: 0; -} - -.register-page__input { - flex: 1 1 0; - min-width: 0; - height: 100%; - padding: 0; - border: 0; - outline: none; - background: transparent; - color: var(--text); - font: inherit; - font-size: 15px; - line-height: 24px; -} - -.register-page__input::placeholder { - color: var(--text-dim); -} - -.register-page__toggle { - width: 18px; - height: 18px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--text-dim); -} - -.register-page__toggle .icon { - width: 18px; - height: 18px; -} - -.register-page__agree { - display: flex; - align-items: flex-start; - gap: 10px; - color: var(--text-muted); - font-size: 13px; - line-height: 20px; - cursor: pointer; -} - -.register-page__agree a { - color: inherit; - text-decoration: underline; - text-underline-offset: 2px; -} - -.register-page__checkbox { - position: absolute; - opacity: 0; - width: 1px; - height: 1px; - pointer-events: none; -} - -.register-page__check { - width: 18px; - height: 18px; - flex-shrink: 0; - margin-top: 1px; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: transparent; - border: 1px solid rgba(255, 255, 255, 0.24); -} - -.register-page__checkbox:checked + .register-page__check { - border-color: transparent; - background: linear-gradient(135deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); -} - -.register-page__checkbox:checked + .register-page__check::after { - content: ""; - width: 10px; - height: 6px; - border-left: 1.5px solid #fff; - border-bottom: 1.5px solid #fff; - transform: translateY(-1px) rotate(-45deg); -} - -.register-page__submit { - align-self: stretch; - height: 52px; - padding: 0 28px; - font-size: 16px; - line-height: 24px; -} - -.register-page__switch { - margin: 0; - align-self: stretch; - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.register-page__switch a { - color: #ff4a7b; - font-size: 14px; - font-weight: 500; - line-height: 20px; -} diff --git a/app/static/css/registerPop.css b/app/static/css/registerPop.css deleted file mode 100644 index f18d276..0000000 --- a/app/static/css/registerPop.css +++ /dev/null @@ -1,304 +0,0 @@ -/* PC 注册弹窗 */ - -.register-pop { - position: fixed; - inset: 0; - z-index: 290; - display: none; - align-items: center; - justify-content: center; - padding: 24px; -} - -.register-pop.is-open { - display: flex; -} - -.register-pop__mask { - position: absolute; - inset: 0; - background: rgba(0, 0, 0, 0.7); -} - -.register-pop__panel { - position: relative; - z-index: 1; - width: 440px; - max-width: 100%; - padding: 32px; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 20px; - background: #141417; - border: 1px solid var(--line); - border-radius: var(--radius-xl); - box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55); - overflow: hidden; -} - -.register-pop__close { - position: absolute; - top: 16px; - right: 16px; - width: 32px; - height: 32px; - display: inline-flex; - align-items: center; - justify-content: center; - background: rgba(255, 255, 255, 0.04); - border-radius: 999px; -} - -.register-pop__close .icon { - width: 18px; - height: 18px; -} - -.register-pop__intro { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - padding-right: 40px; -} - -.register-pop__title { - margin: 0; - color: var(--text); - font-size: 28px; - font-weight: 600; - line-height: 38px; -} - -.register-pop__desc { - margin: 0; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.register-pop__form { - align-self: stretch; - display: flex; - flex-direction: column; - gap: 16px; -} - -.register-pop__field { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; -} - -.register-pop__label { - color: var(--text-muted); - font-size: 14px; - font-weight: 500; - line-height: 20px; -} - -.register-pop__control { - align-self: stretch; - height: 48px; - padding: 0 16px; - display: flex; - align-items: center; - gap: 10px; - background: rgba(255, 255, 255, 0.06); - border: 1px solid transparent; - border-radius: 12px; - outline: 1px solid transparent; - outline-offset: -1px; -} - -.register-pop__control:focus-within { - outline-color: rgba(236, 35, 127, 0.45); -} - -.register-pop__field.is-error .register-pop__control { - outline-color: #ff5a5f; -} - -.register-pop__error { - display: inline-flex; - align-items: center; - gap: 6px; - color: #ff5a5f; - font-size: 12px; - font-weight: 400; - line-height: 18px; -} - -.register-pop__error[hidden] { - display: none !important; -} - -.register-pop__error-icon { - width: 14px; - height: 14px; - flex-shrink: 0; -} - -.register-pop__control > .icon { - width: 18px; - height: 18px; - flex-shrink: 0; -} - -.register-pop__input { - flex: 1 1 0; - min-width: 0; - height: 100%; - padding: 0; - border: 0; - outline: none; - background: transparent; - color: var(--text); - font: inherit; - font-size: 15px; - line-height: 24px; -} - -.register-pop__input::placeholder { - color: var(--text-dim); -} - -.register-pop__toggle { - width: 18px; - height: 18px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--text-dim); -} - -.register-pop__toggle .icon { - width: 18px; - height: 18px; -} - -.register-pop__agree { - display: flex; - align-items: flex-start; - gap: 10px; - color: var(--text-muted); - font-size: 13px; - line-height: 20px; - cursor: pointer; -} - -.register-pop__agree a { - color: inherit; - text-decoration: underline; - text-underline-offset: 2px; -} - -.register-pop__checkbox { - position: absolute; - opacity: 0; - width: 1px; - height: 1px; - pointer-events: none; -} - -.register-pop__check { - width: 18px; - height: 18px; - flex-shrink: 0; - margin-top: 1px; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: transparent; - border: 1px solid rgba(255, 255, 255, 0.24); -} - -.register-pop__checkbox:checked + .register-pop__check { - border-color: transparent; - background: linear-gradient(135deg, #ff4a7b 0%, #ec237f 52%, #93226f 100%); -} - -.register-pop__checkbox:checked + .register-pop__check::after { - content: ""; - width: 10px; - height: 6px; - border-left: 1.5px solid #fff; - border-bottom: 1.5px solid #fff; - transform: translateY(-1px) rotate(-45deg); -} - -.register-pop__submit { - align-self: stretch; - height: 52px; - padding: 0 28px; - font-size: 16px; - line-height: 24px; -} - -.register-pop__submit.is-loading, -.register-pop__submit:disabled { - opacity: 0.72; - cursor: wait; -} - -.register-pop__tip { - margin: 0; - align-self: stretch; - padding: 8px 12px; - border-radius: 8px; - font-size: 13px; - line-height: 20px; - background: rgba(255, 74, 123, 0.1); - color: #ff4a7b; -} - -.register-pop__tip.is-success { - background: rgba(30, 120, 70, 0.12); - color: #1e7846; -} - -.register-pop__tip[hidden] { - display: none !important; -} - -.register-pop__switch { - margin: 0; - align-self: stretch; - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--text-muted); - font-size: 14px; - line-height: 22px; -} - -.register-pop__switch a { - color: #ff4a7b; - font-size: 14px; - font-weight: 500; - line-height: 20px; -} - -@media (max-width: 768px) { - .register-pop { - padding: 16px; - } - - .register-pop__panel { - max-height: calc(100vh - 32px); - max-height: calc(100dvh - 32px); - padding: 24px 20px; - border-radius: 18px; - overflow-y: auto; - } - - .register-pop__title { - font-size: 24px; - line-height: 32px; - } -} diff --git a/app/static/css/setting.css b/app/static/css/setting.css deleted file mode 100644 index 0726b40..0000000 --- a/app/static/css/setting.css +++ /dev/null @@ -1,65 +0,0 @@ -.setting-main { width: min(calc(100% - 48px), var(--page-width)); margin: 0 auto; padding: 24px 0 40px; } -.setting-layout { max-width: 900px; margin: 0 auto; } -.setting-card { display: flex; flex-direction: column; gap: 24px; min-width: 0; padding: 32px; border: 1px solid var(--line-soft); border-radius: 16px; background: #141417; } -.setting-section { display: flex; flex-direction: column; gap: 24px; scroll-margin-top: 90px; } -.setting-section__title { margin: 0; color: var(--text); font-size: 18px; font-weight: 600; line-height: 26px; } -.setting-avatar { display: flex; align-items: center; gap: 16px; } -.setting-avatar__image { flex: 0 0 72px; width: 72px; height: 72px; border-radius: 50%; object-fit: cover; } -.setting-avatar__button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 36px; padding: 0 12px; border: 1px solid rgba(255,255,255,.24); border-radius: 999px; color: var(--text); font-size: 14px; cursor: pointer; } -.setting-avatar__button img { width: 16px; height: 16px; } -.setting-avatar__file { position: absolute; width: 1px; height: 1px; opacity: 0; } -.setting-avatar__hint { margin: 8px 0 0; color: var(--text-dim); font-size: 12px; line-height: 18px; } -.setting-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 24px; } -.setting-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; color: var(--text-muted); font-size: 14px; font-weight: 500; line-height: 20px; } -.setting-field input, .setting-field textarea, .setting-field__action { width: 100%; min-width: 0; min-height: 48px; padding: 12px 16px; border: 1px solid rgba(255,255,255,.12); border-radius: 12px; outline: none; background: rgba(255,255,255,.06); color: var(--text); font: 400 15px/24px var(--font); } -.setting-field textarea { min-height: 96px; resize: vertical; } -.setting-field input:focus, .setting-field textarea:focus { border-color: #ec237f; } -.setting-field input[readonly] { color: var(--text-muted); cursor: default; } -.setting-field small { color: var(--text-dim); font-size: 12px; font-weight: 400; line-height: 18px; } -.setting-field__action { color: #ff4a7b; text-align: right; cursor: pointer; } -.setting-field__value-action { position: relative; min-width: 0; } -.setting-field__value-action input { padding-right: 72px; } -.setting-field__inline-action { position: absolute; top: 50%; right: 16px; color: #ff4a7b; font-size: 14px; font-weight: 500; line-height: 20px; transform: translateY(-50%); cursor: pointer; } -.setting-field--intro { max-width: 100%; } -.setting-actions { display: flex; align-items: center; gap: 12px; padding-top: 24px; border-top: 1px solid var(--line-soft); } -.setting-actions button { min-height: 48px; padding: 0 22px; border-radius: 999px; color: #fff; font-size: 15px; font-weight: 500; cursor: pointer; } -.setting-actions__save { border: 0; background: linear-gradient(135deg, #ff4a7b 15%, #ec237f 51%, #93226f 85%); } -.setting-actions__logout { border: 1px solid rgba(255,255,255,.24); background: transparent; } -.setting-actions__unavailable { margin-left: auto; color: var(--text-dim); font-size: 13px; } -.setting-status { margin: -8px 0 0; min-height: 20px; color: #ff858b; font-size: 13px; line-height: 20px; } -.setting-status.is-success { color: #84d9a1; } -.setting-login-state { padding: 80px 16px; text-align: center; color: var(--text-muted); } -.setting-login-state h2 { color: var(--text); } -.setting-login-state a { color: #ff4a7b; } - -.setting-password-pop { position: fixed; inset: 0; z-index: 295; display: flex; align-items: center; justify-content: center; padding: 24px; } -.setting-password-pop[hidden] { display: none !important; } -.setting-password-pop__mask { position: absolute; inset: 0; width: 100%; height: 100%; background: rgba(0,0,0,.72); cursor: default; } -.setting-password-pop__panel { position: relative; z-index: 1; width: 440px; max-width: 100%; padding: 32px; border: 1px solid var(--line); border-radius: var(--radius-xl); background: #141417; box-shadow: 0 24px 64px rgba(0,0,0,.55); } -.setting-password-pop__close { position: absolute; top: 16px; right: 16px; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,.04); } -.setting-password-pop__close .icon { width: 18px; height: 18px; } -.setting-password-pop__intro { padding-right: 40px; } -.setting-password-pop__intro h2 { margin: 0; color: var(--text); font-size: 28px; font-weight: 600; line-height: 38px; } -.setting-password-pop__intro p { margin: 6px 0 0; color: var(--text-muted); font-size: 14px; line-height: 22px; } -.setting-password-pop__form { display: flex; flex-direction: column; gap: 16px; margin-top: 20px; } -.setting-password-pop__status { min-height: 18px; margin: -4px 0 0; color: #ff858b; font-size: 12px; line-height: 18px; } -.setting-password-pop__status.is-success { color: #84d9a1; } -.setting-password-pop__submit { width: 100%; min-height: 52px; border: 0; border-radius: 999px; color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; } -.setting-password-pop__submit:disabled { opacity: .72; cursor: wait; } - -@media (max-width: 768px) { - .setting-main { width: 100%; padding: 0 16px 24px; } - .setting-layout { max-width: none; } - .setting-card { gap: 16px; padding: 16px 0 0; border: 0; border-radius: 0; background: transparent; } - .setting-section { gap: 16px; } - .setting-section__title { display: none; } - .setting-avatar__image { flex-basis: 56px; width: 56px; height: 56px; } - .setting-fields { grid-template-columns: minmax(0, 1fr); gap: 16px; } - .setting-actions { flex-direction: column; align-items: stretch; gap: 16px; padding-top: 16px; } - .setting-actions button { width: 100%; } - .setting-actions__unavailable { margin: 0; text-align: center; } - .setting-status { text-align: center; } - .setting-password-pop { padding: 16px; } - .setting-password-pop__panel { max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); padding: 24px 20px; border-radius: 18px; overflow-y: auto; } - .setting-password-pop__intro h2 { font-size: 24px; line-height: 32px; } -} diff --git a/app/static/images/check.svg b/app/static/images/check.svg deleted file mode 100644 index 9d95a45..0000000 --- a/app/static/images/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/eye-off.svg b/app/static/images/eye-off.svg deleted file mode 100644 index 3ca0b3e..0000000 --- a/app/static/images/eye-off.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/eye.svg b/app/static/images/eye.svg deleted file mode 100644 index 26466fe..0000000 --- a/app/static/images/eye.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/favorites-empty/arrow-right.svg b/app/static/images/favorites-empty/arrow-right.svg deleted file mode 100644 index bd73f72..0000000 --- a/app/static/images/favorites-empty/arrow-right.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/favorites-empty/bookmark.svg b/app/static/images/favorites-empty/bookmark.svg deleted file mode 100644 index 632230f..0000000 --- a/app/static/images/favorites-empty/bookmark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/favorites/sliders.svg b/app/static/images/favorites/sliders.svg deleted file mode 100644 index ac662dd..0000000 --- a/app/static/images/favorites/sliders.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/app/static/images/gift-dim.svg b/app/static/images/gift-dim.svg deleted file mode 100644 index cb291ac..0000000 --- a/app/static/images/gift-dim.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/app/static/images/logout.svg b/app/static/images/logout.svg deleted file mode 100644 index 8e02ff9..0000000 --- a/app/static/images/logout.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/static/images/menu_gkls.svg b/app/static/images/menu_gkls.svg deleted file mode 100644 index e604c3f..0000000 --- a/app/static/images/menu_gkls.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/menu_grzx.svg b/app/static/images/menu_grzx.svg deleted file mode 100644 index 1e86399..0000000 --- a/app/static/images/menu_grzx.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/menu_jbcz.svg b/app/static/images/menu_jbcz.svg deleted file mode 100644 index ed3f3e3..0000000 --- a/app/static/images/menu_jbcz.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/menu_sc.svg b/app/static/images/menu_sc.svg deleted file mode 100644 index cd882fd..0000000 --- a/app/static/images/menu_sc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/purchased-empty/bag-play.svg b/app/static/images/purchased-empty/bag-play.svg deleted file mode 100644 index 55f6c72..0000000 --- a/app/static/images/purchased-empty/bag-play.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/static/images/recharge/award.svg b/app/static/images/recharge/award.svg deleted file mode 100644 index fd9ba54..0000000 --- a/app/static/images/recharge/award.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/recharge/check-circle.svg b/app/static/images/recharge/check-circle.svg deleted file mode 100644 index 1205ddc..0000000 --- a/app/static/images/recharge/check-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/recharge/check.svg b/app/static/images/recharge/check.svg deleted file mode 100644 index 41309c2..0000000 --- a/app/static/images/recharge/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/recharge/close.svg b/app/static/images/recharge/close.svg deleted file mode 100644 index 7b412a5..0000000 --- a/app/static/images/recharge/close.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/recharge/coin-star.svg b/app/static/images/recharge/coin-star.svg deleted file mode 100644 index ac08866..0000000 --- a/app/static/images/recharge/coin-star.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/recharge/coin3d.png b/app/static/images/recharge/coin3d.png deleted file mode 100644 index 87ff947a17727d5e458677310cb4cceef891987f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 348795 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" ); } @@ -195,26 +160,6 @@ totalEl.textContent = n > 0 ? n + " 条" : "暂无评论"; } - function renderList(list, totalCount) { - if (!listEl) return; - list = Array.isArray(list) ? list : []; - clearReplyTarget(); - if (!list.length) { - listEl.innerHTML = - '
  • 暂无评论

  • '; - } else { - listEl.innerHTML = list.map(renderCommentItem).join(""); - if (typeof window.initLazyImages === "function") { - window.initLazyImages(listEl); - } - } - var total = typeof totalCount === "number" ? totalCount : toInt(commentsRoot && commentsRoot.getAttribute("data-comment-total"), 0); - if (typeof totalCount === "number") { - updateTotal(totalCount); - } - setPagerState(1, list.length, total); - } - // 下一页追加到现有列表底部,不替换已展示评论。 function appendList(list, totalCount) { if (!listEl) return; @@ -222,7 +167,7 @@ var total = typeof totalCount === "number" ? totalCount - : toInt(commentsRoot && commentsRoot.getAttribute("data-comment-total"), 0); + : toInt(commentsRoot.getAttribute("data-comment-total"), 0); if (typeof totalCount === "number") { updateTotal(totalCount); } @@ -264,8 +209,8 @@ if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") { return Promise.reject(new Error("评论服务未就绪")); } - var objectId = toInt(root.getAttribute("data-object-id"), 0); - var objectType = toInt(root.getAttribute("data-object-type"), 10); + var objectId = toInt(commentsRoot.getAttribute("data-object-id"), 0); + var objectType = toInt(commentsRoot.getAttribute("data-object-type"), 10); if (objectId <= 0) return Promise.reject(new Error("缺少评论对象")); var pageSize = getPageSize(); return window.HttpRequest.get("/api/comment/list", { @@ -284,14 +229,6 @@ }); } - function refreshList() { - return fetchCommentPage(1) - .then(function (data) { - renderList(data.list || [], toInt(data.totalCount, 0)); - }) - .catch(function () {}); - } - function loadMoreComments() { if (loadingMore || !moreBtn) return; var nextPage = getPage() + 1; @@ -301,9 +238,7 @@ .then(function (data) { var list = data.list || []; var total = toInt(data.totalCount, 0); - if (commentsRoot) { - commentsRoot.setAttribute("data-comment-page", String(nextPage)); - } + commentsRoot.setAttribute("data-comment-page", String(nextPage)); // 只追加,绝不 renderList 整表替换 appendList(list, total); }) @@ -316,284 +251,10 @@ }); } - function removeInlineComposer() { - if (!listEl) return; - listEl.querySelectorAll("[data-comment-inline-composer]").forEach(function (el) { - el.remove(); - }); - } - - function clearReplyTarget() { - replyTarget = null; - removeInlineComposer(); - } - - function applyLikeUI(btn, isLike, countLike) { - if (!btn) return; - var liked = Number(isLike) === 1; - btn.classList.toggle("is-active", liked); - btn.setAttribute("data-liked", liked ? "1" : "2"); - if (countLike != null) { - btn.setAttribute("data-like-raw", String(countLike)); - var countEl = btn.querySelector("[data-like-count]"); - if (countEl) countEl.textContent = String(countLike); - } - } - - function toggleCommentLike(btn) { - if (!btn || btn.getAttribute("data-liking") === "1") return; - if (root.getAttribute("data-logged-in") !== "1") { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("点赞服务未就绪", "error"); - return; - } - var commentId = toInt(btn.getAttribute("data-comment-like"), 0); - if (commentId <= 0) return; - var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1; - var nextLike = !likedNow; - var prevCount = toInt(btn.getAttribute("data-like-raw"), 0); - var nextCount = Math.max(0, prevCount + (nextLike ? 1 : -1)); - btn.setAttribute("data-liking", "1"); - btn.disabled = true; - applyLikeUI(btn, nextLike ? 1 : 2, nextCount); - var payload = { commentId: commentId, isLike: nextLike }; - window.HttpRequest.post("/api/comment/like/set", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyLikeUI( - btn, - data.isLike != null ? data.isLike : nextLike ? 1 : 2, - data.countLike != null ? data.countLike : nextCount - ); - }) - .catch(function (err) { - applyLikeUI(btn, likedNow ? 1 : 2, prevCount); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-liking"); - btn.disabled = false; - }); - } - - function openInlineComposer(commentEl, target) { - if (!commentEl || !target) return; - removeInlineComposer(); - replyTarget = target; - var body = commentEl.querySelector(".play-comment__body") || commentEl; - var actions = body.querySelector(".play-comment__actions"); - var box = document.createElement("div"); - box.className = "play-comment__inline"; - box.setAttribute("data-comment-inline-composer", ""); - var name = target.replyName || "用户"; - box.innerHTML = - '
    ' + - "回复 @" + - escapeHtml(name) + - "" + - '' + - "
    " + - '
    ' + - '' + - '' + - "
    "; - if (actions && actions.parentNode === body) { - if (actions.nextSibling) body.insertBefore(box, actions.nextSibling); - else body.appendChild(box); - } else { - body.appendChild(box); - } - var inlineInput = box.querySelector("[data-comment-inline-input]"); - if (inlineInput) inlineInput.focus(); - } - - function setSending(on) { - sending = !!on; - if (sendBtn) sendBtn.disabled = !!on; - if (input) input.disabled = !!on; - } - - function submitTopComment() { - if (sending) return; - if (root.getAttribute("data-logged-in") !== "1") { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("评论服务未就绪", "error"); - return; - } - var text = String((input && input.value) || "").trim(); - if (!text) { - toast("请输入评论内容", "error"); - return; - } - if (Array.from(text).length > MAX_LEN) { - toast("评论最多 200 字", "error"); - return; - } - var objectId = toInt(root.getAttribute("data-object-id"), 0); - var objectType = toInt(root.getAttribute("data-object-type"), 10); - if (objectId <= 0) { - toast("缺少评论对象", "error"); - return; - } - - var payload = { - objectId: objectId, - objectType: objectType, - text: text, - parentsId: 0, - replyCommentId: 0, - }; - setSending(true); - window.HttpRequest.post("/api/comment/send", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "发表失败"); - } - if (input) input.value = ""; - syncCount(); - clearReplyTarget(); - toast("发布成功,评论审核后才会展示", "success"); - return refreshList(); - }) - .catch(function (err) { - toast((err && err.message) || "发表失败,请稍后重试", "error"); - }) - .finally(function () { - setSending(false); - }); - } - - function sendInlineReply(box) { - if (!box || inlineSending || !replyTarget) return; - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("评论服务未就绪", "error"); - return; - } - var inlineInput = box.querySelector("[data-comment-inline-input]"); - var btn = box.querySelector("[data-comment-inline-send]"); - if (!inlineInput) return; - var text = String(inlineInput.value || "").trim(); - if (!text) { - toast("请输入评论内容", "error"); - return; - } - if (Array.from(text).length > MAX_LEN) { - toast("评论最多 200 字", "error"); - return; - } - var objectId = toInt(root.getAttribute("data-object-id"), 0); - var objectType = toInt(root.getAttribute("data-object-type"), 10); - if (objectId <= 0) { - toast("缺少评论对象", "error"); - return; - } - inlineSending = true; - if (btn) btn.disabled = true; - inlineInput.disabled = true; - var payload = { - objectId: objectId, - objectType: objectType, - text: text, - parentsId: toInt(replyTarget.parentsId, 0), - replyCommentId: toInt( - replyTarget.replyCommentId || replyTarget.parentsId, - 0 - ), - }; - window.HttpRequest.post("/api/comment/send", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "发表失败"); - } - clearReplyTarget(); - toast("发布成功,评论审核后才会展示", "success"); - return refreshList(); - }) - .catch(function (err) { - toast((err && err.message) || "发表失败,请稍后重试", "error"); - }) - .finally(function () { - inlineSending = false; - if (btn) btn.disabled = false; - if (inlineInput) inlineInput.disabled = false; - }); - } - - if (input) { - input.addEventListener("input", syncCount); - input.addEventListener("keydown", function (event) { - if (event.key === "Enter" && !event.shiftKey) { - event.preventDefault(); - submitTopComment(); - } - }); - syncCount(); - } - if (sendBtn) { - sendBtn.addEventListener("click", submitTopComment); - } if (moreBtn) { moreBtn.addEventListener("click", function (event) { event.preventDefault(); loadMoreComments(); }); } - if (listEl) { - listEl.addEventListener("click", function (event) { - var likeBtn = window.domClosest(event.target, "[data-comment-like]"); - if (likeBtn && listEl.contains(likeBtn)) { - event.preventDefault(); - toggleCommentLike(likeBtn); - return; - } - var cancelBtn = window.domClosest(event.target, "[data-comment-reply-cancel]"); - if (cancelBtn && listEl.contains(cancelBtn)) { - clearReplyTarget(); - return; - } - var sendInlineBtn = window.domClosest(event.target, "[data-comment-inline-send]"); - if (sendInlineBtn && listEl.contains(sendInlineBtn)) { - var box = sendInlineBtn.closest("[data-comment-inline-composer]"); - sendInlineReply(box); - return; - } - var replyBtn = window.domClosest(event.target, "[data-comment-reply]"); - if (!replyBtn || !listEl.contains(replyBtn)) return; - if (root.getAttribute("data-logged-in") !== "1") { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - var parentsId = toInt(replyBtn.getAttribute("data-parents-id"), 0); - var commentId = toInt(replyBtn.getAttribute("data-comment-id"), parentsId); - if (parentsId <= 0) return; - var commentEl = replyBtn.closest(".play-comment"); - openInlineComposer(commentEl, { - parentsId: parentsId, - replyCommentId: commentId, - replyName: replyBtn.getAttribute("data-reply-name") || "用户", - }); - }); - listEl.addEventListener("keydown", function (event) { - if (event.key !== "Enter" || event.shiftKey) return; - var inlineInput = window.domClosest(event.target, "[data-comment-inline-input]"); - if (!inlineInput || !listEl.contains(inlineInput)) return; - event.preventDefault(); - var box = inlineInput.closest("[data-comment-inline-composer]"); - sendInlineReply(box); - }); - } })(); diff --git a/app/static/js/pages/register.js b/app/static/js/pages/register.js deleted file mode 100644 index 66f639a..0000000 --- a/app/static/js/pages/register.js +++ /dev/null @@ -1,169 +0,0 @@ -(function () { - var form = document.querySelector("[data-register-page-form]"); - var password = document.querySelector("[data-register-page-password]"); - var confirm = document.querySelector("[data-register-page-password-confirm]"); - var submitBtn = document.querySelector("[data-register-page-submit]") || (form && form.querySelector('button[type="submit"]')); - var root = form || document; - - function bindToggle(key, input) { - var btn = document.querySelector('[data-register-page-toggle="' + key + '"]'); - var icon = document.querySelector('[data-register-page-toggle-icon="' + key + '"]'); - if (!btn || !input || !icon) return; - btn.addEventListener("click", function () { - var show = input.getAttribute("type") === "password"; - input.setAttribute("type", show ? "text" : "password"); - icon.setAttribute("src", show ? "/static/images/eye-off.svg" : "/static/images/eye.svg"); - btn.setAttribute("aria-label", show ? "隐藏密码" : "显示密码"); - }); - } - - bindToggle("password", password); - bindToggle("confirm", confirm); - - function clearFieldError(name) { - var field = root.querySelector('[data-field="' + name + '"]'); - var err = root.querySelector('[data-field-error="' + name + '"]'); - if (field) field.classList.remove("is-error"); - if (err) { - err.hidden = true; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = ""; - } - } - - function clearAllErrors() { - root.querySelectorAll("[data-field]").forEach(function (el) { - el.classList.remove("is-error"); - }); - root.querySelectorAll("[data-field-error]").forEach(function (err) { - err.hidden = true; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = ""; - }); - } - - function setFieldError(name, message) { - var field = root.querySelector('[data-field="' + name + '"]'); - var err = root.querySelector('[data-field-error="' + name + '"]'); - if (field) field.classList.add("is-error"); - if (err) { - err.hidden = false; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = message || ""; - } - } - - function isAccountNo(value) { - return /^[A-Za-z0-9]{6,20}$/.test(String(value || "")); - } - - function isPassword(value) { - var pwd = String(value || ""); - if (pwd.length < 8) return false; - return /[A-Za-z]/.test(pwd) && /[0-9]/.test(pwd); - } - - function validateForm() { - clearAllErrors(); - var fd = new FormData(form); - var account = String(fd.get("account") || "").trim(); - var pwd = String(fd.get("password") || ""); - var pwd2 = String(fd.get("passwordConfirm") || ""); - var agree = form.querySelector('input[name="agree"]'); - var valid = true; - - if (!account) { - setFieldError("account", "请输入账号"); - valid = false; - } else if (!isAccountNo(account)) { - setFieldError("account", "账号需为 6 到 20 位字母或数字"); - valid = false; - } - - if (!pwd) { - setFieldError("password", "请输入密码"); - valid = false; - } else if (!isPassword(pwd)) { - setFieldError("password", "密码需 8 位以上,且同时包含字母与数字"); - valid = false; - } - - if (!pwd2) { - setFieldError("passwordConfirm", "请再次输入密码"); - valid = false; - } else if (pwd2 !== pwd) { - setFieldError("passwordConfirm", "两次输入的密码不一致"); - valid = false; - } - - if (agree && !agree.checked) { - setFieldError("agree", "请先同意用户条款与隐私政策"); - valid = false; - } - - if (!valid) return null; - return { accountNo: account, password: pwd }; - } - - function mapServerError(err) { - var tip = (err && err.message) || "注册失败,请稍后重试"; - var code = err && err.code; - if (code === 5005 || /已存在|重复|占用/.test(tip)) { - setFieldError("account", tip); - return; - } - if (/密码|password/i.test(tip)) { - setFieldError("password", tip); - return; - } - setFieldError("account", tip); - } - - function setSubmitting(loading) { - if (!submitBtn) return; - submitBtn.disabled = !!loading; - submitBtn.classList.toggle("is-loading", !!loading); - submitBtn.textContent = loading ? "注册中…" : "注册"; - } - - if (form) { - form.querySelectorAll("input").forEach(function (input) { - input.addEventListener("input", function () { - var name = input.getAttribute("name"); - if (name === "agree") clearFieldError("agree"); - else if (name) clearFieldError(name); - }); - if (input.type === "checkbox") { - input.addEventListener("change", function () { - clearFieldError("agree"); - }); - } - }); - - form.addEventListener("submit", function (event) { - event.preventDefault(); - if (!window.AuthAPI || !window.HttpRequest) { - setFieldError("account", "认证脚本未加载,请刷新页面"); - return; - } - if (submitBtn && submitBtn.classList.contains("is-loading")) return; - - var payload = validateForm(); - if (!payload) return; - - setSubmitting(true); - AuthAPI.register(payload) - .then(function (res) { - AuthAPI.toast((res && res.tip) || "注册成功", "success"); - window.setTimeout(function () { - window.location.href = "/"; - }, 400); - }) - .catch(function (err) { - setSubmitting(false); - mapServerError(err); - AuthAPI.toast((err && err.message) || "注册失败", "error"); - }); - }); - } -})(); diff --git a/app/static/js/pages/registerPop.js b/app/static/js/pages/registerPop.js deleted file mode 100644 index aefb823..0000000 --- a/app/static/js/pages/registerPop.js +++ /dev/null @@ -1,256 +0,0 @@ -(function () { - var root = document.querySelector("[data-register-pop]"); - if (!root) return; - - var form = root.querySelector("[data-register-form]"); - var password = root.querySelector("[data-register-password]"); - var confirm = root.querySelector("[data-register-password-confirm]"); - var submitBtn = root.querySelector("[data-register-submit]") || (form && form.querySelector('button[type="submit"]')); - var previousOverflow = ""; - - function closeLogin() { - if (typeof window.closeLoginPop === "function") { - window.closeLoginPop(); - return; - } - var login = document.querySelector("[data-login-pop]"); - if (login) login.classList.remove("is-open"); - } - - function closeUserMenu() { - var menu = document.querySelector("[data-user-menu].is-open"); - if (menu) { - menu.classList.remove("is-open"); - var t = menu.querySelector("[data-user-menu-toggle]"); - if (t) t.setAttribute("aria-expanded", "false"); - } - } - - function clearFieldError(name) { - var field = root.querySelector('[data-field="' + name + '"]'); - var err = root.querySelector('[data-field-error="' + name + '"]'); - if (field) field.classList.remove("is-error"); - if (err) { - err.hidden = true; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = ""; - } - } - - function clearAllErrors() { - root.querySelectorAll("[data-field]").forEach(function (el) { - el.classList.remove("is-error"); - }); - root.querySelectorAll("[data-field-error]").forEach(function (err) { - err.hidden = true; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = ""; - }); - } - - function setFieldError(name, message) { - var field = root.querySelector('[data-field="' + name + '"]'); - var err = root.querySelector('[data-field-error="' + name + '"]'); - if (field) field.classList.add("is-error"); - if (err) { - err.hidden = false; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = message || ""; - } - } - - function isAccountNo(value) { - return /^[A-Za-z0-9]{6,20}$/.test(String(value || "")); - } - - function isPassword(value) { - var pwd = String(value || ""); - if (pwd.length < 8) return false; - return /[A-Za-z]/.test(pwd) && /[0-9]/.test(pwd); - } - - function bindingMessage(message, fallback) { - return String(message || fallback || "").replace(/注册/g, "绑定"); - } - - function validateForm() { - clearAllErrors(); - var fd = new FormData(form); - var account = String(fd.get("account") || "").trim(); - var pwd = String(fd.get("password") || ""); - var pwd2 = String(fd.get("passwordConfirm") || ""); - var agree = form.querySelector('input[name="agree"]'); - var valid = true; - - if (!account) { - setFieldError("account", "请输入账号"); - valid = false; - } else if (!isAccountNo(account)) { - setFieldError("account", "账号需为 6 到 20 位字母或数字"); - valid = false; - } - - if (!pwd) { - setFieldError("password", "请输入密码"); - valid = false; - } else if (!isPassword(pwd)) { - setFieldError("password", "密码需 8 位以上,且同时包含字母与数字"); - valid = false; - } - - if (!pwd2) { - setFieldError("passwordConfirm", "请再次输入密码"); - valid = false; - } else if (pwd2 !== pwd) { - setFieldError("passwordConfirm", "两次输入的密码不一致"); - valid = false; - } - - if (agree && !agree.checked) { - setFieldError("agree", "请先同意用户条款与隐私政策"); - valid = false; - } - - if (!valid) return null; - return { accountNo: account, password: pwd }; - } - - function mapServerError(err) { - var tip = bindingMessage(err && err.message, "绑定失败,请稍后重试"); - var code = err && err.code; - if (code === 5005 || /已存在|重复|占用/.test(tip)) { - setFieldError("account", tip); - return; - } - if (/密码|password/i.test(tip)) { - setFieldError("password", tip); - return; - } - setFieldError("account", tip); - } - - function open(event) { - if (event) event.preventDefault(); - closeLogin(); - closeUserMenu(); - clearAllErrors(); - root.classList.add("is-open"); - previousOverflow = document.body.style.overflow; - document.body.style.overflow = "hidden"; - var first = root.querySelector(".register-pop__input"); - if (first) { - window.setTimeout(function () { - first.focus(); - }, 0); - } - } - - function close() { - root.classList.remove("is-open"); - document.body.style.overflow = previousOverflow; - clearAllErrors(); - } - - window.openRegisterPop = open; - window.closeRegisterPop = close; - - function bindToggle(key, input) { - var btn = root.querySelector('[data-register-toggle="' + key + '"]'); - var icon = root.querySelector('[data-register-toggle-icon="' + key + '"]'); - if (!btn || !input || !icon) return; - btn.addEventListener("click", function () { - var show = input.getAttribute("type") === "password"; - input.setAttribute("type", show ? "text" : "password"); - icon.setAttribute("src", show ? "/static/images/eye-off.svg" : "/static/images/eye.svg"); - btn.setAttribute("aria-label", show ? "隐藏密码" : "显示密码"); - }); - } - - document.addEventListener("click", function (event) { - var opener = event.target.closest("[data-register-open]"); - if (opener) { - open(event); - return; - } - - // 站内注册入口统一使用绑定账号弹窗 - var registerLink = event.target.closest('a[href="/register"], a[href="/register/"], a[href="#register"]'); - if (registerLink) { - open(event); - return; - } - - if (!root.classList.contains("is-open")) return; - if (event.target.closest("[data-register-close]")) { - close(); - } - }); - - bindToggle("password", password); - bindToggle("confirm", confirm); - - function setSubmitting(loading) { - if (!submitBtn) return; - submitBtn.disabled = !!loading; - submitBtn.classList.toggle("is-loading", !!loading); - submitBtn.textContent = loading ? "绑定中…" : "绑定"; - } - - if (form) { - form.querySelectorAll("input").forEach(function (input) { - input.addEventListener("input", function () { - var name = input.getAttribute("name"); - if (name === "agree") clearFieldError("agree"); - else if (name) clearFieldError(name); - }); - if (input.type === "checkbox") { - input.addEventListener("change", function () { - clearFieldError("agree"); - }); - } - }); - - form.addEventListener("submit", function (event) { - event.preventDefault(); - if (!window.AuthAPI || !window.HttpRequest) { - setFieldError("account", "认证脚本未加载,请刷新页面"); - return; - } - if (submitBtn && submitBtn.classList.contains("is-loading")) return; - - var payload = validateForm(); - if (!payload) return; - - setSubmitting(true); - AuthAPI.register(payload) - .then(function (res) { - AuthAPI.toast(bindingMessage(res && res.tip, "绑定成功"), "success"); - window.setTimeout(function () { - close(); - window.location.reload(); - }, 400); - }) - .catch(function (err) { - setSubmitting(false); - mapServerError(err); - AuthAPI.toast(bindingMessage(err && err.message, "绑定失败"), "error"); - }); - }); - } - - document.addEventListener("keydown", function (event) { - if (event.key === "Escape" && root.classList.contains("is-open")) { - close(); - } - }); - - try { - var params = new URLSearchParams(window.location.search || ""); - if (params.get("auth") === "register") { - params.delete("auth"); - var qs = params.toString(); - window.history.replaceState(null, "", window.location.pathname + (qs ? "?" + qs : "") + window.location.hash); - open(); - } - } catch (e) {} -})(); diff --git a/app/static/js/recharge-modal.js b/app/static/js/recharge-modal.js deleted file mode 100644 index 9394129..0000000 --- a/app/static/js/recharge-modal.js +++ /dev/null @@ -1,246 +0,0 @@ -(function () { - var root = document.querySelector("[data-recharge-modal]"); - if (!root) return; - - var panel = root.querySelector(".recharge-modal__panel"); - var openers = document.querySelectorAll("[data-recharge-open]"); - var closers = root.querySelectorAll("[data-recharge-close]"); - var packagesRoot = root.querySelector("[data-recharge-packages]"); - var methodsRoot = root.querySelector("[data-recharge-methods]"); - var amount = root.querySelector("[data-recharge-amount]"); - var pay = root.querySelector("[data-recharge-pay]"); - var status = root.querySelector("[data-recharge-status]"); - var previousOverflow = ""; - var returnFocus = null; - var paying = false; - - function packages() { - return root.querySelectorAll("[data-recharge-package]"); - } - - function methods() { - return root.querySelectorAll("[data-recharge-method]"); - } - - function showStatus(message, ok) { - if (!status) return; - status.textContent = message || ""; - status.hidden = !message; - status.classList.toggle("is-ok", !!ok); - } - - function open(trigger) { - if (!root.hidden) return; - - var oldDialog = document.querySelector("[data-play-coin].is-open"); - if (oldDialog) { - var oldClose = oldDialog.querySelector("button[data-play-coin-close]"); - if (oldClose) oldClose.click(); - } - - returnFocus = trigger && oldDialog ? document.querySelector("[data-play-coin-open]") : trigger; - previousOverflow = document.body.style.overflow; - showStatus(""); - root.hidden = false; - document.body.style.overflow = "hidden"; - if (panel) panel.focus(); - } - - function close() { - if (root.hidden) return; - root.hidden = true; - document.body.style.overflow = previousOverflow; - if (returnFocus && typeof returnFocus.focus === "function") returnFocus.focus(); - } - - function parsePayMethods(raw) { - try { - var list = JSON.parse(raw || "[]"); - return Array.isArray(list) ? list : []; - } catch (e) { - return []; - } - } - - function renderMethods(list) { - if (!methodsRoot) return; - methodsRoot.replaceChildren(); - if (!list.length) { - var empty = document.createElement("p"); - empty.className = "recharge-modal__empty"; - empty.textContent = "该套餐暂无可用支付方式"; - methodsRoot.appendChild(empty); - return; - } - list.forEach(function (item, index) { - var type = String((item && item.type) || "").trim(); - var name = String((item && item.typeName) || type || "支付").trim(); - if (!type) return; - var btn = document.createElement("button"); - btn.className = "recharge-modal__method" + (index === 0 ? " is-selected" : ""); - btn.type = "button"; - btn.setAttribute("data-recharge-method", type); - btn.setAttribute("aria-pressed", index === 0 ? "true" : "false"); - btn.innerHTML = - '' + - '即时到账'; - btn.querySelector(".recharge-modal__method-copy span").textContent = name; - methodsRoot.appendChild(btn); - }); - if (typeof window.initLazyImages === "function") { - window.initLazyImages(methodsRoot); - } - } - - function selectPackage(button) { - Array.prototype.forEach.call(packages(), function (item) { - var selected = item === button; - item.classList.toggle("is-selected", selected); - item.setAttribute("aria-pressed", String(selected)); - }); - if (amount) amount.textContent = "¥" + (button.getAttribute("data-price") || "0"); - renderMethods(parsePayMethods(button.getAttribute("data-pay-methods"))); - showStatus(""); - } - - function bindMethodClicks() { - if (!methodsRoot) return; - methodsRoot.addEventListener("click", function (event) { - var button = event.target.closest("[data-recharge-method]"); - if (!button || !methodsRoot.contains(button)) return; - Array.prototype.forEach.call(methods(), function (item) { - var selected = item === button; - item.classList.toggle("is-selected", selected); - item.setAttribute("aria-pressed", String(selected)); - }); - showStatus(""); - }); - } - - Array.prototype.forEach.call(openers, function (button) { - button.addEventListener("click", function (event) { - event.preventDefault(); - open(button); - }); - }); - - Array.prototype.forEach.call(closers, function (element) { - element.addEventListener("click", close); - }); - - if (packagesRoot) { - packagesRoot.addEventListener("click", function (event) { - var button = event.target.closest("[data-recharge-package]"); - if (!button || !packagesRoot.contains(button)) return; - selectPackage(button); - }); - } - - bindMethodClicks(); - - if (pay) { - pay.addEventListener("click", function () { - if (paying) return; - var selectedPackage = root.querySelector("[data-recharge-package].is-selected"); - var selectedMethod = root.querySelector("[data-recharge-method].is-selected"); - var packageId = selectedPackage ? Number(selectedPackage.getAttribute("data-package-id") || 0) : 0; - var payMode = selectedMethod ? selectedMethod.getAttribute("data-recharge-method") : ""; - if (!packageId) { - showStatus("请选择充值套餐"); - return; - } - if (!payMode) { - showStatus("请选择支付方式"); - return; - } - paying = true; - pay.disabled = true; - showStatus("正在创建支付订单…"); - var payload = { packageId: packageId, payMode: payMode }; - var req = - window.HttpRequest && typeof window.HttpRequest.post === "function" - ? window.HttpRequest.post("/api/coin/buy", payload) - : fetch("/api/coin/buy", { - method: "POST", - credentials: "same-origin", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }).then(function (r) { - return r.json().then(function (body) { - return body; - }); - }); - req - .then(function (result) { - if (!result || Number(result.code) !== 200 || !result.data || !result.data.payUrl) { - throw new Error((result && (result.tip || result.msg)) || "创建支付订单失败"); - } - showStatus("订单创建成功,正在前往支付…", true); - openPayInNewWindow(result.data.payUrl); - paying = false; - pay.disabled = false; - }) - .catch(function (error) { - showStatus((error && error.message) || "创建支付订单失败,请稍后重试"); - paying = false; - pay.disabled = false; - }); - }); - } - - function openPayInNewWindow(url) { - url = String(url || "").trim(); - if (!url) return; - var opened = null; - try { - opened = window.open(url, "_blank", "noopener,noreferrer"); - } catch (e) { - opened = null; - } - if (opened) { - try { - opened.opener = null; - } catch (e2) {} - showStatus("已在新窗口打开支付页面,请完成支付后返回本页", true); - return; - } - // 弹窗被拦截时用
    再试,仍不跳转当前页 - var link = document.createElement("a"); - link.href = url; - link.target = "_blank"; - link.rel = "noopener noreferrer"; - link.style.display = "none"; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - showStatus("若未打开支付页,请允许本站弹窗后重试", true); - } - - document.addEventListener("keydown", function (event) { - if (root.hidden) return; - if (event.key === "Escape") { - event.preventDefault(); - close(); - return; - } - if (event.key !== "Tab") return; - - var focusable = root.querySelectorAll("button:not([disabled]), a[href]"); - if (!focusable.length) return; - var first = focusable[0]; - var last = focusable[focusable.length - 1]; - if (event.shiftKey && (document.activeElement === first || document.activeElement === panel)) { - event.preventDefault(); - last.focus(); - } else if (!event.shiftKey && document.activeElement === last) { - event.preventDefault(); - first.focus(); - } - }); - - window.openRechargeModal = function (trigger) { - open(trigger || null); - }; - - if (new URLSearchParams(window.location.search).get("recharge") === "1") open(null); -})(); diff --git a/app/static/js/recharge.js b/app/static/js/recharge.js deleted file mode 100644 index e75a9b6..0000000 --- a/app/static/js/recharge.js +++ /dev/null @@ -1,322 +0,0 @@ -(() => { - const root = document.querySelector('[data-recharge]'); - if (!root) return; - - const packages = Array.from(root.querySelectorAll('[data-recharge-package]')); - const methodsRoot = root.querySelector('[data-recharge-methods]'); - const methodEmpty = root.querySelector('[data-recharge-method-empty]'); - const payButton = root.querySelector('[data-recharge-pay]'); - const price = root.querySelector('[data-recharge-price]'); - const coins = root.querySelector('[data-recharge-coins]'); - const status = root.querySelector('[data-recharge-status]'); - const rowsRoot = root.querySelector('[data-recharge-rows]'); - const filters = Array.from(root.querySelectorAll('[data-recharge-filter]')); - const pagination = root.querySelector('[data-recharge-pagination]'); - const pageButtons = root.querySelector('[data-recharge-pages]'); - const previous = root.querySelector('[data-recharge-prev]'); - const next = root.querySelector('[data-recharge-next]'); - const total = root.querySelector('[data-recharge-total]'); - const empty = root.querySelector('[data-recharge-history-empty]'); - const more = root.querySelector('[data-recharge-more]'); - const exportBtn = root.querySelector('[data-recharge-export]'); - const mobile = window.matchMedia('(max-width: 768px)'); - let selectedPackage = null; - let selectedMethod = ''; - let activeType = 0; - let currentPage = Number(pagination.dataset.page) || 1; - let pageSize = Number(pagination.dataset.pageSize) || 5; - let totalCount = Number(pagination.dataset.total) || 0; - let expanded = false; - - function showStatus(message, success) { - status.textContent = message; - status.hidden = !message; - status.classList.toggle('is-success', Boolean(success)); - } - - function choose(button, choices) { - choices.forEach(choice => { - const isSelected = choice === button; - choice.classList.toggle('is-selected', isSelected); - choice.setAttribute('aria-checked', String(isSelected)); - }); - } - - function parseMethods(button) { - try { - const parsed = JSON.parse(button.dataset.payMethods || '[]'); - return Array.isArray(parsed) ? parsed : []; - } catch (_) { - return []; - } - } - - function renderMethods(items) { - methodsRoot.replaceChildren(); - selectedMethod = ''; - items.forEach((item, index) => { - const type = String(item.type || '').trim(); - if (!type) return; - const button = document.createElement('button'); - button.className = 'recharge-method'; - button.type = 'button'; - button.setAttribute('role', 'radio'); - button.dataset.rechargeMethod = type; - button.innerHTML = '支付成功后到账'; - button.querySelector('strong').textContent = item.typeName || type; - button.addEventListener('click', () => { - const choices = Array.from(methodsRoot.querySelectorAll('[data-recharge-method]')); - choose(button, choices); - selectedMethod = type; - showStatus(''); - }); - methodsRoot.appendChild(button); - if (index === 0) { - button.classList.add('is-selected'); - button.setAttribute('aria-checked', 'true'); - selectedMethod = type; - } else { - button.setAttribute('aria-checked', 'false'); - } - }); - const hasMethod = Boolean(selectedMethod); - methodEmpty.hidden = hasMethod; - payButton.disabled = !selectedPackage || !hasMethod; - if (typeof window.initLazyImages === 'function') { - window.initLazyImages(methodsRoot); - } - } - - function selectPackage(button) { - selectedPackage = button; - choose(button, packages); - price.textContent = button.dataset.price || '—'; - coins.textContent = button.dataset.coins || '—'; - renderMethods(parseMethods(button)); - showStatus(''); - } - - packages.forEach(button => button.addEventListener('click', () => selectPackage(button))); - const initialPackage = packages.find(button => button.classList.contains('is-selected')) || packages[0]; - if (initialPackage) selectPackage(initialPackage); - else payButton.disabled = true; - - payButton.addEventListener('click', async () => { - if (!selectedPackage || !selectedMethod) { - showStatus('请选择套餐和支付方式'); - return; - } - payButton.disabled = true; - showStatus('正在创建支付订单…'); - const payload = { - packageId: Number(selectedPackage.dataset.packageId), - payMode: selectedMethod, - }; - try { - let result; - if (window.HttpRequest && typeof window.HttpRequest.post === 'function') { - result = await window.HttpRequest.post('/api/coin/buy', payload); - } else { - const response = await fetch('/api/coin/buy', { - method: 'POST', - credentials: 'same-origin', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(payload), - }); - result = await response.json(); - } - if (!result || Number(result.code) !== 200 || !result.data || !result.data.payUrl) { - if (result && (Number(result.code) === 401 || Number(result.code) === 1000)) { - const login = document.querySelector('[data-login-open]'); - if (login) login.click(); - else window.location.href = '/login'; - return; - } - throw new Error((result && (result.tip || result.msg)) || '创建支付订单失败'); - } - showStatus('订单创建成功,正在前往支付…', true); - openPayInNewWindow(result.data.payUrl); - payButton.disabled = false; - } catch (error) { - showStatus(error.message || '创建支付订单失败,请稍后重试'); - payButton.disabled = false; - } - }); - - function openPayInNewWindow(url) { - url = String(url || '').trim(); - if (!url) return; - let opened = null; - try { - opened = window.open(url, '_blank', 'noopener,noreferrer'); - } catch (e) { - opened = null; - } - if (opened) { - try { - opened.opener = null; - } catch (e2) {} - showStatus('已在新窗口打开支付页面,请完成支付后返回本页', true); - return; - } - const link = document.createElement('a'); - link.href = url; - link.target = '_blank'; - link.rel = 'noopener noreferrer'; - link.style.display = 'none'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - showStatus('若未打开支付页,请允许本站弹窗后重试', true); - } - - function appendText(parent, tag, className, value) { - const node = document.createElement(tag); - node.className = className; - node.textContent = value || ''; - parent.appendChild(node); - return node; - } - - function renderRows(items) { - rowsRoot.replaceChildren(); - items.forEach(item => { - const row = document.createElement('div'); - row.className = 'recharge-history__row'; - row.dataset.rechargeRow = ''; - row.dataset.category = item.category || ''; - appendText(row, 'span', 'recharge-history__kind', item.kind); - const detail = document.createElement('span'); - detail.className = 'recharge-history__detail-line'; - appendText(detail, 'span', 'recharge-history__description', item.description); - const time = document.createElement('time'); - time.className = 'recharge-history__time'; - appendText(time, 'span', 'only-desktop', item.time); - appendText(time, 'span', 'only-mobile', item.mobileTime); - detail.appendChild(time); - row.appendChild(detail); - const amount = appendText(row, 'strong', 'recharge-history__amount', item.amount); - amount.classList.toggle('is-credit', Boolean(item.credit)); - rowsRoot.appendChild(row); - }); - empty.hidden = items.length > 0; - empty.textContent = '暂无记录'; - } - - function renderPagination() { - const pageCount = Math.ceil(totalCount / pageSize); - currentPage = Math.min(currentPage, Math.max(1, pageCount)); - pagination.hidden = totalCount === 0; - more.hidden = totalCount <= 5 && !expanded; - previous.disabled = currentPage <= 1; - next.disabled = currentPage >= pageCount; - total.textContent = '共 ' + totalCount + ' 条'; - pageButtons.replaceChildren(); - const firstPage = Math.max(1, Math.min(currentPage - 1, Math.max(1, pageCount - 3))); - for (let page = firstPage; page <= Math.min(pageCount, firstPage + 3); page += 1) { - const button = document.createElement('button'); - button.type = 'button'; - button.textContent = String(page); - button.classList.toggle('is-active', page === currentPage); - if (page === currentPage) button.setAttribute('aria-current', 'page'); - button.addEventListener('click', () => loadHistory(page)); - pageButtons.appendChild(button); - } - } - - async function loadHistory(page) { - empty.hidden = false; - empty.textContent = '加载中…'; - try { - const query = new URLSearchParams({ - page: String(page), - pageSize: String(pageSize), - type: String(activeType), - }); - const url = '/api/coin/trans/list?' + query.toString(); - let result; - if (window.HttpRequest && typeof window.HttpRequest.get === 'function') { - result = await window.HttpRequest.get(url); - } else { - const response = await fetch(url, {credentials: 'same-origin'}); - result = await response.json(); - } - if (!result || Number(result.code) !== 200 || !result.data) { - throw new Error((result && (result.tip || result.msg)) || '交易明细加载失败'); - } - currentPage = Number(result.data.pageNum) || page; - totalCount = Number(result.data.totalCount) || 0; - renderRows(Array.isArray(result.data.list) ? result.data.list : []); - renderPagination(); - } catch (error) { - rowsRoot.replaceChildren(); - empty.hidden = false; - empty.textContent = error.message || '交易明细加载失败'; - pagination.hidden = true; - } - } - - async function exportHistory() { - if (!exportBtn) return; - exportBtn.disabled = true; - exportBtn.classList.add('is-loading'); - try { - const query = new URLSearchParams({type: String(activeType)}); - const url = '/api/coin/trans/export?' + query.toString(); - const response = await fetch(url, {credentials: 'same-origin'}); - const contentType = (response.headers.get('Content-Type') || '').toLowerCase(); - if (contentType.includes('application/json')) { - const result = await response.json(); - throw new Error((result && (result.tip || result.msg)) || '导出失败'); - } - if (!response.ok) { - throw new Error('导出失败'); - } - const blob = await response.blob(); - const disposition = response.headers.get('Content-Disposition') || ''; - let filename = 'coin-trans.csv'; - const match = /filename\*?=(?:UTF-8''|")?([^\";]+)/i.exec(disposition); - if (match && match[1]) { - filename = decodeURIComponent(match[1].replace(/"/g, '').trim()) || filename; - } - const objectURL = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = objectURL; - link.download = filename; - document.body.appendChild(link); - link.click(); - link.remove(); - URL.revokeObjectURL(objectURL); - showStatus('导出成功', true); - } catch (error) { - showStatus(error.message || '导出失败', false); - } finally { - exportBtn.disabled = false; - exportBtn.classList.remove('is-loading'); - } - } - - filters.forEach(button => button.addEventListener('click', () => { - activeType = Number(button.dataset.rechargeFilter) || 0; - filters.forEach(option => { - const selected = option === button; - option.classList.toggle('is-active', selected); - option.setAttribute('aria-pressed', String(selected)); - }); - loadHistory(1); - })); - previous.addEventListener('click', () => { if (currentPage > 1) loadHistory(currentPage - 1); }); - next.addEventListener('click', () => loadHistory(currentPage + 1)); - if (exportBtn) { - exportBtn.addEventListener('click', () => exportHistory()); - } - more.addEventListener('click', () => { - expanded = !expanded; - pageSize = expanded ? 100 : 5; - more.firstChild.textContent = expanded ? '收起 ' : '全部 '; - more.setAttribute('aria-expanded', String(expanded)); - loadHistory(1); - }); - - renderPagination(); -})(); diff --git a/app/static/js/setting.js b/app/static/js/setting.js deleted file mode 100644 index fa867bf..0000000 --- a/app/static/js/setting.js +++ /dev/null @@ -1,143 +0,0 @@ -(() => { - const form = document.querySelector('[data-setting-form]'); - if (!form) return; - - const status = form.querySelector('[data-setting-status]'); - const save = form.querySelector('[data-setting-save]'); - const passwordPop = document.querySelector('[data-setting-password-pop]'); - const passwordForm = document.querySelector('[data-setting-password-form]'); - const passwordOpen = document.querySelector('[data-setting-password-open]'); - const passwordStatus = document.querySelector('[data-setting-password-status]'); - const passwordSubmit = document.querySelector('[data-setting-password-submit]'); - let previousOverflow = ''; - - function showStatus(message, success = false) { - status.textContent = message; - status.classList.toggle('is-success', success); - } - - form.addEventListener('submit', async event => { - event.preventDefault(); - const data = {}; - const nickname = form.elements.nickName.value.trim(); - const introduction = form.elements.introduction.value.trim(); - - if (!nickname || [...nickname].length > 40) { - showStatus('昵称须为 1 至 40 个字符'); - form.elements.nickName.focus(); - return; - } - if ([...introduction].length > 300) { - showStatus('简介不能超过 300 个字符'); - return; - } - if (nickname !== form.elements.nickName.defaultValue) data.nickName = nickname; - if (introduction !== form.elements.introduction.defaultValue.trim()) data.introduction = introduction; - if (!Object.keys(data).length) { - showStatus('没有需要保存的修改'); - return; - } - - save.disabled = true; - showStatus('保存中…'); - try { - const response = await fetch('/api/auth/profile', { - method: 'POST', credentials: 'same-origin', - headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), - }); - const result = await response.json(); - if (!response.ok || result.code !== 200) throw new Error(result.tip || '保存失败,请稍后重试'); - showStatus('保存成功', true); - window.setTimeout(() => window.location.reload(), 800); - } catch (error) { - showStatus(error.message || '保存失败,请稍后重试'); - save.disabled = false; - } - }); - - function setPasswordStatus(message, success = false) { - if (!passwordStatus) return; - passwordStatus.textContent = message || ''; - passwordStatus.classList.toggle('is-success', success); - } - - function openPasswordPop() { - if (!passwordPop || !passwordForm) return; - passwordForm.reset(); - setPasswordStatus(''); - previousOverflow = document.body.style.overflow; - document.body.style.overflow = 'hidden'; - passwordPop.hidden = false; - const first = passwordForm.elements.oldPassword; - if (first) window.setTimeout(() => first.focus(), 0); - } - - function closePasswordPop() { - if (!passwordPop || passwordPop.hidden) return; - passwordPop.hidden = true; - document.body.style.overflow = previousOverflow; - if (passwordForm) passwordForm.reset(); - setPasswordStatus(''); - if (passwordOpen) passwordOpen.focus(); - } - - if (passwordOpen) passwordOpen.addEventListener('click', openPasswordPop); - if (passwordPop) { - passwordPop.querySelectorAll('[data-setting-password-close]').forEach(button => { - button.addEventListener('click', closePasswordPop); - }); - } - - document.addEventListener('keydown', event => { - if (event.key === 'Escape' && passwordPop && !passwordPop.hidden) closePasswordPop(); - }); - - if (passwordForm) { - passwordForm.addEventListener('submit', async event => { - event.preventDefault(); - const oldPassword = passwordForm.elements.oldPassword.value; - const newPassword = passwordForm.elements.newPassword.value; - const confirmPassword = passwordForm.elements.confirmPassword.value; - - if (oldPassword.length < 4 || oldPassword.length > 16) { - setPasswordStatus('请输入 4–16 位当前密码'); - passwordForm.elements.oldPassword.focus(); - return; - } - if (newPassword.length < 4 || newPassword.length > 16) { - setPasswordStatus('请输入 4–16 位新密码'); - passwordForm.elements.newPassword.focus(); - return; - } - if (newPassword !== confirmPassword) { - setPasswordStatus('两次输入的新密码不一致'); - passwordForm.elements.confirmPassword.focus(); - return; - } - - passwordSubmit.disabled = true; - passwordSubmit.textContent = '修改中…'; - setPasswordStatus(''); - try { - const response = await fetch('/api/auth/profile', { - method: 'POST', - credentials: 'same-origin', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ oldPassword, newPassword }), - }); - const result = await response.json(); - if (!response.ok || result.code !== 200) throw new Error(result.tip || '密码修改失败,请稍后重试'); - setPasswordStatus('密码修改成功', true); - window.setTimeout(() => { - closePasswordPop(); - window.location.reload(); - }, 600); - } catch (error) { - setPasswordStatus(error.message || '密码修改失败,请稍后重试'); - passwordSubmit.disabled = false; - passwordSubmit.textContent = '确认修改'; - } - }); - } - -})(); diff --git a/app/static/js/tracker.js b/app/static/js/tracker.js deleted file mode 100644 index 0e965ec..0000000 --- a/app/static/js/tracker.js +++ /dev/null @@ -1,921 +0,0 @@ -/** - * Web SDK v1.2.0 接入层。 - * 页面/点击/导航沿用 jha-1043 的口径,短剧使用最新版 dramaEvent/dramaStatusChange。 - */ -(function (global) { - "use strict"; - - var state = { - initialized: false, - initializing: false, - queue: [], - page: { key: "internal_other", name: "站内其他页面" }, - referrer: { key: "direct", name: "直接访问" }, - userType: "normal", - pageStartedAt: Date.now(), - dramaViewSent: false, - adShown: {}, - }; - - function stringValue(value, fallback) { - var result = value == null ? "" : String(value).trim(); - return result || (fallback == null ? "" : String(fallback)); - } - - function integer(value, fallback) { - var result = parseInt(value, 10); - return isFinite(result) ? result : (fallback || 0); - } - - function clamp(value, min, max) { - var result = Number(value); - if (!isFinite(result)) result = min; - return Math.max(min, Math.min(max, result)); - } - - function sdkAvailable() { - return !!(global.WebSDK && global.WebSDK.track); - } - - function logError() {} - - function invoke(method, payload) { - if (!state.initialized || !sdkAvailable()) { - return new Promise(function (resolve) { - state.queue.push([method, payload, resolve]); - }); - } - try { - var fn = global.WebSDK.track[method]; - if (typeof fn !== "function") { - var missing = new Error("SDK method does not exist: " + method); - logError(missing); - return Promise.resolve({ sent: false, ok: false, reason: "unknown_method" }); - } - var result = fn(payload); - return Promise.resolve(result).then(function (response) { - if (response && response.ok === false) { - logError(method + " rejected: " + JSON.stringify(response)); - } - return response; - }).catch(function (error) { - logError(error); - return { sent: false, ok: false, reason: "exception" }; - }); - } catch (error) { - logError(error); - return Promise.resolve({ sent: false, ok: false, reason: "exception" }); - } - } - - function flushQueue() { - var pending = state.queue.splice(0, state.queue.length); - pending.forEach(function (entry) { - Promise.resolve(invoke(entry[0], entry[1])).then(entry[2]); - }); - } - - function currentChannel(value) { - var channel = stringValue(value); - if (!channel && global.ChannelAttribution && typeof global.ChannelAttribution.get === "function") { - channel = stringValue(global.ChannelAttribution.get()); - } - if (!channel) channel = stringValue(global.__CHANNEL_CODE__, "organic"); - var normalized = channel.toLowerCase(); - if (["self", "natural", "official", "channel", "channel://natural"].indexOf(normalized) >= 0) { - return "organic"; - } - if (!channel || channel.length > 50 || !/^[A-Za-z0-9_.-]+$/.test(channel) || - ["{}", "0", "-", "unknown"].indexOf(normalized) >= 0) { - return "organic"; - } - return channel; - } - - function normalizeUid(value) { - var uid = stringValue(value); - if (uid === "0" || uid.length < 4 || uid.length > 32 || !/^[A-Za-z0-9_]+$/.test(uid) || - ["u_1001", "stress_user", "u_lab"].indexOf(uid.toLowerCase()) >= 0) return ""; - return uid; - } - - function inferPage(path) { - path = stringValue(path, "/").replace(/\/$/, "") || "/"; - if (path === "/") return { key: "home", name: "首页" }; - var rules = [ - [/^\/series-play(?:\/|$)/, "series_play", "AI成人剧集播放页"], - [/^\/series(?:\/|$)/, "series", "AI成人剧集"], - [/^\/play(?:\/|$)/, "play", "短剧播放页"], - [/^\/detail(?:\/|$)/, "detail", "短剧详情"], - [/^\/list(?:\/|$)/, "list", "短剧列表"], - [/^\/rank(?:\/|$)/, "rank", "排行榜"], - [/^\/topic-detail(?:\/|$)/, "topic_detail", "专题详情"], - [/^\/topic(?:\/|$)/, "topic", "专题列表"], - [/^\/tags(?:\/|$)/, "tags", "标签详情"], - [/^\/search(?:-mobile)?(?:\/|$)/, "search", "搜索结果页"], - [/^\/history(?:\/|$)/, "history", "观看历史"], - [/^\/favorites(?:\/|$)/, "favorites", "我的收藏"], - [/^\/profile(?:\/|$)/, "profile", "个人中心"], - ]; - for (var i = 0; i < rules.length; i += 1) { - if (rules[i][0].test(path)) return { key: rules[i][1], name: rules[i][2] }; - } - return { key: "internal_other", name: "站内其他页面" }; - } - - function readReferrer() { - try { - var saved = global.sessionStorage.getItem("__tracker_last_page"); - if (saved) { - var parsed = JSON.parse(saved); - if (parsed && parsed.key) return parsed; - } - } catch (error) {} - try { - if (document.referrer) { - var url = new URL(document.referrer, global.location.href); - if (url.host === global.location.host) return inferPage(url.pathname); - return { key: "external", name: url.host || "外部站点" }; - } - } catch (error) {} - return { key: "direct", name: "直接访问" }; - } - - function rememberPage() { - try { global.sessionStorage.setItem("__tracker_last_page", JSON.stringify(state.page)); } catch (error) {} - } - - function init(options) { - if (state.initialized || state.initializing) return; - options = options || {}; - var inferred = inferPage(global.location && global.location.pathname); - state.page = { - key: stringValue(options.pageKey, inferred.key), - name: stringValue(options.pageName, inferred.name), - }; - state.userType = options.userType === "vip" ? "vip" : "normal"; - state.referrer = readReferrer(); - state.initializing = true; - - var attempts = 0; - var timer = global.setInterval(function () { - attempts += 1; - if (!sdkAvailable() && attempts < 100) return; - global.clearInterval(timer); - if (!sdkAvailable()) { - state.initializing = false; - return; - } - try { - var result = global.WebSDK.init({ - appId: stringValue(options.appId), - uid: normalizeUid(options.uid), - channel: currentChannel(options.channel), - encryptedConfig: stringValue(options.encryptedConfig), - appVersion: stringValue(options.appVersion, "1.0.0"), - disableAutoEvents: ["app_page_view", "page_click"], - }); - Promise.resolve(result).then(function () { - state.initialized = true; - state.initializing = false; - __commonFieldsReady = null; - loadCommonFields(); - flushQueue(); - onReady(); - }).catch(function (error) { - state.initializing = false; - logError(error); - }); - } catch (error) { - state.initializing = false; - logError(error); - } - }, 50); - } - - function whenDOMReady(callback) { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", callback, { once: true }); - } else { - callback(); - } - } - - function trackPageView(extra) { - extra = extra || {}; - invoke("appPageView", { - user_type: stringValue(extra.user_type, state.userType), - page_key: state.page.key, - page_name: state.page.name, - referrer_page_key: state.referrer.key, - referrer_page_name: state.referrer.name, - current_page_key: state.page.key, - current_page_name: state.page.name, - page_load_time: pageLoadDuration(), - recommend_trace_id: stringValue(extra.recommend_trace_id), - }); - rememberPage(); - } - - function pageLoadDuration() { - try { - if (global.performance && typeof global.performance.getEntriesByType === "function") { - var entries = global.performance.getEntriesByType("navigation"); - if (entries && entries[0]) { - var navigation = entries[0]; - var end = navigation.loadEventEnd || navigation.domContentLoadedEventEnd || global.performance.now(); - if (isFinite(end)) return Math.max(0, Math.round(end)); - } - } - if (global.performance && typeof global.performance.now === "function") { - return Math.max(0, Math.round(global.performance.now())); - } - } catch (error) {} - return Math.max(0, Date.now() - state.pageStartedAt); - } - - function trackPageClick(event) { - var doc = document.documentElement || {}; - var viewportWidth = global.innerWidth || doc.clientWidth || 0; - var viewportHeight = global.innerHeight || doc.clientHeight || 0; - var x = integer(event.clientX, 0); - var y = integer(event.clientY, 0); - invoke("pageClick", { - page_key: state.page.key, - page_name: state.page.name, - click_page_x: x, - click_page_y: y, - click_x_percent: viewportWidth ? Math.round(clamp(x / viewportWidth * 100, 0, 100)) : 0, - click_y_percent: viewportHeight ? Math.round(clamp(y / viewportHeight * 100, 0, 100)) : 0, - screen_width: integer(global.screen && global.screen.width, 0), - screen_height: integer(global.screen && global.screen.height, 0), - recommend_trace_id: "", - }); - } - - function trackNavigation(anchor) { - if (!anchor || (!anchor.hasAttribute("data-navigation-key") && !anchor.hasAttribute("data-nav-key")) || - !anchor.getAttribute("href")) return; - var href = anchor.getAttribute("href"); - if (!href || href.charAt(0) === "#" || /^javascript:/i.test(href)) return; - var label = stringValue(anchor.getAttribute("aria-label") || anchor.textContent).replace(/\s+/g, " "); - var navigationKey = stringValue( - anchor.getAttribute("data-navigation-key") || anchor.getAttribute("data-nav-key") - ); - if (!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(navigationKey)) { - navigationKey = ""; - try { - var url = new URL(href, global.location.href); - if (url.origin !== global.location.origin) { - navigationKey = "nav_external"; - } else { - var targetPage = inferPage(url.pathname); - if (targetPage.key !== "internal_other") { - navigationKey = "nav_" + targetPage.key; - } else { - var firstSegment = stringValue(url.pathname.split("/").filter(Boolean)[0]) - .toLowerCase() - .replace(/[^a-z0-9]+/g, "_") - .replace(/^_+|_+$/g, "") - .slice(0, 48); - navigationKey = firstSegment ? "nav_" + firstSegment : "nav_home"; - } - } - } catch (error) { - navigationKey = "nav_other"; - } - } - invoke("navigation", { - navigation_key: navigationKey.toLowerCase(), - navigation_name: stringValue( - anchor.getAttribute("data-navigation-name") || anchor.getAttribute("data-nav-name"), - label || navigationKey - ).slice(0, 255), - }); - } - - function adPayload(node) { - var id = stringValue(node && node.getAttribute("data-ad-id")); - if (!id) return null; - var adType = stringValue(node.getAttribute("data-ad-type"), "banner"); - var allSlots = document.querySelectorAll('[data-track-ad="external"][data-ad-id]'); - var slotPosition = Array.prototype.indexOf.call(allSlots, node) + 1; - if (slotPosition <= 0) slotPosition = 1; - var fallbackSlotKey = state.page.key + "_content_banner_" + slotPosition; - var fallbackSlotName = state.page.name + "内容横幅第" + slotPosition + "位"; - if (adType === "interstitial") { - fallbackSlotKey = state.page.key + "_popup_" + slotPosition; - fallbackSlotName = state.page.name + "弹窗广告位"; - } - return { - page_key: state.page.key, - page_name: state.page.name, - ad_slot_key: stringValue( - node.getAttribute("data-ad-slot-key"), - fallbackSlotKey - ), - ad_slot_name: stringValue( - node.getAttribute("data-ad-slot-name"), - fallbackSlotName - ), - ad_id: id, - creative_id: stringValue(node.getAttribute("data-creative-id")), - ad_type: adType, - }; - } - - function bindAds() { - var selector = '[data-track-ad="external"][data-ad-id]'; - var nodes = document.querySelectorAll(selector); - function show(node) { - trackExternalAd(node, "show"); - } - if (global.IntersectionObserver) { - var observer = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - if (!entry.isIntersecting) return; - show(entry.target); - observer.unobserve(entry.target); - }); - }, { threshold: 0.1 }); - function observe(node) { - if (!node || node.__trackerAdObserved) return; - node.__trackerAdObserved = true; - observer.observe(node); - } - Array.prototype.forEach.call(nodes, observe); - if (global.MutationObserver) { - new MutationObserver(function (records) { - records.forEach(function (record) { - Array.prototype.forEach.call(record.addedNodes || [], function (node) { - if (!node || node.nodeType !== 1) return; - if (node.matches && node.matches(selector)) observe(node); - if (node.querySelectorAll) { - Array.prototype.forEach.call(node.querySelectorAll(selector), observe); - } - }); - }); - }).observe(document.documentElement, { childList: true, subtree: true }); - } - } else { - Array.prototype.forEach.call(nodes, show); - } - } - - function trackExternalAd(node, eventType) { - if (!node || (eventType !== "show" && eventType !== "click")) return; - var payload = adPayload(node); - if (!payload) return; - var key = payload.ad_slot_key + "|" + payload.ad_id; - if (!state.adShown[key]) { - state.adShown[key] = true; - invoke("adImpression", payload); - } - if (eventType === "click" && node.getAttribute("data-ad-clickable") === "1") { - return invoke("adClick", payload); - } - } - - function bindExternalAdEvents() { - document.addEventListener("external-ad:track", function (event) { - var detail = event && event.detail ? event.detail : {}; - trackExternalAd(detail.element, stringValue(detail.eventType)); - }); - } - - function trackAdvertising(node, eventType) { - if (!node || ["show", "click", "close"].indexOf(eventType) < 0) return; - invoke("advertising", { - event_type: eventType, - advertising_key: stringValue(node.getAttribute("data-advertising-key"), "dialog_ad"), - advertising_name: stringValue(node.getAttribute("data-advertising-name"), "弹窗广告"), - advertising_id: stringValue(node.getAttribute("data-advertising-id") || node.getAttribute("data-dialog-ad-id")), - }); - } - - function bindAdvertisingEvents() { - document.addEventListener("dialog-ad:track", function (event) { - var detail = event && event.detail ? event.detail : {}; - var node = detail.element; - if (!node || !node.getAttribute) return; - trackAdvertising(node, stringValue(detail.eventType)); - }); - } - - function dramaRoot() { - return document.querySelector("[data-track-drama]"); - } - - function dramaContext(root) { - root = root || dramaRoot(); - if (!root) return null; - var data = root.dataset || {}; - var dramaID = stringValue(data.dramaId); - var videoID = stringValue(data.videoId); - if (!dramaID || !videoID) return null; - return { - media_id: stringValue(data.mediaId), - drama_id: dramaID, - drama_title: stringValue(data.dramaTitle, "未命名短剧"), - episode: Math.max(1, integer(data.episode, 1)), - total_episode: Math.max(1, integer(data.totalEpisode, 1)), - video_id: videoID, - drama_type_id: stringValue(data.dramaTypeId, "default"), - drama_type_name: stringValue(data.dramaTypeName, "默认分类"), - drama_content_type: "short_drama", - recommend_trace_id: stringValue(data.recommendTraceId), - drama_tag_key: stringValue(data.dramaTagKey, "default"), - drama_tag_name: stringValue(data.dramaTagName, "默认标签"), - drama_duration: Math.max(0, integer(data.dramaDuration, 0)), - }; - } - - var dramaBehaviorNames = { - ep_view: "展示", - ep_play: "播放", - ep_pause: "暂停", - ep_forward: "快进", - ep_rewind: "快退", - ep_complete: "播放完成", - ep_share: "分享", - }; - - function videoPlaybackState(video) { - if (!video) return null; - if (!video.__trackerPlaybackState) { - video.__trackerPlaybackState = { - startedAt: 0, - elapsedMs: 0, - seekFrom: null, - lastPosition: Number(video.currentTime) || 0, - }; - } - return video.__trackerPlaybackState; - } - - function beginPlayback(video) { - var playback = videoPlaybackState(video); - if (playback && !playback.startedAt) playback.startedAt = Date.now(); - } - - function settlePlayback(video) { - var playback = videoPlaybackState(video); - if (!playback || !playback.startedAt) return; - playback.elapsedMs += Math.max(0, Date.now() - playback.startedAt); - playback.startedAt = 0; - } - - function playbackSeconds(video, keepDuration) { - var playback = videoPlaybackState(video); - if (!playback) return 0; - var now = Date.now(); - var activeMs = playback.startedAt ? Math.max(0, now - playback.startedAt) : 0; - var seconds = Math.max(0, Math.round((playback.elapsedMs + activeMs) / 1000)); - if (!keepDuration) { - playback.elapsedMs = 0; - if (playback.startedAt) playback.startedAt = now; - } - return seconds; - } - - function videoMetrics(video, context, keepDuration) { - var duration = video && isFinite(video.duration) ? Math.round(video.duration) : context.drama_duration; - var current = video && isFinite(video.currentTime) ? Math.round(video.currentTime) : 0; - return { - duration: Math.max(0, duration), - current: Math.max(0, current), - played: playbackSeconds(video, keepDuration), - progress: duration > 0 ? Math.round(clamp(current / duration * 100, 0, 100)) : 0, - speed: Math.max(1, Math.round(((video && video.playbackRate) || 1) * 100)), - }; - } - - function trackDramaEvent(behaviorKey, video, options) { - var context = dramaContext(); - if (!context) return; - options = options || {}; - var metrics = videoMetrics(video, context, options.keepPlaybackDuration === true); - context.drama_duration = metrics.duration; - context.play_duration = metrics.played; - context.play_progress = options.forceProgress != null - ? Math.round(clamp(options.forceProgress, 0, 100)) - : behaviorKey === "ep_complete" ? 100 : metrics.progress; - context.play_speed = metrics.speed; - context.play_behavior_key = behaviorKey; - context.play_behavior_name = dramaBehaviorNames[behaviorKey] || behaviorKey; - return invoke("dramaEvent", context); - } - - function trackDramaStatusChange(targetEpisode, targetVideoID, behaviorOverride) { - var context = dramaContext(); - if (!context) return; - var nextEpisode = Math.max(1, integer(targetEpisode, context.episode)); - var videoID = stringValue(targetVideoID, context.video_id); - var behavior = stringValue(behaviorOverride); - var names = { - ep_auto_play: "自动播放", - ep_previous: "上一集", - ep_next: "下一集", - ep_switch: "切换剧集", - ep_unlock: "解锁", - }; - if (!behavior) { - if (nextEpisode === context.episode && videoID === context.video_id) return; - behavior = "ep_switch"; - if (nextEpisode === context.episode + 1) behavior = "ep_next"; - else if (nextEpisode === context.episode - 1) behavior = "ep_previous"; - } - if (!names[behavior]) return; - return invoke("dramaStatusChange", { - media_id: context.media_id, - drama_id: context.drama_id, - drama_title: context.drama_title, - episode: nextEpisode, - video_id: videoID, - pre_episode: context.episode, - drama_behavior_key: behavior, - drama_behavior_name: names[behavior], - }); - } - - function trackDramaUnlock() { - var context = dramaContext(); - if (!context) return Promise.resolve({ sent: false, ok: false, reason: "no_drama_context" }); - return trackDramaStatusChange(context.episode, context.video_id, "ep_unlock"); - } - - function isMainDramaVideo(root, video) { - var mode = video && video.__trackerDramaMode; - if (!mode && root) { - mode = root.getAttribute("data-play-mode") === "preview" ? "preview" : "main"; - } - return !!( - root && - video && - video.tagName === "VIDEO" && - root.contains(video) && - mode !== "preview" - ); - } - - function rememberDramaVideoMode(root, video) { - if (!root || !video || video.tagName !== "VIDEO" || !root.contains(video)) return; - video.__trackerDramaMode = root.getAttribute("data-play-mode") === "preview" ? "preview" : "main"; - } - - function prepareDramaModeChange(nextMode) { - var root = dramaRoot(); - var video = root && root.querySelector("video"); - if (!root || !video) return; - var previousMode = video.__trackerDramaMode || - (root.getAttribute("data-play-mode") === "preview" ? "preview" : "main"); - nextMode = nextMode === "preview" ? "preview" : "main"; - if (previousMode === nextMode) return; - - if (previousMode === "main") { - var playback = video.__trackerPlaybackState; - var wasPlaying = !!(playback && (playback.startedAt || playback.elapsedMs > 0)); - settlePlayback(video); - if (wasPlaying) trackDramaEvent("ep_pause", video); - } - video.__trackerPlaybackState = null; - video.__trackerDramaMode = nextMode; - } - - function bindDramaEvents() { - var root = dramaRoot(); - if (!root) return; - if (!state.dramaViewSent) { - state.dramaViewSent = true; - trackDramaEvent("ep_view", null); - } - document.addEventListener("play", function (event) { - rememberDramaVideoMode(root, event.target); - if (isMainDramaVideo(root, event.target)) { - trackDramaEvent("ep_play", event.target); - } - }, true); - document.addEventListener("playing", function (event) { - rememberDramaVideoMode(root, event.target); - if (isMainDramaVideo(root, event.target)) beginPlayback(event.target); - }, true); - document.addEventListener("loadedmetadata", function (event) { - rememberDramaVideoMode(root, event.target); - }, true); - document.addEventListener("waiting", function (event) { - if (isMainDramaVideo(root, event.target)) settlePlayback(event.target); - }, true); - document.addEventListener("pause", function (event) { - if (isMainDramaVideo(root, event.target) && !event.target.ended) { - var playback = event.target.__trackerPlaybackState; - var wasPlaying = !!(playback && (playback.startedAt || playback.elapsedMs > 0)); - settlePlayback(event.target); - if (event.target.seeking || !wasPlaying) return; - trackDramaEvent("ep_pause", event.target); - } - }, true); - document.addEventListener("timeupdate", function (event) { - if (!isMainDramaVideo(root, event.target) || event.target.seeking) return; - videoPlaybackState(event.target).lastPosition = Number(event.target.currentTime) || 0; - }, true); - document.addEventListener("seeking", function (event) { - if (!isMainDramaVideo(root, event.target)) return; - var playback = videoPlaybackState(event.target); - if (playback.seekFrom != null) return; - settlePlayback(event.target); - playback.seekFrom = playback.lastPosition; - }, true); - document.addEventListener("seeked", function (event) { - if (!isMainDramaVideo(root, event.target)) return; - var playback = videoPlaybackState(event.target); - if (playback.seekFrom == null) return; - var from = playback.seekFrom; - var target = Number(event.target.currentTime) || 0; - playback.seekFrom = null; - playback.lastPosition = target; - if (Math.abs(target - from) >= 0.5) { - trackDramaEvent(target > from ? "ep_forward" : "ep_rewind", event.target); - } - if (!event.target.paused && !event.target.ended) beginPlayback(event.target); - }, true); - document.addEventListener("ended", function (event) { - if (isMainDramaVideo(root, event.target)) { - settlePlayback(event.target); - trackDramaEvent("ep_complete", event.target, { keepPlaybackDuration: true }); - trackDramaEvent("ep_play", event.target, { forceProgress: 100 }); - } - }, true); - - function reportPendingDramaProgress() { - var video = root.querySelector("video"); - if (!isMainDramaVideo(root, video)) return false; - var playback = video.__trackerPlaybackState; - if (!playback || (!playback.startedAt && playback.elapsedMs <= 0)) return false; - var pendingMs = playback.elapsedMs + - (playback.startedAt ? Math.max(0, Date.now() - playback.startedAt) : 0); - if (pendingMs < 500) return false; - settlePlayback(video); - trackDramaEvent("ep_play", video); - return true; - } - - function flushSDKAfterEnqueue() { - if (!global.WebSDK || typeof global.WebSDK.flush !== "function") return; - function flush() { - Promise.resolve(global.WebSDK.flush()).catch(logError); - } - Promise.resolve().then(function () { - return Promise.resolve(); - }).then(flush); - global.setTimeout(flush, 50); - global.setTimeout(flush, 200); - } - - document.addEventListener("visibilitychange", function () { - var video = root.querySelector("video"); - if (document.visibilityState === "hidden") { - if (reportPendingDramaProgress()) flushSDKAfterEnqueue(); - if (isMainDramaVideo(root, video) && !video.paused && !video.ended) beginPlayback(video); - return; - } - if (document.visibilityState === "visible" && isMainDramaVideo(root, video) && - !video.paused && !video.ended) { - beginPlayback(video); - } - }); - global.addEventListener("beforeunload", function () { - if (reportPendingDramaProgress()) flushSDKAfterEnqueue(); - }); - global.addEventListener("pagehide", function () { - if (reportPendingDramaProgress()) flushSDKAfterEnqueue(); - }); - } - - function bindSearchEvents() { - var container = document.querySelector("[data-search-results]"); - if (!container) return; - var keyword = stringValue(container.getAttribute("data-search-keyword")); - if (!keyword) return; - var grid = container.querySelector("[data-search-grid]"); - var cards = grid ? Array.prototype.slice.call(grid.querySelectorAll("[data-search-item-id]")) : []; - var resultCount = Math.max(0, integer(container.getAttribute("data-search-result-count"), cards.length)); - var pageNum = Math.max(1, integer(container.getAttribute("data-search-page"), 1)); - var pageSize = Math.max(1, integer(container.getAttribute("data-search-page-size"), cards.length || 1)); - if (pageNum === 1) { - invoke("keywordSearch", { - keyword: keyword, - search_result_count: resultCount, - search_content_type: "", - search_trace_id: "", - search_id: "", - }); - } - if (!grid) return; - grid.addEventListener("click", function (event) { - var target = event.target && event.target.closest ? event.target : null; - var resultLink = target && target.closest("[data-search-result-click]"); - var card = resultLink && resultLink.closest("[data-search-item-id]"); - if (!card || !grid.contains(card)) return; - var position = cards.indexOf(card); - if (position < 0) return; - invoke("keywordClick", { - keyword: keyword, - click_item_id: stringValue(card.getAttribute("data-search-item-id")), - click_item_type_key: "short_drama", - click_item_type_name: "短剧", - click_position: (pageNum - 1) * pageSize + position + 1, - search_trace_id: "", - }); - }, true); - } - - function bindClicks() { - document.addEventListener("click", function (event) { - var target = event.target && event.target.closest ? event.target : null; - if (!target) return; - var popupClose = target.closest( - "[data-dialog-ad-close],[data-ad-close],[data-register-close]," + - "[data-download-close],[data-login-close],[data-recharge-close]," + - "[data-play-coin-close],[data-setting-password-close],[data-drawer-close]" - ); - if (!popupClose) trackPageClick(event); - var switcher = target.closest("[data-drama-switch]"); - if (switcher) { - trackDramaStatusChange(switcher.getAttribute("data-target-episode"), switcher.getAttribute("data-target-video-id")); - } - if (target.closest("[data-share-page]") && dramaRoot()) { - var root = dramaRoot(); - var video = root.querySelector("video"); - trackDramaEvent("ep_share", isMainDramaVideo(root, video) ? video : null); - } - var ad = target.closest('[data-track-ad="external"][data-ad-clickable="1"][data-ad-id]'); - if (ad) { - trackExternalAd(ad, "click"); - } - trackNavigation(target.closest("a[data-navigation-key],a[data-nav-key]")); - }, true); - } - - function onReady() { - whenDOMReady(function () { - Promise.resolve(loadCommonFields()).then(function () { - trackPageView(); - }); - bindClicks(); - bindAds(); - bindExternalAdEvents(); - bindAdvertisingEvents(); - bindDramaEvents(); - bindSearchEvents(); - try { - global.dispatchEvent(new CustomEvent("tracker:ready", { detail: state.page })); - } catch (error) {} - }); - } - - - var COMMON_FIELD_KEYS = [ - "sid", - "device_id", - "device", - "device_fingerprint", - "fp_version", - ]; - var __commonFields = null; - var __commonFieldsReady = null; - - function emptyCommonFields() { - var out = {}; - for (var i = 0; i < COMMON_FIELD_KEYS.length; i++) { - out[COMMON_FIELD_KEYS[i]] = ""; - } - return out; - } - - function normalizeCommonFields(fields) { - var out = emptyCommonFields(); - fields = fields || {}; - for (var i = 0; i < COMMON_FIELD_KEYS.length; i++) { - var k = COMMON_FIELD_KEYS[i]; - if (fields[k] != null) out[k] = String(fields[k]); - } - return out; - } - - function publishCommonFields(fields) { - __commonFields = normalizeCommonFields(fields); - try { - global.__trackerCommonFields = __commonFields; - } catch (e) {} - try { - if (typeof CustomEvent === "function") { - global.dispatchEvent( - new CustomEvent("tracker:common-fields", { detail: __commonFields }) - ); - } - } catch (e) {} - return __commonFields; - } - - function loadCommonFields() { - if (__commonFieldsReady) return __commonFieldsReady; - __commonFieldsReady = new Promise(function (resolve) { - function finish(fields) { - resolve(publishCommonFields(fields)); - } - if (!global.WebSDK || typeof global.WebSDK.getCommonFields !== "function") { - finish(emptyCommonFields()); - return; - } - try { - Promise.resolve(global.WebSDK.getCommonFields(COMMON_FIELD_KEYS.slice())) - .then(function (fields) { - finish(fields || emptyCommonFields()); - }) - .catch(function (err) { - logError(err); - finish(emptyCommonFields()); - }); - } catch (err) { - logError(err); - finish(emptyCommonFields()); - } - }); - return __commonFieldsReady; - } - - function getCommonFieldsCached(keys) { - return loadCommonFields().then(function (all) { - if (!Array.isArray(keys) || !keys.length) return all; - var out = {}; - for (var i = 0; i < keys.length; i++) { - var k = String(keys[i] || "").trim(); - if (k) out[k] = Object.prototype.hasOwnProperty.call(all, k) ? all[k] : ""; - } - return out; - }); - } - - function getCommonFieldCached(key) { - var k = String(key || "").trim(); - if (!k) return Promise.resolve(""); - return getCommonFieldsCached([k]).then(function (m) { - return m[k] || ""; - }); - } - - function encodeSDKCommonHeader(fields) { - var payload = normalizeCommonFields(fields); - try { - return btoa(unescape(encodeURIComponent(JSON.stringify(payload)))); - } catch (e) { - logError(e); - return ""; - } - } - - function getSDKCommonHeader() { - return loadCommonFields().then(function (fields) { - return encodeSDKCommonHeader(fields); - }); - } - - - global.trackerInit = init; - global.tracker = { - init: init, - pageView: trackPageView, - dramaEvent: trackDramaEvent, - dramaStatusChange: trackDramaStatusChange, - dramaUnlock: trackDramaUnlock, - prepareDramaModeChange: prepareDramaModeChange, - advertising: function (payload) { return invoke("advertising", payload || {}); }, - externalAd: trackExternalAd, - getDramaContext: function () { - var context = dramaContext(); - return context ? Object.assign({}, context) : {}; - }, - keywordSearch: function (payload) { return invoke("keywordSearch", payload || {}); }, - keywordClick: function (payload) { return invoke("keywordClick", payload || {}); }, - getCommonFields: getCommonFieldsCached, - getCommonField: getCommonFieldCached, - getCommonFieldsSync: function () { return __commonFields; }, - encodeSDKCommonHeader: encodeSDKCommonHeader, - getSDKCommonHeader: getSDKCommonHeader, - flush: function () { - if (!global.WebSDK || typeof global.WebSDK.flush !== "function") return Promise.resolve(); - return Promise.resolve(global.WebSDK.flush()).catch(logError); - }, - setUid: function (uid) { - if (global.WebSDK && global.WebSDK.setUid) global.WebSDK.setUid(normalizeUid(uid)); - }, - setChannel: function (channel) { - if (global.WebSDK && global.WebSDK.setChannel) global.WebSDK.setChannel(currentChannel(channel)); - }, - setContext: function (context) { - if (global.WebSDK && global.WebSDK.setContext) global.WebSDK.setContext(context || {}); - }, - }; - - init(global.__TRACKER_BOOT__ || {}); -})(window); diff --git a/app/template/base/accountPagination.html b/app/template/base/accountPagination.html deleted file mode 100644 index 59ac9fe..0000000 --- a/app/template/base/accountPagination.html +++ /dev/null @@ -1,9 +0,0 @@ -{{define "accountPagination"}} -{{with .paging}}{{if gt .Total 1}} - -{{end}}{{end}} -{{end}} diff --git a/app/template/base/ads.html b/app/template/base/ads.html index 440f71f..77cddf3 100644 --- a/app/template/base/ads.html +++ b/app/template/base/ads.html @@ -5,7 +5,7 @@
    {{range .}} - + {{if .Title}}{{.Title}}{{else}}推荐{{end}} 广告 diff --git a/app/template/base/trackerInit.html b/app/template/base/analytics.html similarity index 72% rename from app/template/base/trackerInit.html rename to app/template/base/analytics.html index c3e56a0..9d87bf3 100644 --- a/app/template/base/trackerInit.html +++ b/app/template/base/analytics.html @@ -1,4 +1,4 @@ -{{define "trackerInit"}} +{{define "analytics"}} - - - - {{end}} diff --git a/app/template/base/cardMeta.html b/app/template/base/cardMeta.html new file mode 100644 index 0000000..b2276ab --- /dev/null +++ b/app/template/base/cardMeta.html @@ -0,0 +1 @@ +{{define "cardEpChip"}}{{if eq .UpdateStatus 1}}{{if .TotalEpisode}}更新至 {{.TotalEpisode}} 集{{else}}连载中{{end}}{{else if .TotalEpisode}}全 {{.TotalEpisode}} 集{{else}}{{updateStatusText .UpdateStatus}}{{end}}{{end}} diff --git a/app/template/base/dialogAd.html b/app/template/base/dialogAd.html index a5f40e4..f40a081 100644 --- a/app/template/base/dialogAd.html +++ b/app/template/base/dialogAd.html @@ -13,14 +13,6 @@ data-dialog-ad-item data-dialog-ad-index="{{$index}}" data-dialog-ad-id="{{if $dialog.AdvertiseCode}}{{$dialog.AdvertiseCode}}{{else}}{{$dialog.ID}}{{end}}" - data-track-ad="external-dialog" - data-ad-id="{{if $dialog.AdvertiseCode}}{{$dialog.AdvertiseCode}}{{else}}{{$dialog.ID}}{{end}}" - data-creative-id="" - data-ad-location="{{$dialog.Location}}" - data-ad-slot-key="{{if $dialog.AdvertiseLocationCode}}{{$dialog.AdvertiseLocationCode}}{{else if $dialog.Location}}{{$dialog.Location}}{{end}}" - data-ad-slot-name="{{if $dialog.AdvertiseLocationName}}{{$dialog.AdvertiseLocationName}}{{else if $dialog.Location}}弹窗广告位{{$dialog.Location}}{{else}}弹窗广告位{{end}}" - data-ad-type="interstitial" - {{if $dialog.Link}}data-ad-clickable="1"{{end}} role="dialog" aria-modal="true" aria-label="{{if $dialog.Title}}{{$dialog.Title}}{{else}}广告{{end}}" diff --git a/app/template/base/footer.html b/app/template/base/footer.html index 26cbc83..9baf846 100644 --- a/app/template/base/footer.html +++ b/app/template/base/footer.html @@ -1,5 +1,5 @@ {{define "footer"}} - +
    {{end}}
    - - + + 点赞数 + {{.CountLike}} +
    @@ -111,5 +65,5 @@ 前往下一项 - + {{end}} diff --git a/app/template/base/rechargeModal.html b/app/template/base/rechargeModal.html deleted file mode 100644 index 248f65c..0000000 --- a/app/template/base/rechargeModal.html +++ /dev/null @@ -1,87 +0,0 @@ -{{define "rechargeModal"}} -{{/* 金币充值弹窗:GET /api/app/recharge/package/list → coinList */}} - -{{if .rechargePackageResponse}} -{{end}} -{{end}} diff --git a/app/template/base/registerPop.html b/app/template/base/registerPop.html deleted file mode 100644 index 787d90b..0000000 --- a/app/template/base/registerPop.html +++ /dev/null @@ -1,124 +0,0 @@ -{{define "registerPop"}} - - -{{end}} diff --git a/app/template/base/seoCommon.html b/app/template/base/seoCommon.html index b514c87..9275951 100644 --- a/app/template/base/seoCommon.html +++ b/app/template/base/seoCommon.html @@ -25,6 +25,5 @@ {{if .jsonLD}}{{end}} - -{{template "trackerInit" .}} +{{template "analytics" .}} {{end}} diff --git a/app/template/base/subpageHeader.html b/app/template/base/subpageHeader.html index b6466bf..b10c5ba 100644 --- a/app/template/base/subpageHeader.html +++ b/app/template/base/subpageHeader.html @@ -2,6 +2,6 @@
    返回 {{.pageHeading}} - 充值帮助 + 帮助
    {{end}} diff --git a/app/template/base/userCenterShell.html b/app/template/base/userCenterShell.html deleted file mode 100644 index 07e4ebe..0000000 --- a/app/template/base/userCenterShell.html +++ /dev/null @@ -1,46 +0,0 @@ -{{define "userCenterHero"}} -
    -
    -
    - 个人头像 -
    -
    {{if .userName}}{{.userName}}{{else}}未登录{{end}}
    -

    {{if .userID}}ID {{.userID}}{{else}}登录后查看账户资料{{end}}

    - - - 编辑资料 - -
    -
    -
    -
    -

    金币金币余额

    - {{if .balance}}{{.balance}}{{else}}—{{end}} -

    按集解锁付费剧集  永久观看

    -
    - -
    -
    -
    -
    {{if .historyCount}}{{.historyCount}}{{else}}—{{end}}观看
    -
    {{if .favoriteCount}}{{.favoriteCount}}{{else}}—{{end}}收藏
    -
    {{if .chasingCount}}{{.chasingCount}}{{else}}—{{end}}追剧中
    -
    {{if .boughtChapterCount}}{{.boughtChapterCount}}{{else}}—{{end}}已购剧集
    -
    -
    -{{end}} - -{{define "userCenterTabs"}} - -{{end}} diff --git a/app/template/pages/2257.html b/app/template/pages/2257.html index 429d79c..f797c6e 100644 --- a/app/template/pages/2257.html +++ b/app/template/pages/2257.html @@ -5,7 +5,7 @@ {{.metaTitle}} {{template "seoCommon" .}} - + diff --git a/app/template/pages/404.html b/app/template/pages/404.html index 086ed4e..f258d54 100644 --- a/app/template/pages/404.html +++ b/app/template/pages/404.html @@ -5,7 +5,7 @@ {{.metaTitle}} {{template "seoCommon" .}} - + diff --git a/app/template/pages/about.html b/app/template/pages/about.html index f71fb0b..8146fad 100644 --- a/app/template/pages/about.html +++ b/app/template/pages/about.html @@ -5,7 +5,7 @@ {{.metaTitle}} {{template "seoCommon" .}} - + diff --git a/app/template/pages/dmca.html b/app/template/pages/dmca.html index 8078091..eebcff6 100644 --- a/app/template/pages/dmca.html +++ b/app/template/pages/dmca.html @@ -5,7 +5,7 @@ {{.metaTitle}} {{template "seoCommon" .}} - + diff --git a/app/template/pages/favorites.html b/app/template/pages/favorites.html deleted file mode 100644 index 057c3ec..0000000 --- a/app/template/pages/favorites.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - {{.metaTitle}} - {{template "seoCommon" .}} - - - - - - - - - {{template "header" .}} - {{template "userCenterHero" .}} - {{template "userCenterTabs" .}} -
    -

    我的收藏

    -
    - - {{if .favorites}}{{end}} -
    -
    - {{range .favorites}} - - {{end}} -
    -
    - -

    {{if .favoritesUnavailable}}收藏暂时无法加载{{else if ne .filter "all"}}暂无相关收藏{{else}}还没有收藏的作品{{end}}

    -

    在播放页点击「加入收藏」,更新时第一时间提醒你在播放页点击「收藏」,更新时第一时间提醒你

    - 前往首页去首页逛逛 -
    - {{if and (not .favorites) .recommended}} -
    -
    -

    大家都在看

    - 查看全部全部前往下一项 -
    -
    - {{range .recommended}}{{template "searchCard" .}}{{end}} -
    -
    - {{end}} - {{template "accountPagination" .}} -
    - {{template "footer" .}} - - - diff --git a/app/template/pages/feedback.html b/app/template/pages/feedback.html index 36ad8db..5da296b 100644 --- a/app/template/pages/feedback.html +++ b/app/template/pages/feedback.html @@ -5,7 +5,7 @@ {{.metaTitle}} {{template "seoCommon" .}} - + @@ -117,15 +117,15 @@ diff --git a/app/template/pages/help.html b/app/template/pages/help.html index b76f334..694b129 100644 --- a/app/template/pages/help.html +++ b/app/template/pages/help.html @@ -5,7 +5,7 @@ {{.metaTitle}} {{template "seoCommon" .}} - + @@ -27,23 +27,19 @@
    黄果短剧网页版可以看全集吗? -

    可以。免费集直接观看,付费集可用金币按集解锁,解锁后永久有效。

    +

    免费集可直接在网页观看;标记为 App 专享的剧集,下载黄果短剧 App 即可免费观看完整内容。

    - 解锁过的剧集还需要再付费吗? -

    不需要。已解锁的剧集可在登录后继续观看,无需重复付费。

    + 网页版需要注册账号吗? +

    不需要。网页版无需注册或登录,打开即可浏览和观看。

    - 如何充值金币? -

    登录后进入个人中心,选择金币充值,即可查看可用的充值方式。

    + 如何下载黄果短剧 App? +

    点击页面顶部或播放页的「下载 App」按钮,按提示完成安装即可。

    - 支持哪些支付方式? -

    可用的支付方式会在充值页面列出,请以页面显示为准。

    -
    -
    - 忘记密码怎么办? -

    请通过意见反馈联系我们,提交账号信息后由客服协助处理。

    + 网站打不开怎么办? +

    请收藏页脚的最新地址、备用地址与海外永久地址,任一地址均可访问。

    内容多久更新一次? diff --git a/app/template/pages/history.html b/app/template/pages/history.html deleted file mode 100644 index 1b50693..0000000 --- a/app/template/pages/history.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - {{.metaTitle}} - {{template "seoCommon" .}} - - - - - - - - - {{template "header" .}} - {{template "userCenterHero" .}} - {{template "userCenterTabs" .}} -
    -

    观看历史

    -
    -
    - - - -
    - {{if .groups}} - - {{end}} -
    - {{range .groups}} -
    -

    {{.Title}}

    -
    - {{range .Entries}} -
    - {{if .URL}}{{else}}{{end}} -
    -

    {{if .URL}}{{.Title}}{{else}}{{.Title}}{{end}}

    -
    - {{if .EpisodeText}}{{.EpisodeText}}{{else if .Current}}看至第 {{.Current}} 集{{else}}观看记录{{end}}{{if and (not .EpisodeText) .Total}} / 共 {{.Total}} 集{{end}} - {{if .Genre}}{{.Genre}}{{end}} - {{.Time}} -
    - {{if .Progress}}{{end}} -
    - {{.Time}} - {{if .URL}} - 播放 - 继续观看继续 - {{end}} - -
    - {{end}} -
    -
    - {{end}} - - {{template "accountPagination" .}} -
    - {{template "footer" .}} - - - diff --git a/app/template/pages/index.html b/app/template/pages/index.html index 8685cf0..1b40ce7 100644 --- a/app/template/pages/index.html +++ b/app/template/pages/index.html @@ -6,7 +6,7 @@ {{.metaTitle}} {{template "seoCommon" .}} - + {{template "header" .}} @@ -35,9 +35,9 @@

    {{if $item.PlayName}}{{$item.PlayName}}{{else}}{{$item.Title}}{{end}}

    - {{range $ti, $t := $item.ThemeNames}}{{if $ti}} {{end}}{{$t}}{{end}} - {{if $item.TotalEpisode}} 全 {{$item.TotalEpisode}} 集{{end}} - {{if $item.Anchor}} 主演 {{$item.Anchor}}{{end}} + {{template "cardEpChip" $item}} + {{with $item.ThemeNames}}{{range $ti, $t := .}}{{if $ti}} · {{end}}{{$t}}{{end}}{{end}} + {{with usableAnchor $item.Anchor}}{{if and (ne . "原创") (ne . "已注销")}}主演 {{.}}{{end}}{{end}}

    {{$item.Desc}}

    @@ -87,8 +87,9 @@
    - - {{if .announcements}} + + {{$cfg := .appConfig}} + {{if or .announcements $cfg.ForeverURLAddr $cfg.NewURLAddr $cfg.NewURLAddr1 $cfg.NewURLAddr2}}
    更新提醒 @@ -96,20 +97,8 @@
    -
    - {{range $i, $a := .announcements}} - {{if $i}}{{end}} -

    {{if $a.Title}}{{$a.Title}}:{{end}}{{$a.Content}}

    - {{end}} - -
    - +
    {{template "announceItems" .}}
    +
    @@ -123,33 +112,6 @@ {{end}} - - {{if .continueWatch}} -
    -
    -
    -

    继续观看

    -

    上次看到

    -
    - - 观看历史 - 前往下一项 - -
    -
    - {{range $item := .continueWatch}} - - {{end}} -
    -
    - {{end}} -

    dX| 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 - - - - diff --git a/app/static/images/recharge/lock.svg b/app/static/images/recharge/lock.svg deleted file mode 100644 index edf11ea..0000000 --- a/app/static/images/recharge/lock.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/recharge/zap.svg b/app/static/images/recharge/zap.svg deleted file mode 100644 index 99e9793..0000000 --- a/app/static/images/recharge/zap.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/user.svg b/app/static/images/user.svg deleted file mode 100644 index 3b5b54d..0000000 --- a/app/static/images/user.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/js/common/auth.js b/app/static/js/common/auth.js deleted file mode 100644 index 77b4b08..0000000 --- a/app/static/js/common/auth.js +++ /dev/null @@ -1,515 +0,0 @@ -/** - * 认证相关前端封装:注册 / 登录 / 游客登录走本站 BFF,token 等写入 cookie。 - * 未注册用户自动游客登录(POST /api/auth/guest → 上游 /api/app/auth/guest)。 - */ -(function (global) { - var UUID_KEY = "uuid"; - var DEV_ID_KEY = "guest_dev_id"; - var CHANNEL_KEY = "channel"; - var GUEST_LOGOUT_FLAG_KEY = "guest_logout_manual"; - var guestLoginPromise = null; - var volatileDevID = ""; - /** 上游约定:token 失效需重新登录 */ - var TOKEN_EXPIRED_CODE = 1000; - var tokenRecoveryPromise = null; - - function toast(message, type) { - if (typeof global.showToast === "function") { - global.showToast(message, type || "info"); - return; - } - // 调试兜底:无 toast 组件时用简易提示 - try { - var tip = String(message || "").trim(); - if (!tip) return; - var el = document.getElementById("auth-debug-toast"); - if (!el) { - el = document.createElement("div"); - el.id = "auth-debug-toast"; - el.setAttribute("role", "status"); - el.style.cssText = - "position:fixed;left:50%;bottom:48px;transform:translateX(-50%);z-index:99999;" + - "max-width:86vw;padding:10px 16px;border-radius:8px;font-size:14px;line-height:1.4;" + - "color:#fff;background:rgba(20,20,20,.88);box-shadow:0 8px 24px rgba(0,0,0,.25);"; - document.body.appendChild(el); - } - el.textContent = tip; - el.style.background = - type === "error" - ? "rgba(180,40,40,.92)" - : type === "success" - ? "rgba(30,120,70,.92)" - : "rgba(20,20,20,.88)"; - el.style.display = "block"; - clearTimeout(el._hideTimer); - el._hideTimer = setTimeout(function () { - el.style.display = "none"; - }, 2600); - } catch (e) {} - } - - function getInviteCode() { - try { - var params = new URLSearchParams(global.location.search || ""); - return ( - params.get("inviteCode") || - params.get("invite") || - params.get("code") || - "" - ).trim(); - } catch (e) { - return ""; - } - } - - function hasSessionToken() { - return !!(global.CookieUtil && global.CookieUtil.getToken && global.CookieUtil.getToken()); - } - - // 与 91porn-h5-client generateUid 保持一致:16 位十六进制随机串 + 毫秒时间戳。 - function generateUUID() { - var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - var uuid = []; - for (var i = 0; i < 16; i++) { - uuid[i] = chars.charAt(Math.floor(Math.random() * 16)); - } - return uuid.join("") + Date.now(); - } - - function syncDevID(devID) { - if (!devID) return; - try { - localStorage.setItem(UUID_KEY, devID); - // 保留旧 key,兼容已上线页面和服务端游客 cookie。 - localStorage.setItem(DEV_ID_KEY, devID); - } catch (e) {} - try { - if (global.CookieUtil && typeof global.CookieUtil.set === "function") { - global.CookieUtil.set(DEV_ID_KEY, devID, { days: 365 }); - } - } catch (e2) {} - } - - function getOrCreateDevID() { - var devID = ""; - try { - devID = (localStorage.getItem(UUID_KEY) || "").trim(); - } catch (e) {} - if (devID) { - syncDevID(devID); - return devID; - } - - // 首屏 SSR 可能已生成同规则 UUID 并写入 cookie,优先复用以保持游客身份一致。 - var fromCookie = - global.CookieUtil && typeof global.CookieUtil.get === "function" - ? global.CookieUtil.get(DEV_ID_KEY) - : ""; - if (fromCookie) { - syncDevID(fromCookie); - return fromCookie; - } - - if (!volatileDevID) { - volatileDevID = generateUUID(); - } - syncDevID(volatileDevID); - return volatileDevID; - } - - function getChannel() { - if ( - global.ChannelAttribution && - typeof global.ChannelAttribution.get === "function" - ) { - return global.ChannelAttribution.get(); - } - - var channel = ""; - try { - channel = (localStorage.getItem(CHANNEL_KEY) || "").trim(); - if (channel) { - if (global.CookieUtil && typeof global.CookieUtil.set === "function") { - global.CookieUtil.set(CHANNEL_KEY, channel, { days: 365 }); - } - return channel; - } - } catch (e) {} - try { - channel = - global.CookieUtil && typeof global.CookieUtil.get === "function" - ? String(global.CookieUtil.get(CHANNEL_KEY) || "").trim() - : ""; - if (channel) { - try { - localStorage.setItem(CHANNEL_KEY, channel); - } catch (e2) {} - return channel; - } - } catch (e3) {} - try { - var params = new URLSearchParams(global.location.search || ""); - channel = (params.get("dc") || params.get("ch") || params.get("pc") || "").trim(); - if (channel) { - localStorage.setItem(CHANNEL_KEY, channel); - if (global.CookieUtil && typeof global.CookieUtil.set === "function") { - global.CookieUtil.set(CHANNEL_KEY, channel, { days: 365 }); - } - } - } catch (e4) {} - return channel; - } - - function clearGuestLogoutFlag() { - try { - sessionStorage.removeItem(GUEST_LOGOUT_FLAG_KEY); - } catch (e) {} - try { - if (global.CookieUtil && typeof global.CookieUtil.remove === "function") { - global.CookieUtil.remove("guest_skip_auto"); - } - } catch (e2) {} - } - - function markGuestLogout() { - try { - sessionStorage.setItem(GUEST_LOGOUT_FLAG_KEY, "1"); - } catch (e) {} - try { - if (global.CookieUtil && typeof global.CookieUtil.set === "function") { - // 不设 days → session cookie,配合 SSR 跳过本次自动游客 - global.CookieUtil.set("guest_skip_auto", "1"); - } - } catch (e2) {} - } - - function isGuestLogoutMarked() { - try { - if (sessionStorage.getItem(GUEST_LOGOUT_FLAG_KEY) === "1") return true; - } catch (e) {} - try { - if ( - global.CookieUtil && - typeof global.CookieUtil.get === "function" && - global.CookieUtil.get("guest_skip_auto") === "1" - ) { - return true; - } - } catch (e2) {} - return false; - } - - function syncTrackerUid(user) { - if (!user || !global.tracker || typeof global.tracker.setUid !== "function") return; - var uid = user.userId != null ? user.userId : user.uid != null ? user.uid : user.id; - if (uid == null || String(uid) === "") return; - global.tracker.setUid(String(uid)); - } - - function applyAuthSession(res, failTip) { - if (!res || res.code !== 200 || !res.data) { - var err = new Error((res && (res.tip || res.msg)) || failTip || "请求失败"); - err.code = res && res.code; - err.data = res; - throw err; - } - if (global.CookieUtil && typeof global.CookieUtil.setAuthSession === "function") { - global.CookieUtil.setAuthSession(res.data); - } - clearGuestLogoutFlag(); - syncTrackerUid(res.data); - try { - document.dispatchEvent( - new CustomEvent("auth:login", { detail: { user: res.data } }) - ); - } catch (e) {} - return res; - } - - function register(payload) { - var channel = getChannel(); - var body = { - accountNo: (payload.accountNo || "").trim(), - password: payload.password || "", - devID: getOrCreateDevID(), - channel: channel, - }; - var invite = (payload.agentInviteCode || getInviteCode() || "").trim(); - if (invite) body.agentInviteCode = invite; - - // 游客升级注册:携带当前游客 token,上游按 Authorization 绑定原游客账号。 - var options = {}; - var token = - global.CookieUtil && typeof global.CookieUtil.getToken === "function" - ? global.CookieUtil.getToken() - : ""; - var registered = - global.CookieUtil && typeof global.CookieUtil.isRegistered === "function" - ? global.CookieUtil.isRegistered() - : false; - if (token && !registered) { - options.headers = { Authorization: token }; - } - - return global.HttpRequest.post("/api/auth/register", body, options).then(function (res) { - return applyAuthSession(res, "注册失败"); - }); - } - - function login(payload) { - var body = { - account: (payload.account || "").trim(), - password: payload.password || "", - devID: getOrCreateDevID(), - channel: getChannel(), - }; - return global.HttpRequest.post("/api/auth/login", body).then(function (res) { - return applyAuthSession(res, "登录失败"); - }); - } - - function resetPassword(payload) { - return global.HttpRequest.post("/api/auth/resetPassword", { - accountNo: (payload.accountNo || "").trim(), - email: (payload.email || "").trim(), - newPassword: payload.newPassword || "", - }); - } - - function guestLogin() { - var devId = getOrCreateDevID(); - return global.HttpRequest.post("/api/auth/guest", { - devID: devId, - deviceId: devId, - channel: getChannel(), - mac: "", - }).then(function (res) { - return applyAuthSession(res, "游客登录失败"); - }); - } - - function isAuthEndpoint(url) { - return /\/api\/auth\/(guest|login|register|resetPassword)(\?|$)/.test(String(url || "")); - } - - function isSafeRedirect(path) { - return typeof path === "string" && path.charAt(0) === "/" && path.charAt(1) !== "/"; - } - - /** PC 打开登录弹窗;H5 跳转登录页 */ - function promptRegisteredRelogin() { - toast("登录已过期,请重新登录", "warning"); - var isMobile = - global.matchMedia && global.matchMedia("(max-width: 768px)").matches; - if (!isMobile && typeof global.openLoginPop === "function") { - try { - global.openLoginPop(); - return; - } catch (e) {} - } - if (typeof global.openLoginDialog === "function") { - try { - var opened = global.openLoginDialog(); - if (opened && typeof opened.then === "function") { - opened.catch(function () { - global.location.href = "/login"; - }); - return; - } - return; - } catch (e2) {} - } - global.location.href = "/login"; - } - - /** - * 接口返回 code=1000(token 失效)时的统一处理。 - * - 游客:清会话后走游客登录,成功后可重试原请求 - * - 注册账号:清会话并打开登录弹窗 / 跳转登录页 - * @returns {Promise<{ok:boolean, guest?:boolean, needLogin?:boolean, res?:any}>} - */ - function handleTokenExpired() { - if (tokenRecoveryPromise) { - return tokenRecoveryPromise; - } - - var wasRegistered = - global.CookieUtil && typeof global.CookieUtil.isRegistered === "function" - ? global.CookieUtil.isRegistered() - : false; - - if (global.CookieUtil && typeof global.CookieUtil.clearAuth === "function") { - global.CookieUtil.clearAuth(); - } - try { - document.dispatchEvent( - new CustomEvent("auth:logout", { detail: { reason: "token-expired" } }) - ); - } catch (e) {} - - if (wasRegistered) { - var registeredJob = Promise.resolve().then(function () { - promptRegisteredRelogin(); - return { ok: false, needLogin: true }; - }); - tokenRecoveryPromise = registeredJob; - registeredJob.finally(function () { - setTimeout(function () { - if (tokenRecoveryPromise === registeredJob) { - tokenRecoveryPromise = null; - } - }, 1200); - }); - return registeredJob; - } - - clearGuestLogoutFlag(); - var guestJob = guestLogin() - .then(function (res) { - return { ok: true, guest: true, res: res }; - }) - .catch(function (err) { - toast("会话已失效,请刷新页面重试", "error"); - throw err; - }) - .finally(function () { - if (tokenRecoveryPromise === guestJob) { - tokenRecoveryPromise = null; - } - }); - tokenRecoveryPromise = guestJob; - return guestJob; - } - - /** - * 无 token(未自己登录)时自动游客登录。 - * SSR 也会在首屏补游客;此处作客户端兜底。 - * 手动退出后跳过本次;清掉标记后,下次进页再自动游客。 - * BFF: POST /api/auth/guest → 上游 POST /api/app/auth/guest - */ - function ensureGuestSession() { - if (hasSessionToken()) { - return Promise.resolve({ ok: true, skipped: true }); - } - if (isGuestLogoutMarked()) { - // 本次因手动退出跳过;清标记,下次进页恢复自动游客 - clearGuestLogoutFlag(); - return Promise.resolve({ ok: true, skipped: true, reason: "manual-logout" }); - } - if (!global.HttpRequest) { - return Promise.resolve({ ok: false, skipped: true, reason: "no-http" }); - } - if (!guestLoginPromise) { - guestLoginPromise = guestLogin() - .then(function (res) { - return { ok: true, res: res }; - }) - .catch(function (err) { - return { ok: false, error: err }; - }) - .finally(function () { - guestLoginPromise = null; - }); - } - return guestLoginPromise; - } - - function uploadImage(file) { - var fd = new FormData(); - fd.append("file", file); - return global.HttpRequest.post("/api/upload/image", fd).then(function (res) { - if (!res || res.code !== 200 || !res.data || !res.data.url) { - var err = new Error((res && (res.tip || res.msg)) || "上传失败"); - err.code = res && res.code; - err.data = res; - throw err; - } - return res.data; - }); - } - - function updateProfile(payload) { - var body = {}; - if (payload.nickName != null && String(payload.nickName).trim() !== "") { - body.nickName = String(payload.nickName).trim(); - } - if (payload.introduction != null) { - body.introduction = String(payload.introduction).trim(); - } - if (payload.avatar) body.avatar = payload.avatar; - if (payload.oldPassword) body.oldPassword = payload.oldPassword; - if (payload.newPassword) body.newPassword = payload.newPassword; - if (typeof payload.privacyMode === "boolean") body.privacyMode = payload.privacyMode; - - return global.HttpRequest.post("/api/auth/profile", body).then(function (res) { - if (!res || res.code !== 200) { - var err = new Error((res && (res.tip || res.msg)) || "保存失败"); - err.code = res && res.code; - err.data = res; - throw err; - } - if (res.data && global.CookieUtil && typeof global.CookieUtil.setAuthSession === "function") { - global.CookieUtil.setAuthSession(res.data); - } - if (res.data) syncTrackerUid(res.data); - return res; - }); - } - - function isValidEmail(email) { - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(email || "").trim()); - } - - /** 登录凭证:账号 4–16 位,或合法邮箱 */ - function isValidLoginAccount(account) { - var v = String(account || "").trim(); - if (!v) return false; - if (v.indexOf("@") >= 0) return isValidEmail(v); - return v.length >= 4 && v.length <= 16; - } - - function logout() { - markGuestLogout(); - if (global.CookieUtil && typeof global.CookieUtil.clearAuth === "function") { - global.CookieUtil.clearAuth(); - } - try { - document.dispatchEvent(new CustomEvent("auth:logout", { detail: { reason: "manual" } })); - } catch (e) {} - if (!global.HttpRequest) { - return Promise.resolve({ code: 200 }); - } - return global.HttpRequest.post("/api/auth/logout", {}).catch(function () { - return { code: 200 }; - }); - } - - global.AuthAPI = { - TOKEN_EXPIRED_CODE: TOKEN_EXPIRED_CODE, - register: register, - login: login, - logout: logout, - resetPassword: resetPassword, - guestLogin: guestLogin, - ensureGuestSession: ensureGuestSession, - handleTokenExpired: handleTokenExpired, - isAuthEndpoint: isAuthEndpoint, - markGuestLogout: markGuestLogout, - hasSessionToken: hasSessionToken, - uploadImage: uploadImage, - updateProfile: updateProfile, - getInviteCode: getInviteCode, - getOrCreateDevID: getOrCreateDevID, - getChannel: getChannel, - isValidEmail: isValidEmail, - isValidLoginAccount: isValidLoginAccount, - toast: toast, - }; - - // 每次页面初始化都准备好持久设备 UUID,并捕获首次渠道。 - getOrCreateDevID(); - getChannel(); - - // 首屏无 token → 自动游客登录 - ensureGuestSession(); -})(typeof window !== "undefined" ? window : this); diff --git a/app/static/js/common/channel.js b/app/static/js/common/channel.js deleted file mode 100644 index 23eb1bf..0000000 --- a/app/static/js/common/channel.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * 全站渠道首次归因。 - * 与落地页约定一致,按 dc、ch、pc 顺序识别,并同步到 localStorage 与 cookie。 - */ -(function (global) { - "use strict"; - - var STORAGE_KEY = "channel"; - var QUERY_KEYS = ["dc", "ch", "pc"]; - - function clean(value) { - return String(value == null ? "" : value).trim(); - } - - function readStorage() { - try { - return clean(global.localStorage.getItem(STORAGE_KEY)); - } catch (e) { - return ""; - } - } - - function writeStorage(channel) { - try { - global.localStorage.setItem(STORAGE_KEY, channel); - } catch (e) {} - } - - function readCookie() { - try { - var match = document.cookie.match(/(?:^|;\s*)channel=([^;]*)/); - return match ? clean(decodeURIComponent(match[1])) : ""; - } catch (e) { - return ""; - } - } - - function writeCookie(channel) { - try { - var secure = global.location && global.location.protocol === "https:" ? "; Secure" : ""; - document.cookie = - STORAGE_KEY + - "=" + - encodeURIComponent(channel) + - "; Path=/; Max-Age=31536000; SameSite=Lax" + - secure; - } catch (e) {} - } - - function readQuery() { - try { - var params = new URLSearchParams(global.location.search || ""); - for (var i = 0; i < QUERY_KEYS.length; i += 1) { - var channel = clean(params.get(QUERY_KEYS[i])); - if (channel) return channel; - } - } catch (e) {} - return ""; - } - - function syncTracker(channel) { - try { - if (global.tracker && typeof global.tracker.setChannel === "function") { - global.tracker.setChannel(channel); - } - } catch (e) {} - } - - function capture() { - // localStorage 与服务端 cookie 都属于已归因结果,必须先于当前 URL。 - var channel = readStorage() || readCookie() || readQuery(); - if (!channel) return ""; - - writeStorage(channel); - writeCookie(channel); - global.__CHANNEL_CODE__ = channel; - syncTracker(channel); - return channel; - } - - global.ChannelAttribution = { - get: capture, - capture: capture, - }; - - var channel = capture(); - try { - document.dispatchEvent( - new CustomEvent("channel:ready", { detail: { channel: channel } }) - ); - } catch (e) {} -})(window); diff --git a/app/static/js/common/cookie.js b/app/static/js/common/cookie.js deleted file mode 100644 index 053cd93..0000000 --- a/app/static/js/common/cookie.js +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Cookie 读写(登录 token / sid 等统一落 cookie) - */ -(function (global) { - function encode(value) { - return encodeURIComponent(String(value == null ? "" : value)); - } - - function decode(value) { - try { - // 兼容服务端历史 QueryEscape:空格被编成 "+",decodeURIComponent 不会还原 - return decodeURIComponent(String(value || "").replace(/\+/g, "%20")); - } catch (e) { - return value; - } - } - - function getCookie(name) { - if (!name) return ""; - var key = encodeURIComponent(name) + "="; - var parts = String(document.cookie || "").split(";"); - for (var i = 0; i < parts.length; i++) { - var part = parts[i].replace(/^\s+/, ""); - if (part.indexOf(key) === 0) { - return decode(part.slice(key.length)); - } - } - return ""; - } - - /** - * @param {string} name - * @param {string} value - * @param {{ days?: number, path?: string, sameSite?: string, secure?: boolean }} [options] - */ - function setCookie(name, value, options) { - options = options || {}; - var days = options.days; - var path = options.path || "/"; - var sameSite = options.sameSite || "Lax"; - var parts = [ - encodeURIComponent(name) + "=" + encode(value), - "path=" + path, - "SameSite=" + sameSite, - ]; - if (typeof days === "number") { - var expires = new Date(); - expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000); - parts.push("expires=" + expires.toUTCString()); - } - if (options.secure || (global.location && global.location.protocol === "https:")) { - parts.push("Secure"); - } - document.cookie = parts.join("; "); - } - - function removeCookie(name, path) { - setCookie(name, "", { days: -1, path: path || "/" }); - } - - var AUTH = { - TOKEN: "token", - SID: "sid", - S_TOKEN: "_s_token_", - USER_ID: "userId", - ACCOUNT_NO: "accountNo", - NICK_NAME: "nickName", - INTRODUCTION: "introduction", - AVATAR: "avatar", - EMAIL: "email", - INVITE_CODE: "inviteCode", - RECHARGE: "recharge", - VIP_TYPE: "vipType", - VIP_EXPIRE: "currentVipExpire", - SHOW_CARD_ID: "showCardId", - SHOW_CARD_NAME: "showCardName", - USER_TYPE: "userType", - IS_REGISTER: "isRegister", - }; - - var AUTH_KEYS = [ - AUTH.TOKEN, - AUTH.USER_ID, - AUTH.ACCOUNT_NO, - AUTH.NICK_NAME, - AUTH.INTRODUCTION, - AUTH.AVATAR, - AUTH.EMAIL, - AUTH.INVITE_CODE, - AUTH.RECHARGE, - AUTH.VIP_TYPE, - AUTH.VIP_EXPIRE, - AUTH.SHOW_CARD_ID, - AUTH.SHOW_CARD_NAME, - AUTH.USER_TYPE, - AUTH.IS_REGISTER, - "_token_", - AUTH.SID, - AUTH.S_TOKEN, - ]; - - function getToken() { - return getCookie(AUTH.TOKEN) || getCookie("_token_") || ""; - } - - function setToken(token, days) { - setCookie(AUTH.TOKEN, token || "", { days: typeof days === "number" ? days : 7 }); - } - - function getSid() { - return getCookie(AUTH.SID) || ""; - } - - function setSid(sid, days) { - setCookie(AUTH.SID, sid || "", { days: typeof days === "number" ? days : 7 }); - } - - function setAuthSession(user, days) { - if (!user) return; - var d = typeof days === "number" ? days : 7; - if (user.token) setToken(user.token, d); - if (user.userId != null) setCookie(AUTH.USER_ID, String(user.userId), { days: d }); - if (user.accountNo) setCookie(AUTH.ACCOUNT_NO, user.accountNo, { days: d }); - if (user.nickName) setCookie(AUTH.NICK_NAME, user.nickName, { days: d }); - if (user.introduction != null) setCookie(AUTH.INTRODUCTION, user.introduction || "", { days: d }); - if (user.avatar != null) setCookie(AUTH.AVATAR, user.avatar || "", { days: d }); - if (user.email != null) setCookie(AUTH.EMAIL, user.email || "", { days: d }); - if (user.inviteCode != null) setCookie(AUTH.INVITE_CODE, user.inviteCode || "", { days: d }); - // 仅在响应显式带 recharge 时写入(含 0),避免局部更新把余额抹成 0 - if (Object.prototype.hasOwnProperty.call(user, "recharge")) { - setCookie(AUTH.RECHARGE, String(Number(user.recharge) || 0), { days: d }); - } - if (user.vipType != null) setCookie(AUTH.VIP_TYPE, String(user.vipType), { days: d }); - if (user.currentVipExpire != null) { - setCookie(AUTH.VIP_EXPIRE, user.currentVipExpire || "", { days: d }); - } - if (Object.prototype.hasOwnProperty.call(user, "showCardId")) { - setCookie(AUTH.SHOW_CARD_ID, String(user.showCardId != null ? user.showCardId : ""), { days: d }); - } - if (Object.prototype.hasOwnProperty.call(user, "showCardName")) { - setCookie(AUTH.SHOW_CARD_NAME, user.showCardName || "", { days: d }); - } - if (user.type != null) setCookie(AUTH.USER_TYPE, String(user.type), { days: d }); - if (Object.prototype.hasOwnProperty.call(user, "isRegister") || user.type != null) { - var registered = !!(user.isRegister || Number(user.type) === 1); - setCookie(AUTH.IS_REGISTER, registered ? "1" : "0", { days: d }); - } - } - - function getAuthSession() { - var token = getToken(); - if (!token) return null; - return { - token: token, - userId: getCookie(AUTH.USER_ID), - accountNo: getCookie(AUTH.ACCOUNT_NO), - nickName: getCookie(AUTH.NICK_NAME), - introduction: getCookie(AUTH.INTRODUCTION), - avatar: getCookie(AUTH.AVATAR), - email: getCookie(AUTH.EMAIL), - inviteCode: getCookie(AUTH.INVITE_CODE), - recharge: getCookie(AUTH.RECHARGE), - vipType: getCookie(AUTH.VIP_TYPE), - currentVipExpire: getCookie(AUTH.VIP_EXPIRE), - showCardId: getCookie(AUTH.SHOW_CARD_ID), - showCardName: getCookie(AUTH.SHOW_CARD_NAME), - type: getCookie(AUTH.USER_TYPE), - isRegister: getCookie(AUTH.IS_REGISTER) === "1", - }; - } - - function clearAuth() { - for (var i = 0; i < AUTH_KEYS.length; i++) { - removeCookie(AUTH_KEYS[i]); - } - } - - function isLoggedIn() { - return !!getToken(); - } - - /** 是否正式注册账号(非游客) */ - function isRegistered() { - if (!isLoggedIn()) return false; - if (getCookie(AUTH.IS_REGISTER) === "1") return true; - return Number(getCookie(AUTH.USER_TYPE)) === 1; - } - - global.CookieUtil = { - get: getCookie, - set: setCookie, - remove: removeCookie, - AUTH: AUTH, - getToken: getToken, - setToken: setToken, - getSid: getSid, - setSid: setSid, - setAuthSession: setAuthSession, - getAuthSession: getAuthSession, - clearAuth: clearAuth, - isLoggedIn: isLoggedIn, - isRegistered: isRegistered, - }; -})(typeof window !== "undefined" ? window : this); diff --git a/app/static/js/common/request.js b/app/static/js/common/request.js index fa5c681..73f3517 100644 --- a/app/static/js/common/request.js +++ b/app/static/js/common/request.js @@ -1,160 +1,10 @@ /** * 前端请求封装。 - * - 登录态走 cookie(credentials: include) * - 浏览器无法改写 User-Agent;X-Real-IP 由服务端反代上游时注入 - * - 同源业务请求携带 SDK 设备字段;短剧服务端事件额外携带完整公共字段快照 - * - token 重试复用同一份 SDK Header、埋点上下文和 request_id - * - request_id 只用于请求关联;最终 event_id 由服务端按每条事件的最终字段生成 * - 直连上游时若需自定义头,请走本站 Go 代理接口 */ (function (global) { var DEFAULT_TIMEOUT_MS = 30000; - var TRACKER_CONTEXT_TIMEOUT_MS = 1500; - var DRAMA_SERVER_EVENT_ROUTES = { - "/api/playlet/chapter/like/set": "drama_like", - "/api/playlet/like/set": "drama_like", - "/api/playlet/chapter/collect/set": "drama_collect", - "/api/playlet/collect/set": "drama_collect", - "/api/comment/send": "drama_comment", - "/api/playlet/chapter/unlock": "drama_purchase", - }; - - function randomRequestId() { - var bytes = new Uint8Array(16); - if (global.crypto && typeof global.crypto.getRandomValues === "function") { - global.crypto.getRandomValues(bytes); - return Array.prototype.map.call(bytes, function (value) { - return (value + 256).toString(16).slice(-2); - }).join(""); - } - var fallback = (Date.now().toString(16) + Math.random().toString(16).slice(2) + Math.random().toString(16).slice(2)).slice(0, 32); - while (fallback.length < 32) fallback += "0"; - return fallback; - } - - function cleanHeaderPart(value) { - return String(value == null ? "" : value).replace(/[^\x20-\x7E]|[;=]/g, " ").trim(); - } - - function sameOrigin(url) { - try { - return new URL(url, global.location.href).origin === global.location.origin; - } catch (error) { - return true; - } - } - - function dramaServerEvent(url, method, options) { - if (method === "GET" || method === "HEAD" || !sameOrigin(url)) return ""; - if (options.trackerEvent === false) return ""; - if (typeof options.trackerEvent === "string") { - var explicit = options.trackerEvent.trim(); - return /^(drama_like|drama_comment|drama_collect|drama_purchase)$/.test(explicit) - ? explicit - : ""; - } - try { - return DRAMA_SERVER_EVENT_ROUTES[new URL(url, global.location.href).pathname] || ""; - } catch (error) { - return ""; - } - } - - function commonFields(options) { - if (options._trackerCommonFieldsPromise) return options._trackerCommonFieldsPromise; - var getter = global.tracker && typeof global.tracker.getCommonFields === "function" - ? global.tracker.getCommonFields - : global.WebSDK && typeof global.WebSDK.getCommonFields === "function" - ? global.WebSDK.getCommonFields.bind(global.WebSDK) - : null; - options._trackerCommonFieldsPromise = getter - ? Promise.resolve().then(function () { return getter(); }).catch(function () { return {}; }) - : Promise.resolve({}); - return options._trackerCommonFieldsPromise; - } - - function currentDramaContext(options) { - var context; - if (options && options.trackerContext && typeof options.trackerContext === "object") { - context = options.trackerContext; - } else if (global.tracker && typeof global.tracker.getDramaContext === "function") { - context = global.tracker.getDramaContext() || {}; - } else { - context = {}; - } - return { - media_id: String(context.media_id == null ? "" : context.media_id).trim(), - drama_id: String(context.drama_id == null ? "" : context.drama_id).trim(), - episode: Math.max(1, parseInt(context.episode, 10) || 1), - video_id: String(context.video_id == null ? "" : context.video_id).trim(), - recommend_trace_id: String(context.recommend_trace_id == null ? "" : context.recommend_trace_id).trim(), - }; - } - - function base64URLJSON(value) { - try { - var json = JSON.stringify(value); - var bytes = encodeURIComponent(json).replace(/%([0-9A-F]{2})/g, function (_, hex) { - return String.fromCharCode(parseInt(hex, 16)); - }); - return global.btoa(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); - } catch (error) { - return ""; - } - } - - function trackerHeaders(url, method, options) { - var eventName = dramaServerEvent(url, method, options); - if (!eventName) return Promise.resolve({}); - if (!options._trackerContextPromise) { - if (!options._trackerRequestId) options._trackerRequestId = randomRequestId(); - var drama = currentDramaContext(options); - if (!drama || !String(drama.drama_id || "").trim()) { - options._trackerContextPromise = Promise.resolve({}); - } else { - options._trackerContextPromise = commonFields(options).then(function (fields) { - var packedContext = base64URLJSON({ - v: 1, - request_id: options._trackerRequestId, - event_name: eventName, - common: fields, - drama: drama, - }); - if (!packedContext) return {}; - var result = { - "X-Tracker-Request-Id": options._trackerRequestId, - "X-Tracker-Event-Name": eventName, - "X-DC-Request-Context": packedContext, - }; - result["X-User-Agent"] = [ - "DevID=" + cleanHeaderPart(fields.device_id), - "DevType=" + cleanHeaderPart(fields.device || "OTHER"), - "SysType=" + (fields.device === "Android" ? "h5_android" : fields.device === "iOS" ? "h5_ios" : "h5_pc"), - "Ver=" + cleanHeaderPart(fields.app_version || "1.0.0"), - "BuildID=web", - "device_brand=" + cleanHeaderPart(fields.device_brand), - "device_model=" + cleanHeaderPart(fields.device_model), - "sid=" + cleanHeaderPart(fields.sid), - ].join(";"); - return result; - }); - } - } - return new Promise(function (resolve) { - var settled = false; - var timer = global.setTimeout(function () { - if (settled) return; - settled = true; - resolve({}); - }, TRACKER_CONTEXT_TIMEOUT_MS); - options._trackerContextPromise.then(function (headers) { - if (settled) return; - settled = true; - global.clearTimeout(timer); - resolve(headers); - }); - }); - } function buildQuery(params) { if (!params) return ""; @@ -175,8 +25,6 @@ * params?: Record, * headers?: Record, * timeout?: number, - * trackerEvent?: false|string, - * trackerContext?: Record, * }} [options] */ function request(url, options) { @@ -206,52 +54,20 @@ var timeoutMs = typeof options.timeout === "number" ? options.timeout : DEFAULT_TIMEOUT_MS; - function sdkCommonHeader() { - if (headers["X-SDK-Common"] || options._skipSDKCommon || !sameOrigin(finalUrl)) { - return Promise.resolve(""); - } - if (!options._sdkCommonHeaderPromise) { - options._sdkCommonHeaderPromise = Promise.resolve() - .then(function () { - if ( - global.tracker && - typeof global.tracker.getSDKCommonHeader === "function" - ) { - return global.tracker.getSDKCommonHeader(); - } - return ""; - }) - .catch(function () { return ""; }); - } - return options._sdkCommonHeaderPromise; + var fetchOpts = { + method: method, + headers: headers, + credentials: "include", + signal: controller ? controller.signal : undefined, + }; + if (body != null) fetchOpts.body = body; + if (controller && timeoutMs > 0) { + timer = setTimeout(function () { + controller.abort(); + }, timeoutMs); } - return Promise.all([ - trackerHeaders(finalUrl, method, options).catch(function () { return {}; }), - sdkCommonHeader(), - ]) - .then(function (prepared) { - var tracking = prepared[0] || {}; - Object.keys(tracking).forEach(function (key) { - if (headers[key] == null) headers[key] = tracking[key]; - }); - if (!headers["X-SDK-Common"] && prepared[1]) { - headers["X-SDK-Common"] = prepared[1]; - } - var fetchOpts = { - method: method, - headers: headers, - credentials: "include", - signal: controller ? controller.signal : undefined, - }; - if (body != null) fetchOpts.body = body; - if (controller && timeoutMs > 0) { - timer = setTimeout(function () { - controller.abort(); - }, timeoutMs); - } - return fetch(finalUrl, fetchOpts); - }) + return fetch(finalUrl, fetchOpts) .then(function (res) { var contentType = res.headers.get("content-type") || ""; var parse = contentType.indexOf("application/json") >= 0 @@ -270,49 +86,11 @@ return data; }); }) - .then(function (data) { - return handleTokenExpiredResponse(url, options, data); - }) .finally(function () { if (timer) clearTimeout(timer); }); } - /** - * 业务码 1000:token 失效。 - * 游客 → 重新游客登录并重试一次;注册账号 → 打开登录弹窗/跳转登录页。 - */ - function handleTokenExpiredResponse(url, options, data) { - options = options || {}; - var expiredCode = - (global.AuthAPI && global.AuthAPI.TOKEN_EXPIRED_CODE) || 1000; - if (!data || Number(data.code) !== Number(expiredCode)) { - return data; - } - if (options._skipTokenRecovery || options._tokenRetried) { - return data; - } - if ( - global.AuthAPI && - typeof global.AuthAPI.isAuthEndpoint === "function" && - global.AuthAPI.isAuthEndpoint(url) - ) { - return data; - } - if (!global.AuthAPI || typeof global.AuthAPI.handleTokenExpired !== "function") { - return data; - } - return global.AuthAPI.handleTokenExpired().then(function (result) { - if (result && result.ok && result.guest) { - return request( - url, - Object.assign({}, options, { _tokenRetried: true }) - ); - } - return data; - }); - } - global.HttpRequest = { request: request, get: function (url, options) { diff --git a/app/static/js/favorites-manage.js b/app/static/js/favorites-manage.js deleted file mode 100644 index ef9bd17..0000000 --- a/app/static/js/favorites-manage.js +++ /dev/null @@ -1,56 +0,0 @@ -(() => { - const root = document.querySelector('[data-favorites]'); - const manage = root?.querySelector('[data-favorites-manage]'); - if (!manage) return; - - manage.addEventListener('click', () => { - const active = document.body.classList.toggle('is-managing'); - manage.setAttribute('aria-pressed', String(active)); - manage.querySelector('span').textContent = active ? '完成' : '管理'; - }); - - root.querySelectorAll('[data-favorites-remove]').forEach(button => { - button.addEventListener('click', async (event) => { - event.preventDefault(); - event.stopPropagation(); - if (button.disabled) return; - button.disabled = true; - const kind = button.dataset.kind || 'short'; - const id = Number(button.dataset.id); - let url = '/api/playlet/collect/set'; - let body = {id, isCollect: false}; - if (kind === 'chapter') { - url = '/api/playlet/chapter/collect/set'; - body = {chapterId: id, isCollect: false}; - } else if (kind === 'series') { - url = '/api/video/collect/set'; - body = {id, isCollect: false}; - } - const trackerContext = kind === 'series' ? null : { - media_id: '', - drama_id: String(button.dataset.dramaId || ''), - drama_title: String(button.dataset.dramaTitle || ''), - episode: Math.max(1, Number(button.dataset.episode) || 1), - total_episode: Math.max(1, Number(button.dataset.totalEpisode) || 1), - video_id: String(button.dataset.videoId || ''), - drama_content_type: 'short_drama', - recommend_trace_id: '', - }; - try { - if (!window.HttpRequest || typeof window.HttpRequest.post !== 'function') { - throw new Error('请求服务未就绪'); - } - const result = await window.HttpRequest.post( - url, - body, - trackerContext ? {trackerContext} : undefined, - ); - if (!result || Number(result.code) !== 200) throw new Error((result && (result.tip || result.msg)) || '取消收藏失败'); - window.location.reload(); - } catch (_) { - button.disabled = false; - window.alert('操作失败,请稍后重试'); - } - }); - }); -})(); diff --git a/app/static/js/favorites.js b/app/static/js/favorites.js deleted file mode 100644 index d567331..0000000 --- a/app/static/js/favorites.js +++ /dev/null @@ -1,111 +0,0 @@ -(() => { - const root = document.querySelector('[data-favorites]'); - if (!root) return; - - const cards = Array.from(root.querySelectorAll('[data-favorites-card]')); - const filters = Array.from(root.querySelectorAll('[data-favorites-filter]')); - const manage = root.querySelector('[data-favorites-manage]'); - const manageLabel = manage ? manage.querySelector('span') : null; - const toolbar = root.querySelector('[data-favorites-toolbar]'); - const grid = root.querySelector('[data-favorites-grid]'); - const empty = root.querySelector('[data-favorites-empty]'); - const filterEmpty = root.querySelector('[data-favorites-filter-empty]'); - const recommendations = root.querySelector('[data-favorites-recommendations]'); - const loadMore = root.querySelector('[data-favorites-more]'); - const pagination = root.querySelector('[data-favorites-pagination]'); - const pages = root.querySelector('[data-favorites-pages]'); - const previous = root.querySelector('[data-favorites-prev]'); - const next = root.querySelector('[data-favorites-next]'); - const count = root.querySelector('[data-favorites-count]'); - const mobile = window.matchMedia('(max-width: 768px)'); - let activeFilter = 'all'; - let currentPage = 1; - let mobileVisible = 9; - - function matches(card) { - if (card.dataset.removed === 'true') return false; - if (activeFilter === 'following') return card.dataset.following === 'true'; - if (activeFilter === 'complete') return card.dataset.complete === 'true'; - return activeFilter === 'all' || card.dataset.kind === activeFilter; - } - - function render() { - const hasSavedWorks = cards.some(card => card.dataset.removed !== 'true'); - const collectionEmpty = !hasSavedWorks; - const filtered = cards.filter(matches); - const pageCount = Math.ceil(filtered.length / 12); - currentPage = Math.min(currentPage, Math.max(1, pageCount)); - const start = mobile.matches ? 0 : (currentPage - 1) * 12; - const end = mobile.matches ? mobileVisible : start + 12; - - cards.forEach(card => { card.hidden = true; }); - filtered.slice(start, end).forEach(card => { card.hidden = false; }); - root.classList.toggle('is-empty', collectionEmpty); - toolbar.hidden = collectionEmpty; - grid.hidden = collectionEmpty; - empty.hidden = !collectionEmpty; - if (recommendations) recommendations.hidden = !collectionEmpty; - filterEmpty.hidden = collectionEmpty || filtered.length !== 0; - if (collectionEmpty) { - document.body.classList.remove('is-managing'); - if (manage) manage.setAttribute('aria-pressed', 'false'); - if (manageLabel) manageLabel.textContent = '管理'; - } - loadMore.hidden = filtered.length === 0 || mobileVisible >= filtered.length; - pagination.hidden = filtered.length === 0; - count.textContent = `共 ${filtered.length} 部`; - previous.disabled = currentPage <= 1; - next.disabled = currentPage >= pageCount; - - pages.replaceChildren(); - for (let page = 1; page <= pageCount; page++) { - const button = document.createElement('button'); - button.className = `favorites-pagination__button${page === currentPage ? ' is-active' : ''}`; - button.type = 'button'; - button.textContent = String(page); - button.dataset.favoritesPage = String(page); - if (page === currentPage) button.setAttribute('aria-current', 'page'); - button.addEventListener('click', () => { - currentPage = page; - render(); - }); - pages.append(button); - } - } - - filters.forEach(filter => filter.addEventListener('click', () => { - activeFilter = filter.dataset.favoritesFilter; - currentPage = 1; - mobileVisible = 9; - filters.forEach(option => { - const selected = option === filter; - option.classList.toggle('is-active', selected); - option.setAttribute('aria-pressed', String(selected)); - }); - render(); - })); - - if (manage) manage.addEventListener('click', () => { - const managing = document.body.classList.toggle('is-managing'); - manage.setAttribute('aria-pressed', String(managing)); - manageLabel.textContent = managing ? '完成' : '管理'; - }); - - root.querySelectorAll('[data-favorites-remove]').forEach(button => button.addEventListener('click', () => { - button.closest('[data-favorites-card]').dataset.removed = 'true'; - render(); - })); - - loadMore.addEventListener('click', () => { - mobileVisible += 9; - render(); - }); - previous.addEventListener('click', () => { - if (currentPage > 1) { currentPage--; render(); } - }); - next.addEventListener('click', () => { - if (currentPage < Math.ceil(cards.filter(matches).length / 12)) { currentPage++; render(); } - }); - mobile.addEventListener('change', render); - render(); -})(); diff --git a/app/static/js/header.js b/app/static/js/header.js index 1f6043f..312de27 100644 --- a/app/static/js/header.js +++ b/app/static/js/header.js @@ -2,7 +2,6 @@ var header = document.querySelector(".site-header"); var drawer = document.querySelector("[data-drawer]"); var openBtn = document.querySelector("[data-drawer-open]"); - var userMenu = document.querySelector("[data-user-menu]"); if (header) { var threshold = 48; @@ -30,44 +29,6 @@ if ("ResizeObserver" in window) new ResizeObserver(measure).observe(header); } - // 用户下拉菜单 - if (userMenu) { - var toggle = userMenu.querySelector("[data-user-menu-toggle]"); - - function openUserMenu() { - userMenu.classList.add("is-open"); - if (toggle) toggle.setAttribute("aria-expanded", "true"); - } - - function closeUserMenu() { - userMenu.classList.remove("is-open"); - if (toggle) toggle.setAttribute("aria-expanded", "false"); - } - - function toggleUserMenu(e) { - e.stopPropagation(); - if (userMenu.classList.contains("is-open")) { - closeUserMenu(); - } else { - openUserMenu(); - } - } - - if (toggle) { - toggle.addEventListener("click", toggleUserMenu); - } - - document.addEventListener("click", function (e) { - if (!userMenu.contains(e.target)) { - closeUserMenu(); - } - }); - - document.addEventListener("keydown", function (e) { - if (e.key === "Escape") closeUserMenu(); - }); - } - // 左侧抽屉 if (drawer && openBtn) { var closeEls = drawer.querySelectorAll("[data-drawer-close]"); @@ -99,35 +60,6 @@ }); } - function openAccountSwitch(event) { - if (event) event.preventDefault(); - - if (userMenu) { - userMenu.classList.remove("is-open"); - var userToggle = userMenu.querySelector("[data-user-menu-toggle]"); - if (userToggle) userToggle.setAttribute("aria-expanded", "false"); - } - if (drawer) { - drawer.classList.remove("is-open"); - document.body.classList.remove("drawer-open"); - if (openBtn) openBtn.setAttribute("aria-expanded", "false"); - } - - if (typeof window.openAccountSwitchLogin === "function") { - window.openAccountSwitchLogin(); - return; - } - if (typeof window.openLoginPop === "function") { - window.openLoginPop(); - return; - } - window.location.href = "/login"; - } - - document.querySelectorAll("[data-account-switch]").forEach(function (btn) { - btn.addEventListener("click", openAccountSwitch); - }); - // 搜索表单:走路径 /search/{q},不用 query;同时写入本地搜索历史 var SEARCH_HISTORY_KEY = "hgdj_search_history"; var SEARCH_HISTORY_MAX = 10; @@ -526,108 +458,4 @@ closeDropdown(); }); }); - - var PLACEHOLDER = "/static/images/placeholder.png?v=2"; - var DEFAULT_AVATAR = "/static/images/profile/avatar.png"; - - function usableAvatar(url) { - var u = String(url || "").trim(); - if (!u) return ""; - if (/^https?:\/\//i.test(u) || u.indexOf("/static/") === 0) return u; - return ""; - } - - function setAvatarImg(img, url) { - if (!img) return; - var src = usableAvatar(url); - img.removeAttribute("data-decrypted"); - img.removeAttribute("data-decrypt-failed"); - if (src && typeof window.shouldDecryptImage === "function" && window.shouldDecryptImage(src)) { - img.classList.add("lazy"); - img.src = PLACEHOLDER; - img.setAttribute("data-src", src); - img.setAttribute("data-lazy-eager", "1"); - } else { - img.classList.remove("lazy"); - img.removeAttribute("data-src"); - img.removeAttribute("data-lazy-eager"); - img.src = src || DEFAULT_AVATAR; - } - } - - function refreshAvatarDecrypt(root) { - var scope = root || document; - if (typeof window.initLazyImages === "function") { - window.initLazyImages(scope, { eager: true }); - return; - } - if (typeof window.loadLazyNode !== "function") return; - scope - .querySelectorAll( - "img.user-chip__avatar.lazy[data-src], img.user-menu__avatar.lazy[data-src], img.drawer__avatar.lazy[data-src], img.play-composer__avatar-img.lazy[data-src]" - ) - .forEach(function (img) { - window.loadLazyNode(img); - }); - } - - function formatRecharge(raw) { - var n = Math.floor(Number(raw)); - if (!isFinite(n) || n < 0) n = 0; - var s = String(n); - if (s.length <= 3) return s; - var out = []; - var rem = s.length % 3; - if (rem) out.push(s.slice(0, rem)); - for (var i = rem; i < s.length; i += 3) out.push(s.slice(i, i + 3)); - return out.join(","); - } - - function syncHeaderUser() { - var session = - window.CookieUtil && typeof CookieUtil.getAuthSession === "function" - ? CookieUtil.getAuthSession() - : null; - if (!session) return; - - var avatar = usableAvatar(session.avatar); - document.querySelectorAll("[data-header-avatar]").forEach(function (img) { - if (avatar) setAvatarImg(img, avatar); - }); - - var name = String(session.nickName || session.accountNo || "").trim(); - if (name) { - document.querySelectorAll(".user-chip__name, .user-menu__meta strong, .drawer__meta strong").forEach(function (el) { - el.textContent = name; - }); - } - - if (session.userId) { - document.querySelectorAll(".user-menu__meta span, .drawer__meta span").forEach(function (el) { - if (/^ID\b/.test(String(el.textContent || "").trim())) { - el.textContent = "ID " + session.userId; - } - }); - } - - if (Object.prototype.hasOwnProperty.call(session, "recharge") && session.recharge !== "") { - var text = formatRecharge(session.recharge); - document.querySelectorAll("[data-header-recharge]").forEach(function (el) { - el.textContent = text; - }); - } - - refreshAvatarDecrypt(document); - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", syncHeaderUser); - } else { - syncHeaderUser(); - } - document.addEventListener("auth:login", syncHeaderUser); - document.addEventListener("auth:logout", syncHeaderUser); - window.refreshAvatarDecrypt = refreshAvatarDecrypt; - window.setAvatarImg = setAvatarImg; - window.syncHeaderUser = syncHeaderUser; })(); diff --git a/app/static/js/history.js b/app/static/js/history.js deleted file mode 100644 index 4ac9d73..0000000 --- a/app/static/js/history.js +++ /dev/null @@ -1,115 +0,0 @@ -(() => { - const root = document.querySelector('[data-history]'); - if (!root) return; - - const filters = Array.from(root.querySelectorAll('[data-history-filter]')); - const groups = Array.from(root.querySelectorAll('[data-history-group]')); - const items = Array.from(root.querySelectorAll('[data-history-item]')); - const empty = root.querySelector('[data-history-empty]'); - const clear = root.querySelector('[data-history-clear]'); - let activeKind = 'all'; - - async function postJSON(url, body) { - if (window.HttpRequest && typeof window.HttpRequest.post === 'function') { - return window.HttpRequest.post(url, body || {}); - } - const response = await fetch(url, { - method: 'POST', - credentials: 'same-origin', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(body || {}), - }); - return response.json(); - } - - function update() { - root.dataset.activeKind = activeKind; - items.forEach(item => { - item.hidden = item.dataset.removed === 'true' || (activeKind !== 'all' && item.dataset.kind !== activeKind); - }); - groups.forEach(group => { - group.hidden = !Array.from(group.querySelectorAll('[data-history-item]')).some(item => - !item.hidden && getComputedStyle(item).display !== 'none' - ); - }); - const hasVisible = groups.some(group => !group.hidden); - if (empty) empty.hidden = hasVisible; - if (clear) { - clear.hidden = items.length === 0 || items.every(item => item.dataset.removed === 'true'); - clear.disabled = clear.hidden; - } - filters.forEach(filter => { - const selected = filter.dataset.historyFilter === activeKind; - filter.classList.toggle('is-active', selected); - filter.setAttribute('aria-pressed', String(selected)); - }); - } - - filters.forEach(filter => filter.addEventListener('click', () => { - activeKind = filter.dataset.historyFilter; - update(); - })); - - root.querySelectorAll('[data-history-remove]').forEach(button => button.addEventListener('click', async (event) => { - event.preventDefault(); - event.stopPropagation(); - if (button.disabled) return; - const item = button.closest('[data-history-item]'); - if (!item) return; - const id = Number(item.dataset.id); - const kind = item.dataset.kind || 'short'; - if (!id) { - item.dataset.removed = 'true'; - update(); - return; - } - button.disabled = true; - try { - let result; - if (kind === 'series') { - result = await postJSON('/api/video/history/delete', {videoId: id}); - } else { - result = await postJSON('/api/playlet/history/delete', {playletId: id}); - } - if (!result || Number(result.code) !== 200) { - throw new Error((result && (result.tip || result.msg)) || '删除失败'); - } - item.dataset.removed = 'true'; - update(); - } catch (error) { - window.alert(error.message || '删除失败,请稍后重试'); - button.disabled = false; - } - })); - - if (clear) { - clear.addEventListener('click', async () => { - if (clear.disabled) return; - if (!window.confirm('确定清空全部观看历史?')) return; - clear.disabled = true; - try { - const [playletResult, videoResult] = await Promise.all([ - postJSON('/api/playlet/history/clear', {}), - postJSON('/api/video/history/clear', {}), - ]); - const playletOk = playletResult && Number(playletResult.code) === 200; - const videoOk = videoResult && Number(videoResult.code) === 200; - if (!playletOk && !videoOk) { - throw new Error( - (videoResult && (videoResult.tip || videoResult.msg)) || - (playletResult && (playletResult.tip || playletResult.msg)) || - '清空失败' - ); - } - items.forEach(item => { item.dataset.removed = 'true'; }); - update(); - window.location.reload(); - } catch (error) { - window.alert(error.message || '清空失败,请稍后重试'); - clear.disabled = false; - } - }); - } - window.addEventListener('resize', update); - update(); -})(); diff --git a/app/static/js/pages/dialogAd.js b/app/static/js/pages/dialogAd.js index fc1a6af..5633aa3 100644 --- a/app/static/js/pages/dialogAd.js +++ b/app/static/js/pages/dialogAd.js @@ -75,21 +75,6 @@ var displayedIndex = -1; var loadGeneration = 0; - function emitTrack(item, eventType) { - if (!item || eventType === "close") return; - if (window.tracker && typeof window.tracker.externalAd === "function") { - window.tracker.externalAd(item, eventType); - return; - } - try { - document.dispatchEvent(new CustomEvent("external-ad:track", { - detail: { element: item, eventType: eventType }, - })); - } catch (error) { - // 埋点不可用时不影响广告展示与关闭。 - } - } - function hideAllItems() { items.forEach(function (item) { item.hidden = true; @@ -118,7 +103,6 @@ var item = displayedIndex >= 0 ? items[displayedIndex] : null; if (item && !item.__dialogAdShowTracked) { item.__dialogAdShowTracked = true; - emitTrack(item, "show"); } } @@ -239,18 +223,13 @@ if (!item || item.hidden || !mask.contains(item)) return; var index = items.indexOf(item); if (index < 0 || index !== displayedIndex) return; - if (event.target.closest("[data-dialog-ad-link]")) { - emitTrack(item, "click"); - } if (event.target.closest("[data-dialog-ad-close]")) { - emitTrack(item, "close"); showItem(index + 1); } }); document.addEventListener("keydown", function (event) { if (event.key === "Escape" && mask.classList.contains("is-open") && displayedIndex >= 0) { - emitTrack(items[displayedIndex], "close"); showNextItem(); } }); diff --git a/app/static/js/pages/feedback.js b/app/static/js/pages/feedback.js index 691298b..76a3b8a 100644 --- a/app/static/js/pages/feedback.js +++ b/app/static/js/pages/feedback.js @@ -37,10 +37,6 @@ window.showToast(message, type); return; } - if (window.AuthAPI && typeof window.AuthAPI.toast === "function") { - window.AuthAPI.toast(message, type || "info"); - return; - } window.alert(message); } diff --git a/app/static/js/pages/login.js b/app/static/js/pages/login.js deleted file mode 100644 index 49a29c3..0000000 --- a/app/static/js/pages/login.js +++ /dev/null @@ -1,137 +0,0 @@ -(function () { - var form = document.querySelector("[data-login-page-form]"); - var password = document.querySelector("[data-login-page-password]"); - var toggle = document.querySelector("[data-login-page-toggle]"); - var toggleIcon = document.querySelector("[data-login-page-toggle-icon]"); - var submitBtn = document.querySelector("[data-login-page-submit]") || (form && form.querySelector('button[type="submit"]')); - var root = form || document; - - if (toggle && password && toggleIcon) { - toggle.addEventListener("click", function () { - var show = password.getAttribute("type") === "password"; - password.setAttribute("type", show ? "text" : "password"); - toggleIcon.setAttribute("src", show ? "/static/images/eye-off.svg" : "/static/images/eye.svg"); - toggle.setAttribute("aria-label", show ? "隐藏密码" : "显示密码"); - }); - } - - function clearFieldError(name) { - var field = root.querySelector('[data-field="' + name + '"]'); - var err = root.querySelector('[data-field-error="' + name + '"]'); - if (field) field.classList.remove("is-error"); - if (err) { - err.hidden = true; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = ""; - } - } - - function clearAllErrors() { - root.querySelectorAll("[data-field]").forEach(function (el) { - el.classList.remove("is-error"); - }); - root.querySelectorAll("[data-field-error]").forEach(function (err) { - err.hidden = true; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = ""; - }); - } - - function setFieldError(name, message) { - var field = root.querySelector('[data-field="' + name + '"]'); - var err = root.querySelector('[data-field-error="' + name + '"]'); - if (field) field.classList.add("is-error"); - if (err) { - err.hidden = false; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = message || ""; - } - } - - function isLen4to16(value) { - var len = String(value || "").length; - return len >= 4 && len <= 16; - } - - function validateForm() { - clearAllErrors(); - var fd = new FormData(form); - var account = String(fd.get("account") || "").trim(); - var pwd = String(fd.get("password") || ""); - var valid = true; - - if (!account) { - setFieldError("account", "请输入账号"); - valid = false; - } else if ( - !(window.AuthAPI && AuthAPI.isValidLoginAccount - ? AuthAPI.isValidLoginAccount(account) - : isLen4to16(account) || account.indexOf("@") >= 0) - ) { - setFieldError("account", "账号需为 4–16 位,或合法邮箱"); - valid = false; - } - - if (!pwd) { - setFieldError("password", "请输入密码"); - valid = false; - } else if (!isLen4to16(pwd)) { - setFieldError("password", "密码需为 4–16 位"); - valid = false; - } - - if (!valid) return null; - return { account: account, password: pwd }; - } - - function mapServerError(err) { - var tip = (err && err.message) || "登录失败,请稍后重试"; - if (/账号|账户|用户|不存在|邮箱/.test(tip) && !/密码/.test(tip)) { - setFieldError("account", tip); - return; - } - setFieldError("password", tip); - } - - function setSubmitting(loading) { - if (!submitBtn) return; - submitBtn.disabled = !!loading; - submitBtn.classList.toggle("is-loading", !!loading); - submitBtn.textContent = loading ? "登录中…" : "登录"; - } - - if (form) { - form.querySelectorAll("input").forEach(function (input) { - input.addEventListener("input", function () { - var name = input.getAttribute("name"); - if (name) clearFieldError(name); - }); - }); - - form.addEventListener("submit", function (event) { - event.preventDefault(); - if (!window.AuthAPI || !window.HttpRequest) { - setFieldError("password", "认证脚本未加载,请刷新页面"); - return; - } - if (submitBtn && submitBtn.classList.contains("is-loading")) return; - - var payload = validateForm(); - if (!payload) return; - - setSubmitting(true); - AuthAPI.login(payload) - .then(function (res) { - AuthAPI.toast((res && res.tip) || "登录成功", "success"); - window.setTimeout(function () { - window.location.href = "/"; - }, 400); - }) - .catch(function (err) { - setSubmitting(false); - mapServerError(err); - AuthAPI.toast((err && err.message) || "登录失败", "error"); - }); - }); - } -})(); diff --git a/app/static/js/pages/loginPop.js b/app/static/js/pages/loginPop.js deleted file mode 100644 index b40f5eb..0000000 --- a/app/static/js/pages/loginPop.js +++ /dev/null @@ -1,252 +0,0 @@ -(function () { - var root = document.querySelector("[data-login-pop]"); - if (!root) return; - - var form = root.querySelector("[data-login-form]"); - var password = root.querySelector("[data-login-password]"); - var toggle = root.querySelector("[data-login-toggle]"); - var toggleIcon = root.querySelector("[data-login-toggle-icon]"); - var title = root.querySelector("[data-login-title]"); - var description = root.querySelector("[data-login-desc]"); - var submitBtn = root.querySelector("[data-login-submit]") || (form && form.querySelector('button[type="submit"]')); - var previousOverflow = ""; - var switchAccountMode = false; - - function closeRegister() { - if (typeof window.closeRegisterPop === "function") { - window.closeRegisterPop(); - return; - } - var register = document.querySelector("[data-register-pop]"); - if (register) register.classList.remove("is-open"); - } - - function closeUserMenu() { - var menu = document.querySelector("[data-user-menu].is-open"); - if (menu) { - menu.classList.remove("is-open"); - var t = menu.querySelector("[data-user-menu-toggle]"); - if (t) t.setAttribute("aria-expanded", "false"); - } - } - - function clearFieldError(name) { - var field = root.querySelector('[data-field="' + name + '"]'); - var err = root.querySelector('[data-field-error="' + name + '"]'); - if (field) field.classList.remove("is-error"); - if (err) { - err.hidden = true; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = ""; - } - } - - function clearAllErrors() { - root.querySelectorAll("[data-field]").forEach(function (el) { - el.classList.remove("is-error"); - }); - root.querySelectorAll("[data-field-error]").forEach(function (err) { - err.hidden = true; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = ""; - }); - } - - function setFieldError(name, message) { - var field = root.querySelector('[data-field="' + name + '"]'); - var err = root.querySelector('[data-field-error="' + name + '"]'); - if (field) field.classList.add("is-error"); - if (err) { - err.hidden = false; - var text = err.querySelector("[data-field-error-text]"); - if (text) text.textContent = message || ""; - } - } - - function isLen4to16(value) { - var len = String(value || "").length; - return len >= 4 && len <= 16; - } - - function validateForm() { - clearAllErrors(); - var fd = new FormData(form); - var account = String(fd.get("account") || "").trim(); - var pwd = String(fd.get("password") || ""); - var valid = true; - - if (!account) { - setFieldError("account", "请输入账号"); - valid = false; - } else if ( - !(window.AuthAPI && AuthAPI.isValidLoginAccount - ? AuthAPI.isValidLoginAccount(account) - : isLen4to16(account) || account.indexOf("@") >= 0) - ) { - setFieldError("account", "账号需为 4–16 位,或合法邮箱"); - valid = false; - } - - if (!pwd) { - setFieldError("password", "请输入密码"); - valid = false; - } else if (!isLen4to16(pwd)) { - setFieldError("password", "密码需为 4–16 位"); - valid = false; - } - - if (!valid) return null; - return { account: account, password: pwd }; - } - - function mapServerError(err) { - var tip = (err && err.message) || "登录失败,请稍后重试"; - if (/账号|账户|用户|不存在|邮箱/.test(tip) && !/密码/.test(tip)) { - setFieldError("account", tip); - return; - } - setFieldError("password", tip); - } - - function isMobile() { - return window.matchMedia && window.matchMedia("(max-width: 768px)").matches; - } - - function open(input) { - var event = input && typeof input.preventDefault === "function" ? input : null; - var options = event ? {} : input || {}; - if (isMobile() && !options.forcePopup) { - if (event) event.preventDefault(); - window.location.href = "/login"; - return; - } - if (event) event.preventDefault(); - switchAccountMode = !!options.switchAccount; - closeRegister(); - closeUserMenu(); - clearAllErrors(); - if (options.resetForm && form) form.reset(); - if (password) password.setAttribute("type", "password"); - if (toggle) toggle.setAttribute("aria-label", "显示密码"); - if (toggleIcon) { - toggleIcon.setAttribute("src", "/static/images/eye.svg"); - toggleIcon.setAttribute("data-src", "/static/images/eye.svg"); - } - if (title) title.textContent = switchAccountMode ? "切换账号" : "登录黄果短剧"; - if (description) { - description.textContent = switchAccountMode - ? "请输入其他账号和密码完成切换" - : "登录后可收藏作品、解锁付费剧集并查看观看历史"; - } - root.classList.add("is-open"); - previousOverflow = document.body.style.overflow; - document.body.style.overflow = "hidden"; - var first = root.querySelector(".login-pop__input"); - if (first) { - window.setTimeout(function () { - first.focus(); - }, 0); - } - } - - function close() { - root.classList.remove("is-open"); - document.body.style.overflow = previousOverflow; - clearAllErrors(); - switchAccountMode = false; - } - - window.openLoginPop = open; - window.openAccountSwitchLogin = function () { - open({ forcePopup: true, switchAccount: true, resetForm: true }); - }; - window.closeLoginPop = close; - - document.addEventListener("click", function (event) { - var opener = event.target.closest("[data-login-open]"); - if (opener) { - open(event); - return; - } - - // PC:拦截站内 /login 链接,改为打开弹窗;H5 放行跳转登录页 - var loginLink = event.target.closest('a[href="/login"], a[href="/login/"], a[href="#login"]'); - if (loginLink && !isMobile()) { - open(event); - return; - } - - if (!root.classList.contains("is-open")) return; - if (event.target.closest("[data-login-close]")) { - close(); - } - }); - - if (toggle && password && toggleIcon) { - toggle.addEventListener("click", function () { - var show = password.getAttribute("type") === "password"; - password.setAttribute("type", show ? "text" : "password"); - toggleIcon.setAttribute("src", show ? "/static/images/eye-off.svg" : "/static/images/eye.svg"); - toggle.setAttribute("aria-label", show ? "隐藏密码" : "显示密码"); - }); - } - - function setSubmitting(loading) { - if (!submitBtn) return; - submitBtn.disabled = !!loading; - submitBtn.classList.toggle("is-loading", !!loading); - submitBtn.textContent = loading ? "登录中…" : "登录"; - } - - if (form) { - form.querySelectorAll("input").forEach(function (input) { - input.addEventListener("input", function () { - var name = input.getAttribute("name"); - if (name) clearFieldError(name); - }); - }); - - form.addEventListener("submit", function (event) { - event.preventDefault(); - if (!window.AuthAPI || !window.HttpRequest) { - setFieldError("password", "认证脚本未加载,请刷新页面"); - return; - } - if (submitBtn && submitBtn.classList.contains("is-loading")) return; - - var payload = validateForm(); - if (!payload) return; - - setSubmitting(true); - AuthAPI.login(payload) - .then(function (res) { - AuthAPI.toast((res && res.tip) || "登录成功", "success"); - window.setTimeout(function () { - close(); - window.location.reload(); - }, 400); - }) - .catch(function (err) { - setSubmitting(false); - mapServerError(err); - AuthAPI.toast((err && err.message) || "登录失败", "error"); - }); - }); - } - - document.addEventListener("keydown", function (event) { - if (event.key === "Escape" && root.classList.contains("is-open")) { - close(); - } - }); - - try { - var params = new URLSearchParams(window.location.search || ""); - if (params.get("auth") === "login") { - params.delete("auth"); - var qs = params.toString(); - window.history.replaceState(null, "", window.location.pathname + (qs ? "?" + qs : "") + window.location.hash); - open(); - } - } catch (e) {} -})(); diff --git a/app/static/js/pages/play.js b/app/static/js/pages/play.js index b12bb99..b37c619 100644 --- a/app/static/js/pages/play.js +++ b/app/static/js/pages/play.js @@ -452,7 +452,6 @@ } var playDp = null; - var lastWatchReportKey = ""; var currentPlayMode = "main"; var mainBadgePC = ""; var mainBadgeH5 = ""; @@ -515,32 +514,6 @@ if (label) label.textContent = mainCtaLabel || "立即播放"; } - function toIntAttr(v, fallback) { - var n = parseInt(v, 10); - return isNaN(n) ? fallback || 0 : n; - } - - function reportChapterWatch() { - var root = document.querySelector("[data-play-player]"); - if (!root) return; - var chapterId = toIntAttr(root.getAttribute("data-chapter-id"), 0); - var playletId = toIntAttr(root.getAttribute("data-playlet-id"), 0); - if (chapterId <= 0) return; - if (window.CookieUtil && typeof window.CookieUtil.isLoggedIn === "function") { - if (!window.CookieUtil.isLoggedIn()) return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") return; - var key = String(playletId || 0) + ":" + chapterId; - if (key === lastWatchReportKey) return; - lastWatchReportKey = key; - var body = { chapterId: chapterId }; - if (playletId > 0) body.playletId = playletId; - window.HttpRequest.post("/api/playlet/chapter/watch/report", body) - .catch(function () { - lastWatchReportKey = ""; - }); - } - function hidePosterOverlay() { var root = document.querySelector("[data-play-player]"); if (root) root.classList.add("is-playing"); @@ -571,20 +544,6 @@ } } - function ensureComposerAvatarDecrypt() { - var avatar = document.querySelector(".play-composer__avatar-img.lazy[data-src]"); - if (!avatar) return; - if (typeof window.loadLazyNode === "function") { - window.loadLazyNode(avatar); - return; - } - if (typeof window.refreshAvatarDecrypt === "function") { - window.refreshAvatarDecrypt(avatar.parentNode || document); - } else if (typeof window.initLazyImages === "function") { - window.initLazyImages(avatar.parentNode || document, { eager: true }); - } - } - function initDPlayer(initialURL) { if (typeof window.DPlayer === "undefined") return null; var container = document.querySelector(".dplayer-video"); @@ -624,9 +583,6 @@ playDp.on("play", function () { patchVideoInlinePlayback(playDp); hidePosterOverlay(); - if (currentPlayMode === "main") { - reportChapterWatch(); - } }); } return playDp; @@ -669,9 +625,6 @@ var root = playerRoot(); if (!root) return; var next = mode === "preview" ? "preview" : "main"; - if (window.tracker && typeof window.tracker.prepareDramaModeChange === "function") { - window.tracker.prepareDramaModeChange(next); - } currentPlayMode = next; root.setAttribute("data-play-mode", next); root.classList.toggle("play-player--preview", next === "preview"); @@ -782,7 +735,6 @@ } function bootPlayer() { - ensureComposerAvatarDecrypt(); var root = playerRoot(); if (!root) return; @@ -888,7 +840,7 @@ Array.prototype.forEach.call(openers, function (btn) { btn.addEventListener("click", function () { - // 预告片模式不弹充值;属性被临时移除时也不弹。 + // 预告片模式不弹下载提示;属性被临时移除时也不弹。 if (!btn.hasAttribute("data-play-coin-open")) return; if (isPreviewPlayingSurface()) return; open(); @@ -906,123 +858,6 @@ }); })(); -// 金币解锁:POST /api/playlet/chapter/unlock → upstream /api/app/playlet/chapter/unlock -(function () { - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - } - - function toInt(v, fallback) { - var n = parseInt(v, 10); - return isNaN(n) ? fallback || 0 : n; - } - - function isLoggedIn() { - var composer = document.querySelector("[data-play-composer]"); - if (composer && composer.getAttribute("data-logged-in") === "1") return true; - if (window.CookieUtil && typeof window.CookieUtil.isLoggedIn === "function") { - return !!window.CookieUtil.isLoggedIn(); - } - return false; - } - - function openRecharge() { - var opener = document.querySelector("[data-play-coin-open]"); - if (opener) { - opener.click(); - return; - } - if (typeof window.openRechargeModal === "function") { - window.openRechargeModal(); - return; - } - window.location.href = "/recharge"; - } - - function reloadAfterUnlockTracking() { - var tracking = Promise.resolve(); - if (window.tracker && typeof window.tracker.dramaUnlock === "function") { - tracking = Promise.resolve(window.tracker.dramaUnlock()).catch(function () { - // 埋点失败不影响解锁结果。 - }); - if (typeof window.tracker.flush === "function") { - [50, 200, 500, 700].forEach(function (delay) { - window.setTimeout(function () { - Promise.resolve(window.tracker.flush()).catch(function () { - // 埋点失败不影响解锁结果。 - }); - }, delay); - }); - } - } - var deadline = new Promise(function (resolve) { - window.setTimeout(resolve, 800); - }); - return Promise.race([tracking, deadline]).then(function () { - window.location.reload(); - }); - } - - document.addEventListener("click", function (event) { - var btn = window.domClosest(event.target, "[data-play-unlock]"); - if (!btn) return; - event.preventDefault(); - if (btn.disabled || btn.getAttribute("data-unlocking") === "1") return; - if (!isLoggedIn()) { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("解锁服务未就绪", "error"); - return; - } - var chapterId = toInt(btn.getAttribute("data-chapter-id"), 0); - var playletId = toInt(btn.getAttribute("data-playlet-id"), 0); - if (chapterId <= 0 || playletId <= 0) { - toast("缺少剧集信息", "error"); - return; - } - var payload = { chapterId: chapterId, playletId: playletId }; - btn.setAttribute("data-unlocking", "1"); - btn.disabled = true; - window.HttpRequest.post("/api/playlet/chapter/unlock", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - var code = res && Number(res.code); - if (code === 401 || code === 1000) { - var login = document.querySelector("[data-login-open]"); - if (login) login.click(); - return; - } - // 常见余额不足码,转充值 - if (code === 400 || /金币|余额|不足/.test(String((res && (res.tip || res.msg)) || ""))) { - openRecharge(); - return; - } - throw new Error((res && (res.tip || res.msg)) || "解锁失败"); - } - var data = res.data || {}; - if (data.balance != null && window.CookieUtil && typeof window.CookieUtil.setAuthSession === "function") { - window.CookieUtil.setAuthSession({ recharge: data.balance }); - if (typeof window.syncHeaderUser === "function") window.syncHeaderUser(); - } - toast("购买成功", "success"); - return reloadAfterUnlockTracking(); - }) - .catch(function (err) { - toast((err && err.message) || "解锁失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-unlocking"); - btn.disabled = false; - }); - }); -})(); - (function () { var btn = document.querySelector("[data-guess-refresh]"); var grid = document.querySelector("[data-guess-grid]"); @@ -1350,622 +1185,6 @@ }); })(); -// 剧集点赞:POST /api/playlet/chapter/like/set → upstream /api/app/playlet/chapter/like/set -(function () { - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - } - - function toInt(v, fallback) { - var n = parseInt(v, 10); - return isNaN(n) ? fallback || 0 : n; - } - - function formatLikeCount(n) { - var v = Number(n) || 0; - if (v <= 0) return "0"; - if (v < 10000) return String(v); - var wan = v / 10000; - if (wan >= 100) return Math.round(wan) + " 万"; - return wan.toFixed(1) + " 万"; - } - - function isLoggedIn() { - var composer = document.querySelector("[data-play-composer]"); - if (composer && composer.getAttribute("data-logged-in") === "1") return true; - if (window.CookieUtil && typeof window.CookieUtil.isLoggedIn === "function") { - return !!window.CookieUtil.isLoggedIn(); - } - return false; - } - - function applyChapterLikeUI(btn, isLike, countLike) { - if (!btn) return; - var liked = Number(isLike) === 1; - btn.classList.toggle("is-active", liked); - btn.setAttribute("data-liked", liked ? "1" : "2"); - btn.setAttribute("aria-label", liked ? "取消点赞" : "点赞"); - var label = btn.querySelector("[data-chapter-like-label]"); - if (label) label.textContent = liked ? "已点赞" : "点赞"; - var countEl = btn.querySelector("[data-chapter-like-count]"); - if (countLike != null) { - btn.setAttribute("data-like-raw", String(countLike)); - if (countEl) countEl.textContent = formatLikeCount(countLike); - } - if (countEl) countEl.hidden = liked; - } - - function toggleChapterLike(btn) { - if (!btn || btn.disabled || btn.getAttribute("data-liking") === "1") return; - if (!isLoggedIn()) { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("点赞服务未就绪", "error"); - return; - } - var chapterId = toInt(btn.getAttribute("data-chapter-id"), 0); - if (chapterId <= 0) { - toast("缺少剧集 ID", "error"); - return; - } - var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1; - var nextLike = !likedNow; - var prevCount = toInt(btn.getAttribute("data-like-raw"), 0); - var nextCount = Math.max(0, prevCount + (nextLike ? 1 : -1)); - btn.setAttribute("data-liking", "1"); - btn.disabled = true; - applyChapterLikeUI(btn, nextLike ? 1 : 2, nextCount); - var payload = { chapterId: chapterId, isLike: nextLike }; - window.HttpRequest.post("/api/playlet/chapter/like/set", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyChapterLikeUI( - btn, - data.isLike != null ? data.isLike : nextLike ? 1 : 2, - data.countLike != null ? data.countLike : nextCount - ); - }) - .catch(function (err) { - applyChapterLikeUI(btn, likedNow ? 1 : 2, prevCount); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-liking"); - btn.disabled = false; - }); - } - - document.addEventListener("click", function (event) { - var btn = window.domClosest(event.target, "[data-chapter-like]"); - if (!btn) return; - event.preventDefault(); - toggleChapterLike(btn); - }); -})(); - -// 短剧点赞:POST /api/playlet/like/set → upstream /api/app/playlet/like/set -(function () { - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - } - - function toInt(v, fallback) { - var n = parseInt(v, 10); - return isNaN(n) ? fallback || 0 : n; - } - - function formatLikeCount(n) { - var v = Number(n) || 0; - if (v <= 0) return "0"; - if (v < 10000) return String(v); - var wan = v / 10000; - if (wan >= 100) return Math.round(wan) + " 万"; - return wan.toFixed(1) + " 万"; - } - - function isLoggedIn() { - var composer = document.querySelector("[data-play-composer]"); - if (composer && composer.getAttribute("data-logged-in") === "1") return true; - if (window.CookieUtil && typeof window.CookieUtil.isLoggedIn === "function") { - return !!window.CookieUtil.isLoggedIn(); - } - return false; - } - - function applyPlayletLikeUI(btn, isLike, countLike) { - if (!btn) return; - var liked = Number(isLike) === 1; - btn.classList.toggle("is-active", liked); - btn.setAttribute("data-liked", liked ? "1" : "2"); - btn.setAttribute("aria-label", liked ? "取消点赞" : "点赞"); - var label = btn.querySelector("[data-playlet-like-label]"); - if (label) label.textContent = liked ? "已点赞" : "点赞"; - var countEl = btn.querySelector("[data-playlet-like-count]"); - if (countLike != null) { - btn.setAttribute("data-like-raw", String(countLike)); - if (countEl) countEl.textContent = formatLikeCount(countLike); - } - if (countEl) countEl.hidden = liked; - } - - function togglePlayletLike(btn) { - if (!btn || btn.disabled || btn.getAttribute("data-liking") === "1") return; - if (!isLoggedIn()) { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("点赞服务未就绪", "error"); - return; - } - var playletId = toInt(btn.getAttribute("data-playlet-id"), 0); - if (playletId <= 0) { - toast("缺少短剧 ID", "error"); - return; - } - var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1; - var nextLike = !likedNow; - var prevCount = toInt(btn.getAttribute("data-like-raw"), 0); - var nextCount = Math.max(0, prevCount + (nextLike ? 1 : -1)); - btn.setAttribute("data-liking", "1"); - btn.disabled = true; - applyPlayletLikeUI(btn, nextLike ? 1 : 2, nextCount); - var payload = { id: playletId, isLike: nextLike }; - window.HttpRequest.post("/api/playlet/like/set", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyPlayletLikeUI( - btn, - data.isLike != null ? data.isLike : nextLike ? 1 : 2, - data.countLike != null ? data.countLike : nextCount - ); - }) - .catch(function (err) { - applyPlayletLikeUI(btn, likedNow ? 1 : 2, prevCount); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-liking"); - btn.disabled = false; - }); - } - - document.addEventListener("click", function (event) { - var btn = window.domClosest(event.target, "[data-playlet-like]"); - if (!btn) return; - event.preventDefault(); - togglePlayletLike(btn); - }); -})(); - -// 视频点赞:POST /api/video/like/set → upstream /api/app/video/like/set -(function () { - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - } - - function toInt(v, fallback) { - var n = parseInt(v, 10); - return isNaN(n) ? fallback || 0 : n; - } - - function formatLikeCount(n) { - var v = Number(n) || 0; - if (v <= 0) return "0"; - if (v < 10000) return String(v); - var wan = v / 10000; - if (wan >= 100) return Math.round(wan) + " 万"; - return wan.toFixed(1) + " 万"; - } - - function isLoggedIn() { - var composer = document.querySelector("[data-play-composer]"); - if (composer && composer.getAttribute("data-logged-in") === "1") return true; - if (window.CookieUtil && typeof window.CookieUtil.isLoggedIn === "function") { - return !!window.CookieUtil.isLoggedIn(); - } - return false; - } - - function applyVideoLikeUI(btn, isLike, countLike) { - if (!btn) return; - var liked = Number(isLike) === 1; - btn.classList.toggle("is-active", liked); - btn.setAttribute("data-liked", liked ? "1" : "2"); - btn.setAttribute("aria-label", liked ? "取消点赞" : "点赞"); - var label = btn.querySelector("[data-video-like-label]"); - if (label) label.textContent = liked ? "已点赞" : "点赞"; - var countEl = btn.querySelector("[data-video-like-count]"); - if (countLike != null) { - btn.setAttribute("data-like-raw", String(countLike)); - if (countEl) countEl.textContent = formatLikeCount(countLike); - } - if (countEl) countEl.hidden = liked; - } - - function toggleVideoLike(btn) { - if (!btn || btn.disabled || btn.getAttribute("data-liking") === "1") return; - if (!isLoggedIn()) { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("点赞服务未就绪", "error"); - return; - } - var videoId = toInt(btn.getAttribute("data-video-id"), 0); - if (videoId <= 0) { - toast("缺少视频 ID", "error"); - return; - } - var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1; - var nextLike = !likedNow; - var prevCount = toInt(btn.getAttribute("data-like-raw"), 0); - var nextCount = Math.max(0, prevCount + (nextLike ? 1 : -1)); - btn.setAttribute("data-liking", "1"); - btn.disabled = true; - applyVideoLikeUI(btn, nextLike ? 1 : 2, nextCount); - var payload = { id: videoId, isLike: nextLike }; - window.HttpRequest.post("/api/video/like/set", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyVideoLikeUI( - btn, - data.isLike != null ? data.isLike : nextLike ? 1 : 2, - data.countLike != null ? data.countLike : nextCount - ); - }) - .catch(function (err) { - applyVideoLikeUI(btn, likedNow ? 1 : 2, prevCount); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-liking"); - btn.disabled = false; - }); - } - - document.addEventListener("click", function (event) { - var btn = window.domClosest(event.target, "[data-video-like]"); - if (!btn) return; - event.preventDefault(); - toggleVideoLike(btn); - }); -})(); - -// 视频收藏:POST /api/video/collect/set → upstream /api/app/video/collect/set -(function () { - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - } - - function toInt(v, fallback) { - var n = parseInt(v, 10); - return isNaN(n) ? fallback || 0 : n; - } - - function formatCount(n) { - var v = Number(n) || 0; - if (v <= 0) return "0"; - if (v < 10000) return String(v); - var wan = v / 10000; - if (wan >= 100) return Math.round(wan) + " 万"; - return wan.toFixed(1) + " 万"; - } - - function isLoggedIn() { - var composer = document.querySelector("[data-play-composer]"); - if (composer && composer.getAttribute("data-logged-in") === "1") return true; - if (window.CookieUtil && typeof window.CookieUtil.isLoggedIn === "function") { - return !!window.CookieUtil.isLoggedIn(); - } - return false; - } - - function applyVideoCollectUI(btn, isCollect, countCollect) { - if (!btn) return; - var collected = Number(isCollect) === 1; - btn.classList.toggle("is-active", collected); - btn.setAttribute("data-collected", collected ? "1" : "2"); - btn.setAttribute("aria-label", collected ? "取消收藏" : "收藏"); - var label = btn.querySelector("[data-video-collect-label]"); - if (label) label.textContent = collected ? "已收藏" : "收藏"; - var countEl = btn.querySelector("[data-video-collect-count]"); - if (countCollect != null) { - btn.setAttribute("data-collect-raw", String(countCollect)); - if (countEl) countEl.textContent = formatCount(countCollect); - } - if (countEl) countEl.hidden = collected; - } - - function toggleVideoCollect(btn) { - if (!btn || btn.disabled || btn.getAttribute("data-collecting") === "1") return; - if (!isLoggedIn()) { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("收藏服务未就绪", "error"); - return; - } - var videoId = toInt(btn.getAttribute("data-video-id"), 0); - if (videoId <= 0) { - toast("缺少视频 ID", "error"); - return; - } - var collectedNow = Number(btn.getAttribute("data-collected") || 2) === 1; - var nextCollect = !collectedNow; - var prevCount = toInt(btn.getAttribute("data-collect-raw"), 0); - var nextCount = Math.max(0, prevCount + (nextCollect ? 1 : -1)); - btn.setAttribute("data-collecting", "1"); - btn.disabled = true; - applyVideoCollectUI(btn, nextCollect ? 1 : 2, nextCount); - var payload = { id: videoId, isCollect: nextCollect }; - window.HttpRequest.post("/api/video/collect/set", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyVideoCollectUI( - btn, - data.isCollect != null ? data.isCollect : nextCollect ? 1 : 2, - data.countCollect != null ? data.countCollect : nextCount - ); - }) - .catch(function (err) { - applyVideoCollectUI(btn, collectedNow ? 1 : 2, prevCount); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-collecting"); - btn.disabled = false; - }); - } - - document.addEventListener("click", function (event) { - var btn = window.domClosest(event.target, "[data-video-collect]"); - if (!btn) return; - event.preventDefault(); - toggleVideoCollect(btn); - }); -})(); - -// 剧集收藏:POST /api/playlet/chapter/collect/set → upstream /api/app/playlet/chapter/collect/set -(function () { - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - } - - function toInt(v, fallback) { - var n = parseInt(v, 10); - return isNaN(n) ? fallback || 0 : n; - } - - function formatLikeCount(n) { - var v = Number(n) || 0; - if (v <= 0) return "0"; - if (v < 10000) return String(v); - var wan = v / 10000; - if (wan >= 100) return Math.round(wan) + " 万"; - return wan.toFixed(1) + " 万"; - } - - function isLoggedIn() { - var composer = document.querySelector("[data-play-composer]"); - if (composer && composer.getAttribute("data-logged-in") === "1") return true; - if (window.CookieUtil && typeof window.CookieUtil.isLoggedIn === "function") { - return !!window.CookieUtil.isLoggedIn(); - } - return false; - } - - function applyChapterCollectUI(btn, isCollect, countCollect) { - if (!btn) return; - var collected = Number(isCollect) === 1; - btn.classList.toggle("is-active", collected); - btn.setAttribute("data-collected", collected ? "1" : "2"); - btn.setAttribute("aria-label", collected ? "取消收藏" : "收藏"); - var label = btn.querySelector("[data-chapter-collect-label]"); - if (label) label.textContent = collected ? "已收藏" : "收藏"; - var countEl = btn.querySelector("[data-chapter-collect-count]"); - if (countCollect != null) { - btn.setAttribute("data-collect-raw", String(countCollect)); - if (countEl) countEl.textContent = formatLikeCount(countCollect); - } - if (countEl) countEl.hidden = collected; - } - - function toggleChapterCollect(btn) { - if (!btn || btn.disabled || btn.getAttribute("data-collecting") === "1") return; - if (!isLoggedIn()) { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("收藏服务未就绪", "error"); - return; - } - var chapterId = toInt(btn.getAttribute("data-chapter-id"), 0); - if (chapterId <= 0) { - toast("缺少剧集 ID", "error"); - return; - } - var collectedNow = Number(btn.getAttribute("data-collected") || 2) === 1; - var nextCollect = !collectedNow; - var prevCount = toInt(btn.getAttribute("data-collect-raw"), 0); - var nextCount = Math.max(0, prevCount + (nextCollect ? 1 : -1)); - btn.setAttribute("data-collecting", "1"); - btn.disabled = true; - applyChapterCollectUI(btn, nextCollect ? 1 : 2, nextCount); - var payload = { chapterId: chapterId, isCollect: nextCollect }; - window.HttpRequest.post("/api/playlet/chapter/collect/set", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyChapterCollectUI( - btn, - data.isCollect != null ? data.isCollect : nextCollect ? 1 : 2, - data.countCollect != null ? data.countCollect : nextCount - ); - }) - .catch(function (err) { - applyChapterCollectUI(btn, collectedNow ? 1 : 2, prevCount); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-collecting"); - btn.disabled = false; - }); - } - - document.addEventListener("click", function (event) { - var btn = window.domClosest(event.target, "[data-chapter-collect]"); - if (!btn) return; - event.preventDefault(); - toggleChapterCollect(btn); - }); -})(); - -// 短剧收藏:POST /api/playlet/collect/set → upstream /api/app/playlet/collect/set -(function () { - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - } - - function toInt(v, fallback) { - var n = parseInt(v, 10); - return isNaN(n) ? fallback || 0 : n; - } - - function formatLikeCount(n) { - var v = Number(n) || 0; - if (v <= 0) return "0"; - if (v < 10000) return String(v); - var wan = v / 10000; - if (wan >= 100) return Math.round(wan) + " 万"; - return wan.toFixed(1) + " 万"; - } - - function isLoggedIn() { - var composer = document.querySelector("[data-play-composer]"); - if (composer && composer.getAttribute("data-logged-in") === "1") return true; - if (window.CookieUtil && typeof window.CookieUtil.isLoggedIn === "function") { - return !!window.CookieUtil.isLoggedIn(); - } - return false; - } - - function applyPlayletCollectUI(btn, isCollect, countCollect) { - if (!btn) return; - var collected = Number(isCollect) === 1; - btn.classList.toggle("is-active", collected); - btn.setAttribute("data-collected", collected ? "1" : "2"); - btn.setAttribute("aria-label", collected ? "取消收藏" : "收藏"); - var label = btn.querySelector("[data-playlet-collect-label]"); - if (label) label.textContent = collected ? "已收藏" : "收藏"; - var countEl = btn.querySelector("[data-playlet-collect-count]"); - if (countCollect != null) { - btn.setAttribute("data-collect-raw", String(countCollect)); - if (countEl) countEl.textContent = formatLikeCount(countCollect); - var stats = document.querySelectorAll("[data-playlet-collect-stat]"); - Array.prototype.forEach.call(stats, function (el) { - el.textContent = formatLikeCount(countCollect); - }); - } - if (countEl) countEl.hidden = collected; - } - - function togglePlayletCollect(btn) { - if (!btn || btn.disabled || btn.getAttribute("data-collecting") === "1") return; - if (!isLoggedIn()) { - var loginBtn = document.querySelector("[data-login-open]"); - if (loginBtn) loginBtn.click(); - return; - } - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - toast("收藏服务未就绪", "error"); - return; - } - var playletId = toInt(btn.getAttribute("data-playlet-id"), 0); - if (playletId <= 0) { - toast("缺少短剧 ID", "error"); - return; - } - var collectedNow = Number(btn.getAttribute("data-collected") || 2) === 1; - var nextCollect = !collectedNow; - var prevCount = toInt(btn.getAttribute("data-collect-raw"), 0); - var nextCount = Math.max(0, prevCount + (nextCollect ? 1 : -1)); - btn.setAttribute("data-collecting", "1"); - btn.disabled = true; - applyPlayletCollectUI(btn, nextCollect ? 1 : 2, nextCount); - var payload = { id: playletId, isCollect: nextCollect }; - window.HttpRequest.post("/api/playlet/collect/set", payload) - .then(function (res) { - if (!res || Number(res.code) !== 200) { - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyPlayletCollectUI( - btn, - data.isCollect != null ? data.isCollect : nextCollect ? 1 : 2, - data.countCollect != null ? data.countCollect : nextCount - ); - }) - .catch(function (err) { - applyPlayletCollectUI(btn, collectedNow ? 1 : 2, prevCount); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-collecting"); - btn.disabled = false; - }); - } - - document.addEventListener("click", function (event) { - var btn = window.domClosest(event.target, "[data-playlet-collect]"); - if (!btn) return; - event.preventDefault(); - togglePlayletCollect(btn); - }); -})(); - // 简介展开:PC 超过 1 行、H5 超过 2 行才显示展开按钮 (function () { var root = document.querySelector("[data-play-synopsis]"); diff --git a/app/static/js/pages/playComments.js b/app/static/js/pages/playComments.js index d8b199e..aa4a69e 100644 --- a/app/static/js/pages/playComments.js +++ b/app/static/js/pages/playComments.js @@ -1,30 +1,19 @@ +// 播放页评论:只读列表 + 「查看更多评论」追加翻页 (function () { - var root = document.querySelector("[data-play-composer]"); - if (!root) return; + var commentsRoot = document.querySelector("[data-play-comments]"); + if (!commentsRoot) return; - var commentsRoot = document.querySelector("[data-play-comments]") || root.closest(".play-comments"); - var input = root.querySelector("[data-comment-input]"); - var countEl = root.querySelector("[data-comment-count]"); - var sendBtn = root.querySelector("[data-comment-send]"); var listEl = document.querySelector("[data-comment-list]"); var totalEl = document.querySelector("[data-comment-total-label]"); var moreBtn = document.querySelector("[data-comment-more]"); - var sending = false; - var inlineSending = false; var loadingMore = false; - var MAX_LEN = 200; var PAGE_SIZE = 4; - var replyTarget = null; function toast(message, type) { if (typeof window.showToast === "function") { window.showToast(message, type); return; } - if (window.AuthAPI && typeof window.AuthAPI.toast === "function") { - window.AuthAPI.toast(message, type || "info"); - return; - } } function toInt(v, fallback) { @@ -43,14 +32,14 @@ function getPageSize() { return Math.max( 1, - toInt(commentsRoot && commentsRoot.getAttribute("data-comment-size"), PAGE_SIZE) + toInt(commentsRoot.getAttribute("data-comment-size"), PAGE_SIZE) ); } function getPage() { return Math.max( 1, - toInt(commentsRoot && commentsRoot.getAttribute("data-comment-page"), 1) + toInt(commentsRoot.getAttribute("data-comment-page"), 1) ); } @@ -60,7 +49,6 @@ } function setPagerState(page, loaded, total) { - if (!commentsRoot) return; if (page != null) commentsRoot.setAttribute("data-comment-page", String(page)); if (loaded != null) commentsRoot.setAttribute("data-comment-loaded", String(loaded)); if (total != null) commentsRoot.setAttribute("data-comment-total", String(total)); @@ -80,12 +68,6 @@ } } - function syncCount() { - if (!input || !countEl) return; - var len = Array.from(input.value || "").length; - countEl.textContent = len + " / " + MAX_LEN; - } - function formatTime(raw) { var s = String(raw || "").trim(); if (!s) return "刚刚"; @@ -111,7 +93,6 @@ var text = escapeHtml(item.text || ""); var time = escapeHtml(formatTime(item.createdAt)); var like = toInt(item.countLike, 0); - var liked = Number(item.isLike) === 1; var avatarHtml = avatar ? '" + replies + '