diff --git a/app/api/web/application.go b/app/api/web/application.go index dd0d26b..c5203ac 100644 --- a/app/api/web/application.go +++ b/app/api/web/application.go @@ -12,6 +12,7 @@ func Application(c *gin.Context) { vm := applistser.BuildPageVM(raw) data := gin.H{ "title": "福利应用", + "pageName": "应用推荐", "navActive": "application", "categories": vm.Categories, } diff --git a/app/api/web/auth.go b/app/api/web/auth.go deleted file mode 100644 index 62a53ee..0000000 --- a/app/api/web/auth.go +++ /dev/null @@ -1,548 +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" -) - -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"` -} - -type resetPasswordBody struct { - AccountNo string `json:"accountNo"` - Email string `json:"email"` - NewPassword string `json:"newPassword"` -} - -type updateProfileBody struct { - Avatar string `json:"avatar"` - Introduction string `json:"introduction"` - NickName string `json:"nickName"` - OldPassword string `json:"oldPassword"` - NewPassword string `json:"newPassword"` - PrivacyMode *bool `json:"privacyMode"` -} - -// Register 代理上游注册,成功后写入登录 cookie。 -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 = strings.TrimSpace(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 + "@qmdj.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.Code != 200 || user == nil { - 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": nil, - }) - return - } - - authser.SetAuthCookies(c, user) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": env.Msg, - "tip": firstNonEmpty(env.Tip, "注册成功"), - "data": user, - }) -} - -// 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 = strings.TrimSpace(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.Code != 200 || user == nil { - 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": nil, - }) - return - } - - authser.SetAuthCookies(c, user) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": env.Msg, - "tip": firstNonEmpty(env.Tip, "登录成功"), - "data": user, - }) -} - -// ResetPassword 代理上游找回密码接口。 -func ResetPassword(c *gin.Context) { - var body resetPasswordBody - 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) - if body.AccountNo == "" || body.Email == "" || body.NewPassword == "" { - c.JSON(http.StatusOK, gin.H{"code": 1001, "msg": "参数错误", "tip": "请填写账号、邮箱和新密码", "data": nil}) - return - } - - env, err := authser.ResetPassword(c, authser.ResetPasswordReq{ - AccountNo: body.AccountNo, - Email: body.Email, - NewPassword: body.NewPassword, - }) - if err != nil { - log.Error("reset password upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "reset password failed", "tip": "重置密码失败,请稍后重试", "data": nil}) - return - } - tip := firstNonEmpty(env.Tip, env.Msg) - if env.Code == 200 { - tip = firstNonEmpty(tip, "密码重置成功") - } else { - tip = firstNonEmpty(tip, "密码重置失败") - } - c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": tip, "data": env.Data}) -} - -// Guest 代理上游游客登录,成功后写入登录 cookie。 -// 对应 POST /api/app/auth/guest;需传 devID,上游要求 X-User-Agent(由 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 = strings.TrimSpace(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.Code != 200 || user == nil { - 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": nil, - }) - return - } - - authser.SetAuthCookies(c, user) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "游客登录成功"), - "data": user, - }) -} - -// UpdateProfile 代理上游修改资料,成功后刷新 getInfo 并写 cookie。 -func UpdateProfile(c *gin.Context) { - var body updateProfileBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - - body.Avatar = strings.TrimSpace(body.Avatar) - body.Introduction = strings.TrimSpace(body.Introduction) - body.NickName = strings.TrimSpace(body.NickName) - body.OldPassword = strings.TrimSpace(body.OldPassword) - body.NewPassword = strings.TrimSpace(body.NewPassword) - - req := authser.UpdateProfileReq{ - Avatar: body.Avatar, - Introduction: body.Introduction, - NickName: body.NickName, - OldPassword: body.OldPassword, - NewPassword: body.NewPassword, - PrivacyMode: body.PrivacyMode, - } - if req.Avatar == "" && req.Introduction == "" && req.NickName == "" && req.OldPassword == "" && req.NewPassword == "" && req.PrivacyMode == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "请至少修改一项资料", - "data": nil, - }) - return - } - if (req.OldPassword == "") != (req.NewPassword == "") { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "修改密码需同时填写旧密码和新密码", - "data": nil, - }) - return - } - - env, err := authser.UpdateProfile(c, req) - if err != nil { - log.Error("update profile upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "update profile failed", - "tip": "保存失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = authser.UpdateProfile(c, req) - if err != nil { - log.Error("update profile retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "update profile failed", - "tip": "保存失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "保存失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - // 上游成功后拉最新资料写 cookie - _, user, infoErr := authser.GetInfo(c) - if infoErr != nil || user == nil { - log.Error("update profile getInfo failed", log.E(infoErr)) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": env.Msg, - "tip": firstNonEmpty(env.Tip, "保存成功"), - "data": nil, - }) - return - } - authser.SetAuthCookies(c, user) - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": env.Msg, - "tip": firstNonEmpty(env.Tip, "保存成功"), - "data": user, - }) -} - -// UploadImage 代理上游图片上传(头像不加 watermark)。 -func UploadImage(c *gin.Context) { - fileHeader, err := c.FormFile("file") - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "请选择图片文件", - "data": nil, - }) - return - } - f, err := fileHeader.Open() - if err != nil { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "open file failed", - "tip": "读取图片失败", - "data": nil, - }) - return - } - defer f.Close() - - resp, err := authser.UploadImage(c, fileHeader.Filename, f) - if err != nil { - log.Error("upload image upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "upload failed", - "tip": "上传失败,请稍后重试", - "data": nil, - }) - return - } - if resp == nil || resp.Code != 200 || resp.Data == nil || strings.TrimSpace(resp.Data.SourceNameFs) == "" { - tip := "上传失败" - msg := "" - code := 500 - if resp != nil { - code = resp.Code - msg = resp.Msg - tip = firstNonEmpty(resp.Tip, resp.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(resp.Msg, "success"), - "tip": firstNonEmpty(resp.Tip, "上传成功"), - "data": gin.H{ - "id": resp.Data.ID, - "sourceNameFs": resp.Data.SourceNameFs, - "url": resp.Data.SourceNameFs, - }, - }) -} - -// GetUserInfo 代理上游 GET /api/app/auth/getInfo。 -func GetUserInfo(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录", - "data": nil, - }) - return - } - - env, user, err := authser.GetInfo(c) - if err != nil { - log.Error("getInfo upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "getInfo failed", - "tip": "获取用户信息失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, user, err = authser.GetInfo(c) - if err != nil { - log.Error("getInfo retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "getInfo 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 - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - 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": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "成功"), - "data": user, - }) -} - -func firstNonEmpty(values ...string) string { - for _, v := range values { - if strings.TrimSpace(v) != "" { - return v - } - } - return "" -} diff --git a/app/api/web/auth_redirect.go b/app/api/web/auth_redirect.go deleted file mode 100644 index 2f5464a..0000000 --- a/app/api/web/auth_redirect.go +++ /dev/null @@ -1,57 +0,0 @@ -package web - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" -) - -// LoginEntryURL 首页弹窗登录入口;redirectPath 为登录成功后回跳路径(须为站内相对路径)。 -func LoginEntryURL(redirectPath string) string { - redirectPath = strings.TrimSpace(redirectPath) - if i := strings.IndexByte(redirectPath, '?'); i >= 0 { - redirectPath = redirectPath[:i] - } - if !isSafeRedirect(redirectPath) || redirectPath == "/" { - return "/login/" - } - return "/login/return" + ensureRedirectTrailingSlash(redirectPath) -} - -// RegisterEntryURL 首页弹窗注册入口。 -func RegisterEntryURL() string { - return "/register/" -} - -func ensureRedirectTrailingSlash(path string) string { - if i := strings.IndexByte(path, '?'); i >= 0 { - path = path[:i] - } - if path != "/" && !strings.HasSuffix(path, "/") { - path += "/" - } - return path -} - -// RedirectToLogin 未登录访问受保护页时,跳到首页并打开登录弹窗。 -func RedirectToLogin(c *gin.Context) { - path := "" - if c != nil && c.Request != nil && c.Request.URL != nil { - path = c.Request.URL.RequestURI() - } - if path == "" || path == "/login" || strings.HasPrefix(path, "/login/") { - path = "/" - } - c.Redirect(http.StatusFound, LoginEntryURL(path)) -} - -func isSafeRedirect(path string) bool { - if path == "" || !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "//") { - return false - } - if strings.ContainsAny(path, "\r\n") { - return false - } - return true -} diff --git a/app/api/web/chapter_detail.go b/app/api/web/chapter_detail.go index a53068c..2cc091d 100644 --- a/app/api/web/chapter_detail.go +++ b/app/api/web/chapter_detail.go @@ -4,10 +4,8 @@ import ( "net/http" "strconv" "strings" - "videoweb/app/service/authser" "videoweb/app/service/playletser" "videoweb/pub/common/log" - "videoweb/pub/common/useragent" "github.com/gin-gonic/gin" ) @@ -38,9 +36,6 @@ func ChapterDetail(c *gin.Context) { }) return } - if data != nil && useragent.TokenFromRequest(c.Request) != "" { - authser.SetRechargeCookie(c, data.Balance) - } c.JSON(http.StatusOK, gin.H{ "code": 200, "msg": "success", diff --git a/app/api/web/chapter_like.go b/app/api/web/chapter_like.go deleted file mode 100644 index de71915..0000000 --- a/app/api/web/chapter_like.go +++ /dev/null @@ -1,196 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type chapterLikeBody struct { - ChapterID int64 `json:"chapterId"` - IsLike *bool `json:"isLike"` -} - -// ChapterLikeSet 代理上游 POST /api/app/playlet/chapter/like/set。 -func ChapterLikeSet(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再点赞", - "data": nil, - }) - return - } - - var body chapterLikeBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ChapterID <= 0 || body.IsLike == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少 chapterId 或 isLike", - "data": nil, - }) - return - } - - env, err := playletser.ChapterLikeSet(c, playletser.PlayletChapterLikeSetReq{ - ChapterID: body.ChapterID, - IsLike: *body.IsLike, - }) - if err != nil { - log.Error("chapter like set upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "chapter like failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = playletser.ChapterLikeSet(c, playletser.PlayletChapterLikeSetReq{ - ChapterID: body.ChapterID, - IsLike: *body.IsLike, - }) - if err != nil { - log.Error("chapter like set retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "chapter like failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "操作失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "成功"), - "data": env.Data, - }) -} - -type chapterCollectBody struct { - ChapterID int64 `json:"chapterId"` - IsCollect *bool `json:"isCollect"` -} - -// ChapterCollectSet 代理上游 POST /api/app/playlet/chapter/collect/set。 -func ChapterCollectSet(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再收藏", - "data": nil, - }) - return - } - - var body chapterCollectBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ChapterID <= 0 || body.IsCollect == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少 chapterId 或 isCollect", - "data": nil, - }) - return - } - - env, err := playletser.ChapterCollectSet(c, playletser.PlayletChapterCollectSetReq{ - ChapterID: body.ChapterID, - IsCollect: *body.IsCollect, - }) - if err != nil { - log.Error("chapter collect set upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "chapter collect failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = playletser.ChapterCollectSet(c, playletser.PlayletChapterCollectSetReq{ - ChapterID: body.ChapterID, - IsCollect: *body.IsCollect, - }) - if err != nil { - log.Error("chapter collect set retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "chapter collect failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "操作失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "成功"), - "data": env.Data, - }) -} diff --git a/app/api/web/chapter_unlock.go b/app/api/web/chapter_unlock.go deleted file mode 100644 index afaae08..0000000 --- a/app/api/web/chapter_unlock.go +++ /dev/null @@ -1,106 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/authser" - "videoweb/app/service/playletser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type chapterUnlockBody struct { - ChapterID int64 `json:"chapterId"` - PlayletID int64 `json:"playletId"` -} - -// ChapterUnlock 代理上游 POST /api/app/playlet/chapter/unlock。 -func ChapterUnlock(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再解锁", - "data": nil, - }) - return - } - - var body chapterUnlockBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ChapterID <= 0 || body.PlayletID <= 0 { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少 chapterId 或 playletId", - "data": nil, - }) - return - } - - env, err := playletser.ChapterUnlock(c, playletser.PlayletChapterUnlockReq{ - ChapterID: body.ChapterID, - PlayletID: body.PlayletID, - }) - if err != nil { - log.Error("chapter unlock upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "chapter unlock failed", - "tip": "解锁失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = playletser.ChapterUnlock(c, playletser.PlayletChapterUnlockReq{ - ChapterID: body.ChapterID, - PlayletID: body.PlayletID, - }) - if err != nil { - log.Error("chapter unlock retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "chapter unlock failed", - "tip": "解锁失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "解锁失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - authser.SetRechargeCookie(c, env.Data.Balance) - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "解锁成功"), - "data": env.Data, - }) -} diff --git a/app/api/web/chapter_watch.go b/app/api/web/chapter_watch.go deleted file mode 100644 index e8b57b7..0000000 --- a/app/api/web/chapter_watch.go +++ /dev/null @@ -1,103 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type chapterWatchReportBody struct { - ChapterID int64 `json:"chapterId"` - PlayletID int64 `json:"playletId"` -} - -// ChapterWatchReport 代理上游 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 chapterWatchReportBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ChapterID <= 0 { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少 chapterId", - "data": nil, - }) - return - } - - env, err := playletser.ChapterWatchReport(c, playletser.PlayletChapterWatchReportReq{ - ChapterID: body.ChapterID, - PlayletID: body.PlayletID, - }) - if err != nil { - log.Error("chapter watch report upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "chapter watch report failed", - "tip": "上报失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = playletser.ChapterWatchReport(c, playletser.PlayletChapterWatchReportReq{ - ChapterID: body.ChapterID, - PlayletID: body.PlayletID, - }) - if err != nil { - log.Error("chapter watch report retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "chapter watch report failed", - "tip": "上报失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "上报失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "成功"), - "data": env.Data, - }) -} diff --git a/app/api/web/collect.go b/app/api/web/collect.go deleted file mode 100644 index d8387ac..0000000 --- a/app/api/web/collect.go +++ /dev/null @@ -1,84 +0,0 @@ -package web - -import ( - "videoweb/app/service/playletser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -// Collect 我的收藏 SSR: -// 短剧 GET /api/app/playlet/collect/list;剧集 GET /api/app/playlet/chapter/collect/list。 -func Collect(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - RedirectToLogin(c) - return - } - - q := playletser.ParseCollectListQuery(routeOrQuery(c, "page", "page"), routeOrQuery(c, "type", "type")) - if redirectCanonical(c, playletser.CollectCanonicalPath(q)) { - return - } - data := gin.H{ - "title": "我的收藏", - "description": "查看你在黄果短剧收藏的短剧与剧集,随时继续追更喜欢的内容。", - "navActive": "userCenter", - "collectType": q.Type, - "pageNum": q.PageNum, - "pageSize": q.PageSize, - "totalCount": int64(0), - "pagination": playletser.BuildCollectPagination(q, 0), - } - setNoIndex(data, false) - - if q.Type == playletser.CollectTypeChapter { - raw, code, err := playletser.FetchChapterCollectList(c, q) - if err != nil { - aborted, retry := AbortSSRIfSessionExpired(c, code) - if aborted { - return - } - if retry { - raw, _, err = playletser.FetchChapterCollectList(c, q) - } - if err != nil { - log.Error("fetch playlet chapter collect list failed", log.E(err)) - raw = &playletser.PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - } - if raw == nil { - raw = &playletser.PlayletChapterCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - data["chapterList"] = raw.List - data["pageNum"] = raw.PageNum - data["pageSize"] = raw.PageSize - data["totalCount"] = raw.TotalCount - data["pagination"] = playletser.BuildCollectPagination(q, raw.TotalCount) - } else { - raw, code, err := playletser.FetchCollectList(c, q) - if err != nil { - aborted, retry := AbortSSRIfSessionExpired(c, code) - if aborted { - return - } - if retry { - raw, _, err = playletser.FetchCollectList(c, q) - } - if err != nil { - log.Error("fetch playlet collect list failed", log.E(err)) - raw = &playletser.PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - } - if raw == nil { - raw = &playletser.PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - data["list"] = raw.List - data["pageNum"] = raw.PageNum - data["pageSize"] = raw.PageSize - data["totalCount"] = raw.TotalCount - data["pagination"] = playletser.BuildCollectPagination(q, raw.TotalCount) - } - - HTMLOK(c, "collect.html", data) -} diff --git a/app/api/web/comment.go b/app/api/web/comment.go index 6d9c17f..bc89b64 100644 --- a/app/api/web/comment.go +++ b/app/api/web/comment.go @@ -3,11 +3,8 @@ package web import ( "net/http" "strconv" - "strings" - "unicode/utf8" "videoweb/app/service/commentser" "videoweb/pub/common/log" - "videoweb/pub/common/useragent" "github.com/gin-gonic/gin" ) @@ -55,228 +52,3 @@ func CommentList(c *gin.Context) { "data": data, }) } - -type commentSendBody struct { - ObjectID int64 `json:"objectId"` - ObjectType int `json:"objectType"` - Text string `json:"text"` - ParentsID int64 `json:"parentsId"` - ReplyCommentID int64 `json:"replyCommentId"` -} - -// CommentSend 代理上游 POST /api/app/comment/send。 -// parentsId=0 一级评论;parentsId>0 二级评论;replyCommentId 为实际被回复评论。 -func CommentSend(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再发表评论", - "data": nil, - }) - return - } - - var body commentSendBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - - body.Text = strings.TrimSpace(body.Text) - if body.ObjectID <= 0 { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少 objectId", - "data": nil, - }) - return - } - if body.ObjectType != commentser.ObjectTypeChapter { - body.ObjectType = commentser.ObjectTypePlaylet - } - if body.Text == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "请输入评论内容", - "data": nil, - }) - return - } - if utf8.RuneCountInString(body.Text) > 200 { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "评论最多 200 字", - "data": nil, - }) - return - } - if body.ParentsID < 0 { - body.ParentsID = 0 - } - if body.ReplyCommentID < 0 { - body.ReplyCommentID = 0 - } - - env, err := commentser.Send(c, commentser.CommentSendReq{ - ObjectID: body.ObjectID, - ObjectType: body.ObjectType, - Text: body.Text, - ParentsID: body.ParentsID, - ReplyCommentID: body.ReplyCommentID, - }) - if err != nil { - log.Error("comment send upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "comment send failed", - "tip": "发表失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = commentser.Send(c, commentser.CommentSendReq{ - ObjectID: body.ObjectID, - ObjectType: body.ObjectType, - Text: body.Text, - ParentsID: body.ParentsID, - ReplyCommentID: body.ReplyCommentID, - }) - if err != nil { - log.Error("comment send retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "comment send failed", - "tip": "发表失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "发表失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "发表成功"), - "data": env.Data, - }) -} - -type commentLikeBody struct { - CommentID int64 `json:"commentId"` - IsLike *bool `json:"isLike"` -} - -// CommentLikeSet 代理上游 POST /api/app/comment/like/set。 -func CommentLikeSet(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再点赞", - "data": nil, - }) - return - } - - var body commentLikeBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.CommentID <= 0 || body.IsLike == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少 commentId 或 isLike", - "data": nil, - }) - return - } - - env, err := commentser.LikeSet(c, commentser.CommentLikeSetReq{ - CommentID: body.CommentID, - IsLike: *body.IsLike, - }) - if err != nil { - log.Error("comment like set upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "comment like failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = commentser.LikeSet(c, commentser.CommentLikeSetReq{ - CommentID: body.CommentID, - IsLike: *body.IsLike, - }) - if err != nil { - log.Error("comment like set retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "comment like failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "操作失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "成功"), - "data": env.Data, - }) -} diff --git a/app/api/web/deposit.go b/app/api/web/deposit.go deleted file mode 100644 index 0bcb66a..0000000 --- a/app/api/web/deposit.go +++ /dev/null @@ -1,244 +0,0 @@ -package web - -import ( - "net/http" - "strings" - "videoweb/app/service/authser" - "videoweb/app/service/rechargeser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type buyPayBody struct { - PackageID int64 `json:"packageId"` - PayMode string `json:"payMode"` -} - -// attachWalletFromGetInfo 登录态拉取 getInfo,刷新 cookie,并写入模板金币余额。 -// 返回 true 表示注册账号会话失效,调用方应跳转登录。 -func attachWalletFromGetInfo(c *gin.Context, data gin.H) bool { - if data == nil || useragent.TokenFromRequest(c.Request) == "" { - return false - } - env, user, err := authser.GetInfo(c) - if err != nil { - log.Error("wallet getInfo failed", log.E(err)) - return false - } - if env != nil && authser.IsSessionExpiredCode(env.Code) { - retry, needLogin := authser.OnSessionExpired(c, env.Code) - if needLogin { - return true - } - if retry { - env, user, err = authser.GetInfo(c) - if err != nil { - log.Error("wallet getInfo retry failed", log.E(err)) - return false - } - } - } - if env == nil || env.Code != 200 || user == nil { - return false - } - authser.SetAuthCookies(c, user) - data["userId"] = authser.FormatUserID(user.UserID) - data["recharge"] = user.Recharge - data["rechargeText"] = authser.FormatRecharge(user.Recharge) - data["vipType"] = user.VipType - data["showCardId"] = user.ShowCardID - data["showCardName"] = strings.TrimSpace(user.ShowCardName) - active := authser.IsActiveVipUser(user.VipType, user.CurrentVipExpire) - data["isVip"] = active - if active { - data["vipLabel"] = authser.VipDisplayLabel(user.ShowCardName, user.VipType) - data["vipExpireText"] = authser.FormatVipExpireText(user.CurrentVipExpire) - } else { - delete(data, "vipLabel") - delete(data, "vipExpireText") - } - return false -} - -// Deposit 金币与会员充值页 SSR:套餐列表 + 金币流水明细。 -// Query tab=coin 时默认选中金币充值套餐。 -func Deposit(c *gin.Context) { - // 先补游客会话,保证可拉流水。 - authser.EnsureGuestOnEnter(c) - - preferTab := routeOrQuery(c, "tab", "tab") - if hasQuery(c, "tab") { - target := "/deposit" - if preferTab != "" { - target += "/" + preferTab - } - target += "/" - if redirectCanonical(c, target) { - return - } - } - raw := rechargeser.FetchPackageListOrEmpty(c) - vm := rechargeser.BuildDepositPageVM(raw, preferTab) - if useragent.TokenFromRequest(c.Request) != "" { - list, code, err := rechargeser.FetchCoinTransList(c, 1, 20) - if err != nil { - retry, needLogin := authser.OnSessionExpired(c, code) - if needLogin { - RedirectToLogin(c) - return - } - if retry { - list, _, err = rechargeser.FetchCoinTransList(c, 1, 20) - } - if err != nil { - log.Error("fetch coin trans list failed", log.E(err)) - list = nil - } - } - vm.AttachTxList(list) - } - data := gin.H{ - "title": "金币与会员", - "vipList": vm.VipList, - "coinList": vm.CoinList, - "payMethods": vm.PayMethods, - "txList": vm.TxList, - "preferTab": preferTab, - } - setNoIndex(data, false) - if attachWalletFromGetInfo(c, data) { - RedirectToLogin(c) - return - } - HTMLOK(c, "deposit.html", data) -} - -// CoinBuy 代理上游 POST /api/app/coin/buy。 -func CoinBuy(c *gin.Context) { - handleBuyPay(c, "coin") -} - -// VipPay 代理上游 POST /api/app/vip/pay。 -func VipPay(c *gin.Context) { - handleBuyPay(c, "vip") -} - -func handleBuyPay(c *gin.Context, kind string) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再支付", - "data": nil, - }) - return - } - - var body buyPayBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - - body.PayMode = rechargeser.NormalizePayMode(body.PayMode) - if body.PackageID <= 0 || body.PayMode == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "请选择套餐和支付方式", - "data": nil, - }) - return - } - - req := rechargeser.BuyPayReq{ - PackageID: body.PackageID, - PayMode: body.PayMode, - } - - var ( - env *rechargeser.APIEnvelope[rechargeser.PayOrderResp] - err error - ) - switch kind { - case "coin": - env, err = rechargeser.BuyCoin(c, req) - default: - env, err = rechargeser.VipPay(c, req) - } - if err != nil { - log.Error("buy/pay upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "pay failed", - "tip": "发起支付失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - switch kind { - case "coin": - env, err = rechargeser.BuyCoin(c, req) - default: - env, err = rechargeser.VipPay(c, req) - } - if err != nil { - log.Error("buy/pay retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "pay failed", - "tip": "发起支付失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "发起支付失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - payURL := strings.TrimSpace(env.Data.PayURL) - if payURL == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "empty payUrl", - "tip": "未获取到支付链接,请稍后重试", - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "请完成支付"), - "data": gin.H{ - "isOpenNewBrowser": env.Data.IsOpenNewBrowser, - "payUrl": payURL, - "payUrlMode": env.Data.PayURLMode, - "tradeNo": env.Data.TradeNo, - }, - }) -} diff --git a/app/api/web/dialog.go b/app/api/web/dialog.go index 1c341c9..ce31a55 100644 --- a/app/api/web/dialog.go +++ b/app/api/web/dialog.go @@ -9,8 +9,6 @@ import ( var dialogTemplates = map[string]string{ "download": "downloadAppDialog", - "login": "loginDialog", - "register": "registerDialog", "search": "searchDialog", } diff --git a/app/api/web/feedback.go b/app/api/web/feedback.go index b62dd41..5307e7a 100644 --- a/app/api/web/feedback.go +++ b/app/api/web/feedback.go @@ -4,9 +4,9 @@ import ( "net/http" "strings" "unicode/utf8" + "videoweb/app/service/authser" "videoweb/app/service/feedbackser" "videoweb/pub/common/log" - "videoweb/pub/common/useragent" "github.com/gin-gonic/gin" ) @@ -28,15 +28,7 @@ type contactUsBody struct { // AddFeedback 代理上游 POST /api/app/feedback/add。 func AddFeedback(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再提交反馈", - "data": nil, - }) - return - } + authser.EnsureGuestOnEnter(c) var body feedbackBody if err := c.ShouldBindJSON(&body); err != nil { @@ -158,10 +150,7 @@ func AddFeedback(c *gin.Context) { // AddContactUs 代理上游 POST /api/app/contactus/add。 func AddContactUs(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录后再提交", "data": nil}) - return - } + authser.EnsureGuestOnEnter(c) var body contactUsBody if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1001, "msg": "参数错误", "tip": "参数错误", "data": nil}) diff --git a/app/api/web/history.go b/app/api/web/history.go deleted file mode 100644 index 9cf7c94..0000000 --- a/app/api/web/history.go +++ /dev/null @@ -1,117 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -// History 观看历史 SSR:GET /api/app/playlet/history/list。 -func History(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - RedirectToLogin(c) - return - } - - q := playletser.ParseHistoryListQuery(routeOrQuery(c, "page", "page")) - if redirectCanonical(c, playletser.HistoryCanonicalPath(q)) { - return - } - raw, code, err := playletser.FetchHistoryList(c, q) - if err != nil { - aborted, retry := AbortSSRIfSessionExpired(c, code) - if aborted { - return - } - if retry { - raw, _, err = playletser.FetchHistoryList(c, q) - } - if err != nil { - log.Error("fetch playlet history list failed", log.E(err)) - raw = &playletser.PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - } - if raw == nil { - raw = &playletser.PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - - data := gin.H{ - "title": "观看历史", - "description": "查看黄果短剧观看历史,快速回到上次看到的剧集继续观看。", - "navActive": "userCenter", - "list": raw.List, - "pageNum": raw.PageNum, - "pageSize": raw.PageSize, - "totalCount": raw.TotalCount, - "pagination": playletser.BuildHistoryPagination(q, raw.TotalCount), - "emptyTitle": "暂无观看历史", - "emptyDesc": "开始播放短剧后,记录会出现在这里", - } - setNoIndex(data, false) - HTMLOK(c, "history.html", data) -} - -// HistoryClear 代理上游 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("playlet history clear upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "history clear failed", - "tip": "清空失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = playletser.HistoryClear(c) - if err != nil { - log.Error("playlet history clear retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "history clear failed", - "tip": "清空失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "清空失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "已清空观看历史"), - "data": env.Data, - }) -} diff --git a/app/api/web/index.go b/app/api/web/index.go index 3c6122c..680c498 100644 --- a/app/api/web/index.go +++ b/app/api/web/index.go @@ -8,17 +8,6 @@ import ( // Index 首页 SSR:拉取 /api/app/playlet/home,直接用接口字段渲染。 func Index(c *gin.Context) { - renderIndex(c, "") -} - -func AuthIndex(mode string) gin.HandlerFunc { - return func(c *gin.Context) { - renderIndex(c, mode) - } -} - -func renderIndex(c *gin.Context, authMode string) { - authRedirect := c.Param("redirect") raw := playletser.FetchHomeOrEmpty(c) data := gin.H{ "title": "黄果短剧", @@ -29,11 +18,6 @@ func renderIndex(c *gin.Context, authMode string) { "homeCategoryData": raw.HomeCategoryData, "homeRecommendThemes": raw.HomeRecommendThemes, "homeBannerData": playletser.ActiveHomeBanners(raw.HomeBannerData), - "authMode": authMode, - "authRedirect": authRedirect, - } - if authMode != "" { - setNoIndex(data, true) } HTMLOK(c, "index.html", data) } diff --git a/app/api/web/play.go b/app/api/web/play.go index 2035b26..cdf94aa 100644 --- a/app/api/web/play.go +++ b/app/api/web/play.go @@ -2,9 +2,7 @@ package web import ( "strings" - "videoweb/app/service/authser" "videoweb/app/service/playletser" - "videoweb/pub/common/useragent" "github.com/gin-gonic/gin" ) @@ -56,10 +54,6 @@ func Play(c *gin.Context) { chapters = playlet.PlayletChapterInfo episodeRanges = playletser.BuildEpisodeRanges(chapters, playlet.TotalEpisode, 30) - if chapter != nil && useragent.TokenFromRequest(c.Request) != "" { - authser.SetRechargeCookie(c, chapter.Balance) - } - seoInfo := playletser.ResolvePlayPageSEO(chapter, playlet) data := gin.H{ diff --git a/app/api/web/playlet_like.go b/app/api/web/playlet_like.go deleted file mode 100644 index 6887ff0..0000000 --- a/app/api/web/playlet_like.go +++ /dev/null @@ -1,196 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/playletser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -type playletLikeBody struct { - ID int64 `json:"id"` - IsLike *bool `json:"isLike"` -} - -// PlayletLikeSet 代理上游 POST /api/app/playlet/like/set。 -func PlayletLikeSet(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再点赞", - "data": nil, - }) - return - } - - var body playletLikeBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ID <= 0 || body.IsLike == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少 id 或 isLike", - "data": nil, - }) - return - } - - env, err := playletser.LikeSet(c, playletser.PlayletLikeSetReq{ - ID: body.ID, - IsLike: *body.IsLike, - }) - if err != nil { - log.Error("playlet like set upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "playlet like failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = playletser.LikeSet(c, playletser.PlayletLikeSetReq{ - ID: body.ID, - IsLike: *body.IsLike, - }) - if err != nil { - log.Error("playlet like set retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "playlet like failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "操作失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "成功"), - "data": env.Data, - }) -} - -type playletCollectBody struct { - ID int64 `json:"id"` - IsCollect *bool `json:"isCollect"` -} - -// PlayletCollectSet 代理上游 POST /api/app/playlet/collect/set。 -func PlayletCollectSet(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - c.JSON(http.StatusOK, gin.H{ - "code": 401, - "msg": "unauthorized", - "tip": "请先登录后再收藏", - "data": nil, - }) - return - } - - var body playletCollectBody - if err := c.ShouldBindJSON(&body); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "参数错误", - "data": nil, - }) - return - } - if body.ID <= 0 || body.IsCollect == nil { - c.JSON(http.StatusOK, gin.H{ - "code": 1001, - "msg": "参数错误", - "tip": "缺少 id 或 isCollect", - "data": nil, - }) - return - } - - env, err := playletser.CollectSet(c, playletser.PlayletCollectSetReq{ - ID: body.ID, - IsCollect: *body.IsCollect, - }) - if err != nil { - log.Error("playlet collect set upstream failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "playlet collect failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - if env != nil && TryJSONSessionRecover(c, env.Code) { - env, err = playletser.CollectSet(c, playletser.PlayletCollectSetReq{ - ID: body.ID, - IsCollect: *body.IsCollect, - }) - if err != nil { - log.Error("playlet collect set retry failed", log.E(err)) - c.JSON(http.StatusOK, gin.H{ - "code": 500, - "msg": "playlet collect failed", - "tip": "操作失败,请稍后重试", - "data": nil, - }) - return - } - } - if env == nil || env.Code != 200 { - tip := "操作失败" - msg := "" - code := 500 - if env != nil { - code = env.Code - msg = env.Msg - tip = firstNonEmpty(env.Tip, env.Msg, tip) - } - c.JSON(http.StatusOK, gin.H{ - "code": code, - "msg": msg, - "tip": tip, - "data": nil, - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "msg": firstNonEmpty(env.Msg, "success"), - "tip": firstNonEmpty(env.Tip, "成功"), - "data": env.Data, - }) -} diff --git a/app/api/web/rank.go b/app/api/web/rank.go index f5046df..0dbb155 100644 --- a/app/api/web/rank.go +++ b/app/api/web/rank.go @@ -64,6 +64,7 @@ func Rank(c *gin.Context) { } data := gin.H{ "title": "排行榜", + "pageName": "排行榜", "description": "黄果短剧排行榜实时更新热播短剧,查看热门榜、飙升榜,不错过高口碑必看剧集。", "navActive": "rank", "seoInfo": raw.SeoInfo, diff --git a/app/api/web/render.go b/app/api/web/render.go index 42d8624..11b087b 100644 --- a/app/api/web/render.go +++ b/app/api/web/render.go @@ -13,7 +13,7 @@ import ( "github.com/gin-gonic/gin" ) -// HTML 渲染页面并注入 footer / 导航 / 公共配置 / 登录态(以 cookie 为准)。 +// HTML 渲染页面并注入 footer / 导航 / 公共配置,并确保存在游客会话。 func HTML(c *gin.Context, code int, name string, data gin.H) { if data == nil { data = gin.H{} @@ -69,7 +69,6 @@ func HTML(c *gin.Context, code int, name string, data gin.H) { if !ok { cfg = configser.FetchAppConfigVMOrEmpty(c) } - cfg = configser.ApplyTrackerFallback(cfg) data["appConfig"] = cfg siteURL := seoser.ResolveSiteURL(c, cfg) data["foreverUrlAddr"] = siteURL @@ -79,7 +78,6 @@ func HTML(c *gin.Context, code int, name string, data gin.H) { canonicalURL = seoser.ContentCanonicalURL(siteURL, canonicalURL, map[string]any(data)) data["canonicalURL"] = canonicalURL data["seoVerify"] = seoser.VerifyFromConfig() - attachTrackerPage(c, code, data) noindex := pageNoIndex(data) if noindex { data["noindex"] = true @@ -88,84 +86,14 @@ func HTML(c *gin.Context, code int, name string, data gin.H) { data["jsonLD"] = seoser.PageJSONLD(siteURL, canonicalURL, c.Request.URL.RequestURI(), map[string]any(data), cfg) } - // 无登录 token 时自动游客登录(POST /api/app/auth/guest),保证默认进入即有会话。 + // 无 token 时静默建立游客会话(POST /api/app/auth/guest),上游内容接口依赖该会话。 authser.EnsureGuestOnEnter(c) - loggedIn, userName, avatar, email := authser.AuthFromCookies(c) - registered := authser.IsRegisteredFromCookies(c) - data["isLoggedIn"] = loggedIn - data["isRegistered"] = registered - if _, ok := data["isVip"]; !ok { - data["isVip"] = loggedIn && authser.IsActiveVipFromCookies(c) - } - isVip, _ := data["isVip"].(bool) - data["isVip"] = isVip - if loggedIn { - if userName != "" { - data["userName"] = userName - } - if 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) - } - } - // 允许页面 handler 先注入最新钱包数据(如 getInfo),避免被 cookie 覆盖。 - 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) - } - vipType := toInt(data["vipType"]) - data["vipType"] = vipType - if _, ok := data["showCardName"]; !ok { - data["showCardName"] = authser.ShowCardNameFromCookies(c) - } - showCardName, _ := data["showCardName"].(string) - data["showCardName"] = strings.TrimSpace(showCardName) - if _, ok := data["showCardId"]; !ok { - data["showCardId"] = authser.ShowCardIDFromCookies(c) - } - if isVip { - if _, ok := data["vipLabel"]; !ok { - data["vipLabel"] = authser.VipDisplayLabel(showCardName, vipType) - } - if _, ok := data["vipExpireText"]; !ok { - data["vipExpireText"] = authser.VipExpireTextFromCookies(c) - } - } else { - delete(data, "vipLabel") - delete(data, "vipExpireText") - } - } else { - delete(data, "userName") - delete(data, "avatar") - delete(data, "email") - delete(data, "userId") - delete(data, "recharge") - delete(data, "rechargeText") - delete(data, "vipType") - delete(data, "showCardId") - delete(data, "showCardName") - delete(data, "vipLabel") - delete(data, "vipExpireText") - } - c.HTML(code, name, data) } // buildPageConsoleData 构建浏览器控制台使用的页面返回数据快照。 -// 快照在公共配置和 cookie 登录态注入前生成,避免把会话相关公共字段写入日志。 +// 快照不包含公共配置等大字段。 func buildPageConsoleData(path string, code int, templateName string, data gin.H) gin.H { pageData := make(gin.H, len(data)) for key, value := range data { @@ -191,8 +119,7 @@ func buildPageConsoleData(path string, code int, templateName string, data gin.H func pageConsoleExcludedKey(key string) bool { switch key { - case "pageConsoleData", "appConfig", "footer", "headerNav", "jsonLD", "seoVerify", - "isLoggedIn", "isRegistered", "userName", "avatar", "email", "userId": + case "pageConsoleData", "appConfig", "footer", "headerNav", "jsonLD", "seoVerify": return true default: return false @@ -312,3 +239,12 @@ func toInt64(v any) int64 { func toInt(v any) int { return int(toInt64(v)) } + +func firstNonEmpty(values ...string) string { + for _, v := range values { + if strings.TrimSpace(v) != "" { + return v + } + } + return "" +} diff --git a/app/api/web/render_test.go b/app/api/web/render_test.go index ca8b90f..cfae114 100644 --- a/app/api/web/render_test.go +++ b/app/api/web/render_test.go @@ -7,15 +7,13 @@ import ( "github.com/gin-gonic/gin" ) -func TestBuildPageConsoleDataFiltersCommonSessionFields(t *testing.T) { +func TestBuildPageConsoleDataFiltersCommonFields(t *testing.T) { payload := buildPageConsoleData("/detail/example", http.StatusOK, "detail.html", gin.H{ "title": "示例短剧", "detail": gin.H{"id": 12, "slug": "example"}, "appConfig": "public config", "headerNav": "navigation", "footer": "footer data", - "isLoggedIn": true, - "email": "viewer@example.com", "pageConsoleData": "old payload", }) @@ -32,7 +30,7 @@ func TestBuildPageConsoleDataFiltersCommonSessionFields(t *testing.T) { if payload["headerNav"] != "navigation" || payload["footer"] != "footer data" { t.Fatalf("common navigation/footer data missing: %#v", payload) } - for _, key := range []string{"appConfig", "headerNav", "isLoggedIn", "email", "pageConsoleData"} { + for _, key := range []string{"appConfig", "headerNav", "pageConsoleData"} { if _, ok := pageData[key]; ok { t.Fatalf("excluded key %q remains in console data: %#v", key, pageData) } diff --git a/app/api/web/richtext.go b/app/api/web/richtext.go index e4294bd..5d353b1 100644 --- a/app/api/web/richtext.go +++ b/app/api/web/richtext.go @@ -20,7 +20,7 @@ func Richtext(c *gin.Context) { if redirectCanonical(c, target) { return } - renderRichtext(c, title) + renderRichtext(c, title, "富文本页") } func RichtextPage(title string) gin.HandlerFunc { @@ -34,31 +34,32 @@ func RichtextPage(title string) gin.HandlerFunc { "帮助中心": "helpCenter.html", } if fallbackTemplate := fallbackTemplates[title]; fallbackTemplate != "" { - data := gin.H{"title": title} + data := gin.H{"title": title, "pageName": title} applyRichtextIndexPolicy(data, title) if title == "帮助中心" { data["faqItems"] = []seoser.FAQItem{ - {Question: "无法登录账号怎么办?", Answer: "请确认邮箱与密码是否正确;若忘记密码,可在登录页面使用“找回密码”。"}, - {Question: "观看记录没有同步?", Answer: "请确认已登录同一账号并保持网络连接,记录通常会自动同步。"}, + {Question: "付费剧集怎么观看?", Answer: "网页端可免费观看免费剧集;付费剧集请下载黄果短剧 App 观看完整内容。"}, + {Question: "需要注册账号吗?", Answer: "不需要,网页端无需注册即可直接观看免费剧集。"}, {Question: "视频无法播放或加载缓慢?", Answer: "建议切换网络、刷新页面或更新浏览器;问题持续时可提交反馈。"}, - {Question: "如何管理收藏内容?", Answer: "进入个人中心的“追剧与收藏”页面,可查看或取消收藏。"}, + {Question: "在哪里下载 App?", Answer: "点击页面上的“下载APP”入口,按提示扫码或直接下载安装。"}, } } HTMLOK(c, fallbackTemplate, data) return } } - renderRichtextData(c, title, page) + renderRichtextData(c, title, title, page) } } -func renderRichtext(c *gin.Context, title string) { +func renderRichtext(c *gin.Context, title, pageName string) { title = strings.TrimSpace(title) page := configser.FetchRichTextByTitle(c, title) - renderRichtextData(c, title, page) + renderRichtextData(c, title, pageName, page) } -func renderRichtextData(c *gin.Context, title string, page configser.RichTextPageVM) { +// pageName 为面包屑名称,与上游富文本标题可能不同。 +func renderRichtextData(c *gin.Context, title, pageName string, page configser.RichTextPageVM) { if !page.Found { NotFound(c, "未找到该内容") return @@ -73,6 +74,7 @@ func renderRichtextData(c *gin.Context, title string, page configser.RichTextPag "title": displayTitle, "richtext": page, "richtextKey": title, + "pageName": pageName, } applyRichtextIndexPolicy(data, title) HTMLOK(c, "richtext.html", data) diff --git a/app/api/web/session.go b/app/api/web/session.go index ab243a5..7d7e06c 100644 --- a/app/api/web/session.go +++ b/app/api/web/session.go @@ -6,27 +6,7 @@ import ( "github.com/gin-gonic/gin" ) -// AbortSSRIfSessionExpired 处理 SSR 场景下的会话失效码。 -// aborted=true:已跳转登录,调用方应立刻 return; -// retry=true:游客已重新登录,调用方应重试上游请求。 -func AbortSSRIfSessionExpired(c *gin.Context, code int) (aborted, retry bool) { - if !authser.IsSessionExpiredCode(code) { - return false, false - } - retry, needLogin := authser.OnSessionExpired(c, code) - if needLogin { - RedirectToLogin(c) - return true, false - } - return false, retry -} - -// TryJSONSessionRecover JSON 接口遇到会话失效时:游客重登后返回 true(应重试); -// 注册账号清 cookie 后返回 false,调用方继续把 code=1000 交给前端弹登录。 +// TryJSONSessionRecover 上游返回会话失效时重建游客会话;返回 true 表示调用方应重试一次。 func TryJSONSessionRecover(c *gin.Context, code int) bool { - if !authser.IsSessionExpiredCode(code) { - return false - } - retry, _ := authser.OnSessionExpired(c, code) - return retry + return authser.RecoverContentSession(c, code) } diff --git a/app/api/web/setting.go b/app/api/web/setting.go deleted file mode 100644 index 0391f4d..0000000 --- a/app/api/web/setting.go +++ /dev/null @@ -1,119 +0,0 @@ -package web - -import ( - "net/http" - "videoweb/app/service/authser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -// Setting 账号设置页:SSR 拉取 /api/app/auth/getInfo。 -func Setting(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - RedirectToLogin(c) - return - } - - env, user, err := authser.GetInfo(c) - if err != nil { - log.Error("setting getInfo failed", log.E(err)) - authser.ClearAuthCookies(c) - RedirectToLogin(c) - return - } - if env != nil && authser.IsSessionExpiredCode(env.Code) { - retry, needLogin := authser.OnSessionExpired(c, env.Code) - if needLogin || !retry { - RedirectToLogin(c) - return - } - env, user, err = authser.GetInfo(c) - if err != nil || env == nil || env.Code != 200 || user == nil { - authser.ClearAuthCookies(c) - RedirectToLogin(c) - return - } - } else if env == nil || env.Code != 200 || user == nil { - authser.ClearAuthCookies(c) - RedirectToLogin(c) - return - } - - authser.SetAuthCookies(c, user) - vm := authser.BuildSettingVM(user) - data := gin.H{ - "title": "账号设置", - "setting": vm, - } - setNoIndex(data, false) - HTMLOK(c, "setting.html", data) -} - -// EditProfile 编辑资料页:SSR 拉取 getInfo,头像/昵称等回填。 -func EditProfile(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - RedirectToLogin(c) - return - } - - env, user, err := authser.GetInfo(c) - if err != nil { - log.Error("edit-profile getInfo failed", log.E(err)) - authser.ClearAuthCookies(c) - RedirectToLogin(c) - return - } - if env != nil && authser.IsSessionExpiredCode(env.Code) { - retry, needLogin := authser.OnSessionExpired(c, env.Code) - if needLogin || !retry { - RedirectToLogin(c) - return - } - env, user, err = authser.GetInfo(c) - if err != nil || env == nil || env.Code != 200 || user == nil { - authser.ClearAuthCookies(c) - RedirectToLogin(c) - return - } - } else if env == nil || env.Code != 200 || user == nil { - authser.ClearAuthCookies(c) - RedirectToLogin(c) - return - } - - authser.SetAuthCookies(c, user) - vm := authser.BuildSettingVM(user) - data := gin.H{ - "title": "编辑个人资料", - "profile": vm, - } - setNoIndex(data, false) - HTMLOK(c, "editProfile.html", data) -} - -// EditPassword 修改密码页:需登录。 -func EditPassword(c *gin.Context) { - if useragent.TokenFromRequest(c.Request) == "" { - RedirectToLogin(c) - return - } - data := gin.H{ - "title": "修改登录密码", - } - setNoIndex(data, false) - HTMLOK(c, "editPassword.html", data) -} - -// 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, - }) -} diff --git a/app/api/web/today_update.go b/app/api/web/today_update.go index fe6c34f..41edfc7 100644 --- a/app/api/web/today_update.go +++ b/app/api/web/today_update.go @@ -19,6 +19,7 @@ func TodayUpdate(c *gin.Context) { } data := gin.H{ "title": "今日更新", + "pageName": "今日更新", "description": "黄果短剧今日更新:第一时间追更最新上线短剧与连载剧集,不错过每日精彩更新。", "navActive": "todayUpdate", "seoInfo": raw.SeoInfo, diff --git a/app/api/web/tracker_page.go b/app/api/web/tracker_page.go deleted file mode 100644 index 58d65a3..0000000 --- a/app/api/web/tracker_page.go +++ /dev/null @@ -1,127 +0,0 @@ -package web - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" -) - -// attachTrackerPage 按响应状态和路径注入 pageKey / pageName(handler 可显式覆盖)。 -// 所有 HTML 页面都必须得到明确页面信息,避免埋点落入 unknown / 未知页面。 -func attachTrackerPage(c *gin.Context, code int, data gin.H) { - if data == nil { - return - } - key, name := trackerPageFromRequest(c.Request.URL.Path, code) - if code >= http.StatusBadRequest { - data["pageKey"] = key - data["pageName"] = name - return - } - existingKey, _ := data["pageKey"].(string) - existingName, _ := data["pageName"].(string) - existingKey = strings.TrimSpace(existingKey) - existingName = strings.TrimSpace(existingName) - - if existingKey == "" || existingKey == "unknown" { - data["pageKey"] = key - } - if existingName == "" || existingName == "未知页面" { - data["pageName"] = name - } -} - -func trackerPageFromRequest(path string, code int) (pageKey, pageName string) { - if code >= http.StatusInternalServerError { - return "server_error", "服务异常页面" - } - if code >= http.StatusBadRequest { - return "not_found", "404页面" - } - return trackerPageFromPath(path) -} - -func trackerPageFromPath(path string) (pageKey, pageName string) { - p := strings.TrimSpace(path) - if p == "" || p == "/" { - return "home", "首页" - } - // 去掉尾部斜杠,便于精确匹配 - p = strings.TrimSuffix(p, "/") - switch { - case trackerPathHasPrefix(p, "/list"): - return "list", "短剧列表" - case trackerPathHasPrefix(p, "/series"): - return "series", "AI成人剧集" - case trackerPathHasPrefix(p, "/tag"), trackerPathHasPrefix(p, "/tag-list"): - return "tag_list", "标签详情" - case trackerPathHasPrefix(p, "/rank"): - return "rank", "排行榜" - case trackerPathHasPrefix(p, "/today-update"): - return "today_update", "今日更新" - case trackerPathHasPrefix(p, "/category"): - return "category_detail", "分类详情" - case trackerTopicIndexPath(p): - return "theme_index", "专题列表" - case trackerPathHasPrefix(p, "/topic"), trackerPathHasPrefix(p, "/more"): - return "topic", "专题详情" - case trackerPathHasPrefix(p, "/download-app"): - return "download_app", "下载APP" - case trackerPathHasPrefix(p, "/application"): - return "application", "应用推荐" - case trackerPathHasPrefix(p, "/home-road"): - return "home_road", "回家的路" - case trackerPathHasPrefix(p, "/detail"): - return "detail", "短剧详情" - case trackerPathHasPrefix(p, "/play"): - return "play", "播放页" - case trackerPathHasPrefix(p, "/login"): - return "login", "登录" - case trackerPathHasPrefix(p, "/register"): - return "register", "注册" - case trackerPathHasPrefix(p, "/user-center"): - return "user_center", "个人中心" - case trackerPathHasPrefix(p, "/deposit"): - return "deposit", "充值中心" - case trackerPathHasPrefix(p, "/collect"): - return "collect", "我的收藏" - case trackerPathHasPrefix(p, "/history"): - return "history", "观看历史" - case trackerPathHasPrefix(p, "/setting"): - return "setting", "设置" - case trackerPathHasPrefix(p, "/edit-profile"): - return "edit_profile", "编辑资料" - case trackerPathHasPrefix(p, "/edit-password"): - return "edit_password", "修改密码" - case trackerPathHasPrefix(p, "/search"): - return "search", "搜索结果页" - case trackerPathHasPrefix(p, "/about-us"): - return "about_us", "关于我们" - case trackerPathHasPrefix(p, "/user-agreement"): - return "user_agreement", "用户协议" - case trackerPathHasPrefix(p, "/privacy-policy"): - return "privacy_policy", "隐私政策" - case trackerPathHasPrefix(p, "/help-center"): - return "help_center", "帮助中心" - case trackerPathHasPrefix(p, "/richtext"): - return "rich_text", "富文本页" - case trackerPathHasPrefix(p, "/feedback"): - return "feedback", "问题反馈" - case trackerPathHasPrefix(p, "/nav"), trackerPathHasPrefix(p, "/dh"): - return "nav", "官方导航" - default: - return "internal_other", "站内其他页面" - } -} - -func trackerPathHasPrefix(path, base string) bool { - return path == base || strings.HasPrefix(path, base+"/") -} - -func trackerTopicIndexPath(path string) bool { - if path == "/topic" || path == "/more" { - return true - } - return strings.HasPrefix(path, "/topic/page/") || strings.HasPrefix(path, "/more/page/") -} diff --git a/app/api/web/tracker_page_test.go b/app/api/web/tracker_page_test.go deleted file mode 100644 index f91d392..0000000 --- a/app/api/web/tracker_page_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package web - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/gin-gonic/gin" -) - -func TestTrackerPageFromPath(t *testing.T) { - tests := []struct { - path string - key string - name string - }{ - {"/", "home", "首页"}, - {"/list/ai-short-play/2/page/3/", "list", "短剧列表"}, - {"/series/hot/page/2/", "series", "AI成人剧集"}, - {"/tag/family/page/2/", "tag_list", "标签详情"}, - {"/tag-list/family/", "tag_list", "标签详情"}, - {"/rank/2/page/3/", "rank", "排行榜"}, - {"/today-update/page/2/", "today_update", "今日更新"}, - {"/category/drama/page/2/", "category_detail", "分类详情"}, - {"/topic/page/2/", "theme_index", "专题列表"}, - {"/topic/sweet-love/page/2/", "topic", "专题详情"}, - {"/more/legacy-topic/", "topic", "专题详情"}, - {"/detail/my-playlet/", "detail", "短剧详情"}, - {"/play/my-playlet/42/", "play", "播放页"}, - {"/login/return/detail/foo/", "login", "登录"}, - {"/register/", "register", "注册"}, - {"/collect/chapter/page/2/", "collect", "我的收藏"}, - {"/search/测试/page/2/", "search", "搜索结果页"}, - {"/about-us/", "about_us", "关于我们"}, - {"/user-agreement/", "user_agreement", "用户协议"}, - {"/privacy-policy/", "privacy_policy", "隐私政策"}, - {"/help-center/", "help_center", "帮助中心"}, - {"/richtext/自定义内容/", "rich_text", "富文本页"}, - {"/dh/", "nav", "官方导航"}, - {"/unregistered-path/", "internal_other", "站内其他页面"}, - } - - for _, tt := range tests { - t.Run(tt.path, func(t *testing.T) { - key, name := trackerPageFromPath(tt.path) - if key != tt.key || name != tt.name { - t.Fatalf("trackerPageFromPath(%q) = (%q, %q), want (%q, %q)", tt.path, key, name, tt.key, tt.name) - } - }) - } -} - -func TestTrackerPageFromRequestUsesResponseStatus(t *testing.T) { - tests := []struct { - status int - key string - name string - }{ - {http.StatusNotFound, "not_found", "404页面"}, - {http.StatusInternalServerError, "server_error", "服务异常页面"}, - } - for _, tt := range tests { - key, name := trackerPageFromRequest("/missing/", tt.status) - if key != tt.key || name != tt.name { - t.Fatalf("trackerPageFromRequest(status=%d) = (%q, %q), want (%q, %q)", tt.status, key, name, tt.key, tt.name) - } - } -} - -func TestAttachTrackerPagePreservesExplicitPage(t *testing.T) { - gin.SetMode(gin.TestMode) - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Request = httptest.NewRequest(http.MethodGet, "/topic/custom/", nil) - data := gin.H{"pageKey": "topic", "pageName": "自定义专题"} - - attachTrackerPage(c, http.StatusOK, data) - - if data["pageKey"] != "topic" || data["pageName"] != "自定义专题" { - t.Fatalf("explicit tracker page was overwritten: %#v", data) - } -} - -func TestAttachTrackerPageReplacesUnknownValues(t *testing.T) { - gin.SetMode(gin.TestMode) - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Request = httptest.NewRequest(http.MethodGet, "/play/demo/1/", nil) - data := gin.H{"pageKey": "unknown", "pageName": "未知页面"} - - attachTrackerPage(c, http.StatusOK, data) - - if data["pageKey"] != "play" || data["pageName"] != "播放页" { - t.Fatalf("unknown tracker page was not replaced: %#v", data) - } -} - -func TestAttachTrackerPageMarksErrorResponse(t *testing.T) { - gin.SetMode(gin.TestMode) - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Request = httptest.NewRequest(http.MethodGet, "/topic/deleted/", nil) - data := gin.H{"pageKey": "topic", "pageName": "已删除专题"} - - attachTrackerPage(c, http.StatusNotFound, data) - - if data["pageKey"] != "not_found" || data["pageName"] != "404页面" { - t.Fatalf("error response retained business page mapping: %#v", data) - } -} diff --git a/app/api/web/user_center.go b/app/api/web/user_center.go deleted file mode 100644 index 033fe6d..0000000 --- a/app/api/web/user_center.go +++ /dev/null @@ -1,58 +0,0 @@ -package web - -import ( - "strconv" - "videoweb/app/service/playletser" - "videoweb/pub/common/log" - "videoweb/pub/common/useragent" - - "github.com/gin-gonic/gin" -) - -// UserCenter 个人中心:登录态刷新 getInfo 金币余额,并拉取收藏数量。 -func UserCenter(c *gin.Context) { - data := gin.H{ - "title": "个人中心", - "navActive": "userCenter", - } - setNoIndex(data, false) - if attachWalletFromGetInfo(c, data) { - RedirectToLogin(c) - return - } - if useragent.TokenFromRequest(c.Request) != "" { - raw, code, err := playletser.FetchCollectList(c, playletser.CollectListQuery{ - PageNum: 1, - PageSize: 1, - }) - if err != nil { - aborted, retry := AbortSSRIfSessionExpired(c, code) - if aborted { - return - } - if retry { - raw, _, err = playletser.FetchCollectList(c, playletser.CollectListQuery{ - PageNum: 1, - PageSize: 1, - }) - } - if err != nil { - log.Error("user-center collect count failed", log.E(err)) - raw = nil - } - } - count := int64(0) - if raw != nil { - count = raw.TotalCount - } - if count < 0 { - count = 0 - } - data["collectCount"] = count - data["collectCountText"] = strconv.FormatInt(count, 10) + "部" - } else { - data["collectCount"] = int64(0) - data["collectCountText"] = "0部" - } - HTMLOK(c, "userCenter.html", data) -} diff --git a/app/appg/appg.go b/app/appg/appg.go index e6de45f..e634012 100644 --- a/app/appg/appg.go +++ b/app/appg/appg.go @@ -10,13 +10,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 相关配置。 @@ -25,9 +24,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"` -} diff --git a/app/conf/app.yaml b/app/conf/app.yaml index cb3b621..a97e69b 100644 --- a/app/conf/app.yaml +++ b/app/conf/app.yaml @@ -1,11 +1,6 @@ -serverAddr: "http://172.104.181.99:27413" +serverAddr: "https://webhgdjapi.mfcg.me" listenAddr: "25119" dhListenAddr: "26812" runMod: "dev" tsAuthKey: "fT5xSg4hltHpzVy6aV9rVECDJ1J1pN" -# 数据上报 SDK(web-sdk)。encryptedConfig 优先用上游 config/info.reportSdkConfig; -# 若上游暂未下发,可在此填写密文兜底。 -tracker: - appId: "JHA-1043" - encryptedConfig: "" diff --git a/app/router/gin.go b/app/router/gin.go index 42e54f8..bf4055c 100644 --- a/app/router/gin.go +++ b/app/router/gin.go @@ -9,7 +9,6 @@ import ( "strings" "videoweb/app/api/web" "videoweb/app/appg" - "videoweb/app/service/configser" "videoweb/app/service/playletser" "videoweb/app/service/seoser" @@ -62,7 +61,6 @@ func setupEngine(engine *gin.Engine) { tplFuncs := playletser.TemplateFuncs() tplFuncs["InJSONScript"] = inJSONScript tplFuncs["jsonStr"] = jsonStr - tplFuncs["InTrackerAppId"] = inTrackerAppId engine.SetFuncMap(tplFuncs) engine.LoadHTMLFiles(listHTMLTemplates("templates")...) engine.Use(staticCacheHeaders(), crawlerHeaders()) @@ -145,42 +143,19 @@ func jsonStr(s string) template.JS { return template.JS(b) } -func inTrackerAppId() string { - return configser.ResolveTrackerAppID() -} - func registerRoutes(r *gin.Engine) { r.GET("/", web.Index) r.GET("/ui/dialog/:name", web.DialogFragment) - r.POST("/api/auth/register", web.Register) - r.POST("/api/auth/login", web.Login) - r.POST("/api/auth/resetPassword", web.ResetPassword) - r.POST("/api/auth/guest", web.Guest) - r.POST("/api/auth/logout", web.Logout) - r.POST("/api/auth/profile", web.UpdateProfile) - r.GET("/api/auth/getInfo", web.GetUserInfo) - r.POST("/api/upload/image", web.UploadImage) r.POST("/api/feedback/add", web.AddFeedback) r.POST("/api/contactus/add", web.AddContactUs) 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/like/set", web.PlayletLikeSet) - r.POST("/api/playlet/collect/set", web.PlayletCollectSet) r.POST("/api/playlet/forward/heat", web.PlayletForwardHeat) r.GET("/api/playlet/chapter/detail", web.ChapterDetail) r.GET("/api/playlet/chapter/list", web.ChapterList) r.GET("/api/playlet/operate/list", web.OperateList) r.GET("/api/playlet/hot-like", web.HotLike) r.GET("/api/app/playlet/hot-like", web.HotLike) - 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/history/clear", web.HistoryClear) - r.POST("/api/playlet/chapter/unlock", web.ChapterUnlock) - r.POST("/api/coin/buy", web.CoinBuy) - r.POST("/api/vip/pay", web.VipPay) r.GET("/list/", web.List) r.GET("/list/:slug/", web.List) @@ -239,26 +214,6 @@ func registerRoutes(r *gin.Engine) { r.GET("/play/:slug/", web.Play) r.GET("/play/:slug/:chapterId/", web.Play) - r.GET("/login/", web.AuthIndex("login")) - r.GET("/login/return/*redirect", web.AuthIndex("login")) - r.GET("/register/", web.AuthIndex("register")) - - r.GET("/user-center/", web.UserCenter) - - r.GET("/deposit/", web.Deposit) - r.GET("/deposit/:tab/", web.Deposit) - - r.GET("/collect/", web.Collect) - r.GET("/collect/page/:page/", web.Collect) - r.GET("/collect/:type/", web.Collect) - r.GET("/collect/:type/page/:page/", web.Collect) - - r.GET("/history/", web.History) - r.GET("/history/page/:page/", web.History) - - r.GET("/setting/", web.Setting) - r.GET("/edit-profile/", web.EditProfile) - r.GET("/edit-password/", web.EditPassword) r.GET("/search/", web.Search) r.GET("/search/:keyword/", web.Search) r.GET("/search/:keyword/page/:page/", web.Search) diff --git a/app/router/gin_test.go b/app/router/gin_test.go index a40b31c..30f9aa8 100644 --- a/app/router/gin_test.go +++ b/app/router/gin_test.go @@ -46,7 +46,6 @@ func TestContentPagesOnlyRegisterPathParameterRoutes(t *testing.T) { } } for _, route := range []string{ - "POST /api/auth/resetPassword", "POST /api/contactus/add", "GET /api/playlet/chapter/list", "GET /api/playlet/operate/list", diff --git a/app/router/nav_tracking_template_test.go b/app/router/nav_tracking_template_test.go index 50feafd..ab99d7c 100644 --- a/app/router/nav_tracking_template_test.go +++ b/app/router/nav_tracking_template_test.go @@ -48,9 +48,8 @@ func TestMainSiteKeepsMainVerificationAndAnalytics(t *testing.T) { func renderSEOCommonForTrackingTest(t *testing.T, isNavPage bool) string { t.Helper() tpl, err := template.New("tracking-test").Funcs(template.FuncMap{ - "InTrackerAppId": func() string { return "test-app" }, "jsonStr": func(any) template.JS { return template.JS(`""`) }, - }).ParseFiles("../templates/base/seoCommon.html", "../templates/base/trackerInit.html") + }).ParseFiles("../templates/base/seoCommon.html", "../templates/base/analytics.html") if err != nil { t.Fatal(err) } @@ -58,8 +57,7 @@ func renderSEOCommonForTrackingTest(t *testing.T, isNavPage bool) string { "isNavPage": isNavPage, "seoVerify": map[string]any{"Baidu": ""}, "appConfig": map[string]any{ - "ReportSDKConfig": "", - "SeoInfo": map[string]any{"WebSiteName": ""}, + "SeoInfo": map[string]any{"WebSiteName": ""}, }, } var out bytes.Buffer diff --git a/app/service/authser/auth.go b/app/service/authser/auth.go index 519e1f7..6568c6b 100644 --- a/app/service/authser/auth.go +++ b/app/service/authser/auth.go @@ -3,10 +3,8 @@ package authser import ( "encoding/json" "fmt" - "io" "net/http" "net/url" - "strconv" "strings" "time" "videoweb/app/service/upstream" @@ -16,58 +14,11 @@ import ( ) 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 创建或找回游客账号)。 +// GuestLogin 调用上游游客登录,站点不提供注册/登录,上游内容接口依赖游客会话。 func GuestLogin(c *gin.Context, req GuestLoginReq) (*APIEnvelope, *LoginResp, error) { var env APIEnvelope if _, err := upstream.PostJSON(c, guestPath, req, &env); err != nil { @@ -83,47 +34,6 @@ func GuestLogin(c *gin.Context, req GuestLoginReq) (*APIEnvelope, *LoginResp, er 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") @@ -135,7 +45,7 @@ func decodeLoginData(raw json.RawMessage) (*LoginResp, error) { return &user, nil } -// SetAuthCookies 将登录/注册返回写入 cookie。 +// SetAuthCookies 写入游客会话 cookie。 func SetAuthCookies(c *gin.Context, user *LoginResp) { if c == nil || user == nil { return @@ -144,33 +54,13 @@ func SetAuthCookies(c *gin.Context, user *LoginResp) { 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, "introduction", user.Introduction, secure) - setCookie(c, "avatar", user.Avatar, secure) - setCookie(c, "email", user.Email, secure) - setCookie(c, "inviteCode", user.InviteCode, 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, "showCardId", strconv.FormatInt(user.ShowCardID, 10), secure) - setCookie(c, "showCardName", user.ShowCardName, secure) - setCookie(c, "userType", strconv.Itoa(user.Type), secure) - isReg := "0" - if user.IsRegister || user.Type == 1 { - isReg = "1" - } - setCookie(c, "isRegister", isReg, secure) if sid := useragent.SIDFromRequest(c.Request); sid != "" { setCookie(c, "sid", sid, secure) } } func setCookie(c *gin.Context, name, value string, secure bool) { - // Cookie Value 仅允许 ASCII;中文昵称等需 URL 编码。 - // 使用 PathEscape(空格→%20),与前端 encodeURIComponent 一致; - // 避免 QueryEscape 的空格→+,否则 JS decodeURIComponent 会留下字面量 "+"。 + // Cookie Value 仅允许 ASCII;使用 PathEscape(空格→%20),与前端 encodeURIComponent 一致。 http.SetCookie(c.Writer, &http.Cookie{ Name: name, Value: url.PathEscape(value), @@ -199,7 +89,7 @@ func cookieValue(c *gin.Context, name string) string { return raw } -// ClearAuthCookies 清除登录相关 cookie。 +// ClearAuthCookies 清除会话 cookie,同时清理旧版账号体系遗留的用户信息 cookie。 func ClearAuthCookies(c *gin.Context) { for _, name := range authCookieNames() { http.SetCookie(c.Writer, &http.Cookie{ @@ -218,44 +108,6 @@ func authCookieNames() []string { "token", "_token_", "sid", "_s_token_", "userId", "accountNo", "nickName", "introduction", "avatar", "email", "inviteCode", "recharge", "vipType", "currentVipExpire", "showCardId", "showCardName", - "userType", "isRegister", + "userType", "isRegister", "guest_skip_auto", } } - -// 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 index b8abae9..29548a4 100644 --- a/app/service/authser/guest_enter.go +++ b/app/service/authser/guest_enter.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "encoding/hex" "fmt" - "net/http" "net/url" "strconv" "strings" @@ -16,24 +15,17 @@ import ( ) const guestDevIDCookie = "guest_dev_id" -const guestSkipAutoCookie = "guest_skip_auto" -// EnsureGuestOnEnter 页面进入时:无 token(未自己登录)则走游客登录并写 cookie。 -// 对应上游 POST /api/app/auth/guest。成功后会把会话补进当前 Request,便于同一次 SSR 读到登录态。 -// 手动退出后带 guest_skip_auto 时跳过本次;清掉标记后,下次进页再自动游客。 +// EnsureGuestOnEnter 页面进入时:无 token 则走游客登录并写 cookie。 +// 对应上游 POST /api/app/auth/guest。成功后会把会话补进当前 Request,便于同一次 SSR 直接使用。 func EnsureGuestOnEnter(c *gin.Context) *LoginResp { if c == nil || c.Request == nil { return nil } - // 已有任意会话(游客或正式账号)则跳过 + // 已有会话则跳过 if useragent.TokenFromRequest(c.Request) != "" { return nil } - // 手动退出:本次进页不自动游客,并清除标记,供下次进页恢复 - if IsGuestSkipAuto(c) { - ClearGuestSkipAuto(c) - return nil - } devID := ensureGuestDevID(c) env, user, err := GuestLogin(c, GuestLoginReq{ @@ -55,25 +47,10 @@ func EnsureGuestOnEnter(c *gin.Context) *LoginResp { } SetAuthCookies(c, user) - ClearGuestSkipAuto(c) - patchAuthCookiesToRequest(c, user) + patchRequestCookie(c, "token", user.Token) return user } -// RefreshGuestSession 游客会话失效时(如上游 6001 用户不存在)重新游客登录。 -// 正式注册账号不处理,避免把用户踢成游客。 -func RefreshGuestSession(c *gin.Context) *LoginResp { - if c == nil || c.Request == nil { - return nil - } - if IsRegisteredFromCookies(c) { - return nil - } - dropRequestCookie(c, "token") - dropRequestCookie(c, "_token_") - return EnsureGuestOnEnter(c) -} - func dropRequestCookie(c *gin.Context, name string) { if c == nil || c.Request == nil || name == "" { return @@ -88,43 +65,6 @@ func dropRequestCookie(c *gin.Context, name string) { c.Request.Header.Set("Cookie", strings.Join(parts, "; ")) } -// MarkGuestSkipAuto 手动退出后标记:跳过紧接着的一次自动游客登录。 -func MarkGuestSkipAuto(c *gin.Context) { - if c == nil { - return - } - secure := c.Request != nil && c.Request.TLS != nil - http.SetCookie(c.Writer, &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 - } - http.SetCookie(c.Writer, &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 @@ -164,31 +104,6 @@ func guestChannel(c *gin.Context) string { return "" } -func patchAuthCookiesToRequest(c *gin.Context, user *LoginResp) { - if c == nil || user == nil { - return - } - 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 diff --git a/app/service/authser/session.go b/app/service/authser/session.go index 3c33a77..4b5f7bc 100644 --- a/app/service/authser/session.go +++ b/app/service/authser/session.go @@ -2,7 +2,7 @@ package authser import "github.com/gin-gonic/gin" -// TokenExpiredCode 上游约定:token 失效,需重新登录。 +// TokenExpiredCode 上游约定:token 失效。 const TokenExpiredCode = 1000 // IsSessionExpiredCode 是否为会话失效类业务码。 @@ -15,31 +15,7 @@ func IsSessionExpiredCode(code int) bool { } } -// 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_") - return false, true - } - if RefreshGuestSession(c) != nil { - return true, false - } - ClearAuthCookies(c) - dropRequestCookie(c, "token") - dropRequestCookie(c, "_token_") - return false, false -} - -// RecoverContentSession 用于允许游客访问的内容读取接口。 -// 正式账号或游客 token 失效后,清理旧登录态、重新建立游客会话,调用方随后重试一次原请求。 -// 写操作仍应使用 OnSessionExpired,避免把用户操作静默降级为游客操作。 +// RecoverContentSession 会话失效后清理旧 cookie 并重新建立游客会话,调用方随后重试一次原请求。 func RecoverContentSession(c *gin.Context, code int) bool { if c == nil || !IsSessionExpiredCode(code) { return false @@ -48,7 +24,5 @@ func RecoverContentSession(c *gin.Context, code int) bool { for _, name := range authCookieNames() { dropRequestCookie(c, name) } - dropRequestCookie(c, guestSkipAutoCookie) - ClearGuestSkipAuto(c) return EnsureGuestOnEnter(c) != nil } diff --git a/app/service/authser/types.go b/app/service/authser/types.go index b8316df..12cf50a 100644 --- a/app/service/authser/types.go +++ b/app/service/authser/types.go @@ -2,31 +2,6 @@ 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 { @@ -36,39 +11,10 @@ type GuestLoginReq struct { 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。 +// LoginResp 游客登录成功 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"` // 是否已注册 - LoginType int `json:"loginType"` // 登录方式 - PrivacyMode bool `json:"privacyMode"` // 隐私模式 - Score int64 `json:"score"` // 积分 - Recharge int64 `json:"recharge"` // 金币余额 + Token string `json:"token"` + UserID int64 `json:"userId"` } // APIEnvelope 上游统一响应壳。Data 在失败时可能是 string/null。 @@ -78,17 +24,3 @@ type APIEnvelope struct { 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 66ad8c6..0000000 --- a/app/service/authser/view.go +++ /dev/null @@ -1,87 +0,0 @@ -package authser - -import ( - "fmt" - "strings" -) - -// SettingPageVM 账号设置页展示模型。 -type SettingPageVM struct { - UserID int64 - AccountNo string - 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) - 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, - 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/send.go b/app/service/commentser/send.go deleted file mode 100644 index 7142f06..0000000 --- a/app/service/commentser/send.go +++ /dev/null @@ -1,45 +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") - } - if req.ObjectType != ObjectTypeChapter { - req.ObjectType = ObjectTypePlaylet - } - 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/tracker.go b/app/service/configser/tracker.go deleted file mode 100644 index 87c7bfb..0000000 --- a/app/service/configser/tracker.go +++ /dev/null @@ -1,29 +0,0 @@ -package configser - -import ( - "strings" - "videoweb/app/appg" - "videoweb/pub/pubdef" -) - -// ResolveTrackerAppID 优先 yaml,否则固定 JHA-1043。 -func ResolveTrackerAppID() string { - if id := strings.TrimSpace(appg.Conf.Tracker.AppID); id != "" { - return id - } - return pubdef.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 293542d..8796101 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 b3fcbb7..7d75eeb 100644 --- a/app/service/configser/view.go +++ b/app/service/configser/view.go @@ -24,7 +24,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"` @@ -54,7 +53,6 @@ func BuildAppConfigVM(info *ConfigInfoResp) AppConfigVM { OfficialAppAddr: sanitizeURL(addr.OfficialAppAddr), PDFAddr: sanitizeURL(addr.PDFAddr), TwitterAddr: sanitizeURL(addr.TwitterAddr), - ReportSDKConfig: strings.TrimSpace(info.ReportSDKConfig), NavPageList: buildNavPageList(info.NavPageList), Advitise: info.Advitise, DetailTopList: buildAdvertisementList(info.Advitise.DetailTopList), diff --git a/app/service/playletser/chapter.go b/app/service/playletser/chapter.go index cf47424..256c5be 100644 --- a/app/service/playletser/chapter.go +++ b/app/service/playletser/chapter.go @@ -139,20 +139,6 @@ func (c *PlayletChapterInfoResp) AllowWatch() bool { return strings.TrimSpace(c.VideoURL) != "" && c.PayType == 0 } -// UnlockCost 金币解锁消耗:优先 costCoins。 -func (c *PlayletChapterInfoResp) UnlockCost() int64 { - if c == nil { - return 0 - } - if c.CostCoins > 0 { - return c.CostCoins - } - if c.SingleCoins > 0 { - return int64(c.SingleCoins) - } - return 0 -} - // AllowWatch 详情选集是否可直达播放(优先 canWatch)。 func (c PlayletChapterData) AllowWatch() bool { if c.CanWatch { @@ -166,33 +152,3 @@ func (c PlayletChapterData) AllowWatch() bool { } return c.PayType == 0 } - -// NeedCoinGate 是否弹出金币解锁。 -func (c PlayletChapterData) NeedCoinGate() bool { - if c.AllowWatch() { - return false - } - if c.NeedUnlock { - return true - } - return c.PayType == 1 -} - -// NeedVipGate 是否弹出 VIP 开通。 -func (c PlayletChapterData) NeedVipGate() bool { - if c.AllowWatch() || c.NeedCoinGate() { - return false - } - if c.NeedVip { - return true - } - return c.PayType == 2 -} - -// UnlockCost 金币解锁消耗:优先 costCoins。 -func (c PlayletChapterData) UnlockCost() int { - if c.CostCoins > 0 { - return int(c.CostCoins) - } - return c.SingleCoins -} 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 890002e..0000000 --- a/app/service/playletser/collect.go +++ /dev/null @@ -1,155 +0,0 @@ -package playletser - -import ( - "fmt" - "net/url" - "strconv" - "strings" - "videoweb/app/service/authser" - "videoweb/app/service/upstream" - "videoweb/pub/common/log" - - "github.com/gin-gonic/gin" -) - -const ( - collectSetPath = "/api/app/playlet/collect/set" - collectListPath = "/api/app/playlet/collect/list" - defaultCollectPageSize = 40 - - CollectTypePlaylet = "playlet" - CollectTypeChapter = "chapter" -) - -// PlayletCollectSetReq 对应 swagger proto.PlayletCollectSetReq -type PlayletCollectSetReq struct { - ID int64 `json:"id"` - IsCollect bool `json:"isCollect"` -} - -// PlayletCollectSetResp 对应 swagger proto.PlayletCollectSetResp -type PlayletCollectSetResp struct { - ID int64 `json:"id"` - CountCollect int64 `json:"countCollect"` - IsCollect int `json:"isCollect"` -} - -// PlayletCollectData 对应 swagger proto.PlayletCollectData -type PlayletCollectData struct { - ID int64 `json:"id"` - Title string `json:"title"` - Slug string `json:"slug"` - Cover string `json:"cover"` - Desc string `json:"desc"` - TagIDs []int64 `json:"tagIds"` - TagNames []string `json:"tagNames"` - TagSlugs []string `json:"tagSlugs"` - ThemeIDs []int64 `json:"themeIds"` - ThemeNames []string `json:"themeNames"` - ThemeSlugs []string `json:"themeSlugs"` - TotalEpisode int `json:"totalEpisode"` - UpdateStatus int `json:"updateStatus"` - CountBrowse int64 `json:"countBrowse"` - CountCollect int64 `json:"countCollect"` - CountLike int64 `json:"countLike"` - IsFeatured bool `json:"isFeatured"` - IsNew bool `json:"isNew"` - IsRecommend bool `json:"isRecommend"` - IsCollect int `json:"isCollect"` - CollectedAt string `json:"collectedAt"` - WatchedCount int `json:"watchedCount"` - UnwatchedCount int `json:"unwatchedCount"` - LastChapterID int64 `json:"lastChapterId"` - LastEpisode int `json:"lastEpisode"` -} - -// PlayletCollectListResp 对应 swagger proto.PlayletCollectListResp -type PlayletCollectListResp struct { - List []PlayletCollectData `json:"list"` - PageNum int `json:"pageNum"` - PageSize int `json:"pageSize"` - TotalCount int64 `json:"totalCount"` -} - -// CollectListQuery 收藏列表分页参数。 -type CollectListQuery struct { - PageNum int - PageSize int - Type string // playlet | chapter -} - -// NormalizeCollectType 归一化收藏类型。 -func NormalizeCollectType(raw string) string { - switch strings.TrimSpace(strings.ToLower(raw)) { - case CollectTypeChapter, "2", "episode", "ep": - return CollectTypeChapter - default: - return CollectTypePlaylet - } -} - -// Normalize 校正分页默认值。 -func (q *CollectListQuery) Normalize() { - if q.PageNum <= 0 { - q.PageNum = 1 - } - if q.PageSize <= 0 { - q.PageSize = defaultCollectPageSize - } - if q.PageSize > 50 { - q.PageSize = 50 - } - q.Type = NormalizeCollectType(q.Type) -} - -// CollectSet 设置短剧收藏/取消收藏。 -func CollectSet(c *gin.Context, req PlayletCollectSetReq) (*APIEnvelope[PlayletCollectSetResp], error) { - if req.ID <= 0 { - return nil, fmt.Errorf("playlet collect invalid id") - } - var env APIEnvelope[PlayletCollectSetResp] - if _, err := upstream.PostJSON(c, collectSetPath, req, &env); err != nil { - return nil, err - } - return &env, nil -} - -// FetchCollectList 拉取当前用户短剧收藏列表。 -// 第二个返回值为上游业务 code(网络错误时为 0)。 -func FetchCollectList(c *gin.Context, q CollectListQuery) (*PlayletCollectListResp, int, error) { - q.Normalize() - params := url.Values{} - params.Set("pageNum", strconv.Itoa(q.PageNum)) - params.Set("pageSize", strconv.Itoa(q.PageSize)) - - var env APIEnvelope[PlayletCollectListResp] - if err := upstream.GetJSON(c, collectListPath+"?"+params.Encode(), &env); err != nil { - return nil, 0, err - } - if env.Code != 200 { - return nil, env.Code, fmt.Errorf("playlet collect list code=%d msg=%s", env.Code, env.Msg) - } - return &env.Data, env.Code, nil -} - -// FetchCollectListOrEmpty 失败时返回空列表;游客会话失效时尝试重登后重试一次。 -func FetchCollectListOrEmpty(c *gin.Context, q CollectListQuery) *PlayletCollectListResp { - data, code, err := FetchCollectList(c, q) - if err == nil { - if data == nil { - return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - return data - } - if retry, _ := authser.OnSessionExpired(c, code); retry { - data, _, err = FetchCollectList(c, q) - if err == nil { - if data == nil { - return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - return data - } - } - log.Error("fetch playlet collect list failed", log.E(err)) - return &PlayletCollectListResp{PageNum: q.PageNum, PageSize: q.PageSize} -} diff --git a/app/service/playletser/collect_view.go b/app/service/playletser/collect_view.go deleted file mode 100644 index 1fe9fbd..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/history.go b/app/service/playletser/history.go deleted file mode 100644 index 35e8ff9..0000000 --- a/app/service/playletser/history.go +++ /dev/null @@ -1,139 +0,0 @@ -package playletser - -import ( - "fmt" - "net/url" - "strconv" - "videoweb/app/service/authser" - "videoweb/app/service/upstream" - "videoweb/pub/common/log" - - "github.com/gin-gonic/gin" -) - -const ( - historyListPath = "/api/app/playlet/history/list" - historyClearPath = "/api/app/playlet/history/clear" - defaultHistoryPageSize = 40 -) - -// PlayletHistoryData 对应 swagger proto.PlayletHistoryData -// GET /api/app/playlet/history/list -type PlayletHistoryData struct { - ID int64 `json:"id"` - Status int `json:"status"` - IsFeatured bool `json:"isFeatured"` - IsNew bool `json:"isNew"` - IsRecommend bool `json:"isRecommend"` - ShowOnBanner bool `json:"showOnBanner"` - Banner string `json:"banner"` - OnlineAt string `json:"onlineAt"` - MediaCreatedAt string `json:"mediaCreatedAt"` - MediaUpdatedAt string `json:"mediaUpdatedAt"` - ThemeIDs []int64 `json:"themeIds"` - ThemeNames []string `json:"themeNames"` - ThemeSlugs []string `json:"themeSlugs"` - Title string `json:"title"` - Slug string `json:"slug"` - Desc string `json:"desc"` - Anchor string `json:"anchor"` - Cover string `json:"cover"` - TagIDs []int64 `json:"tagIds"` - TagNames []string `json:"tagNames"` - TagSlugs []string `json:"tagSlugs"` - TotalEpisode int `json:"totalEpisode"` - UpdateStatus int `json:"updateStatus"` - CountBrowse int64 `json:"countBrowse"` - CountCollect int64 `json:"countCollect"` - CountLike int64 `json:"countLike"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` - WatchedCount int `json:"watchedCount"` - UnwatchedCount int `json:"unwatchedCount"` - LastChapterID int64 `json:"lastChapterId"` - LastEpisode int `json:"lastEpisode"` - IsFinished bool `json:"isFinished"` - HistoryAt string `json:"historyAt"` -} - -// PlayletHistoryListResp 对应 swagger proto.PlayletHistoryListResp -type PlayletHistoryListResp struct { - List []PlayletHistoryData `json:"list"` - PageNum int `json:"pageNum"` - PageSize int `json:"pageSize"` - TotalCount int64 `json:"totalCount"` -} - -// PlayletHistoryClearResp 对应 swagger proto.PlayletHistoryClearResp -type PlayletHistoryClearResp struct { - DeletedCount int `json:"deletedCount"` -} - -// HistoryListQuery 观看历史分页参数。 -type HistoryListQuery struct { - PageNum int - PageSize int -} - -// Normalize 校正分页默认值。 -func (q *HistoryListQuery) Normalize() { - if q.PageNum <= 0 { - q.PageNum = 1 - } - if q.PageSize <= 0 { - q.PageSize = defaultHistoryPageSize - } - if q.PageSize > 100 { - q.PageSize = 100 - } -} - -// FetchHistoryList 分页查询当前用户观看历史。 -// 第二个返回值为上游业务 code(网络错误时为 0)。 -func FetchHistoryList(c *gin.Context, q HistoryListQuery) (*PlayletHistoryListResp, int, error) { - q.Normalize() - params := url.Values{} - params.Set("pageNum", strconv.Itoa(q.PageNum)) - params.Set("pageSize", strconv.Itoa(q.PageSize)) - - var env APIEnvelope[PlayletHistoryListResp] - if err := upstream.GetJSON(c, historyListPath+"?"+params.Encode(), &env); err != nil { - return nil, 0, err - } - if env.Code != 200 { - return nil, env.Code, fmt.Errorf("playlet history list code=%d msg=%s", env.Code, env.Msg) - } - return &env.Data, env.Code, nil -} - -// FetchHistoryListOrEmpty 失败时返回空列表;游客会话失效时尝试重登后重试一次。 -func FetchHistoryListOrEmpty(c *gin.Context, q HistoryListQuery) *PlayletHistoryListResp { - data, code, err := FetchHistoryList(c, q) - if err == nil { - if data == nil { - return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - return data - } - if retry, _ := authser.OnSessionExpired(c, code); retry { - data, _, err = FetchHistoryList(c, q) - if err == nil { - if data == nil { - return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize} - } - return data - } - } - log.Error("fetch playlet history list failed", log.E(err)) - return &PlayletHistoryListResp{PageNum: q.PageNum, PageSize: q.PageSize} -} - -// HistoryClear 清空当前用户观看历史。 -// POST /api/app/playlet/history/clear,无请求体。 -func HistoryClear(c *gin.Context) (*APIEnvelope[PlayletHistoryClearResp], error) { - var env APIEnvelope[PlayletHistoryClearResp] - if _, err := upstream.PostJSON(c, historyClearPath, nil, &env); err != nil { - return nil, err - } - return &env, nil -} diff --git a/app/service/playletser/history_view.go b/app/service/playletser/history_view.go deleted file mode 100644 index 6290415..0000000 --- a/app/service/playletser/history_view.go +++ /dev/null @@ -1,147 +0,0 @@ -package playletser - -import ( - "fmt" - "strconv" - "strings" - "time" -) - -func historyPageURL(pageNum int) string { - if pageNum <= 1 { - return "/history/" - } - return "/history/page/" + strconv.Itoa(pageNum) + "/" -} - -func HistoryCanonicalPath(q HistoryListQuery) string { - q.Normalize() - return historyPageURL(q.PageNum) -} - -// BuildHistoryPagination 观看历史分页。 -func BuildHistoryPagination(q HistoryListQuery, totalCount int64) PaginationVM { - q.Normalize() - return BuildPagination(func(p int) string { - return historyPageURL(p) - }, q.PageNum, q.PageSize, totalCount) -} - -// ParseHistoryListQuery 从 query 解析分页。 -func ParseHistoryListQuery(pageStr string) HistoryListQuery { - q := HistoryListQuery{PageSize: defaultHistoryPageSize} - if n, err := strconv.Atoi(strings.TrimSpace(pageStr)); err == nil { - q.PageNum = n - } - q.Normalize() - return q -} - -func historyFinished(item PlayletHistoryData) bool { - if item.IsFinished { - return true - } - return item.TotalEpisode > 0 && item.UnwatchedCount <= 0 && item.WatchedCount > 0 -} - -// historyStatus 卡片筛选状态:finished / unfinished。 -func historyStatus(item PlayletHistoryData) string { - if historyFinished(item) { - return "finished" - } - return "unfinished" -} - -// historyProgressText PC 卡片未看文案,使用接口 unwatchedCount / isFinished。 -func historyProgressText(item PlayletHistoryData) string { - if historyFinished(item) { - return "已看完" - } - if item.UnwatchedCount > 0 { - return fmt.Sprintf("%d集未看", item.UnwatchedCount) - } - if item.LastEpisode > 0 { - return fmt.Sprintf("看到第%d集", item.LastEpisode) - } - return "未看完" -} - -// historyWatchLine H5 进度行。 -func historyWatchLine(item PlayletHistoryData) string { - if historyFinished(item) { - if item.LastEpisode > 0 { - return fmt.Sprintf("第%d集 · 已看完", item.LastEpisode) - } - return "已看完" - } - ep := "未开始" - if item.LastEpisode > 0 { - ep = fmt.Sprintf("第%d集", item.LastEpisode) - } - if item.UnwatchedCount > 0 { - return ep + fmt.Sprintf(" · 还有%d集未看", item.UnwatchedCount) - } - return ep + " · 未看完" -} - -// historyTagText 历史卡片标签:仅展示 TagNames 第一项,无标签时不显示。 -func historyTagText(item PlayletHistoryData) string { - return firstName(item.TagNames) -} - -// historyPlayURL 继续播放:优先最近观看剧集。 -func historyPlayURL(item PlayletHistoryData) string { - return chapterPlayURL(item.Slug, item.ID, item.LastChapterID) -} - -// formatHistoryAt 最近观看时间展示。 -func formatHistoryAt(raw string) string { - raw = strings.TrimSpace(raw) - if raw == "" { - return "" - } - t, ok := parseHistoryTime(raw) - if !ok { - return raw - } - now := time.Now() - startToday := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) - startThat := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) - dayDiff := int(startToday.Sub(startThat).Hours() / 24) - hm := t.Format("15:04") - switch { - case dayDiff <= 0: - return "今天 " + hm - case dayDiff == 1: - return "昨天 " + hm - case dayDiff == 2: - return "前天 " + hm - case dayDiff < 7: - return fmt.Sprintf("%d天前 %s", dayDiff, hm) - case dayDiff < 14: - return "一周前 " + hm - default: - return t.Format("2006-01-02 15:04") - } -} - -func parseHistoryTime(raw string) (time.Time, bool) { - layouts := []string{ - time.RFC3339Nano, - time.RFC3339, - "2006-01-02 15:04:05", - "2006-01-02T15:04:05", - "2006-01-02T15:04:05Z07:00", - "2006-01-02 15:04:05.000", - "2006-01-02 15:04:05.999999999", - } - for _, layout := range layouts { - if t, err := time.ParseInLocation(layout, raw, time.Local); err == nil { - return t, true - } - if t, err := time.Parse(layout, raw); err == nil { - return t.In(time.Local), true - } - } - return time.Time{}, false -} 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/tplfuncs.go b/app/service/playletser/tplfuncs.go index 2d69f52..85eb809 100644 --- a/app/service/playletser/tplfuncs.go +++ b/app/service/playletser/tplfuncs.go @@ -11,62 +11,50 @@ import ( // TemplateFuncs 模板辅助:仅做展示格式化 / URL,不把接口字段映射成另一套名字。 func TemplateFuncs() template.FuncMap { return template.FuncMap{ - "detailURL": detailURL, - "playURL": playURL, - "chapterPlayURL": chapterPlayURL, - "categoryURL": categoryURL, - "categoryDetailURL": categoryDetailURL, - "themeListURL": themeListURLByKey, - "coverURL": coverURL, - "formatHeat": formatHeat, - "formatCollect": formatCollectHeat, - "formatLikeCount": formatLikeCount, - "episodeLabel": episodeText, - "displayTag": displayTag, - "firstTagName": firstTagName, - "themeTagText": themeTagText, - "tagListURL": tagListURL, - "cardTag": cardTag, - "cardTags": cardTags, - "namedTags": namedTags, - "namedThemeTags": namedThemeTags, - "firstName": firstName, - "rankClass": rankClass, - "add": addInt, - "contains": strings.Contains, - "hasPrefix": strings.HasPrefix, - "footerLinkURL": footerLinkURL, - "footerLinkIsExternal": footerLinkIsExternal, - "footerContactURL": footerContactURL, - "footerContactName": footerContactName, - "footerContactValue": footerContactValue, - "navDisplayURL": navDisplayURL, - "urlquery": url.QueryEscape, - "pathescape": url.PathEscape, - "metaLine": metaLine, - "updateStatusText": updateStatusText, - "browseText": browseText, - "usableAnchor": usableAnchor, - "playletYearText": playletYearText, - "playletDateText": playletDateText, - "isPlayletAllFree": isPlayletAllFree, - "collectProgressText": collectProgressText, - "collectTagText": collectTagText, - "chapterCollectEpisodeLabel": chapterCollectEpisodeLabel, - "chapterCollectDesc": chapterCollectDesc, - "chapterCollectMeta": chapterCollectMeta, - "historyProgressText": historyProgressText, - "historyWatchLine": historyWatchLine, - "historyTagText": historyTagText, - "historyPlayURL": historyPlayURL, - "historyStatus": historyStatus, - "formatHistoryAt": formatHistoryAt, - "formatShortDateTime": formatShortDateTime, - "absInt64": absInt64Tpl, - "absURL": absURLTpl, - "canonicalOr": canonicalOr, - "joinStrings": joinStrings, - "dict": dict, + "detailURL": detailURL, + "playURL": playURL, + "chapterPlayURL": chapterPlayURL, + "categoryURL": categoryURL, + "categoryDetailURL": categoryDetailURL, + "themeListURL": themeListURLByKey, + "coverURL": coverURL, + "formatHeat": formatHeat, + "formatCollect": formatCollectHeat, + "formatLikeCount": formatLikeCount, + "episodeLabel": episodeText, + "displayTag": displayTag, + "firstTagName": firstTagName, + "themeTagText": themeTagText, + "tagListURL": tagListURL, + "cardTag": cardTag, + "cardTags": cardTags, + "namedTags": namedTags, + "namedThemeTags": namedThemeTags, + "firstName": firstName, + "rankClass": rankClass, + "add": addInt, + "contains": strings.Contains, + "hasPrefix": strings.HasPrefix, + "footerLinkURL": footerLinkURL, + "footerLinkIsExternal": footerLinkIsExternal, + "footerContactURL": footerContactURL, + "footerContactName": footerContactName, + "footerContactValue": footerContactValue, + "navDisplayURL": navDisplayURL, + "urlquery": url.QueryEscape, + "pathescape": url.PathEscape, + "metaLine": metaLine, + "updateStatusText": updateStatusText, + "browseText": browseText, + "usableAnchor": usableAnchor, + "playletYearText": playletYearText, + "playletDateText": playletDateText, + "isPlayletAllFree": isPlayletAllFree, + "absInt64": absInt64Tpl, + "absURL": absURLTpl, + "canonicalOr": canonicalOr, + "joinStrings": joinStrings, + "dict": dict, } } @@ -315,25 +303,6 @@ func absInt64Tpl(n int64) int64 { return n } -func formatShortDateTime(raw string) string { - raw = strings.TrimSpace(raw) - if raw == "" || strings.HasPrefix(raw, "0001") { - return "" - } - raw = strings.ReplaceAll(raw, "T", " ") - if i := strings.Index(raw, "."); i > 0 { - raw = raw[:i] - } - if i := strings.IndexAny(raw, "Z+"); i > 10 { - raw = strings.TrimSpace(raw[:i]) - } - if len(raw) >= 16 { - raw = raw[:16] - } - raw = strings.ReplaceAll(raw, "-", "/") - return raw -} - func metaLine(item CommonData) string { cat := firstName(item.ThemeNames) if cat == "" { diff --git a/app/service/rechargeser/list.go b/app/service/rechargeser/list.go deleted file mode 100644 index 5010fd2..0000000 --- a/app/service/rechargeser/list.go +++ /dev/null @@ -1,212 +0,0 @@ -package rechargeser - -import ( - "encoding/json" - "fmt" - "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" -) - -// 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) { - if page <= 0 { - page = 1 - } - if pageSize <= 0 { - pageSize = 10 - } - if pageSize > 100 { - pageSize = 100 - } - full := fmt.Sprintf("%s?page=%d&pageSize=%d", coinTransPath, page, pageSize) - 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 decodeCoinTransList(env.Data), env.Code, nil -} - -// 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 { - if len(raw) == 0 || string(raw) == "null" { - return nil - } - var arr []CoinTransListItem - if err := json.Unmarshal(raw, &arr); err == nil { - return arr - } - var obj struct { - List []CoinTransListItem `json:"list"` - } - if err := json.Unmarshal(raw, &obj); err != nil { - return nil - } - return obj.List -} - -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 d6ec54d..0000000 --- a/app/service/rechargeser/types.go +++ /dev/null @@ -1,117 +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"` -} - -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 7e23b10..0000000 --- a/app/service/rechargeser/view.go +++ /dev/null @@ -1,439 +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 - 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 - TimeText string - AmountText string - AmountClass string // deposit-tx-amount--plus / --minus - StatusText string -} - -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, - TimeText: formatTransTime(item.CreatedAt), - AmountText: amountText, - AmountClass: amountClass, - StatusText: strings.TrimSpace(item.FlowDirName), - }) - } - return out -} - -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), - 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 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/view.go b/app/service/translationser/view.go index 098036f..644db5f 100644 --- a/app/service/translationser/view.go +++ b/app/service/translationser/view.go @@ -280,8 +280,6 @@ func navActiveKey(href, name string, download bool) string { return "aiSeries" case "/home-road": return "homeRoad" - case "/user-center": - return "userCenter" case "/download-app": return "downloadApp" default: diff --git a/app/static/common/web-sdk-v1.1.8.js b/app/static/common/web-sdk-v1.1.8.js deleted file mode 100644 index c22f51e..0000000 --- a/app/static/common/web-sdk-v1.1.8.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=(null==e?"":e)+"",a=[];for(t=0;i.length>t;t++)128>(n=i.charCodeAt(t))?a.push(n):2048>n?a.push(192|n>>6,128|63&n):55296>n||n>56319||t+1>=i.length||56320>(r=i.charCodeAt(t+1))||r>57343?a.push(224|n>>12,128|n>>6&63,128|63&n):(a.push(240|(o=r-56320+(n-55296<<10)+65536)>>18,128|o>>12&63,128|o>>6&63,128|63&o),t++);return a}((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,p=0;P.length>p;p+=64){for(_=Array(16),f=0;16>f;f++)_[f]=(P[h=p+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],_[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){var t,n="";for(t=0;4>t;t++)n+=(256+(e>>>8*t&255)).toString(16).slice(1);return n},S(c)+S(u)+S(l)+S(d)}function t(){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 n(e){var t=((null==e?"":e)+"").trim();return t&&/^\d+\.\d+\.\d+$/.test(t)?t:"1.0.0"}function r(e,t){return(e>>>t|e<<32-t)>>>0}function o(e,t,n){return(e&t^~e&n)>>>0}function i(e,t,n){return(e&t^e&n^t&n)>>>0}function a(e){return(r(e,2)^r(e,13)^r(e,22))>>>0}function s(e){return(r(e,6)^r(e,11)^r(e,25))>>>0}function c(e){return(r(e,7)^r(e,18)^e>>>3)>>>0}function u(e){return(r(e,17)^r(e,19)^e>>>10)>>>0}function l(e){for(var t=(e>>>0).toString(16);8>t.length;)t="0"+t;return t}function d(e,t){return((255&e[t])<<24|(255&e[t+1])<<16|(255&e[t+2])<<8|255&e[t+3])>>>0}function p(e){var t,n=function(e){var t,n,r,l,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(536870912>D.length||(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,n=3144134277,r=1013904242,l=2773480762,p=1359893119,_=2600822924,f=528734635,h=1541459225,g=0;D.length>g;g+=64){for(m=Array(64),y=0;16>y;y++)m[y]=d(D,g+4*y);for(y=16;64>y;y++)m[y]=u(m[y-2])+m[y-7]+c(m[y-15])+m[y-16]>>>0;for(v=t,w=n,k=r,b=l,C=p,S=_,P=f,E=h,y=0;64>y;y++)T=E+s(C)+o(C,S,P)+Gt[y]+m[y]>>>0,x=a(v)+i(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,n=n+w>>>0,r=r+k>>>0,l=l+b>>>0,p=p+C>>>0,_=_+S>>>0,f=f+P>>>0,h=h+E>>>0}return[t,n,r,l,p,_,f,h]}(e||[]),r="";for(t=0;n.length>t;t++)r+=l(n[t]);return r}function _(e){return p(function(e){var t,n,r,o,i=(null==e?"":e)+"",a=[];for(t=0;i.length>t;t++)128>(n=i.charCodeAt(t))?a.push(n):2048>n?a.push(192|n>>6,128|63&n):55296>n||n>56319||t+1>=i.length||56320>(r=i.charCodeAt(t+1))||r>57343?a.push(224|n>>12,128|n>>6&63,128|63&n):(a.push(240|(o=r-56320+(n-55296<<10)+65536)>>18,128|o>>12&63,128|o>>6&63,128|63&o),t++);return a}((null==e?"":e)+""))}function f(e,t){return null==e||""===e?"":"int"===t?parseInt(e,10)+"":(e+"").replace(/^\s+|\s+$/g,"").toLowerCase()}function h(e){var t,n,r=e||{},o={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},i={canvas_fp:1,webgl_fp:1,audio_fp:1,fonts_hash:1,plugins_hash:1,mimes_hash:1},a=[];for(t=0;zt.length>t;t++)a.push((n=zt[t])+"="+f(r[n],o[n]?"int":i[n]?"hash":"string"));return _("fp_v3|fingerprint_platform=web|"+a.join("|"))}function g(e,t){try{return e()}catch(n){return t}}function m(t){return e((null==t?"":t)+"").substr(0,32)}function y(e){return m((e||[]).slice().sort().join("|"))}function v(){return g(function(){var e,t,n,r,o,i,a,s,c="undefined"!=typeof OfflineAudioContext?OfflineAudioContext:"undefined"!=typeof webkitOfflineAudioContext?webkitOfflineAudioContext:null;if(!c)return"";if((t=(e=new c(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(!(o=r)||!o.getChannelData)return"";for(i=o.getChannelData(0),a=0,s=4500;5e3>s&&i.length>s;s++)a+=Math.abs(i[s]);return m(a+"")},"")}function w(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(n){return 0}}function k(){try{return"undefined"!=typeof indexedDB?1:0}catch(e){return 0}}function b(){try{return"undefined"!=typeof navigator&&navigator.serviceWorker?1:0}catch(e){return 0}}function C(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 S(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 P(e){return/iPad|Tablet/i.test(e=(e||"")+"")?"tablet":/Mobile|iPhone|Android/i.test(e)?"phone":"desktop"}function E(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 T(){var e=g(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(v){}}for(p=Object.keys(c).sort(),_=[],f=0;p.length>f;f++)_.push((h=p[f])+"="+c[h]);return g=m(_.join("|")),y=m([n,r,a,g].join("|")),{vendor:n,renderer:r,extensions_hash:a,params_hash:g,webgl_fp:y}},{vendor:"",renderer:"",extensions_hash:"",params_hash:"",webgl_fp:""}),F=N.webgl_fp||"",H=v(),U=g(function(){var e,t,n;if("undefined"==typeof navigator||!navigator.plugins)return"";for(e=[],t=0;ti;i++)e("'"+(a=Wt[i])+"'")!==r&&o.push(a);return o.sort(),{hash:y(o.map(function(e){return e.toLowerCase()})),installed:o,count:o.length}},{hash:"",installed:[],count:-1}),G=g(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;Jt.length>r;r++){i="video"===(o=Jt[r]).kind?e:t,a="";try{a=i.canPlayType?(i.canPlayType(o.type)||"")+"":""}catch(s){}a&&"no"!==a&&n.push(o.type+":"+a)}return n.sort(),{hash:y(n.map(function(e){return e.toLowerCase()})),supported:n}},{hash:"",supported:[]}),z=g(function(){var e,t,n="undefined"!=typeof window?window.AudioContext||window.webkitAudioContext:null;if(!n)return-1;if(t=(e=new n).sampleRate||-1,e.close)try{e.close()}catch(r){}return t>0?Math.round(t):-1},-1),W=(L.platform||"")+"",J=0;try{J=L.plugins?L.plugins.length:0}catch(Q){}e=Y.count,t=F?"available":"unavailable",n=!0===L.webdriver?1:0,r=0,o=0;try{B&&B.documentElement&&(r=B.documentElement.clientWidth||q.innerWidth||0,o=B.documentElement.clientHeight||q.innerHeight||0)}catch(Q){}i=0,1===n?i=1:"available"===t||0!==J||0>e||e>2||r>0&&o>0||(i=1),a="";try{"undefined"!=typeof Intl&&Intl.DateTimeFormat&&(a=Intl.DateTimeFormat().resolvedOptions().timeZone||"")}catch(Q){}s=function(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(Q){return""}}(),c="",u="",l="";try{"undefined"!=typeof location&&(c=location.hostname||"",u=location.pathname||"",location.search&&(l=m(location.search)))}catch(Q){}p=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(Q){return{referer_domain:"",referer_path:""}}}(d=B&&B.referrer?B.referrer+"":""),_=function(e){var t,n,r,o;if(/iPhone|iPad|iPod/i.test(o=((o=(e=e||{}).userAgent)||"")+"")&&!/CriOS\/|FxiOS\/|EdgiOS\/|OPiOS\//i.test(o)&&/Version\/[\d.]+.*Safari/i.test(o))return E(e.userAgent);try{if((t=e.userAgentData&&e.userAgentData.brands)&&t.length)for(n=0;t.length>n;n++)if((r=(t[n].brand||"")+"")&&"Chromium"!==r&&0!==r.indexOf("Not")&&"Not.A/Brand"!==r)return{browser_name:r,browser_version:(t[n].version||"unknown")+""}}catch(Q){}return E(e.userAgent)}(L),f=G.supported?G.supported.length:-1,x=Math.round(96*(h=q.devicePixelRatio||1)),D=-1;try{null!=L.hardwareConcurrency&&L.hardwareConcurrency>0&&(D=L.hardwareConcurrency)}catch(Q){}M=D,I=-1;try{null!=L.deviceMemory&&L.deviceMemory>0&&(I=Math.round(10*L.deviceMemory)/10)}catch(Q){}return A=V&&V.type?V.type+"":"unknown",O=L.maxTouchPoints||0,{platform:"web",os_family:S(K,W),device_type:P(K),browser_kernel:C(K),browser_name:_.browser_name,browser_version:_.browser_version,browser_platform:W,display_mode:g(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:g(function(){if("undefined"==typeof document)return"unknown";var e=document.visibilityState;return"visible"===e||"hidden"===e?e:"unknown"},"unknown"),screen_width:R.width||0,screen_height:R.height||0,screen_dpi:x,device_pixel_ratio:Math.round(100*h),orientation:T(),viewport_width:r>0?Math.round(r):-1,viewport_height:o>0?Math.round(o):-1,color_depth:R.colorDepth||0,max_touch_points:O,touch_support:O>0?1:0,hardware_concurrency:D,cpu_cores:M,device_memory_gb:I,is_debug:0,language:L.language||"",timezone:a,cookie_enabled:L.cookieEnabled?1:0,local_storage_enabled:w("local"),session_storage_enabled:w("session"),indexeddb_enabled:k(),service_worker_enabled:b(),network_type:"unknown",connection_type:A,effective_type:V&&V.effectiveType?V.effectiveType+"":"unknown",downlink:V&&null!=V.downlink?Math.round(1e3*V.downlink):-1,rtt:V&&null!=V.rtt?V.rtt:-1,canvas_fp:j,webgl_fp:F,webgl_vendor:N.vendor||"",webgl_renderer:N.renderer||"",webgl_extensions_hash:N.extensions_hash||"",webgl_params_hash:N.params_hash||"",audio_fp:H,audio_sample_rate:z>0?z:44100,fonts:Y.installed||[],fonts_hash:Y.hash,plugins:X,plugins_hash:U,mimes:G.supported||[],mimes_hash:G.hash,mime_count:f,plugin_count:J,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:p.referer_domain,referer_path:p.referer_path}}function D(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 D(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+":"+D(e[n],t)}).join(",")+"}",t.splice(t.indexOf(e),1),r):"__t:any__:"+e}function M(e){return D(e,[])}function I(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 A(e){var t,n,r,o,i=(null==e?"":e)+"",a=[];for(t=0;i.length>t;t++)128>(n=i.charCodeAt(t))?a.push(n):2048>n?a.push(192|n>>6,128|63&n):55296>n||n>56319||t+1>=i.length||56320>(r=i.charCodeAt(t+1))||r>57343?a.push(224|n>>12,128|n>>6&63,128|63&n):(a.push(240|(o=r-56320+(n-55296<<10)+65536)>>18,128|o>>12&63,128|o>>6&63,128|63&o),t++);return a}function O(e){var t,n=(null==e?"":e)+"",r=[];for(t=0;n.length>t;t+=2)r.push(parseInt(n.substr(t,2),16));return r}function L(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 R(e){return null==e?"":e+""}function q(){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 B(){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 K(){if(!an){if(an=!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(en)}catch(t){}if("undefined"!=typeof document&&"string"==typeof document.cookie)try{var e="path=/;max-age=0;SameSite=Lax";"undefined"!=typeof location&&"https:"===location.protocol&&(e+=";Secure"),document.cookie=encodeURIComponent(tn)+"=;"+e}catch(t){}}}function V(e){K();var t=function(e){return JSON.stringify({sid:e.sid,sessionStartTs:e.sessionStartTs,lastValidBehaviorAt:e.lastValidBehaviorAt})}(e);if(B())try{window.sessionStorage.setItem($t,e.sid),window.sessionStorage.setItem(Zt,t)}catch(n){}on=e}function j(){if(on)return on;K();var e=null;if(B())try{e=window.sessionStorage.getItem(Zt)}catch(t){}return on=e?function(e){try{var n=JSON.parse(e);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(t){return null}}(e):null,on}function N(e){return{sid:q(),sessionStartTs:e,lastValidBehaviorAt:e}}function F(e){return"number"==typeof e&&isFinite(e)?1e12>e?1e3*e:e:null}function H(e,t,n){var r=F(e.sessionStartTs)||n,o=F(e.lastValidBehaviorAt)||r,i=n;return"idle"===t?i=Math.min(i=Math.max(r,o+nn),n):"forced"===t&&(i=Math.min(r+rn,n)),Math.max(r,i)}function U(e,t,n){var r,o;return n=n||Date.now(),r=F(e.sessionStartTs)||n,o=H(e,t,n),{sid:e.sid,sessionStartTs:r,lastValidBehaviorAt:F(e.lastValidBehaviorAt)||r,sessionEndTs:o,endReason:t}}function X(e){var t,n,r,o,i;return e=e||Date.now(),(t=j())?(r=e-t.sessionStartTs>rn,e-t.lastValidBehaviorAt>nn||r?(o=U(t,r?"forced":"idle",e),V(i=N(e)),{sid:i.sid,isNew:!0,endedSession:o}):{sid:t.sid,isNew:!1}):(V(n=N(e)),{sid:n.sid,isNew:!0})}function Y(){var e=j();return e?e.sid:""}function G(){on=null}function z(){var e,t;if(!function(){try{return!("undefined"==typeof window||!window.localStorage||(window.localStorage.setItem(cn,"1"),window.localStorage.removeItem(cn),0))}catch(e){return!1}}())return Promise.resolve(un||"NULL");try{return(e=window.localStorage.getItem(sn))&&/^[0-9a-f]{32}$/.test(e)?Promise.resolve(e):(t=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(sn,t),un=t,Promise.resolve(t))}catch(n){return Promise.resolve("NULL")}}function W(){return ln&&dn?Promise.resolve({snapshot:ln,fingerprint:dn}):pn||(pn=new Promise(function(e){g(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){var n,r=t.getChannelData(0),o=0;for(n=4500;5e3>n&&r.length>n;n++)o+=Math.abs(r[n]);e(m(o+""))}).catch(function(){e("")}):e(v())):e("")},e(""))}).then(function(e){var t=x();return e&&(t.audio_fp=e),ln=t,{snapshot:t,fingerprint:dn=h(t)}}).catch(function(){var e=x();return ln=e,{snapshot:e,fingerprint:dn=h(e)}}))}function J(){return dn}function Q(){return Xt}function $(){return _n.digest()}function Z(){try{if("undefined"!=typeof screen&&screen.orientation&&screen.orientation.type){var e=screen.orientation.type+"";if(-1!==e.indexOf("landscape"))return"landscape";if(-1!==e.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(t):-1}catch(n){return-1}}function te(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 ne(){return Sn.pageSeqCounter++,Sn.currentPageSeq=Sn.pageSeqCounter,Sn.currentPageSeq}function re(){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 oe(e){var t,n=e||Sn;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:ae({},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 ie(e,t){return null==e||0>e||t>0&&e>t?-1:e}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 se(e){return mn+":"+(e||"")}function ce(e){var t,n;if(!e)return null;if(t=null,re())try{t=window.sessionStorage.getItem(se(e))}catch(r){}if(!t&&fe())try{t=window.localStorage.getItem(se(e))}catch(r){}if(!t)return null;try{return(n=JSON.parse(t))&&n.sid===e?n:null}catch(r){return null}}function ue(e,t){if(e){var n=JSON.stringify(t||{});if(re())try{window.sessionStorage.setItem(se(e),n)}catch(r){}if(fe())try{window.localStorage.setItem(se(e),n)}catch(r){}}}function le(e){if(!e)return null;var t=ce(e);return t&&t.metrics?t.metrics:null}function de(){var e,t=Y();t&&(vn?wn||(wn=t):(vn=!0,(e=ce(t))&&e.metrics?(Sn=oe(e.metrics),e.sessionStartedAt&&(Pn=e.sessionStartedAt),wn=t):wn=t))}function pe(e){var t=ce(e);return t&&t.behaviorHash?t.behaviorHash:""}function _e(){var e=Y();e&&(wn&&wn!==e||(wn||(wn=e),function(e){e&&ue(e,{sid:e,savedAt:Date.now(),sessionStartedAt:Pn,behaviorHash:$(),metrics:oe(Sn)})}(e)))}function fe(){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 he(){var e,t;if(!fe())return[];try{return(e=window.localStorage.getItem(gn))?(t=JSON.parse(e),Array.isArray(t)?t:[]):[]}catch(n){return[]}}function ge(e){if(fe())try{window.localStorage.setItem(gn,JSON.stringify(e||[]))}catch(t){}}function me(e){ge(he().filter(function(t){return t&&t.sid!==e}))}function ye(e){for(var t,n=e.slice(0,fn);n.length>0;){if(t=JSON.stringify(n),hn>=t.length)return n;n.pop()}return n}function ve(e){var t,n=Sn.currentPage;n&&(n.stay_duration_ms=te(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),Sn.pageStayList.push(t),Sn.pageStayList=ye(Sn.pageStayList),Sn.currentPage=null)}function we(){Sn.firstInteractionRecorded||(Sn.firstInteractionRecorded=!0,Sn.firstInteractionDelayMs=Math.max(0,Date.now()-Pn))}function ke(){Sn.lastOrientation=Z()}function be(e){e&&Sn.currentPage&&Sn.currentPage.page_key===e&&(Sn.currentPage.has_interaction=!0)}function Ce(e,t,n,r){var o=Date.now();ve(o),Sn.currentPage={page_key:(e||"")+"",page_name:(t||"")+"",enter_time:null!=n?1e3*n:o,enter_time_ms:o,page_seq_in_sid:r||Sn.currentPageSeq||0,has_interaction:!1,accumulated_ms:0,resume_at_ms:o,hidden_paused:!1}}function Se(){ve(Date.now())}function Pe(e){var t,n=e;if(null==n)return null;if("string"==typeof n)try{n=JSON.parse(n)}catch(r){return null}return n&&"object"==typeof n?(t={page_key:(n.page_key||"")+"",page_name:(n.page_name||"")+"",stay_duration_ms:null!=n.stay_duration_ms?n.stay_duration_ms:0,enter_time:null!=n.enter_time?n.enter_time:0,has_interaction:n.has_interaction?1:0},null!=n.page_seq_in_sid&&(t.page_seq_in_sid=n.page_seq_in_sid),t):null}function Ee(e,t,n){var r,o,i,a,s,c,u,l,d,p,_,f=e||{},h=f.sid||Y(),g=h&&h===Y();for(n?r=oe(n):g?(Se(),_e(),r=oe(Sn)):r=function(e){var t,n;return de(),!(t=e||Y())||t!==Y()||wn&&wn!==t?(n=le(t))?oe(n):wn&&wn!==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}:oe(Sn):oe(Sn)}(h),o=function(e,t){var n=Date.now(),r=e||{},o=F(r.sessionStartTs)||n,i=null!=r.sessionEndTs?F(r.sessionEndTs):H(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=ae({},r.pageKeys||{}),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=function(e){var t,n,r=[];for(t=0;(e||[]).length>t;t++)(n=Pe(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)})),i,a),l=0;u.length>l;l++)(d=u[l]&&u[l].page_key?u[l].page_key+"":"")&&(c[d]=1);return p=Object.keys(c),_=n?pe(h)||$():g?$():pe(h)||$(),{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:p.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:_,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:ie(null!=r.firstScrollDelayMs?r.firstScrollDelayMs:-1,s),max_scroll_depth_percent:r.maxScrollDepthPercent||0,is_reach_bottom:r.isReachBottom||0,content_height:ee(),keydown_total_count:r.keydownTotalCount||0,input_count:r.inputCount||0,focus_total_count:r.focusTotalCount||0,first_interaction_delay_ms:ie(null!=r.firstInteractionDelayMs?r.firstInteractionDelayMs:-1,s),orientation:r.lastOrientation||Z(),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 Te(){Dn=!0,setTimeout(function(){Dn=!1},2e3)}function xe(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(t){return e((null==t?"":t)+"").substr(0,32)}function Me(e){return null!=e&&""!==(e+"").trim()}function Ie(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(e){return""}}function Ae(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 Oe(){try{return Ae(("undefined"!=typeof location&&location.pathname?location.pathname:"/").replace(/^\/+|\/+$/g,"").replace(/\//g,"_")||"index")}catch(e){return"index"}}function Le(){try{return document.scrollingElement||document.documentElement||document.body||null}catch(e){return null}}function Re(){try{var e=Le();return e?{scroll_top:Math.max(0,Math.round(e.scrollTop||0)),scroll_left:Math.max(0,Math.round(e.scrollLeft||0))}:{scroll_top:-1,scroll_left:-1}}catch(t){return{scroll_top:-1,scroll_left:-1}}}function qe(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 Be(){var e,t,n;try{return(e=document.documentElement||document.body)?(n=e.scrollHeight||0,(t=e.scrollWidth||0)>0&&n>0?{page_width:Math.round(t),page_height:Math.round(n)}:{page_width:-1,page_height:-1}):{page_width:-1,page_height:-1}}catch(r){return{page_width:-1,page_height:-1}}}function Ke(){var e,t;try{return"undefined"==typeof window?{viewport_width:-1,viewport_height:-1}:(e=window.innerWidth||0,t=window.innerHeight||0,e>0&&t>0?{viewport_width:Math.round(e),viewport_height:Math.round(t)}:{viewport_width:-1,viewport_height:-1})}catch(n){return{viewport_width:-1,viewport_height:-1}}}function Ve(){var e,t;try{return"undefined"==typeof screen?{screen_width:-1,screen_height:-1}:(e=screen.width||0,t=screen.height||0,e>0&&t>0?{screen_width:Math.round(e),screen_height:Math.round(t)}:{screen_width:-1,screen_height:-1})}catch(n){return{screen_width:-1,screen_height:-1}}}function je(){var e,t,n;try{if("undefined"!=typeof performance&&performance.timing&&(e=performance.timing).loadEventEnd&&e.navigationStart&&e.loadEventEnd>0)return Math.max(0,e.loadEventEnd-e.navigationStart);if("undefined"!=typeof performance&&"function"==typeof performance.getEntriesByType&&(t=performance.getEntriesByType("navigation"))&&t.length>0&&(n=t[0]).loadEventEnd>0&&n.fetchStart>=0)return Math.max(0,Math.round(n.loadEventEnd-n.fetchStart))}catch(r){}return 0}function Ne(e,t){return t>0?Math.min(100,Math.max(0,Math.round(e/t*100))):0}function Fe(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 He(e){var t,n,r,o,i,a,s,c,u=e||{},l=Ke(),d=Be(),p=Re(),_=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=Ve(),g=f&&p.scroll_top>=0?p.scroll_top:0,m=f&&p.scroll_left>=0?p.scroll_left:0,y=l.viewport_width>0?l.viewport_width:-1,v=l.viewport_height>0?l.viewport_height:-1;return _?(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=qe(i,y),a=qe(a,v),f&&(s=qe(s,d.page_width),c=qe(c,d.page_height)),xe({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?Ne(i,y):-1,click_y_percent:v>0?Ne(a,v):-1,screen_width:h.screen_width,screen_height:h.screen_height},l,f?d:{page_width:-1,page_height:-1},f?p:{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?p.scroll_top:-1,scroll_left:f?p.scroll_left:-1}}function Ue(e){var t=e||{},n=Ae(t.pageKey||Oe()),r=t.pageName||"undefined"!=typeof document&&document.title||n,o=function(){try{return"undefined"==typeof location?{}:{page_url:Ie(),page_domain:(location.hostname||"")+"",page_path:(location.pathname||"")+"",page_query_hash:location.search?De(location.search):""}}catch(e){return{}}}(),i=Ke();return{user_type:"",page_key:n,page_name:r,referrer_page_key:(t.previousPageKey||"")+"",referrer_page_name:(t.previousPageName||"")+"",current_page_key:n,current_page_name:r,recommend_trace_id:"",page_url:o.page_url,viewport_width:i.viewport_width,viewport_height:i.viewport_height,is_sdk_auto_collected:1}}function Xe(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 Ye(e,t){var n,r;return"app_session_summary"===t?Xe({},e.summaryFields||{},e.payload||{}):(n=function(e){var t=Xe({},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 Ge(t,n,r,o,i){var a,s,c,u,l,d,_,f=t||{},h=((r.event||"")+"").trim(),g=r.lifecycle_event_key||r.payload&&r.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)}(h,g)&&(u=Date.now(),(l=j())?(l.lastValidBehaviorAt=u,V(l)):V(N(u))),a="",r.payload&&null!=r.payload.page_key?a=r.payload.page_key+"":null!=r.page_key&&(a=r.page_key+""),function(e,t,n){"app_lifecycle"===e&&t&&Qt[t]?_n.append(t,n):("app_page_view"===e||"page_click"===e||e&&!Cn[e])&&_n.append(e,n)}(h,g,a),s=function(e,t){return I({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:J(),fpVersion:Q(),sdkVersion:e.sdkVersion,appVersion:e.appVersion});var n}(f,n),function(e,t){var n,r;if(de(),(n=(e||"")+"")&&"app_session_summary"!==n){if("app_page_view"===n&&t&&t._metricsRecorded)return Sn.lastEventName=n,void _e();Sn.eventCountTotal++,Sn.lastEventName=n,Cn[n]||Sn.businessEventCount++,r=t&&null!=t.page_key?t.page_key+"":"","app_page_view"===n?(Sn.pageViewCount++,r&&(Sn.pageKeys[r]=1)):"page_click"===n?(Sn.pageClickCount++,be(r)):"network_state_change"===n?Sn.networkChangeCount++:r&&be(r),_e()}}(h,r.payload||{}),c=function(e,t){return function(e,t){var n,r=t||{},o={},i=Object.keys(e||{});for(n=0;i.length>n;n++)o[i[n]]=e[i[n]];return o.event_sig_version=(r.eventSigVersion||Yt)+"",o.event_sig=r.hmacKey?function(e,t){return t?function(e,t){var n,r,o,i,a=A((null==t?"":t)+"");for(a.length>64&&(a=O(p(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=O(p(L(n,A((null==e?"":e)+"")))),p(L(r,i))}(function(e){var t=e||{};return[R(t.event),R(t.event_id),R(t.app_id),R(t.client_ts),R(t.device_fingerprint)].join("|")}(e),t+""):""}(e,r.hmacKey):"",o}(e,{hmacKey:t&&t.hmacKey?t.hmacKey:"",eventSigVersion:Yt})}(function(t,n,r){return function(t,n){var r=function(){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}(t,n);return r.event_id||(r.event_id=function(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(M(r[t]))}).join(""),e(n)):e("")}(r)),r}(t,{event:r,payload:Ye(n,r)})}(s,r,h),o),"app_page_view"===h&&r.payload&&r.payload.page_key&&(r.payload._metricsRecorded||r._metricsRecorded||Ce(r.payload.page_key,r.payload.page_name,r.payload.page_enter_ts,null!=r._pageSeqInSid?r._pageSeqInSid:r.payload.page_seq_in_sid)),"page_click"===h&&r.payload&&!r._sdkAutoClick&&xn.push({x:null!=(d=null!=r.payload.click_viewport_x?r.payload.click_viewport_x:r.payload.click_page_x)?d:0,y:null!=(_=null!=r.payload.click_viewport_y?r.payload.click_viewport_y:r.payload.click_page_y)?_:0,t:Date.now()}),c._sessionInfo=i||null,c}function ze(t,n,r){var o=Xe({},n||{}),i=((o.event||"")+"").trim();return On[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=Ae(o.page_key||Oe());return r=s?Ue(xe(u,{pageKey:l,pageName:o.page_name})):c?xe({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=Ke(),r=Ve(),o=Be(),i=Re(),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,p=null!=t.click_page_y?+t.click_page_y:null,_=-1,f=-1,h=-1,g=-1;return null!=d&&isFinite(d)&&null!=p&&isFinite(p)?(_=d,f=p,a&&(h=d+c,g=p+s)):null!=t.click_x_percent&&null!=t.click_y_percent&&u>0&&l>0&&(_=+t.click_x_percent/100*u,f=+t.click_y_percent/100*l,a&&(h=_+c,g=f+s)),-1!==_&&(_=qe(_,u)),-1!==f&&(f=qe(f,l)),a&&-1!==h&&(h=qe(h,o.page_width)),a&&-1!==g&&(g=qe(g,o.page_height)),{click_viewport_x:_,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=xe({},e||{}),i=Object.keys(t||{});for(n=0;i.length>n;n++)Me(o[r=i[n]])||(o[r]=t[r]);return o}(o,r)}(o.payload,0,i)),W().then(function(){var n,a=X();if(a.endedSession&&An)try{An(a.endedSession)}catch(s){}return n=a.sid||Y(),On[i]&&o.payload&&function(t,n){var r,o,i,a,s,c=((t.event||"")+"").trim();On[c]&&t.payload&&("app_page_view"===c?1===t.payload.is_sdk_auto_collected?(r=t.payload._pageSeqPrealloc||ne(),o=t.payload._metricsRecorded,null!=t.payload._pageSeqPrealloc&&delete t.payload._pageSeqPrealloc,t.payload=function(e,t,n){var r=e||{},o=Ke(),i=Math.floor(Date.now()/1e3),a=De((t||"")+"|"+(n||0)+"|"+i),s=je(),c=Ae(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||Ie(),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||ne();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=Xe(t.payload,i)):"page_click"===c&&1===t.payload.is_sdk_auto_collected&&(t.payload=(s=He((a=t.payload||{})._clickSnap||a),{page_key:Ae(a.page_key||""),page_name:(a.page_name||"")+"",recommend_trace_id:(null!=a.recommend_trace_id?a.recommend_trace_id:"")+"",referrer_page_key:(null!=a.referrer_page_key?a.referrer_page_key:"")+"",click_viewport_x:s.click_viewport_x,click_viewport_y:s.click_viewport_y,click_document_x:s.click_document_x,click_document_y:s.click_document_y,click_page_x:s.click_page_x,click_page_y:s.click_page_y,click_x_percent:s.click_x_percent,click_y_percent:s.click_y_percent,screen_width:s.screen_width,screen_height:s.screen_height,viewport_width:s.viewport_width,viewport_height:s.viewport_height,page_width:s.page_width,page_height:s.page_height,scroll_top:s.scroll_top,scroll_left:s.scroll_left}),t._sdkAutoClick=1,delete t.payload.is_sdk_auto_collected,delete t.payload._clickSnap))}(o,n),Ge(t,n,o,r,a)})}function We(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(e){return""}}function Je(){var e,t;try{return(e=function(){try{return document.scrollingElement||document.documentElement||document.body||null}catch(e){return null}}())&&(t=Math.max(0,(e.scrollHeight||0)-(e.clientHeight||0)))>0?Math.min(100,Math.max(0,Math.round((e.scrollTop||0)/t*100))):0}catch(n){return 0}}function Qe(){if(Dn)return"navigation";try{if("hidden"===document.visibilityState)return"visibility_hidden"}catch(e){}return"unknown"}function $e(){return function(){if(Sn.currentPage&&Sn.currentPage.page_key)return Sn.currentPage.page_key;var e=Sn.pageStayList||[];return e.length>0&&e[e.length-1].page_key||""}()||Oe()}function Ze(){var e,t="unknown";try{t="visible"===document.visibilityState||"hidden"===document.visibilityState?document.visibilityState:"unknown"}catch(n){}return{page_url:We(),unload_reason:Qe(),scroll_depth_percent_at_unload:Je(),web_visibility_state:t,page_stay_duration_ms:(e=Sn.currentPage,e?te(Date.now(),e):0),last_active_page_key:$e()}}function et(e,t,n,r){var o,i,a=n||{};return a.lifecycle_event_key=t,t===Ln&&(a=nt((o=Ke(),{page_url:We(),page_load_time_ms:je(),viewport_width:o.viewport_width,viewport_height:o.viewport_height}),a)),t===Bn&&(a=nt(a,Ze())),t===qn&&((i=Sn.currentPage)&&!i.hidden_paused&&(i.accumulated_ms=te(Date.now(),i),i.resume_at_ms=null,i.hidden_paused=!0),a.last_active_page_key||(a.last_active_page_key=$e())),t===Rn&&function(){var e=Sn.currentPage;e&&e.hidden_paused&&(e.resume_at_ms=Date.now(),e.hidden_paused=!1)}(),ze(e,{event:"app_lifecycle",payload:a},r)}function tt(e,t,n,r){return function(e,t,n,r){var o=Xe({},n||{});return W().then(function(){return Ge(e,t,o,r,null)})}(e,r||Y(),{event:"app_session_summary",summaryFields:t||{}},n)}function nt(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 rt(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 ot(){Hn&&(clearTimeout(Hn),Hn=null)}function it(e,t,n){var r,o,i,a,s,c;if(n=n||{},!e)return Promise.resolve({sent:!1,ok:!1,status:0,reason:"no_endpoint"});if(!Array.isArray(t)||0===t.length)return Promise.resolve({sent:!0,ok:!0,status:200,reason:"empty_batch"});if(r=JSON.stringify(t),o=!0===n.useBeacon,i=!1!==n.requireHttps,s=isFinite(a=+n.timeoutMs)&&a>0?Math.floor(a):Gn,!function(e,t){if(!t)return!0;var n,r=((e||"")+"").match(/^(https?):\/\/([^/:?#]+)/i);return!!r&&("https"===r[1].toLowerCase()||"http"===r[1].toLowerCase()&&("localhost"===(n=((r[2]||"")+"").toLowerCase())||"127.0.0.1"===n||"::1"===n))}(e,i))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(o&&"undefined"!=typeof navigator&&"function"==typeof navigator.sendBeacon)try{if(c=new Blob([r],{type:"application/json"}),navigator.sendBeacon(e,c))return Promise.resolve({sent:!0,ok:!0,status:200,transport:"beacon",uncertain:!0})}catch(u){}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}))}(e,r,s).then(function(t){return null!==t?t:function(e,t,n){return new Promise(function(r){try{if("undefined"==typeof XMLHttpRequest)return r({sent:!1,ok:!1,status:0,reason:"no_xhr"});var o=new XMLHttpRequest;o.open("POST",e,!0),o.timeout=n,o.setRequestHeader("Content-Type","application/json"),o.onreadystatechange=function(){4===o.readyState&&r({sent:!0,ok:o.status>=200&&300>o.status,status:o.status,transport:"xhr"})},o.onerror=function(){r({sent:!1,ok:!1,status:0,reason:"xhr_error"})},o.ontimeout=function(){r({sent:!1,ok:!1,status:0,reason:"timeout",transport:"xhr"})},o.send(t)}catch(u){r({sent:!1,ok:!1,status:0,reason:"xhr_exception"})}})}(e,r,s)})}function at(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 st(e){return 255&(e<<1^(128&e?27:0))}function ct(e){return(Et[e>>>24&255]<<24|Et[e>>>16&255]<<16|Et[e>>>8&255]<<8|Et[255&e])>>>0}function ut(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]=Et[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],p=r[4*s+2],_=r[4*s+3],o[4*s]=255&(st(l=r[4*s])^st(d)^d^p^_),o[4*s+1]=255&(l^st(d)^st(p)^p^_),o[4*s+2]=255&(l^d^st(p)^st(_)^_),o[4*s+3]=255&(st(l)^l^d^p^st(_));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 lt(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 dt(e){var t,n=e.slice(0);for(t=15;t>=12&&(n[t]=n[t]+1&255,!n[t]);t--);return n}function pt(e){this.encryptedConfigText=(e=e||{}).encryptedConfig||e.configCipher||"",this.cachedConfigPromise=null,this._secondGcmKeyBase64="en1BNo0VBrN/zi+mI2LO7E9W40ehCBYwC+frBn8s3rQ="}function _t(e,t){var n=((e&&e.event_id||"")+"").trim();return n?n+"|||"+(t||""):null}function ft(e){var t,n={},r=Object.keys(e);for(t=0;r.length>t;t++)n[r[t]]=e[r[t]];return n}function ht(){this.maxSize=Qn}function gt(e){var t,n,r=e||{},o=r.im_behavior_key,i=At[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 mt(e,t){var n,r,o,i,a,s=Mt[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)&&((1===u?"number"==typeof(t=d)&&isFinite(t)&&t===Math.floor(t):2===u?function(e){return"number"==typeof e&&isFinite(e)}(d):function(e){return"string"==typeof e}(d))?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 yt(){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 vt(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())&&(Rt[n]?r[n]=1:Lt('[sdk][web] disableAutoEvents 含未知事件 "'+n+'",已忽略'));return r}function wt(e){var t,n=[],r=Object.keys(e||{});for(t=0;r.length>t;t++)n.push(r[t]);return n}function kt(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 ht,this._resolveEndpoint=null,this._getEventCodes=null,this.startReplayTimer()}function bt(e){var t,n;if("undefined"==typeof window)return Lt("[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=yt(e)).channel=null==e.channel?"":e.channel+"",this._disableAutoEvents=vt(t.disableAutoEvents),t.disableAutoEvents=wt(this._disableAutoEvents),this.options=t,this.configMgr=new pt(e),this.resolver=new at({pingTimeoutMs:$n}),this.batcher=new kt(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 Ct(e){if(!Vt)throw Error("请先调用 init() 初始化(参数须包含 channel)");return Vt}function St(e,t,n){var r,o,i;if(n>=e.length)return Promise.resolve();if(!Vt||Vt!==t){for(o=(r=e.slice(n)).length-1;o>=0;o--)jt.unshift(r[o]);return Promise.resolve()}return t.track((i=e[n]).event,i.endpoint).catch(function(){}).then(function(){return St(e,t,n+1)})}function Pt(e,t){var n;return Vt?(n=Vt,(Nt||Promise.resolve()).then(function(){return n.track(e,t)})):(jt.push({event:e,endpoint:t}),jt.length>500&&jt.shift(),Promise.resolve({sent:!1,ok:!0,status:0,reason:"pre_init_cached"}))}var Et,Tt,xt,Dt,Mt,It,At,Ot,Lt,Rt,qt,Bt,Kt,Vt,jt,Nt,Ft,Ht,Ut,Xt="3.0.0",Yt="sig_v2",Gt=[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],zt=["os_family","browser_kernel","browser_platform","screen_width","screen_height","screen_dpi","device_pixel_ratio","color_depth","max_touch_points","cpu_cores","language","timezone","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"],Wt=["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"],Jt=[{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"}],Qt={cold_start:1,web_load:1,warm_start:1,foreground:1,background:1,web_unload:1,terminate:1,unknown:1},$t="__web_sdk_sid__",Zt="__web_sdk_sid_meta__",en="__web_sdk_sid__",tn="__web_sdk_sid_v1",nn=18e5,rn=216e5,on=null,an=!1,sn="__web_sdk_device_id__",cn="__web_sdk_ping__",un=null,ln=null,dn="",pn=null,_n=(Ut="",{append:function(e,t){var n=((e||"")+"").trim();n&&(Ut+=n+"|"+(null!=t?t+"":"")+";")},digest:function(){return _(Ut)},reset:function(){Ut=""}}),fn=50,hn=16384,gn="__web_sdk_pending_session_summaries__",mn="__web_sdk_session_metrics_v1__",yn="__web_sdk_summary_claimed__:",vn=!1,wn="",kn={},bn={},Cn={sdk_init:1,app_lifecycle:1,app_session_summary:1,network_state_change:1},Sn={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},Pn=Date.now(),En=0,Tn=0,xn=[],Dn=!1,Mn="",In="",An=null,On={app_page_view:1,page_click:1},Ln="web_load",Rn="foreground",qn="background",Bn="web_unload",Kn=null,Vn=0,jn=0,Nn=0,Fn=0,Hn=null,Un=!1,Xn=null,Yn=!1,Gn=1e4,zn="1.1.8",Wn=5e3,Jn=3e4,Qn=2e3,$n=1500,Zn=!1;if(at.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"})},at.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)})})},at.prototype._xhrRace=function(e,t){return e.length?new Promise(function(n){function r(){for(var e=0;s.length>e;e++)try{s[e].abort()}catch(t){}}var o,i=e.length,a=!1,s=[];for(o=0;e.length>o;o++)(function(e){var o,c,u;try{function d(t){u||(u=!0,c&&(clearTimeout(c),c=null),t&&!a?(a=!0,r(),n(e)):t||(i--,a||0!==i||n("")))}o=new XMLHttpRequest,c=null,u=!1,s.push(o),c=setTimeout(function(){d(!1)},t),o.open("POST",e,!0),o.setRequestHeader("Content-Type","application/json"),o.onreadystatechange=function(){4===o.readyState&&d(o.status>0)},o.onerror=function(){d(!1)},o.send("[]")}catch(l){i--,a||0!==i||n("")}})(e[o])}):Promise.resolve("")},at.prototype._pickFastest=function(e,t){function n(){if(r)for(var e=0;o.length>e;e++)try{o[e].abort()}catch(t){}}var r,o,i=this;return"undefined"==typeof fetch?i._xhrRace(e,t):e.length?(r="undefined"!=typeof AbortController,o=[],new Promise(function(a){var s,c=e.length,u=!1;for(s=0;e.length>s;s++)(function(e){var s,l=r?new AbortController:null;l&&o.push(l),s={method:"POST",mode:"cors",headers:{"Content-Type":"application/json"},body:"[]",cache:"no-store"},l&&(s.signal=l.signal),i._withTimeout(fetch(e,s),t).then(function(t){t&&t.status>0?u||(u=!0,n(),a(e)):(c--,u||0!==c||a(""))}).catch(function(){c--,u||0!==c||a("")})})(e[s])})):Promise.resolve("")},at.prototype._notifyReady=function(){var e=this.selectedBatchEndpoint;this.endpointReadyListeners.forEach(function(t){try{t(e)}catch(n){}})},at.prototype._setSelectedEndpoint=function(e){var t=(e||"")+"";t&&t!==this.selectedBatchEndpoint&&(this.selectedBatchEndpoint=t,this._notifyReady())},at.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("")},at.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})},at.prototype.init=function(e){return this.initialized?Promise.resolve():(this.initPromise||(this.initialized=!0,this.initPromise=this.refreshDomainConfig(e).catch(function(){})),this.initPromise)},at.prototype.resolveEndpoint=function(e,t){if(t)return Promise.resolve(t);var n=this;return this.init(e).then(function(){return n.selectedBatchEndpoint||""})},at.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)}},at.prototype.consumeProbeFailurePersistSignal=function(){return!!this.shouldPersistOnFail&&(this.shouldPersistOnFail=!1,!0)},at.prototype.getEndpointState=function(){return this.selectedBatchEndpoint?"ready":this.remoteEndpoints.length>0?"probing":"unavailable"},at.prototype.getSelectedBatchEndpoint=function(){return this.selectedBatchEndpoint},Et=function(){var e,t=[];for(e=0;1272>e;e+=2)t.push(parseInt("637c777bf26b6fc53001672bed2a28a248888da76f3248a3043813899432c6b326fb8d0d37e84760dad3574759e0b764532a380602b1fc71a9e7f97d3b6415c21d02b5612440a4500bf9c043d33e085f45c20e0b65124603dfa8cd7a76b65c11b829e69a4e7f7872c6c7150b6a0f0801d163ae27d4d6cd5d19e77a9b1b48aaf2428594d3752f1d67250bab67676203b9b40df3d7cdf7eebf4b1bd5b6e16416be39a4c4a56e644726087d013b8ea78a6750d8b0339b871ee1b6a9ab66c8032cc1ac2462c695459dc034deae0678ae1779d9e7abce45d167e719b7776c73222f3c7e3cc11ae6da5cd659b79ce60ca82ec97f6059f674c029a68db0853dc22bb6518048ed4f66256d430b74de2f1a95f0589c341befce783e7c6d9dc7dd8e21cb643ee710b3c11329260195e0c0a956f14f970a9e61e594c663f68266a11045baf84a11e051ea5b6a42067253b2d37392c3516e1fc226a5cd4ab8990c475651a4698910219b94e2eb7d2b7129f38c94e3b1246a3298fef32dcae306137c39e1f0e32fe97d11b986c41a55bd5351319f7f078010fa81eb86b8a0556f880000e3630c6a0b136307027bf70e1b5722632937a93b53bf2d240e3949fa5a6e4e6a2a6f4d5b43a39d051df60bc98f25ff3130c31fd85b6e4167b276aed3276b70c3e527f2f2963b60554543e32e1f0c1754766b610c20b0ffe75d3fb88f6fa49605d6a3d6ca3022ffc62f5c8d297b65c778da96c8941ac9c55845e094f1c4a020bff5bb01a0cb04f5a8ac42b1cb41c38d27c1e1358256f3e99e3295a6e3b8c9c0481ed49af7d43f580a8571a0b13162171ebe8666ee5365e9ce6bef0a682080268b3bb67a1076a5776a996c26c2c8c12c7490c356348d10351b5279fa09851ebf".substr(e,2),16));return t}(),Tt=[1,2,4,8,16,32,64,128,27,54],xt="__web_sdk_remote_config_cache__",pt.prototype.setEncryptedConfig=function(e){this.encryptedConfigText!==e&&(this.encryptedConfigText=e,this.cachedConfigPromise=null)},pt.prototype._tryParse=function(e){try{return JSON.parse(e)}catch(t){return e}},pt.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},pt.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()]):[]},pt.prototype._getStorage=function(){try{return"undefined"!=typeof window&&window.localStorage?window.localStorage:null}catch(e){return null}},pt.prototype._readPersisted=function(){var e,t,n;try{return(e=this._getStorage())&&(t=e.getItem(xt))?(n=JSON.parse(t),Date.now()-(n.updatedAt||0)>24e5?{}:this._mapPayload(n)):{}}catch(r){return{}}},pt.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||"")+""}},pt.prototype._persist=function(e){try{var t=this._getStorage();if(!t)return;t.setItem(xt,JSON.stringify({batchUrls:this._toStringArray(e.batchUrls),eventCodes:this._toStringArray(e.eventCodes),signKey:(e.signKey||"")+"",securityKeyVersion:(e.securityKeyVersion||"")+"",updatedAt:Date.now()}))}catch(n){}},pt.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)},pt.prototype._decryptAes256Gcm=function(e,t){var n,r,o,i,a,s,c;try{if(28>(n=this._base64ToBytes(e)).length)return Promise.reject(Error("密文过短"));if(r=this._base64ToBytes(t),o=n.slice(0,12),i=n.slice(12),"undefined"!=typeof crypto&&crypto.subtle)return crypto.subtle.importKey("raw",r,{name:"AES-GCM"},!1,["decrypt"]).then(function(e){return crypto.subtle.decrypt({name:"AES-GCM",iv:o},e,i)}).then(function(e){return new Uint8Array(e)});if(!(a=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=ct(n)):n=(ct((n<<8|n>>>24)>>>0)^Tt[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=ut(s,a),u=y.slice(0),r=12;15>r;r++)u[r]=0;for(u[15]=1,l=ut(u,a),d=dt(u),p=[],r=0;i>r;r+=16)for(_=ut(d,a),d=dt(d),o=0;16>o&&i>r+o;o++)p[r+o]=v[r+o]^_[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=lt(o,e)}return o}(c,f),r=0;16>r;r++)if((l[r]^g[r])!==w[r])return null;return p}(r,o,i)))return Promise.reject(Error("AES-GCM 认证标签校验失败"));if("undefined"!=typeof Uint8Array)for(s=new Uint8Array(a.length),c=0;a.length>c;c++)s[c]=a[c];else s=a;return Promise.resolve(s)}catch(u){return Promise.reject(u)}},pt.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))},pt.prototype.getBatchEndpoints=function(){return this.fetchAndDecryptConfig().then(function(e){return e.batchUrls||[]})},pt.prototype.getEventCodes=function(){return this.fetchAndDecryptConfig().then(function(e){return Array.isArray(e.eventCodes)&&e.eventCodes.length>0?e.eventCodes:[]})},pt.prototype.getSigningConfig=function(){return this.fetchAndDecryptConfig().then(function(e){return{hmacKey:(e.signKey||"")+"",securityKeyVersion:(e.securityKeyVersion||"")+""}})},pt.prototype.prewarm=function(){this.fetchAndDecryptConfig().catch(function(){})},pt.prototype.forceRefresh=function(){this.cachedConfigPromise=null},Dt="events",ht.prototype.isAvailable=function(){return"undefined"!=typeof indexedDB&&"undefined"!=typeof window&&null!=window.indexedDB},ht.prototype.openDb=function(){var e=this;return new Promise(function(t,n){if(!e.isAvailable())return t(null);var r=indexedDB.open("web_sdk_event_queue",3);r.onerror=function(){n(r.error||Error("open indexedDB failed"))},r.onupgradeneeded=function(e){var t,n,o,i,a=r.result,s=e.oldVersion,c=e.target.transaction;if(!a.objectStoreNames.contains(Dt))return(t=a.createObjectStore(Dt,{keyPath:"id",autoIncrement:!0})).createIndex("createdAt","createdAt",{unique:!1}),void t.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0});2>s&&c&&((n=c.objectStore(Dt)).indexNames.contains("pendingDedupeKey")||n.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!1})),3>s&&c&&((o=c.objectStore(Dt)).indexNames.contains("pendingDedupeKey")&&o.deleteIndex("pendingDedupeKey"),(i=o.getAll()).onsuccess=function(){var e,t,n,r,a,s,c,u=i.result||[],l={};for(e=0;u.length>e;e++)(n=(t=u[e]).pendingDedupeKey||_t(t.record,t.endpointOverride))&&(l[n]||(l[n]=[]),l[n].push(t.id));for(r=Object.keys(l),a=0;r.length>a;a++)if((s=l[r[a]]).length>=2)for(s.sort(function(e,t){return e-t}),c=1;s.length>c;c++)o.delete(s[c]);try{o.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0})}catch(d){}},i.onerror=function(){try{o.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0})}catch(e){}})},r.onsuccess=function(){t(r.result)}})},ht.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"))}})},ht.prototype._trimOverflow=function(e){var t=this,n=Dt,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)})})},ht.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=_t(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(Dt,"readwrite"),a=i.objectStore(Dt);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()},ht.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(Dt,"readonly").objectStore(Dt).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=ft(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})):[]})},ht.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(Dt,"readwrite")).objectStore(Dt),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()},ht.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(Dt,"readwrite")).objectStore(Dt),f=0;e.length>f;f++)u<(m=(isFinite(g=+(h=e[f]).retryCount)?Math.floor(g):0)+1)?(l++,_.delete(h.id)):(d++,y=h.pendingDedupeKey||_t(h.record,h.endpointOverride||""),v=ft(h),y&&(v.pendingDedupeKey=y),v.retryCount=m,v.nextRetryAt=i+s,_.put(v));return r._txDone(p).then(function(){return o.close(),{dropped:l,kept:d}}).catch(function(e){throw o.close(),e})}):Promise.resolve({dropped:0,kept:0})},ht.prototype.count=function(){var e=this;return this.openDb().then(function(t){if(!t)return 0;var n=t.transaction(Dt,"readonly");return new Promise(function(e,t){var r=n.objectStore(Dt).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})})},Mt={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,video_behavior_key:1,video_behavior_name:1},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},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},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}},At={message_send:"发送消息",message_receive:"收到消息",message_read:"消息已读",message_recall:"消息撤回",conversation_pin:"置顶会话",conversation_unpin:"取消置顶",conversation_archive:"归档会话",conversation_unarchive:"取消归档"},Ot=Object.keys(It={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"}).reduce(function(e,t){var n=It[t];return e[t]="imEvent"===t?function(e){return{event:n,payload:mt(t,gt(e))}}:function(e){return{event:n,payload:mt(t,e||{})}},e},{}),Lt=void 0!==console&&console.warn?function(e){console.warn(e)}:function(){},Rt={sdk_init:1,app_lifecycle:1,network_state_change:1,app_session_summary:1,app_page_view:1,page_click:1},qt=6e4,Bt="__web_sdk_recent_ok_event_ids__",Kt=function(){function e(e){var t,r=Object.keys(n);for(t=0;r.length>t;t++)e-n[r[t]]>qt&&delete n[r[t]]}function t(){try{"undefined"!=typeof window&&window.localStorage&&window.localStorage.setItem(Bt,JSON.stringify(n))}catch(e){}}var n={};if(function(){var e,t,r,o,i;try{if("undefined"!=typeof window&&window.localStorage&&(e=window.localStorage.getItem(Bt)))for(t=JSON.parse(e),r=Date.now(),o=Object.keys(t),i=0;o.length>i;i++)/^[0-9a-f]{32}$/.test(o[i])&&(r-t[o[i]]>qt||(n[o[i]]=t[o[i]]))}catch(a){}}(),"undefined"!=typeof window)try{window.addEventListener("storage",function(e){var t,r,o,i,a;try{if(e.key!==Bt||!e.newValue)return;for(t=JSON.parse(e.newValue),r=Date.now(),o=Object.keys(t),i=0;o.length>i;i++)a=t[o[i]],/^[0-9a-f]{32}$/.test(o[i])&&(r-a>qt||(n[o[i]]=a))}catch(s){}})}catch(r){}return{has:function(t){var r,o;return!!t&&(e(r=Date.now()),void 0!==(o=n[t])&&qt>=r-o)},set:function(r){var o,i,a,s,c,u=Date.now();for(e(u),o=0;r.length>o;o++)r[o]&&(n[r[o]]=u);if((i=Object.keys(n)).length>500){for(a="",s=1/0,c=0;i.length>c;c++)s>n[i[c]]&&(s=n[i[c]],a=i[c]);a&&delete n[a]}t()},reset:function(){n={},t()}}}(),kt.prototype.startReplayTimer=function(){var e=this;this.replayTimer=setInterval(function(){e.replayPersistent()},Wn)},kt.prototype._stopTimer=function(){this.timer&&(clearTimeout(this.timer),this.timer=null)},kt.prototype._settleItem=function(e,t){e.settled||(e.settled=!0,e.timeoutTimer&&(clearTimeout(e.timeoutTimer),e.timeoutTimer=null),e.resolve(t))},kt.prototype._purgeSettled=function(){this.queue.length&&(this.queue=this.queue.filter(function(e){return!e.settled}))},kt.prototype._toPersistentEntries=function(e){return e.map(function(e){return{record:e.record,endpointOverride:e.endpointOverride||"",retryCount:0,nextRetryAt:0}})},kt.prototype._getRetryDelay=function(e){return Math.min(6e4,Math.max(Wn,Math.floor(Wn*Math.pow(2,Math.min(10,Math.max(0,e))))))},kt.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))},kt.prototype.flush=function(){var e=this,t=this.flushTail.then(function(){return e.flushOnce()});return this.flushTail=t.catch(function(){}),t},kt.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())&&Kt.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(Lt("[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&&p.record.event_id||"")+"").trim()||"__empty__:"+d]||(c[_]=!0,u.push(p.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())&&Kt.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 it(e,r,{useBeacon:Zn}).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);Kt.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])})},kt.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})},kt.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())&&Kt.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 it(e,o,{useBeacon:Zn})}).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(Kt.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(p=0,_=0;t.length>_;_++)(f=t[_].retryCount||0)>p&&(p=f);return h=n._getRetryDelay(p),n.persistentQueue.updateRetryMeta(t,h,5)}):d}}):(Lt("[sdk][web] _resolveEndpoint 未注入,持久队列重试跳过"),Promise.resolve())},kt.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}))},kt.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()+Jn,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"})})},Jn),n.queue.push(o),10>n.queue.length?n.schedule():n.flush().catch(function(){})})})},kt.prototype.track=function(e,r){var o=this;return z().then(function(i){var a={appId:o.opts.appId,channel:o.opts.channel,uid:o.opts.uid||"",deviceId:o.opts.deviceId||i,deviceType:t(),sdkVersion:zn,appVersion:n(o.opts.appVersion)};return(o._getSigningConfig?o._getSigningConfig():Promise.resolve({hmacKey:"",securityKeyVersion:""})).then(function(t){return ze(a,e,t)}).then(function(e){return delete e._sessionInfo,(o._getEventCodes?o._getEventCodes():Promise.resolve([])).then(function(t){return t&&0===t.length?(Lt("[sdk][web] 当前没有事件允许上报,请检查服务端配置(eventCodes 为空)"),{sent:!1,ok:!1,status:0,reason:"event_code_not_allowed",event:e.event}):t&&-1===t.indexOf(e.event)?(Lt('[sdk][web] 事件 "'+e.event+'" 不在服务端允许上报的列表中,已忽略'),{sent:!1,ok:!1,status:0,reason:"event_code_not_allowed",event:e.event}):new Promise(function(t,n){var i={record:e,endpointOverride:r||"",resolve:t,reject:n,settled:!1,timeoutAt:Date.now()+Jn,timeoutTimer:null};i.timeoutTimer=setTimeout(function(){i.settled||o.persistentQueue.enqueueMany(o._toPersistentEntries([i])).catch(function(){}).then(function(){o.hasPersistentItems=!0,o._settleItem(i,{sent:!1,ok:!1,status:0,reason:"track_timeout",transport:"queue"})})},Jn),o.queue.push(i),10>o.queue.length?o.schedule():o.flush().catch(function(){})})})})})},kt.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)})},kt.prototype.getConfig=function(){return{batchMaxSize:10,batchIntervalMs:5e3,replayBatchSize:10,replayIntervalMs:Wn}},bt.prototype._sdkOpts=function(){return{appId:this.options.appId,channel:this.options.channel,uid:this.options.uid||"",deviceId:this.options.deviceId||"",deviceType:t(),sdkVersion:zn,appVersion:n(this.options.appVersion)}},bt.prototype._isAutoDisabled=function(e){return!(!this._disableAutoEvents||!this._disableAutoEvents[e])},bt.prototype._handleEndedSession=function(e){var t,n,r,o,i,a,s;return e&&e.sid&&!this._ssrSkipped?this._isAutoDisabled("app_session_summary")?Promise.resolve():function(e){if(!e)return!1;if(kn[e]||bn[e])return!1;if(re())try{if(window.sessionStorage.getItem(yn+(e+"")))return kn[e]=1,!1}catch(t){}return bn[e]=1,!0}(e.sid)?(t=this,r=e.sid,i=(null!=e.sessionEndTs?1e12>e.sessionEndTs?1e3*e.sessionEndTs:e.sessionEndTs:Date.now())||Date.now(),a=le(r),s=!wn||wn===r||!a&&(Sn.eventCountTotal>0||Sn.currentPage||(Sn.pageStayList||[]).length>0),a&&wn&&wn!==r&&(s=!1),a&&s&&(a.eventCountTotal||0)>(Sn.eventCountTotal||0)&&(s=!1),s?(Se(),o=oe(Sn)):o=a?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=ye((e.pageStayList||[]).concat([o])),e.currentPage=null,e):e}(oe(a),i):{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},ue(r,{sid:r,savedAt:Date.now(),sessionStartedAt:Pn,behaviorHash:$(),metrics:o}),n=o,z().then(function(r){var o=yt(t._sdkOpts(),{deviceId:r});return t.configMgr.getSigningConfig().then(function(t){var r=Ee(e,e.endReason,n);return tt(o,r,t,e.sid)})}).then(function(e){return e}).then(function(e){return delete e._sessionInfo,t.batcher.enqueueAssembled(e,"")}).then(function(){var t,n;!function(e){if(e&&(delete bn[e],kn[e]=1,re()))try{window.sessionStorage.setItem(yn+(e+""),"1")}catch(t){}}(e.sid),me(e.sid),t={pageKey:Mn||"",pageName:In||""},n=function(e){if(!e||null==e.sessionStartTs)return Date.now();var t=e.sessionStartTs;return 1e12>t?1e3*t:t}(j()),function(e,t){var n,r;e&&function(e){if(e){if(re())try{window.sessionStorage.removeItem(se(e))}catch(t){}if(fe())try{window.localStorage.removeItem(se(e))}catch(t){}}}(e),Sn={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},Pn=n=t&&"number"==typeof t.sessionStartMs&&isFinite(t.sessionStartMs)?t.sessionStartMs:Date.now(),Sn.lastOrientation=Z(),_n.reset(),vn=!0,wn=Y()||"",(r=t&&t.carryOver)&&r.pageKey?function(e,t,n){var r,o,i,a,s=((e||"")+"").trim();if(!s)return!1;r="number"==typeof n&&isFinite(n)?n:Date.now(),o=Date.now(),i=!1;try{i="undefined"!=typeof document&&"hidden"===document.visibilityState}catch(c){}Sn.pageKeys[s]=1,a=ne(),Sn.currentPage={page_key:s,page_name:(t||"")+"",enter_time:r,enter_time_ms:r,page_seq_in_sid:a,has_interaction:!1,accumulated_ms:0,resume_at_ms:i?null:o,hidden_paused:i},_e()}(r.pageKey,r.pageName,n):_e()}(e.sid,{sessionStartMs:n,carryOver:t.pageKey?{pageKey:t.pageKey,pageName:t.pageName}:null})}).catch(function(){var t;(t=e.sid)&&delete bn[t],function(e){if(e&&e.sid){var t=he().filter(function(t){return t&&t.sid!==e.sid});t.push(e),ge(t)}}({sid:e.sid,sessionStartTs:e.sessionStartTs,sessionEndTs:e.sessionEndTs,endReason:e.endReason,fields:Ee(e,e.endReason,n)})})):Promise.resolve():Promise.resolve()},bt.prototype._replayPendingSummaries=function(e,t,n){var r,o,i=he();if(!i.length)return Promise.resolve();for(r=Promise.resolve(),o=0;i.length>o;o++)(function(o){r=r.then(function(){return n(tt(e,o.fields,t,o.sid)).then(function(){return me(o.sid),null}).catch(function(){})})})(i[o]);return r},bt.prototype._startAutoEvents=function(){if(!this._ssrSkipped&&!this._autoEventsStarted){var e=this;this._autoEventsStarted=!0,An=function(t){e._handleEndedSession(t).catch(function(){})},W().then(function(){return z()}).then(function(t){var n=yt(e._sdkOpts(),{deviceId:t});return e.configMgr.getSigningConfig().then(function(t){return e.configMgr.fetchAndDecryptConfig().then(function(r){function o(e){return!a||-1!==a.indexOf(e)}function i(t){return o(t)&&!e._isAutoDisabled(t)}var a=r.eventCodes&&r.eventCodes.length>0?r.eventCodes:null,s=function(t){return t.then(function(t){return delete t._sessionInfo,e.batcher.enqueueAssembled(t,"")})};return e._replayPendingSummaries(n,t,s).then(function(){function r(t){if(!a&&(a=!0,i("app_session_summary"))){var n=function(e){var t,n,r=Date.now()||Date.now(),o=j();return o?(t=U(o,e||"web_visibility_hidden",r),"web_visibility_hidden"!==e&&"app_exit"!==e||(t.sessionEndTs=r),V(n=N(r)),{endedSession:t,sid:n.sid}):null}(t);n&&n.endedSession&&e._handleEndedSession(n.endedSession).catch(function(){})}}var a,c,u,l,d,p,_,f,h,g,m,y,v,w,k,b,C,S,P,E;i("sdk_init")&&s(function(e,t){var n,r=ln||{},o={},i=Object.keys(r);for(n=0;i.length>n;n++)o[i[n]]=r[i[n]];return ze(e,{event:"sdk_init",payload:o},t)}(n,t)).catch(function(){}),i("app_lifecycle")&&(e._webLoadTeardown=function(e,t,n){function r(){i||(i=!0,a&&(window.removeEventListener("load",a,!1),a=null),e(et(t,Ln,{},n)).catch(function(){}))}function o(){(function(){var e,t,n;try{if("undefined"!=typeof performance&&performance.timing&&(e=performance.timing).loadEventEnd&&e.navigationStart&&e.loadEventEnd>0)return!0;if("undefined"!=typeof performance&&"function"==typeof performance.getEntriesByType&&(t=performance.getEntriesByType("navigation"))&&t.length>0&&(n=t[0]).loadEventEnd>0&&n.fetchStart>=0)return!0}catch(r){}return!1})()&&r()}var i,a;return"undefined"==typeof window?(e(et(t,Ln,{},n)).catch(function(){}),function(){}):(i=!1,a=null,"complete"===document.readyState?(o(),i||r(),function(){}):(a=function(){o(),i||r()},window.addEventListener("load",a,!1),setTimeout(function(){i||r()},1e4),function(){i=!0,a&&(window.removeEventListener("load",a,!1),a=null)}))}(s,n,t)),a=!1,e._autoLifecycleTeardown=(u=function(e,r){return i("app_lifecycle")?s(et(n,e,r||{},t)):Promise.resolve()},"undefined"==typeof window||"undefined"==typeof document?function(){}:(d=!0===(l={onHidden:function(){e.batcher.flush().catch(function(){})},onPageUnload:function(){e.batcher.flush().catch(function(){}),r("app_exit")},onPageHide:function(){e.batcher.flush().catch(function(){}),r("web_visibility_hidden")}}||{}).webMode,p=function(){"visible"===document.visibilityState?d?l.onVisible&&l.onVisible():u(Rn,{}):"hidden"===document.visibilityState&&(d?l.onVisibleHidden&&l.onVisibleHidden():u(qn,{}),l.onHidden&&l.onHidden())},_=function(){u(Bn,Ze()),l.onPageUnload&&l.onPageUnload()},f=function(e){e&&e.persisted||l.onPageHide&&l.onPageHide()},window.addEventListener("visibilitychange",p,!1),window.addEventListener("beforeunload",_,!1),window.addEventListener("pagehide",f,!1),function(){window.removeEventListener("visibilitychange",p,!1),window.removeEventListener("beforeunload",_,!1),window.removeEventListener("pagehide",f,!1)})),e._autoNetworkTeardown="undefined"==typeof navigator?function(){}:(C=navigator.connection||navigator.mozConnection||navigator.webkitConnection)?(S="unknown",P=C.type?C.type+"":"",E="boolean"==typeof navigator.onLine?navigator.onLine:null,C.onchange=function(){var e,r,o,a,c,u,l,d=C.type?C.type+"":"",p="boolean"==typeof navigator.onLine?navigator.onLine:null;(d!==P||p!==E)&&(P=d,E=p,a="boolean"==typeof(r=p)?r?1:0:-1,"none"==(o=(d||"")+"")&&1===a?o="unknown":o&&"none"!==o&&"unknown"!==o&&0===a&&(a=1),l=nt({network_type:S,from_network_type:S,to_network_type:S,is_online:(e={connection_type:o,is_online:a}).is_online,change_count_in_sid:(Sn.networkChangeCount||0)+1},(u={},(c=e.connection_type)?u.connection_type=c+"":C.type&&(u.connection_type=C.type+""),C.effectiveType&&(u.effective_type=C.effectiveType+""),null!=C.downlink&&(u.downlink=Math.round(1e3*C.downlink)),null!=C.rtt&&(u.rtt=C.rtt),u)),i("network_state_change")?s(function(e,t,n){return ze(e,{event:"network_state_change",payload:t||{}},n)}(n,l,t)):Promise.resolve())},function(){C.onchange=null}):function(){},e._webInteractionTeardown="undefined"==typeof window||"undefined"==typeof document?function(){}:(h=function(e){!e||e.repeat||e.isComposing||(we(),de(),Sn.keydownTotalCount++,_e())},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(),Kn===n&&50>t-Vn||(Kn=n,Vn=t,we(),de(),Sn.inputCount++,_e()))},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)&&(we(),de(),Sn.focusTotalCount++,_e())},y=function(){ke()},v=function(e){e&&e.changedTouches&&e.changedTouches.length&&(we(),de(),Sn.touchTotalCount++,_e(),e.touches&&1===e.touches.length?(jn=e.touches[0].clientX,Nn=e.touches[0].clientY,Fn=Date.now(),ot(),Un=!1,Hn=setTimeout(function(){Un=!0,de(),Sn.longPressTotalCount++,_e()},500)):(Fn=0,ot()),e.touches&&2===e.touches.length&&(Xn=rt(e.touches),Yn=!1))},w=function(e){var t,n,r;e&&e.touches&&(1===e.touches.length&&Fn>0&&Math.sqrt((t=e.touches[0].clientX-jn)*t+(n=e.touches[0].clientY-Nn)*n)>8&&ot(),2===e.touches.length&&(r=rt(e.touches),null==Xn||Yn||12>Math.abs(r-Xn)||(de(),Sn.pinchTotalCount++,_e(),Yn=!0),Xn=r))},k=function(e){var t,n;ot(),Fn>0&&e.changedTouches&&e.changedTouches.length&&!Un&&(30>Math.sqrt((t=e.changedTouches[0].clientX-jn)*t+(n=e.changedTouches[0].clientY-Nn)*n)||(de(),Sn.swipeTotalCount++,_e())),Fn=0,Un=!1,e.touches&&e.touches.length>=2||(Xn=null,Yn=!1)},b=function(){ot(),Fn=0,Un=!1,Xn=null,Yn=!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),ke(),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),ot()}),(o("app_page_view")||o("page_click"))&&(c=i("app_page_view"),e._pageTrackerTeardown=function(e){function t(){if(c.resolvePageKey)try{var e=c.resolvePageKey();if(e)return Ae(e+"")}catch(t){}return Oe()}function n(){var e,n=t(),r=Ue({previousPageKey:l,previousPageName:d,pageKey:n});l=n,d=r.page_name,Mn=n,In=r.page_name||"",u&&(e=ne(),function(e,t,n,r){de(),Sn.eventCountTotal++,Sn.lastEventName="app_page_view",Sn.pageViewCount++;var o=(e||"")+"";o&&(Sn.pageKeys[o]=1),Ce(o,t,n,r),_e()}(n,r.page_name,Math.floor(Date.now()/1e3),e),r._pageSeqPrealloc=e,r._metricsRecorded=1,c.onPageView&&c.onPageView(r))}function r(){var e,t,n,r,o=Date.now();200>o-En||(En=o,(e=Le())&&(n=(t=e.scrollTop||0)-Tn,Tn=t,r=function(){var e,t,n;try{return(e=Le())?(t=Math.max(0,(e.scrollHeight||0)-(e.clientHeight||0)))>0?{depthPercent:Math.min(100,Math.max(0,Math.round((n=e.scrollTop||0)/t*100))),atBottom:n>=t-8}:{depthPercent:0,atBottom:!0}:{depthPercent:0,atBottom:!1}}catch(r){return{depthPercent:0,atBottom:!1}}}(),c.onScroll&&c.onScroll(n,r.depthPercent,r.atBottom)))}function o(e){var n,r,o,i;e&&0>=e.button&&((n=e.target)&&"function"==typeof n.closest&&n.closest("[data-sdk-no-auto-click]")||(r=null!=e.clientX?e.clientX:0,o=null!=e.clientY?e.clientY:0,i=Fe(e),setTimeout(function(){if(!function(e,t){var n,r;for(function(e){for(var t=xn.length-1;t>=0;t--)e-xn[t].t>300&&xn.splice(t,1)}(Date.now()),n=0;xn.length>n;n++)if(10>=Math.abs((r=xn[n]).x-e)&&10>=Math.abs(r.y-t))return!0;return!1}(r,o)){we();var e,n,a,s,u,p=(e={pageKey:l||t(),pageName:d,previousPageKey:l},n=i,s=Ae((a=e||{}).pageKey||Oe()),u=Fe(n),xe({page_key:s,page_name:a.pageName||"undefined"!=typeof document&&document.title||s,recommend_trace_id:"",referrer_page_key:a.previousPageKey||"",is_sdk_auto_collected:1,_clickSnap:u},He(u)));c.onPageClick&&c.onPageClick(p)}},50)))}function i(){Te(),n()}function a(e){e&&e.persisted&&n()}function s(){var e,t,r=Le();Tn=r&&r.scrollTop||0,n(),!p&&window.history&&(p=!0,e=history.pushState,t=history.replaceState,history.pushState=function(){var t=e.apply(history,arguments);return Te(),setTimeout(n,0),t},history.replaceState=function(){var e=t.apply(history,arguments);return Te(),setTimeout(n,0),e},window.addEventListener("popstate",i,!1))}var c,u,l,d,p;return"undefined"==typeof window||"undefined"==typeof document?function(){}:(u=!1!==(c=e||{}).enableAutoPageView,l="",d="",p=!1,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",s,!1):s(),window.addEventListener("pageshow",a,!1),window.addEventListener("scroll",r,!1),document.addEventListener("click",o,!0),function(){document.removeEventListener("click",o,!0),window.removeEventListener("scroll",r,!1),window.removeEventListener("pageshow",a,!1),window.removeEventListener("popstate",i,!1),document.removeEventListener("DOMContentLoaded",s,!1)})}({resolvePageKey:e.options.pageKeyResolver,enableAutoPageView:c,onPageView:function(t){c&&e.batcher.track({event:"app_page_view",payload:t},"").catch(function(){})},onPageClick:function(t){i("page_click")&&e.batcher.track({event:"page_click",payload:t},"").catch(function(){})},onScroll:function(e,t,n){!function(e,t,n){var r,o;de(),8>(r=Math.abs(e))||(Sn.hasScrolled||(Sn.hasScrolled=!0,Sn.firstScrollDelayMs=Math.max(0,Date.now()-Pn)),Sn.scrollTotalCount++,Sn.scrollTotalDistance+=r,o=e>0?1:-1,0!==Sn.lastScrollDirection&&o!==Sn.lastScrollDirection&&Sn.scrollDirectionChangeCount++,Sn.lastScrollDirection=o,0>e?Sn.scrollUpCount++:e>0&&Sn.scrollDownCount++,null!=t&&(Sn.maxScrollDepthPercent=Math.max(Sn.maxScrollDepthPercent,Math.min(100,Math.max(0,Math.round(t))))),n&&(Sn.isReachBottom=1),_e())}(e,t,n)}})),e._sessionPollTimer&&clearInterval(e._sessionPollTimer),e._sessionPollTimer=setInterval(function(){var t=X();t.endedSession&&e._handleEndedSession(t.endedSession).catch(function(){})},6e4)})})})}).catch(function(){})}},bt.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=vt(this.options.disableAutoEvents),this.options.disableAutoEvents=wt(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()},bt.prototype.refreshConfig=function(){var e=this;return this.configMgr.forceRefresh(),this.resolver.refreshDomainConfig(function(){return e.configMgr.getBatchEndpoints()}).catch(function(){})},bt.prototype.track=function(e,t){return this._ssrSkipped?Promise.resolve({sent:!1,ok:!0,status:0,reason:"ssr_skipped"}):this.batcher.track(e,t)},bt.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=yt(this.options),this.getConfig()},bt.prototype.setUid=function(e){return this.setContext({uid:e})},bt.prototype.getSelectedHost=function(){var e,t;return this._ssrSkipped||!this.resolver?"":(e=this.resolver.getSelectedBatchEndpoint())&&(t=/^(https?:\/\/[^/]+)/.exec(e))?t[1]:""},bt.prototype._buildInitReturn=function(){return this._ssrSkipped||!this.batcher?{selectedHost:"",version:zn}:yt(this.batcher.getConfig(),{selectedHost:this.getSelectedHost(),version:zn})},bt.prototype.getConfig=function(){if(this._ssrSkipped||!this.batcher)return yt(this.options,{selectedHost:"",selectedBatchEndpoint:"",version:zn});var e=yt(this.options,this.batcher.getConfig(),{selectedHost:this.getSelectedHost(),selectedBatchEndpoint:this.resolver.getSelectedBatchEndpoint(),version:zn});return delete e.encryptedConfig,delete e.configCipher,e},bt.prototype.flush=function(){return this._ssrSkipped?Promise.resolve():this.batcher.flush()},bt.prototype.refreshDomainConfig=function(){if(this._ssrSkipped)return Promise.resolve({selectedBatchEndpoint:"",selectedHost:"",version:zn});var e=this;return this.resolver.refreshDomainConfig(function(){return e.configMgr.getBatchEndpoints()}).then(function(t){return{selectedBatchEndpoint:t,selectedHost:e.getSelectedHost(),version:zn}})},bt.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())},Vt=null,jt=[],Nt=null,(Ft=Pt).setContext=function(e){return Ct().setContext(e)},Ft.setUid=function(e){return Ct().setUid(e)},Ft.setChannel=function(e){return Ct().setContext({channel:e})},Ft.notifyPageLoad=function(){!function(){if(on=null,B())try{window.sessionStorage.removeItem($t),window.sessionStorage.removeItem(Zt)}catch(e){}}(),X(Date.now())},Ft.debugInvalidateSessionCache=function(){G()},Ft.debugForceEnsureSession=function(){var e,t;return G(),e=X(),t=Ct(),e.endedSession&&t._handleEndedSession(e.endedSession).catch(function(){}),e},Ft.getConfig=function(){return Ct().getConfig()},Ft.flush=function(){return Ct().flush()},Ft.refreshDomainConfig=function(){return Ct().refreshDomainConfig()},Ft.stop=function(){Vt?(Vt.stop(),Vt=null):jt.length=0},Ft.version=zn,Ft.getCommonFields=function(e){var r=Vt?Vt.getConfig():{};return W().then(function(){return z().then(function(o){var i,a,s,c,u=I({appId:r.appId,channel:r.channel,uid:r.uid,deviceId:r.deviceId||o,sid:(c=Y(),c||X().sid),deviceType:t(),deviceBrand:"",deviceModel:"",deviceFingerprint:J(),fpVersion:Q(),sdkVersion:zn,appVersion:n(r.appVersion)});if(!Array.isArray(e)||!e.length)return u;for(i={},a=0;e.length>a;a++)(s=((e[a]||"")+"").trim())&&(i[s]=Object.prototype.hasOwnProperty.call(u,s)?u[s]:"");return i})})},Ft.getCommonField=function(e){var t=((e||"")+"").trim();return t?Ft.getCommonFields([t]).then(function(e){return e[t]}):Promise.resolve("")},Ft.init=function(e){var t,n,r,o;return Vt&&(t=Vt,Vt=null,t.flush().catch(function(){}).then(function(){t.stop()})),n=new bt(e),Vt=n,o=St(jt.splice(0),n,0).then(function(){Nt===r&&(Nt=null)},function(){Nt===r&&(Nt=null)}),Nt=r=o,yt(n._buildInitReturn(),{track:Ft.track,setContext:Ft.setContext,setUid:Ft.setUid,setChannel:Ft.setChannel,notifyPageLoad:Ft.notifyPageLoad,getCommonFields:Ft.getCommonFields,getCommonField:Ft.getCommonField})},Ht=function(){return Lt("[sdk][web] 非法上报:禁止直接调用 WebSDK.track({...}),请使用 WebSDK.track.videoEvent({...}) 等具名方法"),Promise.resolve({sent:!1,ok:!1,status:0,reason:"direct_track_forbidden"})},Object.keys(Ot).forEach(function(e){var t=Ot[e];Ht[e]=function(n,r){try{return Pt(t(n),r)}catch(o){if(o&&"EventValidationError"===o.name)return Promise.resolve({sent:!1,ok:!1,status:0,reason:"event_validation_failed",event:o.eventMethod||e,missingFields:o.missingFields||[],typeErrorFields:o.typeErrorFields||[]});throw o}}}),Ft.track="undefined"!=typeof Proxy?new Proxy(Ht,{get:function(e,t){return"string"!=typeof t||t in e||!/^[a-z]/.test(t)?e[t]:function(){return Lt('[sdk][web] 未知上报事件 "'+t+'",该方法不存在,事件未上报'),Promise.resolve({sent:!1,ok:!1,status:0,reason:"unknown_event_method",method:t})}}}):Ht,"undefined"!=typeof globalThis)try{globalThis.WebSDK=Ft}catch(er){}else"undefined"!=typeof window&&(window.WebSDK=Ft);return Ft}); diff --git a/app/static/css/accountShared.css b/app/static/css/accountShared.css deleted file mode 100644 index 29fd433..0000000 --- a/app/static/css/accountShared.css +++ /dev/null @@ -1,1620 +0,0 @@ -:root { - --account-bg: #0f0f0f; - --account-panel: rgba(255, 255, 255, 0.05); - --account-line: rgba(255, 255, 255, 0.1); - --account-primary: #ec237f; - --account-secondary: rgba(255, 255, 255, 0.55); -} - -.account-mobile-breadcrumb { - display: none; -} - -.account-label-mobile { - display: none; -} - -.deposit-payment-data { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - clip: rect(0 0 0 0); - clip-path: inset(50%); -} - -.deposit-main, -.deposit-mobile-buy { - display: none; -} - -@media (min-width: 768px) { - body.site-shell .footer-brand { - flex-direction: row; - align-items: center; - } - - body.site-shell .footer-brand .footer-muted-text { - max-width: min(600px, 31.25vw); - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - body.is-user-center-page, - body.is-deposit-page, - body.is-collect-page, - body.is-history-page, - body.is-setting-page, - body.is-edit-profile-page, - body.is-edit-password-page { - --account-s: min(1, 100vw / 1920px); - --deposit-s: var(--account-s); - background: var(--account-bg); - } - - /* —— 个人中心 —— */ - .user-center-layout .user-center-page { - width: 100%; - min-height: 0; - padding: calc(36px * var(--account-s)) calc(60px * var(--account-s)); - gap: calc(24px * var(--account-s)); - background: var(--account-bg); - box-sizing: border-box; - } - - .user-center-layout .user-center-pc { - width: 100%; - max-width: calc(1800px * var(--account-s)); - margin: 0 auto; - gap: calc(24px * var(--account-s)); - } - - .user-center-layout .user-center-head { - gap: calc(12px * var(--account-s)); - } - - .user-center-layout .user-center-eyebrow { - color: var(--account-primary); - font-size: calc(12px * var(--account-s)); - line-height: calc(18px * var(--account-s)); - } - - .user-center-layout .user-center-title, - .user-center-layout .user-center-title--logged { - font-size: calc(24px * var(--account-s)); - line-height: calc(32px * var(--account-s)); - font-weight: 600; - } - - .user-center-layout .user-center-desc { - color: var(--account-secondary); - font-size: calc(14px * var(--account-s)); - line-height: calc(20px * var(--account-s)); - } - - .user-center-layout .user-center-profile { - height: calc(148px * var(--account-s)); - min-height: calc(148px * var(--account-s)); - padding: calc(32px * var(--account-s)) calc(28px * var(--account-s)); - gap: calc(12px * var(--account-s)); - background: var(--account-panel); - border-radius: calc(24px * var(--account-s)); - } - - .user-center-layout .user-center-profile-info { - width: calc(230px * var(--account-s)); - gap: calc(12px * var(--account-s)); - } - - .user-center-layout .user-center-coin-card { - width: calc(343px * var(--account-s)); - height: calc(81px * var(--account-s)); - padding: calc(15px * var(--account-s)) calc(16px * var(--account-s)) calc(15px * var(--account-s)) calc(26px * var(--account-s)); - flex-direction: row; - justify-content: space-between; - align-items: center; - gap: calc(8px * var(--account-s)); - border: 0; - border-radius: calc(18px * var(--account-s)); - background: url('/static/images/figma-account/balance-card.svg') center/100% 100% no-repeat; - } - - .user-center-layout .user-center-coin-copy { - display: flex; - flex-direction: column; - gap: calc(4px * var(--account-s)); - } - - .user-center-layout .user-center-coin-label { - color: transparent; - background: linear-gradient(90deg, #ff9365 0%, #ffd8bc 100%); - -webkit-background-clip: text; - background-clip: text; - font-family: "Alimama FangYuanTi VF", "PingFang SC", sans-serif; - font-size: calc(16px * var(--account-s)); - line-height: calc(18px * var(--account-s)); - font-weight: 600; - } - - .user-center-layout .user-center-coin-label small { - color: transparent; - background: linear-gradient(90deg, #ff9365 0%, #ffd8bc 100%); - -webkit-background-clip: text; - background-clip: text; - font-size: calc(10px * var(--account-s)); - font-weight: 400; - } - - .user-center-layout .user-center-coin-value { - color: #fff; - font-family: "Arial Narrow", sans-serif; - font-size: calc(24px * var(--account-s)); - line-height: 1; - font-weight: 800; - } - - .user-center-layout .user-center-coin-action { - position: relative; - width: calc(72px * var(--account-s)); - height: calc(30px * var(--account-s)); - margin-right: calc(5px * var(--account-s)); - border-radius: 0; - background: none; - color: transparent; - font-size: 0; - line-height: 0; - display: block; - flex: 0 0 auto; - object-fit: contain; - object-position: center right; - } - - .user-center-layout .user-center-profile-name { - font-size: calc(18px * var(--account-s)); - line-height: calc(24px * var(--account-s)); - font-weight: 600; - } - - .user-center-layout .user-center-login-banner { - min-height: calc(178px * var(--account-s)); - padding: calc(28px * var(--account-s)) calc(32px * var(--account-s)); - gap: calc(14px * var(--account-s)); - background: var(--account-panel); - } - - .user-center-layout .user-center-login-title { - font-size: calc(24px * var(--account-s)); - line-height: calc(32px * var(--account-s)); - font-weight: 600; - } - - .user-center-layout .user-center-login-btn { - background: var(--account-primary); - color: #fff; - } - - .user-center-layout .user-center-login-arrow { - display: none; - } - - .user-center-layout .user-center-feature { - height: calc(120px * var(--account-s)); - padding: calc(18px * var(--account-s) - 1px) calc(22px * var(--account-s) - 1px); - gap: calc(6px * var(--account-s)); - border-radius: calc(20px * var(--account-s)); - } - - .user-center-layout .user-center-feature-title { - font-size: calc(18px * var(--account-s)); - line-height: calc(24px * var(--account-s)); - font-weight: 600; - } - - .user-center-layout .user-center-feature-desc { - color: var(--account-secondary); - font-size: calc(12px * var(--account-s)); - line-height: calc(18px * var(--account-s)); - } - - .deposit-layout .deposit-page { - width: 100%; - flex: 1 0 auto; - min-height: 0; - padding: calc(36px * var(--account-s)) calc(60px * var(--account-s)); - background: var(--account-bg); - align-items: center; - gap: calc(24px * var(--account-s)); - box-sizing: border-box; - } - - .deposit-layout .deposit-pc { - width: 100%; - max-width: calc(1800px * var(--account-s)); - margin: 0 auto; - display: flex; - flex-direction: column; - align-items: center; - gap: calc(24px * var(--account-s)); - } - - .deposit-layout .deposit-breadcrumb, - .deposit-layout .deposit-head, - .deposit-layout .deposit-hero, - .deposit-layout .deposit-purchase-title, - .deposit-layout .deposit-package-panel, - .deposit-layout .deposit-transactions { - align-self: stretch; - } - - .deposit-layout .deposit-breadcrumb, - .setting-layout .setting-breadcrumb, - .collect-layout .collect-breadcrumb, - .history-layout .history-breadcrumb { - height: auto; - overflow: hidden; - gap: calc(8px * var(--account-s)); - font-size: calc(12px * var(--account-s)); - line-height: calc(18px * var(--account-s)); - } - - .deposit-layout .deposit-breadcrumb-link, - .deposit-layout .deposit-breadcrumb-sep { - color: var(--account-secondary); - font-weight: 400; - } - - .deposit-layout .deposit-breadcrumb-link { - font-weight: 500; - } - - .deposit-layout .deposit-breadcrumb-current { - color: var(--account-primary); - font-weight: 400; - } - - .deposit-layout .deposit-head { - gap: calc(12px * var(--account-s)); - overflow: hidden; - } - - .deposit-layout .deposit-eyebrow { - color: var(--account-primary); - font-size: calc(12px * var(--account-s)); - font-weight: 400; - line-height: calc(18px * var(--account-s)); - } - - .deposit-layout .deposit-title { - font-size: calc(24px * var(--account-s)); - line-height: calc(32px * var(--account-s)); - font-weight: 600; - } - - .deposit-layout .deposit-subtitle, - .deposit-layout .deposit-hero-desc { - color: var(--account-secondary); - font-size: calc(14px * var(--account-s)); - line-height: calc(20px * var(--account-s)); - font-weight: 400; - } - - .deposit-layout .deposit-hero { - width: 100%; - height: auto; - min-height: 0; - padding: calc(36px * var(--account-s)) calc(32px * var(--account-s)); - gap: calc(12px * var(--account-s)); - border: 1px solid var(--account-line); - border-radius: calc(20px * var(--account-s)); - background: var(--account-panel); - overflow: hidden; - box-sizing: border-box; - } - - .deposit-layout .deposit-hero-badge { - color: var(--account-primary); - width: auto; - height: auto; - padding: 0; - background: transparent; - border-radius: 0; - font-size: calc(12px * var(--account-s)); - font-weight: 400; - line-height: calc(18px * var(--account-s)); - } - - .deposit-layout .deposit-hero-title, - .deposit-layout .deposit-section-title { - margin: 0; - color: #fff; - font-size: calc(24px * var(--account-s)); - line-height: calc(32px * var(--account-s)); - font-weight: 600; - } - - .deposit-package-panel { - width: 100%; - padding: calc(36px * var(--account-s)) calc(20px * var(--account-s)); - display: flex; - flex-direction: column; - gap: calc(16px * var(--account-s)); - border: 1px solid var(--account-line); - border-radius: calc(20px * var(--account-s)); - background: var(--account-panel); - overflow: hidden; - box-sizing: border-box; - } - - .deposit-package-heading { - display: inline-flex; - align-items: center; - gap: calc(10px * var(--account-s)); - overflow: hidden; - } - - .deposit-package-heading strong { - color: #fff; - font-size: calc(16px * var(--account-s)); - font-weight: 600; - line-height: calc(22px * var(--account-s)); - } - - .deposit-package-heading span { - color: var(--account-secondary); - font-size: calc(12px * var(--account-s)); - font-weight: 400; - line-height: calc(18px * var(--account-s)); - } - - .deposit-package-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: calc(12px * var(--account-s)); - align-self: stretch; - } - - .deposit-layout .deposit-package { - position: relative; - width: auto; - min-width: 0; - height: calc(128px * var(--account-s)); - min-height: calc(128px * var(--account-s)); - padding: calc(16px * var(--account-s)) calc(18px * var(--account-s)); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: calc(12px * var(--account-s)); - border: 1px solid var(--account-line); - border-radius: calc(20px * var(--account-s)); - background: var(--account-panel); - color: #fff; - text-align: left; - overflow: hidden; - box-sizing: border-box; - } - - .deposit-layout .deposit-package--active { - border: 2px solid var(--account-primary); - } - - .deposit-layout .deposit-package-price { - color: #fff; - font-size: calc(24px * var(--account-s)); - font-weight: 600; - line-height: calc(32px * var(--account-s)); - } - - .deposit-layout .deposit-package-name { - color: #fff; - font-size: calc(18px * var(--account-s)); - line-height: calc(24px * var(--account-s)); - font-weight: 600; - } - - .deposit-layout .deposit-package-hint { - color: var(--account-secondary); - font-size: calc(12px * var(--account-s)); - font-weight: 400; - line-height: calc(18px * var(--account-s)); - } - - .deposit-layout .deposit-package-check { - position: absolute; - top: calc(16px * var(--account-s)); - right: calc(18px * var(--account-s)); - width: calc(24px * var(--account-s)); - height: calc(24px * var(--account-s)); - border-radius: 50%; - background: var(--account-primary); - color: #fff; - font-size: calc(12px * var(--account-s)); - font-weight: 500; - line-height: calc(18px * var(--account-s)); - display: none; - align-items: center; - justify-content: center; - } - - .deposit-layout .deposit-package--active .deposit-package-check { - display: flex; - } - - .deposit-layout .deposit-pc-submit { - width: min(calc(964px * var(--account-s)), 100%); - height: calc(56px * var(--account-s)); - min-height: 48px; - border: 0; - border-radius: calc(28px * var(--account-s)); - background: var(--account-primary); - color: #fff; - font-size: calc(18px * var(--account-s)); - font-weight: 600; - line-height: calc(24px * var(--account-s)); - cursor: pointer; - } - - .deposit-layout .deposit-selected-note { - margin: 0; - color: var(--account-secondary); - font-size: calc(14px * var(--account-s)); - font-weight: 400; - line-height: calc(20px * var(--account-s)); - text-align: center; - } - - .deposit-transactions { - width: 100%; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: calc(16px * var(--account-s)); - overflow: hidden; - } - - .deposit-layout .deposit-tx-list { - align-self: stretch; - padding: calc(24px * var(--account-s)); - gap: calc(18px * var(--account-s)); - border: 0; - border-radius: calc(20px * var(--account-s)); - background: var(--account-panel); - overflow: hidden; - } - - .deposit-layout .deposit-tx-item { - width: 100%; - height: auto; - min-height: 0; - padding: 0; - overflow: hidden; - } - - .deposit-layout .deposit-tx-item + .deposit-tx-item { - padding-top: calc(18px * var(--account-s)); - border-top: 1px solid rgba(255, 255, 255, 0.05); - } - - .deposit-layout .deposit-tx-info { - gap: calc(12px * var(--account-s)); - } - - .deposit-layout .deposit-tx-name { - color: #fff; - font-size: calc(14px * var(--account-s)); - font-weight: 500; - line-height: calc(20px * var(--account-s)); - } - - .deposit-layout .deposit-tx-time { - color: var(--account-secondary); - font-size: calc(12px * var(--account-s)); - font-weight: 400; - line-height: calc(18px * var(--account-s)); - } - - .deposit-layout .deposit-tx-amount { - font-size: calc(16px * var(--account-s)); - font-weight: 600; - line-height: calc(22px * var(--account-s)); - } - - .deposit-layout .deposit-tx-amount--plus { - color: #0abe75; - } - - .deposit-layout .deposit-tx-amount--minus { - color: #ef4444; - } - - .deposit-layout .deposit-main, - .deposit-layout .deposit-mobile-buy { - display: none; - } - - /* —— 账号设置 / 编辑资料 / 改密 —— */ - .setting-layout .setting-breadcrumb, - .collect-layout .collect-breadcrumb, - .history-layout .history-breadcrumb, - .edit-profile-layout .setting-breadcrumb, - .edit-password-layout .edit-password-breadcrumb { - height: auto; - overflow: hidden; - gap: calc(8px * var(--account-s)); - font-size: calc(12px * var(--account-s)); - line-height: calc(18px * var(--account-s)); - } - - .setting-layout .setting-breadcrumb-link, - .setting-layout .setting-breadcrumb-sep, - .collect-layout .collect-breadcrumb-link, - .collect-layout .collect-breadcrumb-sep, - .history-layout .history-breadcrumb-link, - .history-layout .history-breadcrumb-sep, - .edit-profile-layout .setting-breadcrumb-link, - .edit-profile-layout .setting-breadcrumb-sep, - .edit-password-layout .edit-password-breadcrumb-link, - .edit-password-layout .edit-password-breadcrumb-sep { - color: var(--account-secondary); - font-weight: 400; - } - - .setting-layout .setting-breadcrumb-link, - .collect-layout .collect-breadcrumb-link, - .history-layout .history-breadcrumb-link, - .edit-profile-layout .setting-breadcrumb-link, - .edit-password-layout .edit-password-breadcrumb-link { - font-weight: 500; - text-decoration: none; - } - - .setting-layout .setting-breadcrumb-current, - .collect-layout .collect-breadcrumb-current, - .history-layout .history-breadcrumb-current, - .edit-profile-layout .setting-breadcrumb-current, - .edit-password-layout .edit-password-breadcrumb-current { - color: var(--account-primary); - font-weight: 400; - } - - .setting-layout .setting-page { - width: 100%; - min-height: 0; - padding: calc(36px * var(--account-s)) calc(60px * var(--account-s)); - gap: calc(18px * var(--account-s)); - background: var(--account-bg); - box-sizing: border-box; - } - - .setting-layout .setting-pc, - .setting-layout .setting-main { - width: 100%; - max-width: calc(1800px * var(--account-s)); - margin-right: auto; - margin-left: auto; - } - - .setting-layout .setting-pc { - gap: calc(18px * var(--account-s)); - } - - .setting-layout .setting-head { - flex-direction: row; - align-items: center; - gap: calc(12px * var(--account-s)); - } - - .setting-layout .setting-title { - font-size: calc(24px * var(--account-s)); - line-height: calc(32px * var(--account-s)); - font-weight: 600; - } - - .setting-layout .setting-main { - height: auto; - min-height: calc(408px * var(--account-s)); - gap: calc(18px * var(--account-s)); - } - - .setting-layout .setting-panel { - flex: 1 1 0; - width: auto; - min-width: 0; - padding: calc(24px * var(--account-s)); - gap: calc(18px * var(--account-s)); - border-radius: calc(20px * var(--account-s)); - min-height: calc(408px * var(--account-s)); - } - - .setting-layout .setting-aside { - flex: 0 0 calc(326px * var(--account-s)); - width: calc(326px * var(--account-s)); - padding: calc(24px * var(--account-s)); - gap: calc(24px * var(--account-s)); - border-radius: calc(20px * var(--account-s)); - min-height: calc(408px * var(--account-s)); - box-sizing: border-box; - } - - .setting-layout .setting-avatar { - width: calc(84px * var(--account-s)); - height: calc(84px * var(--account-s)); - } - - .setting-layout .setting-save-btn { - width: calc(132px * var(--account-s)); - height: calc(44px * var(--account-s)); - border-radius: 999px; - background: var(--account-primary); - color: #fff; - } - - .setting-layout .setting-logout { - color: var(--account-primary); - border-color: var(--account-primary); - background: rgba(236, 35, 127, 0.05); - } - - .edit-profile-layout .edit-profile-page, - .edit-password-layout .edit-password-page { - width: 100%; - min-height: 0; - padding: calc(36px * var(--account-s)) calc(60px * var(--account-s)); - gap: calc(18px * var(--account-s)); - background: var(--account-bg); - align-items: center; - box-sizing: border-box; - } - - .edit-profile-layout .edit-profile-pc, - .edit-profile-layout .edit-profile-h5, - .edit-password-layout .edit-password-pc, - .edit-password-layout .edit-password-shell { - width: 100%; - max-width: calc(1800px * var(--account-s)); - margin: 0 auto; - } - - .edit-profile-layout .edit-profile-h5-nav, - .edit-password-layout .edit-password-nav { - display: none; - } - - .edit-profile-layout .edit-profile-pc, - .edit-password-layout .edit-password-pc { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: calc(14px * var(--account-s)); - } - - .edit-password-layout .edit-password-pc-title { - font-size: calc(24px * var(--account-s)); - line-height: calc(32px * var(--account-s)); - font-weight: 600; - } - - /* —— 收藏 / 历史 —— */ - .collect-layout .collect-page, - .history-layout .history-page { - width: 100%; - flex: 1 0 auto; - padding: calc(36px * var(--account-s)) calc(60px * var(--account-s)); - gap: calc(24px * var(--account-s)); - background: var(--account-bg); - box-sizing: border-box; - } - - .collect-layout .collect-pc, - .collect-layout .collect-main, - .history-layout .history-pc, - .history-layout .history-main { - width: 100%; - max-width: calc(1800px * var(--account-s)); - margin: 0 auto; - } - - .collect-layout .collect-pc, - .history-layout .history-pc { - gap: calc(24px * var(--account-s)); - } - - .collect-layout .collect-head, - .history-layout .history-head { - gap: calc(12px * var(--account-s)); - } - - .collect-layout .collect-title, - .history-layout .history-title { - font-size: calc(24px * var(--account-s)); - line-height: calc(32px * var(--account-s)); - font-weight: 600; - } - - .collect-layout .collect-tabs, - .history-layout .history-clear-btn { - display: none; - } - - .collect-layout .collect-grid, - .history-layout .history-grid { - width: 100%; - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: calc(24px * var(--account-s)) calc(16px * var(--account-s)); - } - - .collect-layout .collect-card, - .history-layout .history-card.poster-card { - width: 100%; - height: auto; - min-width: 0; - border-radius: calc(18px * var(--account-s)); - overflow: hidden; - } - - .collect-layout .collect-card-cover, - .history-layout .history-card .poster-cover { - width: 100%; - aspect-ratio: 296 / 399; - height: auto; - border-radius: 0; - } - - .collect-layout .collect-card-body, - .history-layout .history-card .poster-card-body { - min-height: calc(120px * var(--account-s)); - padding: calc(12px * var(--account-s)) calc(10px * var(--account-s)); - background: var(--account-panel); - box-sizing: border-box; - } - - .history-layout .history-info { - display: none; - } -} - -@media (max-width: 767px) { - .account-label-pc { - display: none; - } - - .account-label-mobile { - display: inline; - } - body.site-shell.is-user-center-page, - body.site-shell.is-deposit-page, - body.site-shell.is-collect-page, - body.site-shell.is-history-page, - body.site-shell.is-setting-page, - body.site-shell.is-edit-profile-page, - body.site-shell.is-edit-password-page { - min-width: 0; - padding-top: calc(48px + env(safe-area-inset-top, 0px)); - background: var(--account-bg); - } - - body.is-user-center-page .site-header, - body.is-deposit-page .site-header, - body.is-collect-page .site-header, - body.is-history-page .site-header, - body.is-setting-page .site-header, - body.is-edit-profile-page .site-header, - body.is-edit-password-page .site-header { - display: flex !important; - position: fixed !important; - top: env(safe-area-inset-top, 0px) !important; - height: 48px !important; - min-height: 48px !important; - padding: 0 var(--site-shell-gutter) !important; - overflow: hidden !important; - } - - body.is-user-center-page .footer-box, - body.is-deposit-page .footer-box, - body.is-collect-page .footer-box, - body.is-history-page .footer-box, - body.is-setting-page .footer-box, - body.is-edit-profile-page .footer-box, - body.is-edit-password-page .footer-box, - body.is-user-center-page .home-mobile-footer, - body.is-deposit-page .home-mobile-footer, - body.is-collect-page .home-mobile-footer, - body.is-history-page .home-mobile-footer, - body.is-setting-page .home-mobile-footer, - body.is-edit-profile-page .home-mobile-footer, - body.is-edit-password-page .home-mobile-footer, - body.is-user-center-page .h5-tabbar, - body.is-deposit-page .h5-tabbar, - body.is-collect-page .h5-tabbar, - body.is-history-page .h5-tabbar, - body.is-setting-page .h5-tabbar, - body.is-edit-profile-page .h5-tabbar, - body.is-edit-password-page .h5-tabbar, - body.is-user-center-page .floating-action, - body.is-deposit-page .floating-action, - body.is-collect-page .floating-action, - body.is-history-page .floating-action, - body.is-setting-page .floating-action, - body.is-edit-profile-page .floating-action, - body.is-edit-password-page .floating-action, - body.is-user-center-page .download-fab, - body.is-deposit-page .download-fab, - body.is-collect-page .download-fab, - body.is-history-page .download-fab, - body.is-setting-page .download-fab { - display: none !important; - } - - .user-center-layout .user-center-page { - min-height: calc(100dvh - 48px); - padding: 0; - background: var(--account-bg); - } - - .user-center-layout .user-center-main { - display: flex; - align-items: center; - } - - .user-center-layout .user-center-mobile-profile { - width: calc(100% - 32px); - max-width: 343px; - height: 56px; - margin: 12px auto 0; - padding: 0; - gap: 12px; - background: transparent; - border: 1px solid rgba(255, 255, 255, 0.06); - } - - .user-center-layout .user-center-mobile-body { - width: calc(100% - 32px); - max-width: 343px; - margin: 14px auto 0; - gap: 14px; - overflow: visible; - } - - .user-center-layout .user-center-mobile-menu { - border: 0; - } - - .deposit-layout .deposit-page { - min-height: calc(100dvh - 48px); - padding: 0 0 106px; - background: var(--account-bg); - overflow: visible; - } - - .deposit-layout .deposit-pc, - .deposit-layout .deposit-nav { - display: none; - } - - .deposit-layout .deposit-main { - width: 100%; - max-width: none; - padding: 12px 16px 0; - display: flex; - flex-direction: column; - gap: 18px; - box-sizing: border-box; - } - - .account-mobile-breadcrumb { - display: flex; - align-items: center; - gap: 8px; - color: var(--account-secondary); - font-size: 12px; - line-height: 18px; - } - - .account-mobile-breadcrumb a { - color: inherit; - text-decoration: none; - } - - .account-mobile-breadcrumb strong { - color: var(--account-primary); - font-weight: 400; - } - - body.is-collect-page .collect-page, - body.is-history-page .history-page { - min-height: calc(100dvh - 48px); - padding: 12px 16px 0; - gap: 14px; - background: var(--account-bg); - box-sizing: border-box; - } - - body.is-collect-page .collect-main, - body.is-history-page .history-main { - width: 100%; - max-width: none; - min-height: 0; - padding: 0; - gap: 0; - } - - body.is-history-page .history-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - column-gap: 7px; - row-gap: 12px; - } - - .deposit-mobile-balance { - width: 100%; - height: 81px; - padding: 15px 26px; - border-radius: 18px; - background: url('/static/images/figma-account/deposit-balance-card.png') center/100% 100% no-repeat; - box-sizing: border-box; - } - - .deposit-mobile-balance-label { - color: #fff; - font-size: 14px; - font-weight: 600; - line-height: 20px; - } - - .deposit-mobile-balance-label small { - font-size: 10px; - font-weight: 400; - } - - .deposit-mobile-balance-value { - color: #fff; - font-family: "Arial Narrow", sans-serif; - font-size: 24px; - font-weight: 800; - line-height: 28px; - } - - .deposit-mobile-packages { - padding: 20px 12px; - display: flex; - flex-direction: column; - gap: 18px; - border: 1px solid rgba(255, 211, 153, 0.08); - border-radius: 24px; - background: rgba(35, 27, 21, 0.12); - box-sizing: border-box; - } - - .deposit-mobile-package-head { - display: flex; - align-items: center; - gap: 12px; - } - - .deposit-mobile-package-head strong { - color: #fff; - font-size: 16px; - font-weight: 500; - line-height: 22px; - } - - .deposit-mobile-package-head span { - color: rgba(255, 255, 255, 0.35); - font-size: 12px; - line-height: 18px; - } - - .deposit-mobile-package-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 18px 10px; - } - - .deposit-layout .deposit-mobile-pkg { - position: relative; - width: 100%; - height: 152px; - padding: 21px 8px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 8px; - border: 1px solid rgba(255, 211, 153, 0.2); - border-radius: 18px; - background: rgba(255, 255, 255, 0.05); - color: #fff; - box-sizing: border-box; - min-width: 0; - max-width: none; - justify-self: stretch; - } - - .deposit-layout .deposit-mobile-pkg--active { - border: 2px solid #febf8b; - background: linear-gradient(180deg, rgba(254, 190, 139, 0.3), rgba(254, 190, 139, 0.1)); - } - - .deposit-mobile-pkg img { - width: 24px; - height: 24px; - } - - .deposit-mobile-pkg-name { - font-size: 14px; - line-height: 20px; - } - - .deposit-mobile-pkg-hint { - max-width: 100%; - color: #999; - font-size: 12px; - line-height: 18px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .deposit-mobile-pkg-price { - font-size: 18px; - font-weight: 600; - line-height: 24px; - } - - .deposit-mobile-pkg-price small { - margin-right: 4px; - font-size: 12px; - font-weight: 400; - } - - .deposit-mobile-pkg-check { - position: absolute; - bottom: -9px; - left: 50%; - width: 20px; - height: 20px; - border-radius: 50%; - transform: translateX(-50%); - background: #febf8b; - color: #2d251e; - display: none; - align-items: center; - justify-content: center; - font-size: 13px; - font-weight: 700; - } - - .deposit-mobile-pkg--active .deposit-mobile-pkg-check { - display: flex; - } - - .deposit-usage { - display: flex; - flex-direction: column; - gap: 12px; - } - - .deposit-usage h2 { - margin: 0 2px; - color: #fff; - font-size: 16px; - font-weight: 500; - line-height: 22px; - } - - .deposit-usage-card { - padding: 20px 12px; - border: 1px solid rgba(255, 211, 153, 0.08); - border-radius: 24px; - background: rgba(35, 27, 21, 0.12); - } - - .deposit-usage-item { - min-height: 42px; - padding: 8px 4px; - display: flex; - align-items: flex-start; - gap: 12px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - box-sizing: border-box; - } - - .deposit-usage-item:first-child { - padding-top: 0; - } - - .deposit-usage-item:last-child { - padding-bottom: 0; - border-bottom: 0; - } - - .deposit-usage-item img { - width: 24px; - height: 24px; - flex: 0 0 auto; - } - - .deposit-usage-item span { - min-width: 0; - display: flex; - flex-direction: column; - gap: 2px; - } - - .deposit-usage-item strong, - .deposit-usage-item small { - font-size: 12px; - line-height: 18px; - font-weight: 400; - } - - .deposit-usage-item strong { - color: #fff; - font-weight: 500; - } - - .deposit-usage-item small { - color: rgba(255, 255, 255, 0.4); - } - - .deposit-layout .deposit-mobile-buy { - position: fixed; - right: 0; - bottom: 0; - left: 0; - z-index: 90; - height: 106px; - padding: 10px 16px 20px; - display: flex; - flex-direction: column; - gap: 8px; - background: var(--account-bg); - box-sizing: border-box; - } - - .deposit-mobile-buy button { - width: 100%; - height: 54px; - border: 0; - border-radius: 999px; - color: #171717; - background: linear-gradient(180deg, #fbc297, #fae1c0); - font-size: 16px; - font-weight: 600; - } - - .deposit-mobile-buy p { - margin: 0; - color: rgba(255, 255, 255, 0.4); - font-size: 12px; - line-height: 18px; - text-align: center; - } - - .deposit-mobile-buy a { - color: inherit; - } - - .collect-layout .collect-page, - .history-layout .history-page { - min-height: calc(100dvh - 48px); - padding: 12px 16px 0; - gap: 14px; - background: var(--account-bg); - box-sizing: border-box; - overflow: visible; - } - - .collect-layout .collect-pc, - .history-layout .history-pc { - width: 100%; - max-width: none; - display: block !important; - padding: 0; - } - - .collect-layout .collect-head, - .history-layout .history-head, - .collect-layout .collect-nav, - .history-layout .history-nav, - .collect-layout .collect-tabs, - .history-layout .history-clear-btn, - .collect-layout .collect-pagination, - .history-layout .history-pagination { - display: none !important; - } - - .collect-layout .collect-breadcrumb, - .history-layout .history-breadcrumb { - display: flex !important; - gap: 8px; - font-size: 12px; - line-height: 18px; - } - - .collect-layout .collect-main, - .history-layout .history-main { - width: 100%; - max-width: none; - padding: 0; - margin: 0; - } - - .collect-layout .collect-grid, - .history-layout .history-grid { - width: 100%; - display: grid !important; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px 7px; - } - - .collect-layout .collect-card, - .history-layout .history-card.poster-card { - width: 100%; - height: auto; - min-width: 0; - background: transparent; - border-radius: 12px; - overflow: visible; - } - - .collect-layout .collect-card-cover, - .history-layout .history-card .poster-cover { - width: 100%; - height: auto; - aspect-ratio: 168 / 224; - border-radius: 12px; - overflow: hidden; - } - - .collect-layout .collect-card-body { - min-height: 80px; - padding: 8px 0; - gap: 4px; - background: transparent; - box-sizing: border-box; - } - - .collect-layout .collect-card-title, - .history-layout .history-name { - font-size: 14px; - font-weight: 500; - line-height: 20px; - } - - .collect-layout .collect-card-desc, - .history-layout .history-intro { - font-size: 12px; - line-height: 18px; - color: var(--account-secondary); - } - - .collect-layout .collect-card-eps, - .history-layout .episode-badge { - right: 6px; - bottom: 6px; - padding: 3px 8px; - border-radius: 6px; - } - - .history-layout .history-card .poster-card-body { - display: none; - } - - .history-layout .history-info { - min-height: 80px; - padding: 8px 0; - display: flex; - flex-direction: column; - gap: 4px; - box-sizing: border-box; - } - - .history-layout .history-intro { - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .history-layout .history-watch, - .history-layout .history-time { - font-size: 12px; - line-height: 18px; - } - - .history-layout .history-time { - display: none; - } - - body.is-collect-page .collect-card { - border-radius: 12px; - } - - body.is-collect-page .collect-card-body { - min-height: 80px; - padding: 8px 0; - gap: 4px; - background: transparent; - } - - body.is-collect-page .collect-card-title { - font-weight: 500; - } - - body.is-history-page .history-card.poster-card { - padding: 0; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 0; - border: 0; - border-radius: 12px; - background: transparent; - } - - body.is-history-page .history-card .poster-cover { - width: 100%; - height: auto; - padding-bottom: 0; - aspect-ratio: 168 / 224; - border-radius: 12px; - } - - body.is-history-page .history-card .episode-badge { - display: flex !important; - } - - body.is-history-page .history-info { - width: 100%; - min-height: 80px; - padding: 8px 0; - flex: 0 0 auto; - box-sizing: border-box; - } - - body.is-history-page .history-name { - font-weight: 500; - } - - body.is-history-page .history-time { - display: none; - } - - .setting-layout .setting-page { - min-height: calc(100dvh - 48px); - padding: 12px 16px 0; - gap: 14px; - background: var(--account-bg); - box-sizing: border-box; - } - - .setting-layout .setting-pc { - width: 100%; - display: block !important; - padding: 0; - } - - .setting-layout .setting-head, - .setting-layout .setting-nav { - display: none; - } - - .setting-layout .setting-breadcrumb { - display: flex !important; - gap: 8px; - font-size: 12px; - line-height: 18px; - height: 18px; - overflow: hidden; - } - - .setting-layout .setting-main { - width: 100%; - max-width: none; - padding: 0; - gap: 14px; - } - - .setting-layout .setting-profile-mobile, - .setting-layout .setting-aside { - width: 100%; - display: flex; - flex-direction: column; - gap: 14px; - } - - .setting-layout .setting-aside { - padding: 0; - border: 0; - background: transparent; - } - - .setting-layout .setting-section-label, - .setting-layout .setting-section-label--aside { - display: block; - color: #fff; - font-size: 16px; - font-weight: 600; - line-height: 22px; - } - - .setting-layout .setting-aside-title { - display: none; - } - - .setting-layout .setting-group { - width: 100%; - border: 0; - border-radius: 16px; - background: var(--account-panel); - } - - .setting-layout .setting-item { - height: 48px; - padding: 0 14px; - } - - .setting-layout .setting-item--avatar { - height: 72px; - } - - .setting-layout .setting-item-avatar { - width: 48px; - height: 48px; - } - - .setting-layout .setting-group--security .setting-item { - height: 48px; - border: 1px solid var(--account-line); - border-radius: 12px; - } - - .setting-layout .setting-logout { - width: 100%; - height: 48px; - margin: 0; - border: 1px solid var(--account-line); - border-radius: 12px; - background: var(--account-panel); - color: rgba(255, 255, 255, 0.9); - font-size: 16px; - font-weight: 600; - } - - .edit-profile-layout .edit-profile-page, - .edit-password-layout .edit-password-page { - min-height: calc(100dvh - 48px); - padding: 12px 16px 0; - gap: 14px; - background: var(--account-bg); - box-sizing: border-box; - } - - .edit-profile-layout .edit-profile-pc, - .edit-password-layout .edit-password-pc { - width: 100%; - max-width: none; - display: block !important; - padding: 0; - } - - .edit-profile-layout .setting-breadcrumb, - .edit-password-layout .edit-password-breadcrumb { - display: flex; - gap: 8px; - color: var(--account-secondary); - font-size: 12px; - line-height: 18px; - height: 18px; - } - - .edit-profile-layout .setting-breadcrumb a, - .edit-password-layout .edit-password-breadcrumb a { - color: inherit; - } - - .edit-profile-layout .edit-profile-h5, - .edit-password-layout .edit-password-shell { - width: 100%; - max-width: none; - min-height: 0; - display: block; - padding: 0; - } - - .edit-profile-layout .edit-profile-h5-nav, - .edit-password-layout .edit-password-nav, - .edit-password-layout .edit-password-pc-head { - display: none; - } - - .edit-profile-layout .edit-profile-h5-body, - .edit-password-layout .edit-password-form { - width: 100%; - padding: 0; - display: flex; - flex-direction: column; - gap: 14px; - box-sizing: border-box; - } - - .edit-profile-layout .edit-profile-avatar-card { - width: 100%; - height: 112px; - padding: 0 16px; - border-radius: 18px; - } - - .edit-profile-layout .edit-profile-avatar-desc { - display: block; - } - - .edit-profile-layout .edit-profile-avatar, - .edit-profile-layout .edit-profile-avatar-wrap { - width: 64px; - height: 64px; - } - - .edit-profile-layout .edit-profile-field { - width: 100%; - height: 76px; - min-height: 76px; - padding: 12px 14px; - gap: 8px; - border-radius: 18px; - } - - .edit-profile-layout .edit-profile-field--bio { - height: 96px; - min-height: 96px; - } - - .edit-profile-layout .edit-profile-field-input { - padding: 0; - border: 0; - background: transparent; - } - - .edit-profile-layout .edit-profile-submit, - .edit-password-layout .edit-password-submit { - width: 100%; - height: 50px; - border-radius: 12px; - background: var(--account-primary); - color: #fff; - font-size: 16px; - font-weight: 600; - } - - .edit-profile-layout .edit-profile-tip, - .edit-password-layout .edit-password-reset { - display: none; - } - - .edit-password-layout .edit-password-intro { - width: 100%; - height: 72px; - min-height: 72px; - padding: 12px 14px; - gap: 8px; - border-radius: 18px; - box-sizing: border-box; - } - - .edit-password-layout .edit-password-field { - width: 100%; - height: 70px; - min-height: 70px; - padding: 12px 14px; - gap: 6px; - border-radius: 18px; - box-sizing: border-box; - } - - .edit-password-layout .edit-password-input-row { - padding: 0; - border: 0; - background: transparent; - } -} diff --git a/app/static/css/coinDialog.css b/app/static/css/coinDialog.css deleted file mode 100644 index 1ceb0eb..0000000 --- a/app/static/css/coinDialog.css +++ /dev/null @@ -1,137 +0,0 @@ -.coin-dialog-mask { - position: fixed; - inset: 0; - z-index: 1100; - display: none; - align-items: center; - justify-content: center; - padding: 24px; - box-sizing: border-box; - background: rgba(0, 0, 0, 0.7); - -webkit-backdrop-filter: blur(10px); - backdrop-filter: blur(10px); -} -.coin-dialog-mask.is-open { display: flex; } -.coin-dialog-stack { - width: 440px; - max-width: 100%; - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; -} -.coin-dialog-card { - width: 440px; - max-width: 100%; - padding: 36px; - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: center; - gap: 18px; - color: #fff; - background: #0d0d0d; - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 40px; - font-family: "PingFang SC", "Noto Sans SC", sans-serif; -} -.coin-dialog-icon { width: 60px; height: 60px; flex: 0 0 auto; object-fit: contain; } -.coin-dialog-title { color: #fff; font-size: 24px; font-weight: 600; line-height: 32px; text-align: center; } -.coin-dialog-desc { color: rgba(255,255,255,.55); font-size: 16px; font-weight: 400; line-height: 22px; text-align: center; } -.coin-dialog-balance { - width: 100%; - padding: 18px; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - background: rgba(255,255,255,.05); - border: 1px solid rgba(255,255,255,.1); - border-radius: 12px; -} -.coin-dialog-balance-col { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; } -.coin-dialog-balance-col--end { align-items: flex-end; gap: 2px; } -.coin-dialog-balance-label { color: rgba(255,255,255,.55); font-size: 12px; font-weight: 400; line-height: 18px; } -.coin-dialog-balance-value, -.coin-dialog-balance-cost { color: #fff; font-size: 16px; font-weight: 600; line-height: 22px; } -.coin-dialog-balance-cost { color: #ec237f; } -.coin-dialog-hint { color: rgba(255,255,255,.55); font-size: 14px; font-weight: 400; line-height: 20px; text-align: center; } -.coin-dialog-actions { width: 100%; display: flex; gap: 18px; } -.coin-dialog-download, -.coin-dialog-cta { - min-width: 0; - min-height: 58px; - padding: 18px 24px; - box-sizing: border-box; - display: flex; - flex: 1 1 0; - align-items: center; - justify-content: center; - color: #fff; - font-family: inherit; - font-size: 16px; - font-weight: 600; - line-height: 22px; - text-decoration: none; - white-space: nowrap; - border: 0; - border-radius: 18px; - cursor: pointer; -} -.coin-dialog-download { background: linear-gradient(135deg, #ff5f6e 0%, #fe2a7f 50%, #8c206e 100%); } -.coin-dialog-cta { - padding-right: 30px; - padding-left: 30px; - color: #8b1a15; - background: linear-gradient(180deg, #fbc297 0%, #fae1c0 100%); -} -.coin-dialog-cta-label-h5 { display: none; } -.coin-dialog-cta-loading { display: none; } -.coin-dialog-cta.is-loading, -.coin-dialog-cta:disabled { opacity: .75; cursor: not-allowed; } -.coin-dialog-cta.is-loading .coin-dialog-cta-label { display: none; } -.coin-dialog-cta.is-loading .coin-dialog-cta-loading { display: inline; } -.coin-dialog-recharge { - width: 100%; - color: #ec237f; - font-size: 16px; - font-weight: 600; - line-height: 22px; - text-align: center; - text-decoration: none; -} -.coin-dialog-close { - width: 36px; - height: 36px; - padding: 0; - flex: 0 0 36px; - border: 0; - background: transparent; - cursor: pointer; -} -.coin-dialog-close img { display: block; width: 36px; height: 36px; } -@media (max-width: 767px) { - .coin-dialog-mask { - padding: 16px; - -webkit-backdrop-filter: blur(2px); - backdrop-filter: blur(2px); - } - .coin-dialog-stack { width: 300px; } - .coin-dialog-card { width: 300px; padding: 24px 20px; border-radius: 20px; } - .coin-dialog-title { font-size: 18px; line-height: 24px; } - .coin-dialog-desc { font-size: 14px; line-height: 20px; } - .coin-dialog-hint { font-size: 12px; line-height: 18px; } - .coin-dialog-actions { flex-direction: column; gap: 18px; } - .coin-dialog-download, - .coin-dialog-cta { - width: 100%; - min-height: 46px; - padding: 12px 24px; - flex: none; - border-radius: 9px; - } - .coin-dialog-cta { padding-right: 48px; padding-left: 48px; } - .coin-dialog-cta-label-pc { display: none; } - .coin-dialog-cta-label-h5 { display: inline; } - .coin-dialog-recharge { font-size: 14px; font-weight: 400; line-height: 20px; } -} diff --git a/app/static/css/collect.css b/app/static/css/collect.css deleted file mode 100644 index 73f42fc..0000000 --- a/app/static/css/collect.css +++ /dev/null @@ -1,489 +0,0 @@ -.collect-layout { - min-height: 100vh; - display: flex; - flex-direction: column; -} - -.collect-layout .site-header, -.collect-layout .footer-box { - flex-shrink: 0; -} - -.collect-page { - width: 100%; - flex: 1 1 auto; - min-height: 0; - padding: 36px 0; - background: #0A0A0A; - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; - box-sizing: border-box; -} - -.collect-pc { - width: min(1800px, calc(100% - 120px)); - max-width: none; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 24px; -} - -.collect-nav { - display: none; -} - -.collect-main { - width: min(1800px, calc(100% - 120px)); - max-width: none; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 24px; -} - -.collect-breadcrumb { - align-self: stretch; - overflow: hidden; - display: inline-flex; - justify-content: flex-start; - align-items: center; - gap: 10px; -} - -.collect-breadcrumb-link { - color: #EC237F; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - text-decoration: none; -} - -.collect-breadcrumb-sep { - color: #666666; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.collect-breadcrumb-current { - color: #8A8A8A; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.collect-head { - align-self: stretch; - overflow: hidden; - display: flex; - justify-content: flex-start; - align-items: center; - gap: 12px; -} - -.collect-title { - margin: 0; - color: white; - font-size: 30px; - font-family: PingFang SC; - font-weight: 400; - line-height: 36px; - flex-shrink: 0; -} - -.collect-desc { - margin: 0; - color: #A3A3A3; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.collect-tabs { - overflow: hidden; - justify-content: flex-start; - align-items: center; - gap: 9px; - display: inline-flex; -} - -.collect-tab { - padding: 7px 16px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - display: inline-flex; - justify-content: center; - align-items: center; - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - text-decoration: none; - box-sizing: border-box; -} - -.collect-tab--active { - background: #EC237F; - border: none; - color: #181818; - font-weight: 700; -} - -.collect-grid { - align-self: stretch; - width: 100%; - display: grid; - grid-template-columns: repeat(8, minmax(0, 1fr)); - justify-content: stretch; - align-items: stretch; - gap: 14px 16px; -} - -.collect-card { - width: 100%; - min-width: 0; - background: transparent; - overflow: hidden; - border-radius: 14px; - display: flex; - flex-direction: column; - align-items: stretch; - text-decoration: none; - color: inherit; - box-sizing: border-box; -} - -.collect-card-cover { - width: 100%; - height: auto; - aspect-ratio: 3 / 4; - position: relative; - overflow: hidden; - background: rgba(255, 255, 255, 0.05); -} - -.collect-card-cover-img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} - -.collect-card-eps { - position: absolute; - right: 8px; - bottom: 8px; - height: 28px; - padding: 4px 8px; - background: rgba(0, 0, 0, 0.60); - border-radius: 8px; - display: inline-flex; - justify-content: center; - align-items: center; - color: #fff; - font-size: 12px; - font-family: PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - box-sizing: border-box; - z-index: 2; -} - -.collect-card-body { - width: 100%; - flex: 1 1 auto; - padding: 8px 10px; - background: rgba(255, 255, 255, 0.05); - display: flex; - flex-direction: column; - align-items: stretch; - gap: 4px; - box-sizing: border-box; -} - -.collect-card-title { - width: 100%; - min-height: 20px; - color: #F5F5F5; - font-size: 14px; - font-family: PingFang SC, sans-serif; - font-weight: 400; - line-height: 20px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - word-break: break-all; -} - -.collect-card-desc { - width: 100%; - min-height: 18px; - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.collect-card-meta { - width: 100%; - margin-top: auto; - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; - overflow: hidden; -} - -.collect-card-tag { - min-width: 0; - color: #8A8A8A; - font-size: 12px; - font-family: PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.collect-card-spacer { - flex: 1 1 0; - min-width: 4px; - height: 9px; -} - -.collect-card-progress { - flex-shrink: 0; - color: #808080; - font-size: 12px; - font-family: PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - white-space: nowrap; -} - -.collect-empty { - width: 100%; - grid-column: 1 / -1; - padding: 48px 16px; - text-align: center; - color: #A3A3A3; -} - -.collect-pagination { - align-self: stretch; - width: 100%; - margin-top: 0; -} - -.collect-pagination .pagination { - width: 100%; -} - -@media (max-width: 767px) { - body.is-collect-page { - background: #0A0A0A; - } - - body.is-collect-page .site-header, - body.is-collect-page .footer-box, - body.is-collect-page .floating-action, - body.is-collect-page .download-fab { - display: none !important; - height: 0 !important; - min-height: 0 !important; - overflow: hidden !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - } - - body.is-collect-page .collect-layout { - min-height: 100vh; - background: #0A0A0A; - } - - body.is-collect-page .collect-page { - padding: 0; - background: #0A0A0A; - align-items: stretch; - justify-content: flex-start; - gap: 0; - min-height: 100vh; - } - - body.is-collect-page .collect-pc { - display: none; - } - - body.is-collect-page .collect-nav { - position: sticky; - top: 0; - z-index: 40; - width: 100%; - height: 48px; - padding: 0 16px; - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - flex-shrink: 0; - background: #181818; - } - - body.is-collect-page .collect-nav-back { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - } - - body.is-collect-page .collect-nav-back img { - width: 24px; - height: 24px; - display: block; - opacity: 0.65; - filter: brightness(0) invert(1); - } - - body.is-collect-page .collect-nav-title { - margin: 0; - flex: 1 1 0; - height: 24px; - display: flex; - justify-content: center; - align-items: center; - color: #fff; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - } - - body.is-collect-page .collect-nav-spacer { - width: 24px; - height: 24px; - flex-shrink: 0; - } - - body.is-collect-page .collect-main { - width: calc(100% - 32px); - max-width: 343px; - margin: 0 auto; - padding: 12px 0; - align-items: stretch; - gap: 12px; - box-sizing: border-box; - } - - body.is-collect-page .collect-tabs { - width: auto; - height: 42px; - padding: 4px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.08); - display: inline-flex; - justify-content: flex-start; - align-items: flex-start; - gap: 4px; - box-sizing: border-box; - align-self: flex-start; - margin: 0 auto; - } - - body.is-collect-page .collect-tab { - width: 109px; - flex: 0 0 109px; - min-width: 109px; - height: 34px; - padding: 0; - background: transparent; - border: none; - overflow: hidden; - border-radius: 9px; - display: flex; - justify-content: center; - align-items: center; - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - box-sizing: border-box; - } - - body.is-collect-page .collect-tab--active { - background: #EC237F; - color: #171717; - font-weight: 700; - } - - body.is-collect-page .collect-grid { - width: 100%; - display: grid; - grid-template-columns: 1fr 1fr; - justify-content: stretch; - gap: 12px 7px; - } - - body.is-collect-page .collect-card { - width: 100%; - border-radius: 10px; - } - - body.is-collect-page .collect-card-body { - background: rgba(255, 255, 255, 0.05); - } - - body.is-collect-page .collect-card-title { - color: rgba(255, 255, 255, 0.95); - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - } - - body.is-collect-page .collect-card-desc { - color: rgba(255, 255, 255, 0.60); - } - - body.is-collect-page .collect-card-tag { - color: rgba(255, 255, 255, 0.45); - } - - body.is-collect-page .collect-card-progress { - color: rgba(255, 255, 255, 0.45); - } - - body.is-collect-page .collect-card-cover { - height: 0; - padding-bottom: calc(224 / 168 * 100%); - } - - body.is-collect-page .collect-card-cover-img { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - } - - body.is-collect-page .collect-pagination { - margin-top: 0; - } -} diff --git a/app/static/css/deposit.css b/app/static/css/deposit.css deleted file mode 100644 index dab1926..0000000 --- a/app/static/css/deposit.css +++ /dev/null @@ -1,1195 +0,0 @@ -.deposit-layout { - min-height: 100vh; - display: flex; - flex-direction: column; - background: #0F0F0F; -} - -.deposit-layout .site-header, -.deposit-layout .footer-box { - flex-shrink: 0; -} - -.deposit-page { - width: 100%; - flex: 1 1 auto; - min-height: 0; - padding: 36px 0; - background: #0F0F0F; - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; - box-sizing: border-box; -} - -.deposit-pc { - width: 964px; - max-width: calc(100% - 32px); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 24px; -} - -.deposit-nav, -.deposit-main, -.deposit-footer { - display: none; -} - -.deposit-breadcrumb { - height: 18px; - display: inline-flex; - align-items: center; - gap: 8px; -} - -.deposit-breadcrumb-link { - color: rgba(255, 255, 255, 0.55); - font-size: 12px; - font-family: "PingFang SC", "Noto Sans SC", sans-serif; - font-weight: 500; - line-height: 18px; - text-decoration: none; -} - -.deposit-breadcrumb-sep { - color: rgba(255, 255, 255, 0.55); - font-size: 12px; - font-family: "PingFang SC", "Noto Sans SC", sans-serif; - font-weight: 400; - line-height: 18px; -} - -.deposit-breadcrumb-current { - color: #EC237F; - font-size: 12px; - font-family: "PingFang SC", "Noto Sans SC", sans-serif; - font-weight: 400; - line-height: 18px; -} - -.deposit-head { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; -} - -.deposit-eyebrow { - color: #EC237F; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; -} - -.deposit-title { - margin: 0; - color: white; - font-size: 42px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 48px; -} - -.deposit-subtitle { - color: #A3A3A3; - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 20px; -} - -.deposit-hero { - align-self: stretch; - padding: 32px; - background: linear-gradient(90deg, #381433 0%, #141429 52%, #121C3B 100%); - border-radius: 30px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 12px; - box-sizing: border-box; -} - -.deposit-hero-badge { - padding: 8px 12px; - background: #EC237F; - border-radius: 99px; - color: #181818; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; -} - -.deposit-hero-title { - color: white; - font-size: 30px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 36px; -} - -.deposit-hero-desc { - color: #8B5CF6; - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 20px; -} - -.deposit-section { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 16px; -} - -.deposit-section--packages { - gap: 20px; -} - -.deposit-section--submit { - align-items: center; -} - -.deposit-section-head { - width: 100%; - height: 48px; - min-height: 48px; - display: flex; - justify-content: space-between; - align-items: center; - gap: 16px; -} - -.deposit-section-title { - margin: 0; - color: white; - font-size: 28px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 34px; -} - -.deposit-pc-submit { - min-width: 160px; - height: 48px; - padding: 0 28px; - background: #EC237F; - border: none; - border-radius: 99px; - color: #fff; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - cursor: pointer; -} - -.deposit-pc-submit:disabled, -.deposit-pc-submit.is-loading, -.deposit-mobile-submit:disabled, -.deposit-mobile-submit.is-loading { - opacity: 0.7; - cursor: not-allowed; -} - -.deposit-pay-methods { - display: flex; - flex-wrap: wrap; - align-items: flex-start; - gap: 8px; -} - -.deposit-pay-btn { - padding: 13px 14px; - background: rgba(255, 255, 255, 0.05); - border-radius: 99px; - border: 1px solid rgba(255, 255, 255, 0.10); - color: #808080; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - cursor: pointer; -} - -.deposit-pay-btn--active { - background: #EC237F; - border: none; - color: #181818; -} - -.deposit-package-group { - width: 100%; - padding: 20px; - border-radius: 28px; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 16px; - box-sizing: border-box; -} - -.deposit-package-group--vip { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.20); - scroll-margin-top: 24px; -} - -.deposit-package-group--coin { - background: rgba(245, 158, 11, 0.10); - border: 1px solid rgba(245, 158, 11, 0.10); -} - -.deposit-package-group-head { - width: 100%; - height: 40px; - min-height: 40px; - display: flex; - align-items: center; - gap: 12px; -} - -.deposit-package-group-icon { - width: 36px; - height: 36px; - border-radius: 18px; - display: flex; - justify-content: center; - align-items: center; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - flex-shrink: 0; - overflow: hidden; -} - -.deposit-package-group-icon--vip { - background: #EC237F; - color: #181818; - font-size: 12px; - line-height: 18px; -} - -.deposit-package-group-icon--coin { - background: transparent; - padding: 0; -} - -.deposit-package-group-icon--coin img { - width: 36px; - height: 36px; - display: block; - object-fit: contain; -} - -.deposit-package-group-text { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 2px; -} - -.deposit-package-group-title { - color: white; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; -} - -.deposit-package-group-desc { - color: #8A8A8A; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; -} - -.deposit-package-row { - width: 100%; - display: flex; - align-items: stretch; - gap: 12px; - flex-wrap: wrap; -} - -.deposit-package-empty { - width: 100%; - padding: 20px 12px; - color: #A3A3A3; - font-size: 13px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - line-height: 20px; - text-align: center; - box-sizing: border-box; -} - -.deposit-package { - flex: 1 1 0; - min-width: 180px; - height: 128px; - min-height: 128px; - padding: 16px; - position: relative; - background: rgba(255, 255, 255, 0.05); - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - box-sizing: border-box; - text-align: left; - cursor: pointer; -} - -.deposit-package-group--coin .deposit-package { - border-color: rgba(255, 255, 255, 0.05); -} - -.deposit-package--active { - border-color: #EC237F; -} - -.deposit-package-price { - color: white; - font-size: 24px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 30px; -} - -.deposit-package-group--coin .deposit-package-price, -.deposit-package-group--coin .deposit-package-name { - color: #F5F5F5; -} - -.deposit-package-original { - display: none; - color: #A3A3A3; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 16px; - text-decoration: line-through; -} - -.deposit-package-name { - color: white; - font-size: 18px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 24px; -} - -.deposit-package-hint { - color: #A3A3A3; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; -} - -.deposit-package-check { - display: none; - width: 24px; - height: 24px; - position: absolute; - right: 12px; - top: 16px; - background: #EC237F; - border-radius: 12px; - color: #fff; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 500; - line-height: 18px; - align-items: center; - justify-content: center; - box-sizing: border-box; - padding: 0; -} - -.deposit-package--active .deposit-package-check { - display: inline-flex; -} - -.deposit-tx-list { - width: 100%; - padding: 8px; - background: rgba(255, 255, 255, 0.05); - border-radius: 30px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - flex-direction: column; - box-sizing: border-box; -} - -.deposit-tx-item { - width: 100%; - height: 64px; - min-height: 64px; - padding: 0 16px; - display: flex; - justify-content: space-between; - align-items: center; - gap: 16px; - box-sizing: border-box; -} - -.deposit-tx-info { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - min-width: 0; -} - -.deposit-tx-name { - color: #F5F5F5; - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 20px; -} - -.deposit-tx-time { - color: #8A8A8A; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; -} - -.deposit-tx-amount { - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - flex-shrink: 0; -} - -.deposit-tx-amount--plus { - color: #0ABE75; -} - -.deposit-tx-amount--minus { - color: #EF4444; -} - -@media (max-width: 767px) { - body.is-deposit-page { - background: #0A0A0A; - padding-bottom: 0; - } - - body.is-deposit-page .site-header, - body.is-deposit-page .footer-box, - body.is-deposit-page .floating-action, - body.is-deposit-page .download-fab { - display: none !important; - height: 0 !important; - min-height: 0 !important; - overflow: hidden !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - } - - .deposit-layout { - min-height: 100vh; - background: #0A0A0A; - } - - .deposit-page { - padding: 0; - padding-bottom: calc(108px + env(safe-area-inset-bottom, 0px)); - background: #0A0A0A; - align-items: stretch; - gap: 0; - min-height: 100vh; - } - - .deposit-pc { - display: none; - } - - .deposit-nav { - position: sticky; - top: 0; - z-index: 40; - width: 100%; - height: 48px; - padding: 0 16px; - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - flex-shrink: 0; - background: #181818; - overflow: hidden; - } - - .deposit-nav-back { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - } - - .deposit-nav-back img { - width: 24px; - height: 24px; - display: block; - opacity: 0.65; - filter: brightness(0) invert(1); - } - - .deposit-nav-title { - margin: 0; - flex: 1 1 0; - height: 24px; - display: flex; - justify-content: center; - align-items: center; - color: #fff; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - overflow: hidden; - } - - .deposit-nav-spacer { - width: 24px; - height: 24px; - flex-shrink: 0; - } - - .deposit-main { - display: flex; - width: calc(100% - 32px); - max-width: 343px; - margin: 0 auto; - padding: 12px 0; - flex-direction: column; - align-items: stretch; - gap: 14px; - box-sizing: border-box; - overflow: hidden; - } - - .deposit-mobile-wallet { - width: 100%; - height: 98px; - min-height: 98px; - padding: 14px 16px; - background: rgba(255, 255, 255, 0.05); - border-radius: 18px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - box-sizing: border-box; - } - - .deposit-mobile-wallet-label { - color: #EC237F; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - } - - .deposit-mobile-wallet-balance { - color: #fff; - font-size: 20px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 26px; - } - - .deposit-mobile-wallet-meta { - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - } - - .deposit-mobile-section-title { - color: #fff; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - } - - .deposit-mobile-row { - width: 100%; - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - align-items: stretch; - gap: 8px; - overflow: visible; - } - - .deposit-mobile-pkg { - flex: 0 0 calc((100% - 16px) / 3); - width: calc((100% - 16px) / 3); - max-width: calc((100% - 16px) / 3); - min-width: 0; - border: none; - cursor: pointer; - box-sizing: border-box; - font: inherit; - color: inherit; - -webkit-tap-highlight-color: transparent; - } - - .deposit-mobile-row > .deposit-package-empty { - flex: 1 1 100%; - width: 100%; - max-width: none; - color: rgba(255, 255, 255, 0.50); - } - - .deposit-mobile-pkg--vip { - height: auto; - min-height: 112px; - padding: 12px; - background: rgba(255, 255, 255, 0.05); - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: visible; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - gap: 6px; - } - - .deposit-mobile-pkg--vip.deposit-mobile-pkg--active { - background: rgba(250, 220, 68, 0.12); - border: 1.5px solid rgba(250, 220, 68, 0.70); - } - - .deposit-mobile-pkg--vip .deposit-mobile-pkg-name, - .deposit-mobile-pkg--vip .deposit-mobile-pkg-price { - color: rgba(255, 255, 255, 0.90); - } - - .deposit-mobile-pkg--vip.deposit-mobile-pkg--active .deposit-mobile-pkg-name, - .deposit-mobile-pkg--vip.deposit-mobile-pkg--active .deposit-mobile-pkg-price { - color: rgba(255, 255, 255, 0.95); - } - - .deposit-mobile-pkg--vip .deposit-mobile-pkg-hint { - color: rgba(255, 255, 255, 0.50); - text-align: center; - } - - .deposit-mobile-pkg--vip.deposit-mobile-pkg--active .deposit-mobile-pkg-hint { - color: rgba(255, 255, 255, 0.60); - } - - .deposit-mobile-pkg--coin { - height: auto; - min-height: 86px; - padding: 12px; - background: rgba(255, 255, 255, 0.05); - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: visible; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - gap: 4px; - } - - .deposit-mobile-pkg--coin.deposit-mobile-pkg--active { - background: rgba(250, 220, 68, 0.12); - border: 1.5px solid rgba(250, 220, 68, 0.70); - } - - .deposit-mobile-pkg-name { - color: rgba(255, 255, 255, 0.90); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 20px; - } - - .deposit-mobile-pkg-price { - color: rgba(255, 255, 255, 0.90); - font-size: 20px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 26px; - } - - .deposit-mobile-pkg-price--coin { - color: #EC237F; - font-size: 16px; - line-height: 22px; - } - - .deposit-mobile-pkg-original { - display: none; - color: rgba(255, 255, 255, 0.40); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 16px; - text-decoration: line-through; - } - - .deposit-mobile-pkg-hint { - color: rgba(255, 255, 255, 0.50); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - width: 100%; - text-align: left; - overflow: visible; - display: block; - white-space: normal; - word-break: break-word; - } - - .deposit-mobile-pkg--coin .deposit-mobile-pkg-hint { - display: none; - } - - .deposit-mobile-pay-methods { - width: 100%; - min-height: 42px; - display: flex; - justify-content: flex-start; - align-items: flex-start; - gap: 8px; - flex-wrap: wrap; - overflow: hidden; - } - - .deposit-mobile-pay { - min-width: 72px; - height: 42px; - padding: 0 16px; - background: rgba(255, 255, 255, 0.05); - border-radius: 21px; - border: 1px solid rgba(255, 255, 255, 0.08); - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - display: inline-flex; - justify-content: center; - align-items: center; - cursor: pointer; - box-sizing: border-box; - -webkit-tap-highlight-color: transparent; - } - - .deposit-mobile-pay--active { - min-width: 88px; - background: #EC237F; - border: none; - color: #171717; - } - - .deposit-mobile-tx { - width: 100%; - padding: 4px 0; - background: rgba(255, 255, 255, 0.05); - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - display: flex; - flex-direction: column; - box-sizing: border-box; - } - - .deposit-mobile-tx-item { - width: 100%; - min-height: 64px; - padding: 12px 14px; - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - box-sizing: border-box; - } - - .deposit-mobile-tx-item + .deposit-mobile-tx-item { - border-top: 1px solid rgba(255, 255, 255, 0.10); - } - - .deposit-mobile-tx-info { - min-width: 0; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - } - - .deposit-mobile-tx-name { - color: #F5F5F5; - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 20px; - max-width: 220px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .deposit-mobile-tx-time { - color: rgba(255, 255, 255, 0.50); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - } - - .deposit-mobile-tx-amount { - flex-shrink: 0; - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 20px; - } - - .deposit-mobile-tx .deposit-package-empty { - padding: 20px 14px; - text-align: center; - color: rgba(255, 255, 255, 0.50); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - } - - .deposit-mobile-tx .deposit-tx-amount--plus, - .deposit-mobile-tx-amount.deposit-tx-amount--plus { - color: #10B981; - } - - .deposit-mobile-tx .deposit-tx-amount--minus, - .deposit-mobile-tx-amount.deposit-tx-amount--minus { - color: #F68071; - } - - .deposit-footer { - position: fixed; - left: 0; - right: 0; - bottom: 0; - z-index: 30; - width: 100%; - padding: 10px 16px calc(20px + env(safe-area-inset-bottom, 0px)); - background: #101010; - border-top: 1px rgba(255, 255, 255, 0.10) solid; - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - box-sizing: border-box; - overflow: hidden; - } - - .deposit-mobile-submit { - width: 100%; - max-width: 343px; - height: 50px; - background: #EC237F; - border: none; - border-radius: 14px; - color: #171717; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - display: inline-flex; - justify-content: center; - align-items: center; - cursor: pointer; - box-sizing: border-box; - } - - .deposit-mobile-agree { - width: 100%; - max-width: 343px; - text-align: center; - color: rgba(255, 255, 255, 0.40); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - } - - .deposit-mobile-agree a { - color: rgba(255, 255, 255, 0.40); - text-decoration: none; - } -} -.deposit-confirm-mask { - position: fixed; - inset: 0; - z-index: 1200; - display: none; - align-items: center; - justify-content: center; - overflow-y: auto; - padding: 24px; - box-sizing: border-box; - background: rgba(0,0,0,.7); - -webkit-backdrop-filter: blur(10px); - backdrop-filter: blur(10px); -} -.deposit-confirm-mask.is-open { display: flex; } -.deposit-confirm-stack { - width: 440px; - max-width: 100%; - margin: auto; - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; -} -.deposit-confirm-card { - width: 440px; - max-width: 100%; - padding: 36px; - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 18px; - color: #fff; - background: #0d0d0d; - border: 1px solid rgba(255,255,255,.2); - border-radius: 40px; - font-family: "PingFang SC", "Noto Sans SC", sans-serif; -} -.deposit-confirm-head { - display: flex; - align-items: center; - gap: 12px; -} -.deposit-confirm-head h2, -.deposit-confirm-method-title, -.deposit-confirm-note { margin: 0; } -.deposit-confirm-head h2 { - color: #fff; - font-size: 24px; - font-weight: 600; - line-height: 32px; -} -.deposit-confirm-head span { - color: rgba(255,255,255,.55); - font-size: 14px; - font-weight: 400; - line-height: 20px; -} -.deposit-confirm-order { - width: 100%; - min-height: 78px; - padding: 16px 18px; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - background: rgba(255,255,255,.05); - border: 1px solid rgba(255,255,255,.05); - border-radius: 20px; -} -.deposit-confirm-order-copy { - min-width: 0; - display: flex; - flex-direction: column; - gap: 6px; -} -.deposit-confirm-order-copy strong { - overflow: hidden; - color: #fff; - font-size: 20px; - font-weight: 400; - line-height: 28px; - text-overflow: ellipsis; - white-space: nowrap; -} -.deposit-confirm-order-copy span { - color: rgba(255,255,255,.55); - font-size: 14px; - font-weight: 400; - line-height: 20px; -} -.deposit-confirm-price { - width: 90px; - height: 44px; - flex: 0 0 90px; - display: flex; - align-items: center; - justify-content: center; - color: #ec237f; - font-size: 20px; - font-weight: 600; - line-height: 28px; - background: rgba(255,74,74,.08); - border-radius: 22px; -} -.deposit-confirm-method-title { - color: #fff; - font-size: 18px; - font-weight: 600; - line-height: 24px; -} -.deposit-confirm-methods { - display: flex; - flex-direction: column; - gap: 12px; -} -.deposit-confirm-method { - width: 100%; - min-height: 70px; - padding: 16px 18px; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - color: #fff; - font-family: inherit; - background: rgba(255,255,255,.05); - border: 1px solid rgba(255,255,255,.05); - border-radius: 20px; - cursor: pointer; -} -.deposit-confirm-method.is-active { border-color: #ec237f; } -.deposit-confirm-method-copy { display: flex; align-items: center; gap: 12px; } -.deposit-confirm-method-icon { - width: 36px; - height: 36px; - flex: 0 0 36px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 999px; -} -.deposit-confirm-method-icon--alipay { background: #335ae8; } -.deposit-confirm-method-icon--wechat { background: #15ba11; } -.deposit-confirm-method-icon--usdt { background: #27a17c; } -.deposit-confirm-method-icon--other { background: rgba(255,255,255,.1); } -.deposit-confirm-method-icon img { display: block; width: 28px; height: 28px; object-fit: contain; } -.deposit-confirm-method-icon--wechat img { width: 24px; height: 24px; } -.deposit-confirm-method-name { color: #fff; font-size: 16px; font-weight: 600; line-height: 22px; } -.deposit-confirm-method-check { - width: 20px; - height: 20px; - flex: 0 0 20px; - display: flex; - align-items: center; - justify-content: center; - color: transparent; - font: 700 12px/1 "Noto Sans SC", sans-serif; - background: rgba(255,255,255,.1); - border-radius: 10px; -} -.deposit-confirm-method.is-active .deposit-confirm-method-check { color: #fff; background: #ec237f; } -.deposit-confirm-submit { - width: 100%; - min-height: 50px; - padding: 13px 0; - box-sizing: border-box; - color: #fff; - font: 600 18px/24px "PingFang SC", "Noto Sans SC", sans-serif; - background: #ec237f; - border: 0; - border-radius: 12px; - cursor: pointer; -} -.deposit-confirm-submit:disabled { opacity: .75; cursor: not-allowed; } -.deposit-confirm-note { - color: rgba(255,255,255,.55); - font-size: 12px; - font-weight: 400; - line-height: 18px; - text-align: center; -} -.deposit-confirm-close { - width: 36px; - height: 36px; - padding: 0; - flex: 0 0 36px; - background: transparent; - border: 0; - cursor: pointer; -} -.deposit-confirm-close img { display: block; width: 36px; height: 36px; } -@media (max-width: 767px) { - .deposit-confirm-mask { - padding: 16px; - -webkit-backdrop-filter: blur(2px); - backdrop-filter: blur(2px); - } - .deposit-confirm-stack { width: 300px; } - .deposit-confirm-card { width: 300px; padding: 24px 20px; border-radius: 20px; } - .deposit-confirm-head h2 { font-size: 16px; line-height: 22px; } - .deposit-confirm-head span { font-size: 12px; line-height: 18px; } - .deposit-confirm-order { - min-height: 70px; - padding: 12px; - } - .deposit-confirm-order-copy strong { font-size: 16px; font-weight: 600; line-height: 22px; } - .deposit-confirm-order-copy span { font-size: 12px; line-height: 18px; } - .deposit-confirm-price { - width: auto; - height: auto; - min-width: 58px; - padding: 4px 12px; - flex: 0 0 auto; - box-sizing: border-box; - font-size: 18px; - line-height: 24px; - } - .deposit-confirm-method-title { font-size: 16px; line-height: 22px; } - .deposit-confirm-method { - min-height: 50px; - padding: 12px; - border-radius: 12px; - } - .deposit-confirm-method-icon { - width: 24px; - height: 24px; - flex-basis: 24px; - } - .deposit-confirm-method-icon img { width: 18.67px; height: 18.67px; } - .deposit-confirm-method-icon--wechat img { width: 16px; height: 16px; } - .deposit-confirm-method-name { font-size: 14px; font-weight: 400; line-height: 20px; } - .deposit-confirm-method-check { - width: 16px; - height: 16px; - flex-basis: 16px; - font-size: 10px; - border-radius: 8px; - } - .deposit-confirm-submit { font-weight: 400; } -} diff --git a/app/static/css/detail.css b/app/static/css/detail.css index 72b4401..aa3c9a5 100644 --- a/app/static/css/detail.css +++ b/app/static/css/detail.css @@ -309,6 +309,21 @@ flex-wrap: wrap; } +.detail-episode-lock { + position: absolute; + top: 0; + right: 0; + padding: 0 4px; + border-radius: 0 7px 0 7px; + background: #ec237f; + color: #fff; + font-size: 9px; + font-family: PingFang SC; + font-weight: 500; + line-height: 14px; + pointer-events: none; +} + .detail-episode { flex: 0 0 calc((100% - 98px) / 8); width: calc((100% - 98px) / 8); @@ -331,29 +346,6 @@ box-sizing: border-box; } -.detail-episode-coin { - width: 16px; - height: 16px; - right: 6px; - left: auto; - top: 6px; - position: absolute; - display: block; - padding: 0; - background: none; - overflow: visible; - border-radius: 0; - box-sizing: border-box; - pointer-events: none; -} - -.detail-episode-coin img { - width: 16px; - height: 16px; - object-fit: contain; - display: block; -} - .detail-comments { align-self: stretch; width: 100%; @@ -416,189 +408,6 @@ color: #F5F5F5; } -.detail-comment-composer { - align-self: stretch; - padding: 12px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 10px; - justify-content: flex-start; - align-items: flex-start; - gap: 12px; - display: flex; - box-sizing: border-box; -} - -.detail-comment-composer-avatar { - width: 36px; - height: 36px; - border-radius: 18px; - object-fit: cover; - flex-shrink: 0; -} - -.detail-comment-composer-main { - flex: 1; - min-width: 0; - overflow: hidden; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - gap: 12px; - display: flex; -} - -.detail-comment-composer-input { - width: 100%; - min-height: 56px; - padding: 10px 12px; - background: #000; - overflow: hidden; - border-radius: 8px; - border: none; - resize: none; - box-sizing: border-box; - box-shadow: none; - outline: none; - outline-color: transparent; - -webkit-appearance: none; - appearance: none; - color: #F5F5F5; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.detail-comment-composer-input:focus, -.detail-comment-composer-input:focus-visible { - border: none; - outline: none; - outline-color: transparent; - box-shadow: none; -} - -.detail-comment-composer-input::placeholder { - color: #A3A3A3; -} - -.detail-comment-composer-footer { - width: 100%; - height: 28px; - overflow: hidden; - justify-content: space-between; - align-items: center; - display: flex; -} - -.detail-comment-composer-status { - height: 20px; - overflow: hidden; - justify-content: flex-start; - align-items: center; - gap: 8px; - display: flex; -} - -.detail-comment-composer-logged { - color: #ec237f; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.detail-comment-composer-user { - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.detail-comment-composer-count { - color: #D4D4D4; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.detail-comment-composer-submit { - width: 84px; - height: 28px; - background: #ec237f; - overflow: hidden; - border-radius: 14px; - justify-content: center; - align-items: center; - display: flex; - border: none; - cursor: pointer; - color: #fff; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - padding: 0; - position: relative; - gap: 6px; - box-sizing: border-box; -} - -.detail-comment-composer-submit:disabled, -.detail-comment-composer-submit.is-loading { - opacity: 0.75; - cursor: not-allowed; -} - -.detail-comment-composer-submit.is-loading::before { - content: ""; - width: 12px; - height: 12px; - border: 2px solid rgba(24, 24, 24, 0.35); - border-top-color: #181818; - border-radius: 50%; - animation: detail-comment-spin 0.7s linear infinite; - flex-shrink: 0; -} - -@keyframes detail-comment-spin { - to { - transform: rotate(360deg); - } -} - -.detail-comments-login { - align-self: stretch; - height: 44px; - padding: 0 12px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 7px; - justify-content: flex-start; - align-items: center; - display: flex; - box-sizing: border-box; -} - -.detail-comments-login-link { - color: #ec237f; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - text-decoration: none; -} - -.detail-comments-login-hint { - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - .detail-comment-list { align-self: stretch; width: 100%; @@ -675,50 +484,6 @@ line-height: 18px; } -.detail-comment-like { - display: inline-flex; - align-items: center; - gap: 4px; - background: none; - border: none; - cursor: pointer; - padding: 0; - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.detail-comment-like img { - width: 14px; - height: 14px; -} - -.detail-comment-like--active { - color: #A3A3A3; -} - -.detail-comment-like--active img { - filter: none; -} - -.detail-comment-reply-label { - color: #A3A3A3; - font-size: 12px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 400; - line-height: 18px; - background: none; - border: none; - padding: 0; - cursor: pointer; -} - -.detail-comment-reply-label:hover { - color: #ec237f; -} - .detail-comment-reply-to { color: #A3A3A3; } @@ -778,89 +543,6 @@ opacity: 0.92; } -.detail-comment-reply-hint { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 6px; - color: #A3A3A3; - font-size: 12px; - line-height: 18px; -} - -.detail-comment-reply-hint button { - background: none; - border: none; - padding: 0; - color: #ec237f; - cursor: pointer; - font-size: 12px; -} - -.detail-comment-inline-composer { - width: 100%; - margin-top: 8px; - padding: 10px 12px; - background: rgba(255, 255, 255, 0.05); - border-radius: 6px; - box-sizing: border-box; - display: flex; - flex-direction: column; - gap: 8px; -} - -.detail-comment-inline-input { - width: 100%; - min-height: 56px; - padding: 8px 10px; - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 6px; - background: #0A0A0A; - color: #F5F5F5; - font-size: 13px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - line-height: 20px; - resize: none; - box-sizing: border-box; -} - -.detail-comment-inline-input::placeholder { - color: #737373; -} - -.detail-comment-inline-input:focus, -.detail-comment-inline-input:focus-visible { - border-color: rgba(236, 35, 127, 0.55); - outline: none; - outline-color: transparent; - box-shadow: none; -} - -.detail-comment-inline-footer { - display: flex; - justify-content: flex-end; -} - -.detail-comment-inline-send { - height: 32px; - padding: 0 14px; - border: none; - border-radius: 6px; - background: #ec237f; - color: #fff; - font-size: 13px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 500; - cursor: pointer; -} - -.detail-comment-inline-send:disabled, -.detail-comment-inline-send.is-loading { - opacity: 0.6; - cursor: not-allowed; -} - .detail-comments-more { align-self: stretch; height: 43px; @@ -1048,23 +730,6 @@ box-sizing: border-box; } - .detail-h5-top-btn--like { - width: auto; - min-width: 20px; - height: auto; - gap: 4px; - } - - .detail-h5-top-like-count { - color: rgba(255, 255, 255, 0.92); - font-size: 11px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 400; - line-height: 12px; - white-space: nowrap; - text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.55); - } - .detail-h5-top-btn img { width: 18px; height: 18px; @@ -1074,12 +739,6 @@ opacity: 0.92; } - .detail-h5-top-btn--active img, - .detail-h5-top-btn img[src*="h5_liked"] { - filter: none; - opacity: 1; - } - .detail-page.page-shell { position: relative; z-index: 1; @@ -1305,19 +964,6 @@ text-align: center; } - .detail-episode-coin { - width: 16px; - height: 16px; - left: auto; - right: 6px; - top: 6px; - } - - .detail-episode-coin img { - width: 16px; - height: 16px; - } - .detail-related { width: 100%; gap: 12px; @@ -1467,50 +1113,6 @@ gap: 12px; } - .detail-h5-collect, - .detail-h5-like { - width: 132px; - padding: 11px 24px; - background: rgba(255, 255, 255, 0.10); - border: none; - border-radius: 999px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 12px; - cursor: pointer; - color: white; - font-size: 16px; - font-family: PingFang SC; - font-weight: 400; - line-height: 22px; - flex-shrink: 0; - } - - .detail-h5-collect--active, - .detail-h5-like--active { - color: #ec237f; - } - - .detail-h5-collect--active img, - .detail-h5-like--active img { - filter: none; - } - - .detail-h5-collect img, - .detail-h5-like img { - width: 22px; - height: 22px; - display: block; - object-fit: contain; - filter: none; - } - - .detail-h5-collect--active img, - .detail-h5-like--active img { - filter: none; - } - .detail-h5-play { flex: 1; padding: 11px 24px; @@ -1823,18 +1425,6 @@ text-align: center; } - .detail-episode-coin { - width: calc(16px * var(--detail-s)); - height: calc(16px * var(--detail-s)); - top: calc(6px * var(--detail-s)); - right: calc(6px * var(--detail-s)); - } - - .detail-episode-coin img { - width: 100%; - height: 100%; - } - .content-official-info { padding: calc(24px * var(--detail-s)) calc(30px * var(--detail-s)); border-radius: calc(18px * var(--detail-s)); @@ -1876,15 +1466,6 @@ line-height: calc(18px * var(--detail-s)); } - .detail-comment-composer, - .detail-comment-login { - min-height: calc(44px * var(--detail-s)); - padding: 12px calc(12px * var(--detail-s)); - border-radius: calc(7px * var(--detail-s)); - font-size: calc(12px * var(--detail-s)); - line-height: calc(18px * var(--detail-s)); - } - .detail-related { display: flex; width: calc(296px * var(--detail-s)); @@ -2034,7 +1615,5 @@ line-height: 14px; } - .detail-h5-collect--active, - .detail-h5-like--active { color: #ec237f; } .detail-h5-play { background: #ec237f; color: #fff; } } diff --git a/app/static/css/editPassword.css b/app/static/css/editPassword.css deleted file mode 100644 index b735c60..0000000 --- a/app/static/css/editPassword.css +++ /dev/null @@ -1,382 +0,0 @@ -.edit-password-layout { - min-height: 100vh; - display: flex; - flex-direction: column; - background: #0A0A0A; -} - -.edit-password-layout .site-header, -.edit-password-layout .footer-box { - flex-shrink: 0; -} - -.edit-password-page { - width: 100%; - flex: 1 1 auto; - padding: 36px 0 48px; - background: #0A0A0A; - display: flex; - flex-direction: column; - align-items: center; - gap: 18px; - box-sizing: border-box; -} - -.edit-password-pc { - width: 100%; - max-width: calc(100% - 32px); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 14px; -} - -.edit-password-breadcrumb { - display: inline-flex; - align-items: center; - gap: 10px; -} - -.edit-password-breadcrumb-link { - color: #EC237F; - font-size: 14px; - font-family: PingFang SC, "Noto Sans SC", sans-serif; - font-weight: 400; - line-height: 20px; - text-decoration: none; -} - -.edit-password-breadcrumb-sep { - color: #666666; - font-size: 14px; - font-family: PingFang SC, "Noto Sans SC", sans-serif; - font-weight: 400; - line-height: 20px; -} - -.edit-password-breadcrumb-current { - color: #8A8A8A; - font-size: 14px; - font-family: PingFang SC, "Noto Sans SC", sans-serif; - font-weight: 400; - line-height: 20px; -} - -.edit-password-pc-head { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; -} - -.edit-password-pc-title { - margin: 0; - color: #F5F5F5; - font-size: 30px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 36px; -} - -.edit-password-pc-desc { - color: rgba(255, 255, 255, 0.60); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 20px; -} - -.edit-password-shell { - width: 100%; - max-width: calc(100% - 32px); - display: flex; - flex-direction: column; - align-items: stretch; -} - -.edit-password-nav { - display: none; -} - -.edit-password-form { - width: 100%; - max-width: 480px; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 14px; - overflow: hidden; -} - -.edit-password-intro { - width: 100%; - height: 86px; - min-height: 86px; - padding: 14px 16px; - background: rgba(255, 255, 255, 0.05); - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - box-sizing: border-box; -} - -.edit-password-intro-title { - color: rgba(255, 255, 255, 0.95); - font-size: 18px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 24px; -} - -.edit-password-intro-desc { - width: 100%; - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; -} - -.edit-password-field { - width: 100%; - height: 78px; - min-height: 78px; - padding: 12px 14px; - background: rgba(255, 255, 255, 0.05); - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; - box-sizing: border-box; -} - -.edit-password-field.is-error { - height: auto; - min-height: 78px; - border-color: rgba(255, 107, 107, 0.65); -} - -.edit-password-error { - margin: 0; - color: #FF6B6B; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; -} - -.edit-password-error[hidden] { - display: none; -} - -.edit-password-label { - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; -} - -.edit-password-input-row { - width: 100%; - height: 30px; - min-height: 30px; - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - overflow: hidden; -} - -.edit-password-input { - flex: 1 1 0; - min-width: 0; - height: 30px; - padding: 0; - border: none; - background: transparent; - box-shadow: none; - outline: none; - outline-color: transparent; - -webkit-appearance: none; - appearance: none; - color: rgba(255, 255, 255, 0.95); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 20px; -} - -.edit-password-input:focus, -.edit-password-input:focus-visible { - border: none; - outline: none; - outline-color: transparent; - box-shadow: none; -} - -.edit-password-input::placeholder { - color: rgba(255, 255, 255, 0.40); -} - -.edit-password-toggle { - flex-shrink: 0; - padding: 0; - border: none; - background: transparent; - color: #EC237F; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - cursor: pointer; - -webkit-tap-highlight-color: transparent; -} - -.edit-password-submit { - width: 100%; - height: 50px; - background: #EC237F; - border: none; - border-radius: 14px; - color: #171717; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - display: inline-flex; - justify-content: center; - align-items: center; - cursor: pointer; - box-sizing: border-box; - overflow: hidden; -} - -.edit-password-submit:disabled { - opacity: 0.7; - cursor: not-allowed; -} - -.edit-password-reset { - width: 100%; - min-height: 18px; - padding: 0; - display: inline-flex; - justify-content: center; - align-items: center; - border-radius: 14px; - color: #EC237F; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - text-decoration: none; - overflow: hidden; - box-sizing: border-box; -} - -@media (max-width: 767px) { - body.is-edit-password-page { - background: #0A0A0A; - padding-bottom: 0; - } - - body.is-edit-password-page .site-header, - body.is-edit-password-page .footer-box, - body.is-edit-password-page .floating-action, - body.is-edit-password-page .download-fab { - display: none !important; - height: 0 !important; - min-height: 0 !important; - overflow: hidden !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - } - - .edit-password-page { - padding: 0; - gap: 0; - align-items: stretch; - min-height: 100vh; - } - - .edit-password-pc { - display: none; - } - - .edit-password-shell { - width: 100%; - max-width: 100%; - min-height: 100vh; - align-items: center; - } - - .edit-password-nav { - position: sticky; - top: 0; - z-index: 40; - width: 100%; - height: 48px; - padding: 0 16px; - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - flex-shrink: 0; - background: #181818; - overflow: hidden; - } - - .edit-password-back { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - } - - .edit-password-back img { - width: 24px; - height: 24px; - display: block; - opacity: 0.65; - filter: brightness(0) invert(1); - } - - .edit-password-nav-title { - flex: 1 1 0; - height: 24px; - display: flex; - justify-content: center; - align-items: center; - color: #F5F5F5; - font-size: 18px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 24px; - overflow: hidden; - } - - .edit-password-nav-spacer { - width: 24px; - height: 24px; - flex-shrink: 0; - } - - .edit-password-form { - width: calc(100% - 32px); - max-width: 343px; - margin: 0 auto; - padding: 16px 0; - box-sizing: border-box; - } -} diff --git a/app/static/css/editProfile.css b/app/static/css/editProfile.css deleted file mode 100644 index e0a14a5..0000000 --- a/app/static/css/editProfile.css +++ /dev/null @@ -1,347 +0,0 @@ -.edit-profile-layout { - min-height: 100vh; - display: flex; - flex-direction: column; - background: #0A0A0A; -} - -.edit-profile-layout .site-header, -.edit-profile-layout .footer-box { - flex-shrink: 0; -} - -.edit-profile-page { - width: 100%; - flex: 1 1 auto; - background: #0A0A0A; - display: flex; - flex-direction: column; - align-items: center; - gap: 18px; - box-sizing: border-box; -} - -.edit-profile-pc { - display: none; -} - -.setting-breadcrumb { - display: inline-flex; - align-items: center; - gap: 10px; -} - -.setting-breadcrumb-link { - color: #EC237F; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; - text-decoration: none; -} - -.setting-breadcrumb-sep { - color: #666666; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.setting-breadcrumb-current { - color: #8A8A8A; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.edit-profile-h5 { - width: 100%; - max-width: 480px; - min-height: 100%; - display: flex; - flex-direction: column; - align-items: center; - box-sizing: border-box; -} - -.edit-profile-h5-nav { - position: sticky; - top: 0; - z-index: 40; - width: 100%; - height: 48px; - padding: 0 16px; - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - flex-shrink: 0; - background: #181818; - overflow: hidden; -} - -.edit-profile-h5-back { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} - -.edit-profile-h5-back img { - width: 24px; - height: 24px; - display: block; - opacity: 0.65; - filter: brightness(0) invert(1); -} - -.edit-profile-h5-nav-title { - flex: 1 1 0; - height: 24px; - display: flex; - justify-content: center; - align-items: center; - color: #F5F5F5; - font-size: 18px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 24px; - overflow: hidden; -} - -.edit-profile-h5-nav-spacer { - width: 24px; - height: 24px; - flex-shrink: 0; -} - -.edit-profile-h5-body { - width: calc(100% - 32px); - max-width: 343px; - margin: 0 auto; - padding: 16px 0; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 14px; - box-sizing: border-box; - overflow: hidden; -} - -.edit-profile-avatar-card { - width: 100%; - height: 112px; - padding: 0 16px; - background: rgba(255, 255, 255, 0.05); - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - box-sizing: border-box; -} - -.edit-profile-avatar-text { - width: 190px; - max-width: 100%; - min-width: 0; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - overflow: hidden; -} - -.edit-profile-avatar-title { - color: rgba(255, 255, 255, 0.95); - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; -} - -.edit-profile-avatar-desc { - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; -} - -.edit-profile-avatar-wrap { - position: relative; - width: 64px; - height: 64px; - flex-shrink: 0; -} - -.edit-profile-avatar { - width: 64px; - height: 64px; - border-radius: 999px; - object-fit: cover; - display: block; - border: 1px solid rgba(250, 220, 68, 0.35); -} - -.edit-profile-field { - width: 100%; - height: 76px; - min-height: 76px; - padding: 12px 14px; - background: rgba(255, 255, 255, 0.05); - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; - box-sizing: border-box; -} - -.edit-profile-field--bio { - height: 96px; - min-height: 96px; -} - -.edit-profile-field-label { - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; -} - -.edit-profile-field-input { - width: 100%; - padding: 0; - border: none; - background: transparent; - box-shadow: none; - outline: none; - outline-color: transparent; - -webkit-appearance: none; - appearance: none; - color: rgba(255, 255, 255, 0.95); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 20px; - box-sizing: border-box; -} - -.edit-profile-field-input:focus, -.edit-profile-field-input:focus-visible { - border: none; - outline: none; - outline-color: transparent; - box-shadow: none; -} - -.edit-profile-field-input::placeholder { - color: rgba(255, 255, 255, 0.40); -} - -.edit-profile-field-input--bio { - resize: none; - min-height: 40px; -} - -.edit-profile-submit { - width: 100%; - height: 50px; - background: #EC237F; - border: none; - border-radius: 14px; - color: #171717; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - display: inline-flex; - justify-content: center; - align-items: center; - cursor: pointer; - box-sizing: border-box; - overflow: hidden; -} - -.edit-profile-submit:disabled { - opacity: 0.7; - cursor: not-allowed; -} - -.edit-profile-tip { - margin: 0; - color: rgba(255, 255, 255, 0.40); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; -} - -@media (min-width: 768px) { - .edit-profile-page { - padding: 36px 0 60px; - gap: 18px; - } - - .edit-profile-pc { - width: 100%; - max-width: calc(100% - 32px); - display: flex; - flex-direction: column; - align-items: flex-start; - } - - .edit-profile-h5 { - width: 100%; - max-width: calc(100% - 32px); - } - - .edit-profile-h5-nav { - display: none; - } - - .edit-profile-h5-body { - width: 100%; - max-width: 480px; - margin: 0; - padding-top: 0; - } -} - -@media (max-width: 767px) { - body.is-edit-profile-page { - background: #0A0A0A; - padding-bottom: 0; - } - - body.is-edit-profile-page .site-header, - body.is-edit-profile-page .footer-box, - body.is-edit-profile-page .floating-action { - display: none !important; - height: 0 !important; - min-height: 0 !important; - overflow: hidden !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - } - - .edit-profile-pc { - display: none; - } - - .edit-profile-h5 { - max-width: 100%; - min-height: 100vh; - } -} diff --git a/app/static/css/header.css b/app/static/css/header.css index 21e170d..2baafa3 100644 --- a/app/static/css/header.css +++ b/app/static/css/header.css @@ -615,7 +615,7 @@ } .site-shell .brand-logo { - content: url("/static/images/logo-h5.png"); + content: url("/static/images/logo-h5.png?v=3"); width: 92px; height: 24px; object-fit: contain; diff --git a/app/static/css/history.css b/app/static/css/history.css deleted file mode 100644 index 3f508f1..0000000 --- a/app/static/css/history.css +++ /dev/null @@ -1,552 +0,0 @@ -.history-layout { - min-height: 100vh; - display: flex; - flex-direction: column; -} - -.history-layout .site-header, -.history-layout .footer-box { - flex-shrink: 0; -} - -.history-page { - width: 100%; - flex: 1 1 auto; - min-height: 0; - padding: 36px 0; - background: #0A0A0A; - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; - box-sizing: border-box; -} - -.history-pc { - width: min(1800px, calc(100% - 120px)); - max-width: none; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 24px; -} - -.history-nav { - display: none; -} - -.history-main { - width: min(1800px, calc(100% - 120px)); - max-width: none; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 24px; -} - -.history-breadcrumb { - align-self: stretch; - overflow: hidden; - display: inline-flex; - justify-content: flex-start; - align-items: center; - gap: 10px; -} - -.history-breadcrumb-link { - color: #EC237F; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - text-decoration: none; -} - -.history-breadcrumb-sep { - color: #666666; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.history-breadcrumb-current { - color: #8A8A8A; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.history-head { - align-self: stretch; - overflow: hidden; - display: flex; - justify-content: flex-start; - align-items: center; - gap: 6px; -} - -.history-head-text { - flex: 1 1 0; - min-width: 0; - overflow: hidden; - display: flex; - justify-content: flex-start; - align-items: center; - gap: 12px; -} - -.history-title { - margin: 0; - color: white; - font-size: 30px; - font-family: PingFang SC; - font-weight: 400; - line-height: 36px; - flex-shrink: 0; -} - -.history-desc { - margin: 0; - color: #A3A3A3; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.history-clear-btn { - width: 96px; - height: 36px; - background: rgba(255, 255, 255, 0.10); - overflow: hidden; - border: none; - border-radius: 18px; - color: #F5F5F5; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - cursor: pointer; - flex-shrink: 0; - display: flex; - justify-content: center; - align-items: center; -} - -.history-clear-btn:hover { - background: rgba(255, 255, 255, 0.14); - color: #fff; -} - -.history-confirm-mask { - position: fixed; - inset: 0; - z-index: 1000; - display: none; - align-items: center; - justify-content: center; - background: rgba(0, 0, 0, 0.55); - padding: 24px; - box-sizing: border-box; -} - -.history-confirm-mask.is-open { - display: flex; -} - -.history-confirm-mask[hidden] { - display: none !important; -} - -.history-confirm-card { - width: 420px; - max-width: 100%; - padding: 32px 28px 28px; - position: relative; - background: linear-gradient(90deg, #21142E 0%, #0A0A17 100%); - box-shadow: 0 20px 48px rgba(0, 0, 0, 0.46); - border-radius: 20px; - border: 1px solid rgba(241, 43, 158, 0.44); - display: flex; - flex-direction: column; - align-items: center; - gap: 14px; - box-sizing: border-box; -} - -.history-confirm-close { - width: 28px; - height: 28px; - right: 14px; - top: 14px; - position: absolute; - overflow: hidden; - border: none; - padding: 0; - background: transparent; - cursor: pointer; -} - -.history-confirm-close-ring { - width: 26px; - height: 26px; - left: 1px; - top: 1px; - position: absolute; - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.18); - border-radius: 50%; - box-sizing: border-box; -} - -.history-confirm-close-x { - width: 8px; - height: 8px; - left: 10px; - top: 10px; - position: absolute; - box-sizing: border-box; -} - -.history-confirm-close-x::before, -.history-confirm-close-x::after { - content: ""; - position: absolute; - left: 50%; - top: 50%; - width: 10px; - height: 1.5px; - background: #C5C0D0; - border-radius: 1px; -} - -.history-confirm-close-x::before { - transform: translate(-50%, -50%) rotate(45deg); -} - -.history-confirm-close-x::after { - transform: translate(-50%, -50%) rotate(-45deg); -} - -.history-confirm-title { - margin-top: 4px; - text-align: center; - color: #fff; - font-size: 20px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 700; - line-height: 28px; -} - -.history-confirm-desc { - text-align: center; - color: #B2ABC4; - font-size: 14px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 400; - line-height: 20px; -} - -.history-confirm-actions { - width: 100%; - margin-top: 8px; - display: flex; - align-items: center; - gap: 12px; -} - -.history-confirm-btn { - flex: 1 1 0; - height: 44px; - border: none; - border-radius: 12px; - font-size: 14px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 500; - line-height: 20px; - cursor: pointer; -} - -.history-confirm-btn--cancel { - background: rgba(255, 255, 255, 0.08); - color: #C5C0D0; -} - -.history-confirm-btn--cancel:hover { - background: rgba(255, 255, 255, 0.12); - color: #fff; -} - -.history-confirm-btn--ok { - background: #EC237F; - color: #181818; -} - -.history-confirm-btn--ok:hover { - background: #ffe56a; -} - -.history-grid { - align-self: stretch; - width: 100%; - display: grid; - grid-template-columns: repeat(8, minmax(0, 1fr)); - justify-content: stretch; - align-items: stretch; - gap: 14px 16px; -} - -.history-card.poster-card { - width: 100%; - min-width: 0; - overflow: hidden; - border-radius: 14px; - text-decoration: none; - color: inherit; -} - -.history-empty { - width: 100%; -} - -.history-card .poster-card-spacer { - flex: 1 1 0; - min-width: 8px; - height: 9px; -} - -.history-progress { - color: #808080 !important; -} - -.history-info { - display: none; -} - -.history-pagination { - align-self: stretch; - width: 100%; - overflow: hidden; - margin-top: 0; -} - -@media (max-width: 767px) { - body.is-history-page { - background: #0A0A0A; - } - - body.is-history-page .site-header, - body.is-history-page .footer-box, - body.is-history-page .floating-action, - body.is-history-page .download-fab { - display: none !important; - height: 0 !important; - min-height: 0 !important; - overflow: hidden !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - } - - body.is-history-page .history-layout { - min-height: 100vh; - background: #0A0A0A; - } - - body.is-history-page .history-page { - padding: 0; - background: #0A0A0A; - align-items: stretch; - justify-content: flex-start; - gap: 0; - min-height: 100vh; - } - - body.is-history-page .history-pc { - display: none; - } - - body.is-history-page .history-nav { - position: sticky; - top: 0; - z-index: 40; - width: 100%; - height: 48px; - padding: 0 16px; - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - flex-shrink: 0; - background: #181818; - } - - body.is-history-page .history-nav-back { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - } - - body.is-history-page .history-nav-back img { - width: 24px; - height: 24px; - display: block; - opacity: 0.65; - filter: brightness(0) invert(1); - } - - body.is-history-page .history-nav-title { - margin: 0; - flex: 1 1 0; - height: 24px; - display: flex; - justify-content: center; - align-items: center; - color: #fff; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - } - - body.is-history-page .history-nav-clear { - min-width: 24px; - height: 24px; - padding: 0; - border: none; - background: transparent; - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - cursor: pointer; - flex-shrink: 0; - -webkit-tap-highlight-color: transparent; - } - - body.is-history-page .history-main { - width: calc(100% - 32px); - max-width: 343px; - margin: 0 auto; - padding: 12px 0; - align-items: stretch; - gap: 12px; - box-sizing: border-box; - } - - body.is-history-page .history-grid { - width: 100%; - display: flex; - flex-direction: column; - flex-wrap: nowrap; - gap: 12px; - } - - body.is-history-page .history-card.poster-card { - width: 100%; - max-width: none; - padding: 10px 12px; - background: rgba(255, 255, 255, 0.05); - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: center; - gap: 12px; - box-sizing: border-box; - } - - body.is-history-page .history-card .poster-cover { - width: 90px; - height: 120px; - padding-bottom: 0; - border-radius: 10px; - overflow: hidden; - position: relative; - flex-shrink: 0; - } - - body.is-history-page .history-card .poster-cover-img { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - object-fit: cover; - } - - body.is-history-page .history-card .free-badge-large, - body.is-history-page .history-card .hot-badge-large, - body.is-history-page .history-card .community-badge, - body.is-history-page .history-card .poster-corner-badges, - body.is-history-page .history-card .episode-badge, - body.is-history-page .history-card .poster-card-body, - body.is-history-page .history-pagination { - display: none !important; - } - - body.is-history-page .history-info { - flex: 1 1 0; - min-width: 0; - overflow: hidden; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - gap: 4px; - } - - body.is-history-page .history-name { - width: 100%; - color: rgba(255, 255, 255, 0.95); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 20px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - body.is-history-page .history-intro { - width: 100%; - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - line-clamp: 2; - overflow: hidden; - } - - body.is-history-page .history-watch { - width: 100%; - color: #EC237F; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - } - - body.is-history-page .history-watch--done { - color: rgba(255, 255, 255, 0.45); - } - - body.is-history-page .history-time { - color: rgba(255, 255, 255, 0.45); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - } -} diff --git a/app/static/css/loginDialog.css b/app/static/css/loginDialog.css deleted file mode 100644 index 1987f97..0000000 --- a/app/static/css/loginDialog.css +++ /dev/null @@ -1,205 +0,0 @@ -.login-dialog-mask { - position: fixed; - inset: 0; - z-index: 1100; - display: none; - align-items: center; - justify-content: center; - overflow-y: auto; - padding: 24px; - box-sizing: border-box; - background: rgba(0, 0, 0, 0.7); - -webkit-backdrop-filter: blur(10px); - backdrop-filter: blur(10px); -} -.login-dialog-mask.is-open { display: flex; } -.login-dialog-stack { - width: 440px; - max-width: 100%; - margin: auto; - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; -} -.login-dialog-card { - width: 440px; - max-width: 100%; - padding: 36px; - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: center; - gap: 18px; - color: #fff; - background: #0d0d0d; - border: 1px solid rgba(255,255,255,.2); - border-radius: 40px; - font-family: "PingFang SC", "Noto Sans SC", sans-serif; -} -.login-dialog-head { - width: 100%; - display: flex; - flex-direction: column; - gap: 12px; -} -.login-dialog-title { - color: #fff; - font-size: 24px; - font-weight: 600; - line-height: 32px; - text-align: left; -} -.login-dialog-title-h5 { display: none; } -.login-dialog-desc { - color: rgba(255,255,255,.55); - font-size: 14px; - font-weight: 400; - line-height: 20px; - text-align: left; -} -.login-dialog-form { - width: 100%; - display: flex; - flex-direction: column; - gap: 24px; -} -.login-dialog-field-group { - width: 100%; - display: flex; - flex-direction: column; - gap: 12px; -} -.login-dialog-label { - display: block; - color: rgba(255,255,255,.55); - font-size: 16px; - font-weight: 400; - line-height: 22px; -} -.login-dialog-field { - width: 100%; - min-height: 60px; - padding: 18px; - box-sizing: border-box; - display: flex; - align-items: center; - overflow: hidden; - background: rgba(255,255,255,.05); - border: 1px solid rgba(255,255,255,.05); - border-radius: 6px; -} -.login-dialog-field--password { justify-content: space-between; gap: 10px; } -.login-dialog-sr { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0,0,0,0); - white-space: nowrap; - border: 0; -} -.login-dialog-input { - width: 100%; - min-width: 0; - padding: 0; - color: #fff; - font: 400 16px/22px "PingFang SC", "Noto Sans SC", sans-serif; - background: transparent; - border: 0; - outline: 0; -} -.login-dialog-input::placeholder { color: rgba(255,255,255,.35); opacity: 1; } -.login-dialog-eye { - width: 20px; - height: 20px; - padding: 0; - flex: 0 0 20px; - display: flex; - align-items: center; - justify-content: center; - background: transparent; - border: 0; - cursor: pointer; -} -.login-dialog-eye img { display: block; width: 20px; height: 20px; } -.login-dialog-error { - margin: 0; - color: #ef4444; - font-size: 12px; - font-weight: 400; - line-height: 18px; -} -.login-dialog-error[hidden] { display: none; } -.login-dialog-submit { - width: 100%; - min-height: 50px; - padding: 13px 0; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - color: #fff; - font: 600 18px/24px "PingFang SC", "Noto Sans SC", sans-serif; - background: #ec237f; - border: 0; - border-radius: 12px; - cursor: pointer; -} -.login-dialog-submit.is-loading, -.login-dialog-submit:disabled { opacity: .75; cursor: not-allowed; } -.login-dialog-register { - width: 100%; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - gap: 10px; -} -.login-dialog-register-prefix, -.login-dialog-register-link { - font-size: 14px; - font-weight: 400; - line-height: 20px; -} -.login-dialog-register-prefix { color: rgba(255,255,255,.55); } -.login-dialog-register-link { color: #ec237f; text-decoration: none; } -.login-dialog-register-link:hover { color: #ff5b9f; } -.login-dialog-register-link--h5 { display: none; } -.login-dialog-close { - width: 36px; - height: 36px; - padding: 0; - flex: 0 0 36px; - border: 0; - background: transparent; - cursor: pointer; -} -.login-dialog-close img { display: block; width: 36px; height: 36px; } -@media (max-width: 767px) { - .login-dialog-mask { - padding: 16px; - -webkit-backdrop-filter: blur(2px); - backdrop-filter: blur(2px); - } - .login-dialog-stack { width: 300px; } - .login-dialog-card { width: 300px; padding: 24px 20px; border-radius: 20px; } - .login-dialog-title-pc { display: none; } - .login-dialog-title-h5 { display: inline; } - .login-dialog-title { font-size: 16px; line-height: 22px; } - .login-dialog-desc { font-size: 12px; line-height: 18px; } - .login-dialog-head { gap: 6px; } - .login-dialog-form { gap: 12px; } - .login-dialog-field-group { gap: 6px; } - .login-dialog-label { font-size: 14px; line-height: 20px; } - .login-dialog-field { min-height: 54px; padding: 16px 18px; } - .login-dialog-input { font-size: 14px; line-height: 20px; } - .login-dialog-submit { font-weight: 400; } - .login-dialog-register { height: 24px; } - .login-dialog-register-prefix, - .login-dialog-register-link { font-size: 12px; line-height: 18px; } - .login-dialog-register-link--pc { display: none; } - .login-dialog-register-link--h5 { display: inline; } -} diff --git a/app/static/css/play.css b/app/static/css/play.css index ed17ffc..5b3d413 100644 --- a/app/static/css/play.css +++ b/app/static/css/play.css @@ -739,37 +739,70 @@ a.play-info-tag:focus-visible { display: none; } -.play-ep-lock, -.play-ep-coin { +.play-ep-lock { position: absolute; - right: 6px; + right: 4px; left: auto; - top: 6px; - width: 16px; + top: 4px; height: 16px; - padding: 0; - background: none; - border-radius: 0; + padding: 0 4px; + background: linear-gradient(135deg, #FF5F62 0%, #EC237F 100%); + border-radius: 4px; display: block; - overflow: visible; box-sizing: border-box; pointer-events: none; z-index: 1; - object-fit: contain; + color: #fff; + font-size: 10px; + font-weight: 500; + line-height: 16px; + white-space: nowrap; } -.play-ep-coin img { - width: 16px; - height: 16px; - object-fit: contain; - display: block; -} - -.play-ep--active .play-ep-coin, .play-ep--active .play-ep-lock { display: none; } +/* 付费剧集:播放器内引导下载 APP。 */ +.play-player-locked { + position: absolute; + left: 50%; + top: 50%; + z-index: 4; + transform: translate(-50%, -50%); + width: calc(100% - 32px); + max-width: 320px; + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + text-align: center; +} + +.play-player-locked-text { + margin: 0; + color: rgba(255, 255, 255, 0.85); + font-size: 14px; + line-height: 20px; +} + +.play-player-locked-btn { + padding: 10px 24px; + border: none; + border-radius: 999px; + background: #ec237f; + color: #fff; + font-size: 15px; + font-weight: 500; + line-height: 22px; + cursor: pointer; + white-space: nowrap; +} + +.play-player-locked-btn:hover { + opacity: 0.9; +} + @media (max-width: 1200px) { .play-layout, .play-main, @@ -1202,11 +1235,6 @@ a.play-info-tag:focus-visible { transform-origin: center; } - .play-h5-side-item [data-h5-like-icon], - .play-h5-side-item [data-h5-collect-icon] { - transform: scale(0.8); - } - .play-h5-side-item[data-play-comment-open] img { transform: scale(1.09); } @@ -1215,12 +1243,6 @@ a.play-info-tag:focus-visible { transform: scale(1); } - .play-h5-side-item--active img, - .play-h5-side-item img[src*="h5_liked"], - .play-h5-side-item img[src*="h5_collected"] { - filter: none; - } - .play-h5-meta { display: inline-flex; position: absolute; @@ -1609,26 +1631,6 @@ a.play-info-tag:focus-visible { line-height: 18px; } - .play-ep-sheet-collect { - flex-shrink: 0; - padding: 4px 12px; - border: none; - border-radius: 999px; - background: #ec237f; - color: #fff; - font-size: 10px; - font-family: PingFang SC; - font-weight: 400; - line-height: 16px; - cursor: pointer; - text-align: center; - } - - .play-ep-sheet-collect.is-collected { - background: rgba(255, 255, 255, 0.10); - color: #ec237f; - } - .play-ep-sheet-tabs { width: 100%; display: flex; @@ -1724,33 +1726,26 @@ a.play-info-tag:focus-visible { font-weight: 400; } - .play-ep-sheet-coin, .play-ep-sheet-lock { position: absolute; - right: 6px; + right: 4px; left: auto; - top: 6px; - width: 16px; + top: 4px; height: 16px; - padding: 0; - background: none; - border-radius: 0; + padding: 0 4px; + background: linear-gradient(135deg, #FF5F62 0%, #EC237F 100%); + border-radius: 4px; display: block; - overflow: visible; z-index: 2; box-sizing: border-box; pointer-events: none; - object-fit: contain; + color: #fff; + font-size: 10px; + font-weight: 500; + line-height: 16px; + white-space: nowrap; } - .play-ep-sheet-coin img { - width: 16px; - height: 16px; - object-fit: contain; - display: block; - } - - .play-ep-sheet-item--active .play-ep-sheet-coin, .play-ep-sheet-item--active .play-ep-sheet-lock { display: none; } @@ -1861,7 +1856,7 @@ a.play-info-tag:focus-visible { display: flex; flex-direction: column; gap: 12px; - padding: 18px 16px 0; + padding: 18px 16px calc(12px + env(safe-area-inset-bottom, 0px)); min-height: 0; flex: 1; overflow: hidden; @@ -2190,70 +2185,6 @@ a.play-info-tag:focus-visible { font-size: 12px; } - .play-comment-inline-composer { - width: 100%; - margin-top: 8px; - padding: 8px 10px; - background: rgba(255, 255, 255, 0.06); - border-radius: 8px; - box-sizing: border-box; - display: flex; - flex-direction: column; - gap: 6px; - } - - .play-comment-inline-row { - display: flex; - align-items: center; - gap: 8px; - } - - .play-comment-inline-input { - flex: 1; - min-width: 0; - height: 34px; - padding: 0 10px; - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 17px; - background: #0A0A0A; - color: #fff; - font-size: 13px; - font-family: PingFang SC; - box-sizing: border-box; - } - - .play-comment-inline-input::placeholder { - color: rgba(255, 255, 255, 0.35); - } - - .play-comment-inline-input:focus, - .play-comment-inline-input:focus-visible { - border-color: rgba(236, 35, 127, 0.55); - outline: none; - outline-color: transparent; - box-shadow: none; - } - - .play-comment-inline-send { - flex-shrink: 0; - height: 34px; - padding: 0 12px; - border: none; - border-radius: 17px; - background: #ec237f; - color: #fff; - font-size: 13px; - font-family: PingFang SC; - font-weight: 500; - cursor: pointer; - } - - .play-comment-inline-send:disabled, - .play-comment-inline-send.is-loading { - opacity: 0.6; - cursor: not-allowed; - } - .play-comment-replies-all { background: none; border: none; @@ -2267,119 +2198,6 @@ a.play-info-tag:focus-visible { text-align: left; } - .play-comment-composer { - width: 100%; - height: calc(58px + env(safe-area-inset-bottom, 0px)); - padding: 6px 16px calc(6px + env(safe-area-inset-bottom, 0px)); - background: #1A1A1A; - box-sizing: border-box; - flex-shrink: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 10px; - } - - .play-comment-composer-box { - align-self: stretch; - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - min-height: 36px; - padding: 6px 12px; - background: rgba(255, 255, 255, 0.05); - border-radius: 3px; - box-sizing: border-box; - text-decoration: none; - color: inherit; - } - - .play-comment-composer-placeholder, - .play-comment-composer-login { - flex: 1; - min-width: 0; - color: rgba(255, 255, 255, 0.35); - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - text-decoration: none; - word-wrap: break-word; - } - - .play-comment-composer-input { - flex: 1 1 0; - width: auto; - min-width: 0; - min-height: 0; - height: 18px; - max-height: 18px; - margin: 0; - padding: 0; - border: none; - border-radius: 0; - background: transparent; - box-shadow: none; - outline: none; - outline-color: transparent; - resize: none; - overflow: hidden; - -webkit-appearance: none; - appearance: none; - -webkit-tap-highlight-color: transparent; - color: #fff; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - caret-color: #fff; - } - - .play-comment-composer-input:focus, - .play-comment-composer-input:focus-visible, - .play-comment-composer-input:active { - border: none; - outline: none; - outline-color: transparent; - box-shadow: none; - } - - .play-comment-composer-input::placeholder { - color: rgba(255, 255, 255, 0.35); - opacity: 1; - } - - .play-comment-composer-emoji { - width: 24px; - height: 24px; - padding: 0; - margin: 0; - border: none; - background: none; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - position: relative; - overflow: hidden; - } - - .play-comment-composer-emoji.is-loading { - opacity: 0.5; - pointer-events: none; - } - - .play-comment-composer-emoji img { - width: 24px; - height: 24px; - object-fit: contain; - display: block; - pointer-events: none; - } - body.play-comment-sheet-open { overflow: hidden; } @@ -2859,11 +2677,12 @@ a.play-info-tag:focus-visible { color: #ec237f; } - .play-ep-coin { - width: calc(16px * var(--play-s)); + .play-ep-lock { height: calc(16px * var(--play-s)); top: calc(6px * var(--play-s)); right: calc(6px * var(--play-s)); + font-size: calc(10px * var(--play-s)); + line-height: calc(16px * var(--play-s)); } .play-comments { width: 100%; } @@ -3000,6 +2819,5 @@ a.play-info-tag:focus-visible { .play-h5-bottom { background: #181818; } .play-speed-sheet-option.is-active, - .play-ep-sheet-tab--active, - .play-ep-sheet-collect.is-collected { color: #ec237f; } + .play-ep-sheet-tab--active { color: #ec237f; } } diff --git a/app/static/css/registerDialog.css b/app/static/css/registerDialog.css deleted file mode 100644 index ab86a82..0000000 --- a/app/static/css/registerDialog.css +++ /dev/null @@ -1,191 +0,0 @@ -.register-dialog-mask { - position: fixed; - inset: 0; - z-index: 1100; - display: none; - align-items: center; - justify-content: center; - overflow-y: auto; - padding: 24px; - box-sizing: border-box; - background: rgba(0,0,0,.7); - -webkit-backdrop-filter: blur(10px); - backdrop-filter: blur(10px); -} -.register-dialog-mask.is-open { display: flex; } -.register-dialog-stack { - width: 440px; - max-width: 100%; - margin: auto; - display: flex; - flex-direction: column; - align-items: center; - gap: 24px; -} -.register-dialog-card { - width: 440px; - max-width: 100%; - padding: 36px; - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: center; - gap: 18px; - color: #fff; - background: #0d0d0d; - border: 1px solid rgba(255,255,255,.2); - border-radius: 40px; - font-family: "PingFang SC", "Noto Sans SC", sans-serif; -} -.register-dialog-head { - width: 100%; - display: flex; - flex-direction: column; - gap: 12px; -} -.register-dialog-title { - color: #fff; - font-size: 24px; - font-weight: 600; - line-height: 32px; - text-align: left; -} -.register-dialog-desc { - color: rgba(255,255,255,.55); - font-size: 14px; - font-weight: 400; - line-height: 20px; - text-align: left; -} -.register-dialog-desc-h5 { display: none; } -.register-dialog-form { - width: 100%; - display: flex; - flex-direction: column; - gap: 24px; -} -.register-dialog-field-group { - width: 100%; - display: flex; - flex-direction: column; - gap: 12px; -} -.register-dialog-label { - display: block; - color: rgba(255,255,255,.55); - font-size: 16px; - font-weight: 400; - line-height: 22px; -} -.register-dialog-field { - width: 100%; - min-height: 60px; - padding: 18px; - box-sizing: border-box; - display: flex; - align-items: center; - overflow: hidden; - background: rgba(255,255,255,.05); - border: 1px solid rgba(255,255,255,.05); - border-radius: 6px; -} -.register-dialog-sr { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0,0,0,0); - white-space: nowrap; - border: 0; -} -.register-dialog-input { - width: 100%; - min-width: 0; - padding: 0; - color: #fff; - font: 400 16px/22px "PingFang SC", "Noto Sans SC", sans-serif; - background: transparent; - border: 0; - outline: 0; -} -.register-dialog-input::placeholder { color: rgba(255,255,255,.35); opacity: 1; } -.register-dialog-error { - margin: 0; - color: #ef4444; - font-size: 12px; - font-weight: 400; - line-height: 18px; -} -.register-dialog-error[hidden] { display: none; } -.register-dialog-submit { - width: 100%; - min-height: 50px; - padding: 13px 0; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - color: #fff; - font: 600 18px/24px "PingFang SC", "Noto Sans SC", sans-serif; - background: #ec237f; - border: 0; - border-radius: 12px; - cursor: pointer; -} -.register-dialog-submit.is-loading, -.register-dialog-submit:disabled { opacity: .75; cursor: not-allowed; } -.register-dialog-login { - width: 100%; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - gap: 10px; -} -.register-dialog-login-prefix, -.register-dialog-login-link { - font-size: 14px; - font-weight: 400; - line-height: 20px; -} -.register-dialog-login-prefix { display: inline; color: rgba(255,255,255,.55); } -.register-dialog-login-link { color: #ec237f; text-decoration: none; } -.register-dialog-login-link:hover { color: #ff5b9f; } -.register-dialog-login-link--h5 { display: none; } -.register-dialog-close { - width: 36px; - height: 36px; - padding: 0; - flex: 0 0 36px; - border: 0; - background: transparent; - cursor: pointer; -} -.register-dialog-close img { display: block; width: 36px; height: 36px; } -@media (max-width: 767px) { - .register-dialog-mask { - padding: 16px; - -webkit-backdrop-filter: blur(2px); - backdrop-filter: blur(2px); - } - .register-dialog-stack { width: 300px; } - .register-dialog-card { width: 300px; padding: 24px 20px; border-radius: 20px; } - .register-dialog-title { font-size: 16px; line-height: 22px; } - .register-dialog-desc { font-size: 12px; line-height: 18px; } - .register-dialog-desc-pc { display: none; } - .register-dialog-desc-h5 { display: inline; } - .register-dialog-head { gap: 6px; } - .register-dialog-form { gap: 12px; } - .register-dialog-field-group { gap: 6px; } - .register-dialog-label { font-size: 14px; line-height: 20px; } - .register-dialog-field { min-height: 54px; padding: 16px 18px; } - .register-dialog-input { font-size: 14px; line-height: 20px; } - .register-dialog-submit { font-weight: 400; } - .register-dialog-login { height: 24px; } - .register-dialog-login-prefix, - .register-dialog-login-link { font-size: 12px; line-height: 18px; } - .register-dialog-login-link--pc { display: none; } - .register-dialog-login-link--h5 { display: inline; } -} diff --git a/app/static/css/setting.css b/app/static/css/setting.css deleted file mode 100644 index 6e8306a..0000000 --- a/app/static/css/setting.css +++ /dev/null @@ -1,721 +0,0 @@ -.setting-layout { - min-height: 100vh; - display: flex; - flex-direction: column; - background: #0A0A0A; -} - -.setting-layout .site-header, -.setting-layout .footer-box { - flex-shrink: 0; -} - -.setting-page { - width: 100%; - flex: 1 1 auto; - min-height: 0; - padding: 36px 0; - background: #0A0A0A; - display: flex; - flex-direction: column; - align-items: center; - gap: 18px; - box-sizing: border-box; -} - -.setting-pc { - width: 100%; - max-width: calc(100% - 32px); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 18px; -} - -.setting-nav { - display: none; -} - -.setting-main { - width: 100%; - max-width: calc(100% - 32px); - height: 370px; - display: flex; - justify-content: flex-start; - align-items: flex-start; - gap: 18px; - box-sizing: border-box; -} - -.setting-breadcrumb { - display: inline-flex; - align-items: center; - gap: 10px; - overflow: hidden; -} - -.setting-breadcrumb-link { - color: #EC237F; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; - text-decoration: none; -} - -.setting-breadcrumb-sep { - color: #666666; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.setting-breadcrumb-current { - color: #8A8A8A; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.setting-head { - align-self: stretch; - display: flex; - align-items: center; - gap: 12px; - flex-wrap: wrap; - overflow: hidden; -} - -.setting-title { - margin: 0; - color: white; - font-size: 30px; - font-family: PingFang SC; - font-weight: 400; - line-height: 36px; -} - -.setting-desc { - margin: 0; - color: #A3A3A3; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.setting-panel, -.setting-aside { - height: 100%; - min-height: 0; - padding: 24px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 14px; - box-sizing: border-box; -} - -.setting-profile-pc { - width: 560px; - flex-shrink: 0; -} - -.setting-profile-mobile { - display: none; -} - -.setting-aside { - flex: 1 1 0; - align-self: stretch; - min-width: 0; -} - -.setting-panel-title, -.setting-aside-title { - margin: 0; - color: white; - font-size: 20px; - font-family: PingFang SC; - font-weight: 400; - line-height: 26px; -} - -.setting-section-label { - display: none; -} - -.setting-avatar-row { - display: flex; - align-items: center; - gap: 14px; - overflow: hidden; -} - -.setting-avatar { - width: 64px; - height: 64px; - border-radius: 999px; - object-fit: cover; - flex-shrink: 0; -} - -.setting-avatar-text { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 3px; - overflow: hidden; -} - -.setting-avatar-name { - color: white; - font-size: 18px; - font-family: PingFang SC; - font-weight: 400; - line-height: 24px; -} - -.setting-avatar-change { - padding: 0; - border: none; - background: none; - color: #EC237F; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - cursor: pointer; - text-decoration: none; -} - -.setting-field { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 6px; - overflow: visible; -} - -.setting-label { - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.setting-input { - align-self: stretch; - width: 100%; - min-height: 44px; - padding: 12px 14px; - background: rgba(255, 255, 255, 0.05); - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.10); - color: #F5F5F5; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; - box-sizing: border-box; - -webkit-appearance: none; - appearance: none; - box-shadow: none; - outline: none; - outline-color: transparent; - opacity: 1; - -webkit-text-fill-color: #F5F5F5; -} - -.setting-input--static { - display: flex; - align-items: center; - min-height: 44px; -} - -.setting-input:focus, -.setting-input:focus-visible { - border-color: rgba(250, 220, 68, 0.55); - outline: none; - outline-color: transparent; - box-shadow: none; -} - -.setting-input:read-only, -.setting-input[readonly] { - opacity: 1; - color: #F5F5F5; - -webkit-text-fill-color: #F5F5F5; - cursor: default; -} - -.setting-input::placeholder { - color: #A3A3A3; - -webkit-text-fill-color: #A3A3A3; - opacity: 1; -} - -.setting-save-btn { - width: 128px; - height: 40px; - background: #EC237F; - border: none; - border-radius: 20px; - color: #181818; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - text-decoration: none; - box-sizing: border-box; -} - -.setting-group { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: stretch; -} - -.setting-group--security { - margin: 0; -} - -.setting-item { - align-self: stretch; - padding: 12px 0; - border: none; - background: transparent; - display: grid; - grid-template-columns: 1fr auto; - grid-template-areas: - "label action" - "hint action"; - align-items: center; - column-gap: 12px; - row-gap: 2px; - box-sizing: border-box; - text-align: left; - text-decoration: none; - color: inherit; - cursor: pointer; - font: inherit; - -webkit-tap-highlight-color: transparent; - overflow: hidden; -} - -.setting-group--notify .setting-item { - display: flex; - padding: 8px 0; - cursor: default; -} - -.setting-item-label { - grid-area: label; - color: white; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.setting-group--notify .setting-item-label { - color: #DBD7E4; -} - -.setting-item-hint { - grid-area: hint; - color: #8A8A8A; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.setting-item-action { - grid-area: action; - padding: 0; - border: none; - background: none; - color: #EC237F; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - flex-shrink: 0; -} - -.setting-item-right, -.setting-item-value, -.setting-item-arrow, -.setting-item-avatar { - display: none; -} - -.setting-switch { - width: 42px; - height: 24px; - margin-left: auto; - position: relative; - background: rgba(255, 255, 255, 0.12); - border: none; - border-radius: 12px; - cursor: pointer; - flex-shrink: 0; - padding: 0; -} - -.setting-switch::after { - content: ""; - width: 18px; - height: 18px; - position: absolute; - left: 3px; - top: 3px; - background: white; - border-radius: 9999px; - transition: left 0.15s ease; -} - -.setting-switch--on { - background: #EC237F; -} - -.setting-switch--on::after { - left: 21px; -} - -.setting-logout { - width: 130px; - height: 40px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 20px; - border: 1px solid #EC237F; - display: inline-flex; - justify-content: center; - align-items: center; - color: #EC237F; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; - text-decoration: none; - box-sizing: border-box; - cursor: pointer; -} - -@media (max-width: 1100px) { - .setting-main { - height: auto; - flex-wrap: wrap; - } - - .setting-profile-pc, - .setting-aside { - width: 100%; - height: auto; - } -} - -@media (max-width: 767px) { - body.is-setting-page { - background: #0A0A0A; - padding-bottom: 0; - } - - body.is-setting-page .site-header, - body.is-setting-page .footer-box, - body.is-setting-page .floating-action, - body.is-setting-page .download-fab { - display: none !important; - height: 0 !important; - min-height: 0 !important; - overflow: hidden !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - } - - .setting-layout { - min-height: 100vh; - background: #0A0A0A; - } - - .setting-page { - padding: 0; - background: #0A0A0A; - align-items: stretch; - justify-content: flex-start; - gap: 0; - min-height: 100vh; - height: auto; - overflow: visible; - flex: 1 1 auto; - } - - .setting-pc { - display: none; - } - - .setting-nav { - position: sticky; - top: 0; - z-index: 40; - width: 100%; - height: 48px; - padding: 0 16px; - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - flex-shrink: 0; - background: #181818; - overflow: hidden; - } - - .setting-nav-back { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - } - - .setting-nav-back img { - width: 24px; - height: 24px; - display: block; - opacity: 0.55; - filter: brightness(0) invert(1); - } - - .setting-nav-title { - margin: 0; - flex: 1 1 0; - height: 24px; - display: flex; - justify-content: center; - align-items: center; - color: #fff; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - overflow: hidden; - } - - .setting-nav-spacer { - width: 24px; - height: 24px; - flex-shrink: 0; - } - - .setting-main { - width: calc(100% - 32px); - max-width: 343px; - height: auto; - margin: 0 auto; - padding: 14px 0; - flex: 1 1 auto; - flex-direction: column; - align-items: stretch; - gap: 10px; - box-sizing: border-box; - min-height: 0; - overflow: hidden; - } - - .setting-profile-pc { - display: none; - } - - .setting-profile-mobile { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 10px; - flex: none; - width: 100%; - overflow: hidden; - } - - .setting-aside { - flex: none; - width: 100%; - min-height: 0; - height: auto; - padding: 0; - background: transparent; - border: none; - border-radius: 0; - overflow: visible; - gap: 10px; - align-self: stretch; - } - - .setting-aside-title { - display: none; - } - - .setting-section-label { - display: block; - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - } - - .setting-group { - width: 100%; - background: rgba(255, 255, 255, 0.05); - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - } - - .setting-item { - width: 100%; - height: 52px; - padding: 0 14px; - border-bottom: 1px rgba(255, 255, 255, 0.10) solid; - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - box-sizing: border-box; - overflow: hidden; - } - - .setting-group .setting-item:last-child { - border-bottom: none; - } - - .setting-item--avatar { - height: 76px; - } - - .setting-item-hint, - .setting-item-action { - display: none; - } - - .setting-item-label { - color: rgba(255, 255, 255, 0.90); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 20px; - flex-shrink: 0; - } - - .setting-item-right { - min-width: 0; - display: flex; - justify-content: flex-start; - align-items: center; - gap: 6px; - overflow: hidden; - } - - .setting-item-value { - display: block; - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - max-width: 180px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .setting-item-avatar { - width: 48px; - height: 48px; - border-radius: 999px; - object-fit: cover; - display: block; - flex-shrink: 0; - border: 1px solid rgba(250, 220, 68, 0.35); - } - - .setting-item-arrow { - width: 18px; - height: 18px; - display: block; - flex-shrink: 0; - opacity: 0.55; - filter: brightness(0) invert(1); - } - - .setting-group--security .setting-item { - height: 52px; - } - - .setting-group--notify .setting-item { - height: 52px; - padding: 0 14px; - cursor: default; - } - - .setting-group--notify .setting-item-label { - color: rgba(255, 255, 255, 0.90); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 20px; - } - - .setting-switch { - width: 44px; - height: 24px; - margin-left: 0; - background: rgba(255, 255, 255, 0.12); - } - - .setting-switch::after { - width: 20px; - height: 20px; - left: 2px; - top: 2px; - background: #fff; - } - - .setting-switch--on { - background: #EC237F; - } - - .setting-switch--on::after { - left: 22px; - } - - .setting-logout { - width: 100%; - height: 48px; - margin-top: 0; - background: rgba(255, 255, 255, 0.05); - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.08); - color: rgba(255, 255, 255, 0.90); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 20px; - } -} diff --git a/app/static/css/userCenter.css b/app/static/css/userCenter.css deleted file mode 100644 index 889404f..0000000 --- a/app/static/css/userCenter.css +++ /dev/null @@ -1,1146 +0,0 @@ -.user-center-layout { - min-height: 100vh; - display: flex; - flex-direction: column; -} - - -/* Figma account module — desktop canvas 1920, mobile canvas 375. */ -@media (min-width: 768px) { - .user-center-layout .user-center-page { - min-height: 0; - padding: calc(36px * var(--account-s, min(1, 100vw / 1920px))) calc(60px * var(--account-s, min(1, 100vw / 1920px))); - gap: calc(24px * var(--account-s, min(1, 100vw / 1920px))); - background: #0f0f0f; - box-sizing: border-box; - } - - .user-center-layout .user-center-pc { - width: 100%; - max-width: calc(1800px * var(--account-s, min(1, 100vw / 1920px))); - margin: 0 auto; - gap: calc(24px * var(--account-s, min(1, 100vw / 1920px))); - } - - .user-center-layout .user-center-head { - gap: min(12px, 0.625vw); - } - - .user-center-layout .user-center-eyebrow { - color: #ec237f; - font-size: clamp(11px, 0.625vw, 12px); - line-height: clamp(16px, 0.9375vw, 18px); - } - - .user-center-layout .user-center-title, - .user-center-layout .user-center-title--logged { - font-size: clamp(20px, 1.25vw, 24px); - font-weight: 600; - line-height: clamp(28px, 1.666667vw, 32px); - } - - .user-center-layout .user-center-desc { - font-size: clamp(12px, 0.729167vw, 14px); - line-height: clamp(18px, 1.041667vw, 20px); - color: rgba(255, 255, 255, 0.55); - } - - - .user-center-layout .user-center-profile { - height: min(148px, 7.708333vw); - min-height: min(148px, 7.708333vw); - padding: min(32px, 1.666667vw) min(28px, 1.458333vw); - gap: min(12px, 0.625vw); - background: rgba(255, 255, 255, 0.05); - border-radius: min(24px, 1.25vw); - } - - .user-center-layout .user-center-avatar { - width: min(84px, 4.375vw); - height: min(84px, 4.375vw); - } - - .user-center-layout .user-center-profile-info { - width: min(230px, 11.979167vw); - gap: min(3px, 0.15625vw); - } - - .user-center-layout .user-center-profile-name { - font-size: clamp(16px, 0.9375vw, 18px); - font-weight: 600; - line-height: clamp(22px, 1.25vw, 24px); - } - - .user-center-layout .user-center-profile-email { - font-size: clamp(12px, 0.729167vw, 14px); - line-height: clamp(18px, 1.041667vw, 20px); - } - - .user-center-layout .user-center-profile-id { - color: #fff; - font-size: clamp(12px, 0.729167vw, 14px); - font-weight: 400; - line-height: clamp(18px, 1.041667vw, 20px); - } - - .user-center-layout .user-center-coin-card { - position: relative; - width: min(343px, 17.864583vw); - height: min(81px, 4.21875vw); - padding: min(15px, 0.78125vw) min(16px, 0.833333vw) min(15px, 0.78125vw) min(26px, 1.354167vw); - flex-direction: row; - justify-content: space-between; - align-items: center; - gap: min(8px, 0.416667vw); - background: url('/static/images/figma-account/balance-card.svg') center/100% 100% no-repeat; - border: 0; - border-radius: min(18px, 0.9375vw); - } - - .user-center-coin-copy { - display: flex; - flex-direction: column; - gap: min(4px, 0.208333vw); - } - - .user-center-layout .user-center-coin-label { - color: transparent; - background: linear-gradient(90deg, #ff9365 0%, #ffd8bc 100%); - -webkit-background-clip: text; - background-clip: text; - font-family: "Alimama FangYuanTi VF", "PingFang SC", sans-serif; - font-size: min(16px, 0.833333vw); - font-weight: 600; - line-height: min(18px, 0.9375vw); - } - - .user-center-layout .user-center-coin-label small { - color: transparent; - background: linear-gradient(90deg, #ff9365 0%, #ffd8bc 100%); - -webkit-background-clip: text; - background-clip: text; - font-size: min(10px, 0.520833vw); - font-weight: 400; - } - - .user-center-layout .user-center-coin-value { - font-family: "Arial Narrow", sans-serif; - font-size: min(24px, 1.25vw); - font-weight: 800; - line-height: 1; - } - - .user-center-layout .user-center-coin-action { - position: relative; - width: min(72px, 3.75vw); - height: min(30px, 1.5625vw); - margin-right: min(5px, 0.260417vw); - border-radius: 0; - background: none; - color: transparent; - font-size: 0; - line-height: 0; - display: block; - flex: 0 0 auto; - object-fit: contain; - object-position: center right; - } - - .user-center-layout .user-center-login-banner { - min-height: min(178px, 9.270833vw); - padding: min(28px, 1.458333vw) min(32px, 1.666667vw); - gap: min(14px, 0.729167vw); - background: rgba(255, 255, 255, 0.05); - border-radius: min(24px, 1.25vw); - } - - .user-center-layout .user-center-login-title { - font-size: clamp(20px, 1.25vw, 24px); - font-weight: 600; - line-height: clamp(28px, 1.666667vw, 32px); - } - - .user-center-layout .user-center-login-btn { - background: #ec237f; - color: #fff; - font-weight: 500; - } - - .user-center-layout .user-center-login-arrow { - display: none; - } - - .user-center-layout .user-center-features { - gap: min(16px, 0.833333vw); - } - - .user-center-layout .user-center-feature { - height: min(120px, 6.25vw); - padding: calc(min(18px, 0.9375vw) - 1px) calc(min(22px, 1.145833vw) - 1px); - gap: min(6px, 0.3125vw); - border-radius: min(20px, 1.041667vw); - } - - .user-center-layout .user-center-feature-icon, - .user-center-layout .user-center-feature-icon img { - width: min(30px, 1.5625vw); - height: min(30px, 1.5625vw); - } - - .user-center-layout .user-center-feature-title { - font-size: min(18px, 0.9375vw); - font-weight: 600; - line-height: min(24px, 1.25vw); - } - - .user-center-layout .user-center-feature-desc { - font-size: min(12px, 0.625vw); - line-height: min(18px, 0.9375vw); - } -} - -@media (max-width: 767px) { - body.site-shell.is-user-center-page { - padding-top: calc(48px + env(safe-area-inset-top, 0px)); - background: #0f0f0f; - } - - body.is-user-center-page .site-header { - display: flex !important; - height: 48px !important; - min-height: 48px !important; - padding: 0 var(--site-shell-gutter) !important; - overflow: hidden !important; - } - - body.is-user-center-page .footer-box, - body.is-user-center-page .floating-action, - body.is-user-center-page .download-fab { - display: none !important; - } - - .user-center-layout .user-center-page { - min-height: calc(100dvh - 48px - env(safe-area-inset-top, 0px)); - padding: 0; - background: #0f0f0f; - } - - .user-center-layout .user-center-main { - display: flex; - align-items: center; - } - - .user-center-layout .user-center-mobile-profile { - width: calc(100% - 32px); - max-width: 343px; - height: 56px; - margin: 12px auto 0; - padding: 0; - gap: 12px; - background: transparent; - border: 1px solid rgba(255, 255, 255, 0.06); - } - - .user-center-layout .user-center-mobile-avatar { - width: 56px; - height: 56px; - } - - .user-center-layout .user-center-mobile-body { - width: calc(100% - 32px); - max-width: 343px; - margin: 14px auto 0; - gap: 14px; - overflow: visible; - } - - .user-center-mobile-balance { - position: relative; - width: 100%; - height: 81px; - padding: 15px 16px 15px 26px; - border-radius: 18px; - background: url('/static/images/figma-account/balance-card.svg') center/100% 100% no-repeat; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - color: #fff; - text-decoration: none; - } - - .user-center-mobile-balance-copy { - display: flex; - flex-direction: column; - gap: 4px; - } - - .user-center-mobile-balance-label { - color: transparent; - background: linear-gradient(90deg, #ff9365 0%, #ffd8bc 100%); - -webkit-background-clip: text; - background-clip: text; - font-family: "Alimama FangYuanTi VF", "PingFang SC", sans-serif; - font-size: 16px; - font-weight: 600; - line-height: 18px; - } - - .user-center-mobile-balance-label small { - color: transparent; - background: linear-gradient(90deg, #ff9365 0%, #ffd8bc 100%); - -webkit-background-clip: text; - background-clip: text; - font-size: 10px; - font-weight: 400; - } - - .user-center-mobile-balance-value { - font-family: "Arial Narrow", sans-serif; - font-size: 24px; - font-weight: 800; - line-height: 24px; - } - - .user-center-mobile-balance-action { - position: relative; - width: 72px; - height: 30px; - margin-right: 5px; - border-radius: 15px; - background: #f8d9ac; - color: #171717; - font-size: 12px; - font-weight: 400; - line-height: 18px; - display: flex; - align-items: center; - justify-content: center; - } - - .user-center-mobile-balance-action::after { - content: ""; - position: absolute; - z-index: 1; - top: 50%; - right: -5px; - width: 20px; - height: 23px; - background: url('/static/images/figma-account/balance-action-accent.svg') center/contain no-repeat; - transform: translateY(-50%); - pointer-events: none; - } - - - .user-center-layout .user-center-mobile-menu { - border: 0; - border-radius: 16px; - background: rgba(255, 255, 255, 0.05); - } - - .user-center-layout .user-center-mobile-menu-item { - height: 48px; - padding: 0 14px; - } - - .user-center-layout .user-center-mobile-menu-label, - .user-center-layout .user-center-mobile-menu-hint { - font-weight: 400; - } - - .user-center-layout .user-center-mobile-menu-hint { - color: rgba(255, 255, 255, 0.55); - } -} - - -.user-center-layout .site-header, -.user-center-layout .footer-box { - flex-shrink: 0; -} - -.user-center-page { - width: 100%; - flex: 1 1 auto; - min-height: 0; - padding: 36px 0; - background: #0A0A0A; - overflow: hidden; - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: center; - gap: 24px; - box-sizing: border-box; -} - -.user-center-pc { - width: 100%; - max-width: calc(100% - 32px); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 24px; -} - -.user-center-nav { - display: none; -} - -.user-center-main { - display: none; -} - -.user-center-head { - align-self: stretch; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 2px; -} - -.user-center-head-row { - align-self: stretch; - display: flex; - justify-content: space-between; - align-items: center; - gap: 16px; - flex-wrap: wrap; -} - -.user-center-head-title-wrap { - display: flex; - align-items: center; - gap: 12px; - flex-wrap: wrap; - min-width: 0; -} - -.user-center-guest-register-btn { - height: 32px; - padding: 0 14px; - border-radius: 16px; - background: #EC237F; - color: #181818; - font-size: 13px; - font-family: PingFang SC; - font-weight: 500; - line-height: 18px; - text-decoration: none; - display: inline-flex; - align-items: center; - justify-content: center; - white-space: nowrap; - box-sizing: border-box; -} - -.user-center-guest-register-btn:hover { - filter: brightness(0.96); -} - -.user-center-state-switch { - display: none; -} - -.user-center-state-btn { - height: 30px; - padding: 0 12px; - border-radius: 8px; - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; - text-decoration: none; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.user-center-state-btn--active { - background: #EC237F; - color: #181818; -} - -.user-center-eyebrow { - color: #EC237F; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.user-center-title { - margin: 0; - color: white; - font-size: 36px; - font-family: PingFang SC; - font-weight: 400; - line-height: 42px; -} - -.user-center-title--logged { - font-size: 34px; - line-height: 40px; -} - -.user-center-desc { - color: #A3A3A3; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.user-center-profile { - align-self: stretch; - height: 176px; - min-height: 176px; - padding: 24px 28px; - background: linear-gradient(90deg, rgba(241, 43, 158, 0.17) 0%, rgba(62, 102, 255, 0.18) 100%); - overflow: hidden; - border-radius: 24px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - justify-content: flex-start; - align-items: center; - gap: 20px; - box-sizing: border-box; -} - -.user-center-avatar { - width: 84px; - height: 84px; - border-radius: 999px; - object-fit: cover; - flex-shrink: 0; -} - -.user-center-profile-info { - overflow: hidden; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - min-width: 0; -} - -.user-center-profile-name { - color: white; - font-size: 24px; - font-family: PingFang SC; - font-weight: 400; - line-height: 30px; -} - -.user-center-profile-id { - max-width: 100%; - overflow: hidden; - color: #fff; - font-family: PingFang SC, sans-serif; - font-size: 14px; - font-weight: 400; - line-height: 20px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.user-center-profile-email { - color: #8A8A8A; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 280px; -} - -.user-center-profile-badge { - padding: 4px 10px; - background: rgba(255, 255, 255, 0.08); - overflow: hidden; - border-radius: 12px; - color: #D4D4D4; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.user-center-profile-spacer { - flex: 1 1 0; - height: 1px; - min-width: 12px; -} - -.user-center-vip-card { - width: 220px; - height: 112px; - padding: 16px 18px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - box-sizing: border-box; - text-decoration: none; - flex-shrink: 0; -} - -.user-center-vip-card--active { - background: linear-gradient(180deg, #FFD597 0%, #F8D9AC 100%); - border-color: #695700; -} - -.user-center-vip-card--active .user-center-vip-label, -.user-center-vip-card--active .user-center-vip-expire { - color: #9D6200; -} - -.user-center-vip-card--active .user-center-vip-status { - color: #000; -} - -.user-center-vip-label { - color: #F59E0B; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; -} - -.user-center-vip-status { - color: white; - font-size: 18px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 24px; -} - -.user-center-vip-expire { - color: #9D6200; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; -} - -.user-center-vip-action { - color: #EC237F; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; -} - -.user-center-coin-card { - width: 180px; - height: 112px; - padding: 16px 18px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; - box-sizing: border-box; - text-decoration: none; - flex-shrink: 0; -} - -.user-center-coin-label { - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -.user-center-coin-value { - color: white; - font-size: 30px; - font-family: PingFang SC; - font-weight: 400; - line-height: 36px; -} - -.user-center-coin-action { - display: block; - width: 72px; - height: 30px; - flex: 0 0 auto; - object-fit: contain; - object-position: center right; -} - -.user-center-login-banner { - align-self: stretch; - min-height: 190px; - padding: 28px 32px; - background: linear-gradient(90deg, rgba(241, 43, 158, 0.16) 0%, rgba(62, 102, 255, 0.18) 100%); - overflow: hidden; - border-radius: 24px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - gap: 16px; - box-sizing: border-box; -} - -.user-center-login-title { - color: white; - font-size: 24px; - font-family: PingFang SC; - font-weight: 400; - line-height: 30px; -} - -.user-center-login-desc { - color: #A3A3A3; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.user-center-login-btn { - width: 126px; - height: 42px; - background: white; - overflow: hidden; - border-radius: 21px; - justify-content: center; - align-items: center; - gap: 8px; - display: inline-flex; - text-decoration: none; - color: #181818; - font-size: 14px; - font-family: PingFang SC; - font-weight: 400; - line-height: 20px; -} - -.user-center-login-arrow { - color: #181818; - font-size: 16px; - font-family: PingFang SC; - font-weight: 400; - line-height: 22px; -} - -.user-center-features { - align-self: stretch; - display: flex; - justify-content: flex-start; - align-items: stretch; - gap: 16px; -} - -.user-center-feature { - flex: 1 1 0; - min-width: 0; - padding: 18px 22px; - background: rgba(255, 255, 255, 0.05); - overflow: hidden; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.10); - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - gap: 6px; - box-sizing: border-box; - text-decoration: none; - color: inherit; -} - -.user-center-feature-icon { - width: 30px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; -} - -.user-center-feature-icon img { - width: 30px; - height: 30px; - object-fit: contain; -} - -.user-center-feature-title { - color: white; - font-size: 18px; - font-family: PingFang SC; - font-weight: 400; - line-height: 24px; -} - -.user-center-feature-desc { - color: #A3A3A3; - font-size: 12px; - font-family: PingFang SC; - font-weight: 400; - line-height: 18px; -} - -@media (max-width: 1100px) { - .user-center-pc .user-center-profile { - flex-wrap: wrap; - } - - .user-center-pc .user-center-profile-spacer { - display: none; - } - - .user-center-pc .user-center-vip-card, - .user-center-pc .user-center-coin-card { - flex: 1 1 180px; - width: auto; - } -} - -@media (max-width: 767px) { - body.is-user-center-page { - background: #0A0A0A; - } - - body.is-user-center-page .site-header, - body.is-user-center-page .footer-box, - body.is-user-center-page .floating-action, - body.is-user-center-page .download-fab { - display: none !important; - height: 0 !important; - min-height: 0 !important; - overflow: hidden !important; - margin: 0 !important; - padding: 0 !important; - border: none !important; - } - - .user-center-layout { - min-height: 100vh; - background: #0A0A0A; - } - - .user-center-page { - padding: 0; - padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px)); - background: #0A0A0A; - align-items: stretch; - gap: 0; - min-height: 100vh; - overflow: visible; - } - - .user-center-pc { - display: none; - } - - .user-center-nav { - position: sticky; - top: 0; - z-index: 40; - width: 100%; - height: 48px; - padding: 0 16px; - display: flex; - justify-content: center; - align-items: center; - box-sizing: border-box; - flex-shrink: 0; - background: #181818; - overflow: hidden; - } - - .user-center-nav-back, - .user-center-nav-spacer { - display: none; - } - - .user-center-nav-title { - margin: 0; - flex: 1 1 0; - height: 24px; - display: flex; - justify-content: center; - align-items: center; - color: #fff; - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - } - - .user-center-main { - display: flex; - width: 100%; - max-width: 100%; - flex-direction: column; - align-items: center; - box-sizing: border-box; - } - - .user-center-mobile-profile { - width: 100%; - height: 88px; - padding: 0 16px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.06); - display: flex; - justify-content: flex-start; - align-items: center; - gap: 12px; - box-sizing: border-box; - flex-shrink: 0; - overflow: hidden; - } - - .user-center-mobile-profile--guest { - gap: 10px; - } - - .user-center-mobile-avatar { - width: 56px; - height: 56px; - border-radius: 999px; - object-fit: cover; - flex-shrink: 0; - display: block; - border: 1px solid rgba(250, 220, 68, 0.35); - } - - .user-center-mobile-profile-info { - width: 199px; - max-width: 100%; - min-width: 0; - flex: 1 1 0; - height: 46px; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 2px; - overflow: hidden; - } - - .user-center-mobile-name { - color: #fff; - font-size: 18px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 24px; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .user-center-mobile-meta { - color: rgba(255, 255, 255, 0.60); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .user-center-mobile-login-btn { - flex-shrink: 0; - height: 30px; - padding: 0 14px; - background: #EC237F; - border-radius: 15px; - color: #171717; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - text-decoration: none; - display: inline-flex; - align-items: center; - justify-content: center; - } - - .user-center-mobile-guest-register-btn { - flex-shrink: 0; - height: 30px; - padding: 0 14px; - background: #EC237F; - border-radius: 15px; - color: #171717; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - text-decoration: none; - display: inline-flex; - align-items: center; - justify-content: center; - white-space: nowrap; - } - - .user-center-mobile-body { - width: calc(100% - 32px); - max-width: 343px; - margin: 14px auto 0; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 14px; - box-sizing: border-box; - overflow: hidden; - } - - .user-center-mobile-vip { - position: relative; - width: 100%; - height: 88px; - min-height: 88px; - padding: 14px 16px; - background: rgba(250, 220, 68, 0.09); - border-radius: 18px; - border: 1px solid rgba(250, 220, 68, 0.24); - overflow: hidden; - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 5px; - box-sizing: border-box; - text-decoration: none; - } - - .user-center-mobile-vip--active { - background: linear-gradient(180deg, rgba(255, 213, 151, 0.18) 0%, rgba(248, 217, 172, 0.12) 100%); - border-color: rgba(250, 220, 68, 0.35); - } - - .user-center-mobile-vip--active .user-center-mobile-vip-title { - color: rgba(255, 255, 255, 0.95); - } - - .user-center-mobile-vip-title { - color: rgba(255, 255, 255, 0.95); - font-size: 16px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 22px; - padding-right: 100px; - } - - .user-center-mobile-vip-desc { - color: rgba(255, 255, 255, 0.62); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - padding-right: 100px; - } - - .user-center-mobile-vip--active .user-center-mobile-vip-title, - .user-center-mobile-vip--active .user-center-mobile-vip-desc { - padding-right: 100px; - } - - .user-center-mobile-vip-btn { - position: absolute; - right: 16px; - top: 50%; - transform: translateY(-50%); - width: 92px; - height: 30px; - background: #EC237F; - border-radius: 15px; - color: #171717; - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 700; - line-height: 18px; - display: inline-flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - } - - .user-center-mobile-vip-btn[hidden] { - display: none !important; - } - - .user-center-mobile-menu { - width: 100%; - background: rgba(255, 255, 255, 0.05); - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - overflow: hidden; - display: flex; - flex-direction: column; - align-items: stretch; - } - - .user-center-mobile-menu-item { - width: 100%; - height: 48px; - padding: 0 14px; - border-bottom: 1px rgba(255, 255, 255, 0.10) solid; - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - box-sizing: border-box; - text-decoration: none; - overflow: hidden; - } - - .user-center-mobile-menu-item:last-child { - border-bottom: none; - } - - .user-center-mobile-menu-label { - color: rgba(255, 255, 255, 0.90); - font-size: 14px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 20px; - } - - .user-center-mobile-menu-label--accent { - color: rgba(255, 255, 255, 0.90); - font-weight: 700; - } - - .user-center-mobile-menu-right { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - overflow: hidden; - } - - .user-center-mobile-menu-hint { - color: rgba(255, 255, 255, 0.90); - font-size: 12px; - font-family: "Noto Sans SC", PingFang SC, sans-serif; - font-weight: 400; - line-height: 18px; - } - - .user-center-mobile-menu-arrow { - width: 18px; - height: 18px; - display: block; - opacity: 0.55; - filter: brightness(0) invert(1); - } -} diff --git a/app/static/css/vipDialog.css b/app/static/css/vipDialog.css deleted file mode 100644 index c70d864..0000000 --- a/app/static/css/vipDialog.css +++ /dev/null @@ -1,241 +0,0 @@ -.vip-dialog-mask { - position: fixed; - inset: 0; - z-index: 1000; - display: none; - align-items: center; - justify-content: center; - background: rgba(0, 0, 0, 0.55); - padding: 24px; -} - -.vip-dialog-mask.is-open { - display: flex; -} - -.vip-dialog-card { - width: 456px; - max-width: 100%; - padding: 36px; - position: relative; - background: #0A0A0A; - box-shadow: 0px 20px 48px rgba(0, 0, 0, 0.46); - overflow: hidden; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.10); - flex-direction: column; - justify-content: center; - align-items: center; - gap: 18px; - display: inline-flex; - box-sizing: border-box; -} - -.vip-dialog-close { - width: 28px; - height: 28px; - right: 20px; - top: 20px; - position: absolute; - overflow: hidden; - border: none; - padding: 0; - background: transparent; - cursor: pointer; -} - -.vip-dialog-close-ring { - width: 26px; - height: 26px; - left: 1px; - top: 1px; - position: absolute; - background: #fff; - border: 1px solid #fff; - border-radius: 50%; - box-sizing: border-box; -} - -.vip-dialog-close-x { - width: 8px; - height: 8px; - left: 10px; - top: 10px; - position: absolute; - box-sizing: border-box; -} - -.vip-dialog-close-x::before, -.vip-dialog-close-x::after { - content: ""; - position: absolute; - left: 50%; - top: 50%; - width: 10px; - height: 1.5px; - background: #808080; - border-radius: 1px; -} - -.vip-dialog-close-x::before { - transform: translate(-50%, -50%) rotate(45deg); -} - -.vip-dialog-close-x::after { - transform: translate(-50%, -50%) rotate(-45deg); -} - -.vip-dialog-icon { - width: 52px; - height: 52px; - position: relative; - overflow: hidden; - border-radius: 12px; - flex-shrink: 0; - color: #EC237F; -} - -.vip-dialog-icon-bg { - width: 52px; - height: 52px; - left: 0; - top: 0; - position: absolute; - opacity: 0.10; - background: #EC237F; -} - -.vip-dialog-icon-crown { - width: 24px; - height: 21px; - left: 14px; - top: 16px; - position: absolute; - display: block; -} - -.vip-dialog-title { - text-align: center; - color: white; - font-size: 24px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 700; - line-height: 30px; - word-wrap: break-word; -} - -.vip-dialog-desc { - text-align: center; - color: #8A8A8A; - font-size: 14px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 400; - line-height: 20px; - word-wrap: break-word; -} - -.vip-dialog-benefits { - text-align: center; - color: #F59E0B; - font-size: 12px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 700; - line-height: 18px; - word-wrap: break-word; -} - -.vip-dialog-cta { - align-self: stretch; - width: 100%; - height: 48px; - background: #EC237F; - overflow: hidden; - border-radius: 24px; - justify-content: center; - align-items: center; - display: inline-flex; - color: #181818; - font-size: 16px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 700; - line-height: 22px; - word-wrap: break-word; - text-decoration: none; - box-sizing: border-box; -} - -.vip-dialog-skip { - text-align: center; - color: #8A8A8A; - font-size: 12px; - font-family: "Noto Sans SC", "PingFang SC", sans-serif; - font-weight: 400; - line-height: 18px; - word-wrap: break-word; - border: none; - background: transparent; - padding: 0; - cursor: pointer; -} - -@media (max-width: 767px) { - .vip-dialog-mask { - background: rgba(0, 0, 0, 0.70); - padding: 16px; - } - - .vip-dialog-card { - width: 300px; - max-width: calc(100vw - 32px); - padding: 32px 24px; - background: rgba(23, 23, 23, 0.98); - box-shadow: 0px 16px 32px rgba(0, 0, 0, 0.45); - border-radius: 24px; - border: 1px solid rgba(255, 255, 255, 0.10); - } - - .vip-dialog-close { - right: 12px; - top: 12px; - background: rgba(255, 255, 255, 0.08); - border-radius: 14px; - } - - .vip-dialog-close-ring { - display: none; - } - - .vip-dialog-close-x::before, - .vip-dialog-close-x::after { - background: rgba(255, 255, 255, 0.60); - } - - .vip-dialog-icon { - color: #fff; - border-radius: 0; - } - - .vip-dialog-icon-bg { - opacity: 1; - background: linear-gradient(135deg, #F12B9E 0%, #6D4BFF 100%); - } - - .vip-dialog-desc { - align-self: stretch; - color: rgba(255, 255, 255, 0.60); - } - - .vip-dialog-benefits { - width: 100%; - max-width: 287px; - color: #EC237F; - } - - .vip-dialog-cta { - color: #171717; - } - - .vip-dialog-skip { - color: rgba(255, 255, 255, 0.60); - } -} diff --git a/app/static/favicon.ico b/app/static/favicon.ico index 55998fc..6b1a285 100644 Binary files a/app/static/favicon.ico and b/app/static/favicon.ico differ diff --git a/app/static/favicon.png b/app/static/favicon.png index c057355..301467b 100644 Binary files a/app/static/favicon.png and b/app/static/favicon.png differ diff --git a/app/static/favicon.svg b/app/static/favicon.svg index c52a98a..ebf0f22 100644 --- a/app/static/favicon.svg +++ b/app/static/favicon.svg @@ -1,74 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/static/images/coin.png b/app/static/images/coin.png deleted file mode 100644 index c5bdc2f..0000000 Binary files a/app/static/images/coin.png and /dev/null differ diff --git a/app/static/images/figma-account/balance-action-accent.svg b/app/static/images/figma-account/balance-action-accent.svg deleted file mode 100644 index 6cfd86b..0000000 --- a/app/static/images/figma-account/balance-action-accent.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/static/images/figma-account/balance-card.svg b/app/static/images/figma-account/balance-card.svg deleted file mode 100644 index 57d21ff..0000000 --- a/app/static/images/figma-account/balance-card.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/static/images/figma-account/deposit-balance-card.png b/app/static/images/figma-account/deposit-balance-card.png deleted file mode 100644 index 04182ee..0000000 Binary files a/app/static/images/figma-account/deposit-balance-card.png and /dev/null differ diff --git a/app/static/images/figma-account/feature-follow.svg b/app/static/images/figma-account/feature-follow.svg deleted file mode 100644 index 14c6f0a..0000000 --- a/app/static/images/figma-account/feature-follow.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/static/images/figma-account/feature-history.svg b/app/static/images/figma-account/feature-history.svg deleted file mode 100644 index b7bf0c2..0000000 --- a/app/static/images/figma-account/feature-history.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/app/static/images/figma-account/feature-setting.svg b/app/static/images/figma-account/feature-setting.svg deleted file mode 100644 index fbb48ac..0000000 --- a/app/static/images/figma-account/feature-setting.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/static/images/figma-account/feature-wallet.svg b/app/static/images/figma-account/feature-wallet.svg deleted file mode 100644 index 09b9c1f..0000000 --- a/app/static/images/figma-account/feature-wallet.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/static/images/figma-account/package-coin.svg b/app/static/images/figma-account/package-coin.svg deleted file mode 100644 index 3cda36c..0000000 --- a/app/static/images/figma-account/package-coin.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/static/images/figma-account/usage-member.svg b/app/static/images/figma-account/usage-member.svg deleted file mode 100644 index c2c9500..0000000 --- a/app/static/images/figma-account/usage-member.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/app/static/images/figma-account/usage-reward.svg b/app/static/images/figma-account/usage-reward.svg deleted file mode 100644 index 12f561c..0000000 --- a/app/static/images/figma-account/usage-reward.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/static/images/figma-account/usage-unlock.svg b/app/static/images/figma-account/usage-unlock.svg deleted file mode 100644 index 2cd1b54..0000000 --- a/app/static/images/figma-account/usage-unlock.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/static/images/figma-dialogs/alipay.svg b/app/static/images/figma-dialogs/alipay.svg deleted file mode 100644 index 9231e14..0000000 --- a/app/static/images/figma-dialogs/alipay.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/figma-dialogs/coin-unlock.svg b/app/static/images/figma-dialogs/coin-unlock.svg deleted file mode 100644 index 2528267..0000000 --- a/app/static/images/figma-dialogs/coin-unlock.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/static/images/figma-dialogs/download-logo-mobile.svg b/app/static/images/figma-dialogs/download-logo-mobile.svg index bc534c6..01050c0 100644 --- a/app/static/images/figma-dialogs/download-logo-mobile.svg +++ b/app/static/images/figma-dialogs/download-logo-mobile.svg @@ -1,65 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/static/images/figma-dialogs/download-logo-pc.svg b/app/static/images/figma-dialogs/download-logo-pc.svg index 69ace31..b695f62 100644 --- a/app/static/images/figma-dialogs/download-logo-pc.svg +++ b/app/static/images/figma-dialogs/download-logo-pc.svg @@ -1,65 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/app/static/images/figma-dialogs/password-eye.svg b/app/static/images/figma-dialogs/password-eye.svg deleted file mode 100644 index 2a78751..0000000 --- a/app/static/images/figma-dialogs/password-eye.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/figma-dialogs/usdt.svg b/app/static/images/figma-dialogs/usdt.svg deleted file mode 100644 index bda3787..0000000 --- a/app/static/images/figma-dialogs/usdt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/static/images/figma-dialogs/wechat.svg b/app/static/images/figma-dialogs/wechat.svg deleted file mode 100644 index 5b31973..0000000 --- a/app/static/images/figma-dialogs/wechat.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/app/static/images/figma-home/footer-logo-desktop.png b/app/static/images/figma-home/footer-logo-desktop.png index a7031f3..5ae6f95 100644 Binary files a/app/static/images/figma-home/footer-logo-desktop.png and b/app/static/images/figma-home/footer-logo-desktop.png differ diff --git a/app/static/images/figma-home/header-coin.png b/app/static/images/figma-home/header-coin.png deleted file mode 100644 index ab64949..0000000 Binary files a/app/static/images/figma-home/header-coin.png and /dev/null differ diff --git a/app/static/images/figma-home/header-logo-desktop.png b/app/static/images/figma-home/header-logo-desktop.png deleted file mode 100644 index 41b3d11..0000000 Binary files a/app/static/images/figma-home/header-logo-desktop.png and /dev/null differ diff --git a/app/static/images/figma-home/mobile/drawer-logo.png b/app/static/images/figma-home/mobile/drawer-logo.png index a64f5c0..740fa75 100644 Binary files a/app/static/images/figma-home/mobile/drawer-logo.png and b/app/static/images/figma-home/mobile/drawer-logo.png differ diff --git a/app/static/images/figma-home/mobile/footer-logo-mobile.png b/app/static/images/figma-home/mobile/footer-logo-mobile.png deleted file mode 100644 index c38f93e..0000000 Binary files a/app/static/images/figma-home/mobile/footer-logo-mobile.png and /dev/null differ diff --git a/app/static/images/figma-home/mobile/header-logo.png b/app/static/images/figma-home/mobile/header-logo.png deleted file mode 100644 index 695cc87..0000000 Binary files a/app/static/images/figma-home/mobile/header-logo.png and /dev/null differ diff --git a/app/static/images/figma-nav/logo.svg b/app/static/images/figma-nav/logo.svg deleted file mode 100644 index 0c8c689..0000000 --- a/app/static/images/figma-nav/logo.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/static/images/isVip.png b/app/static/images/isVip.png deleted file mode 100644 index ddbb7eb..0000000 Binary files a/app/static/images/isVip.png and /dev/null differ diff --git a/app/static/images/jbcz.png b/app/static/images/jbcz.png deleted file mode 100644 index 78a30eb..0000000 Binary files a/app/static/images/jbcz.png and /dev/null differ diff --git a/app/static/images/logo-h5.png b/app/static/images/logo-h5.png index 8f94b49..7e7b0b4 100644 Binary files a/app/static/images/logo-h5.png and b/app/static/images/logo-h5.png differ diff --git a/app/static/images/logo.png b/app/static/images/logo.png index 2a73bfe..47e5c3c 100644 Binary files a/app/static/images/logo.png and b/app/static/images/logo.png differ diff --git a/app/static/images/tab_app.svg b/app/static/images/tab_app.svg new file mode 100644 index 0000000..c4ad58e --- /dev/null +++ b/app/static/images/tab_app.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/static/images/tab_app_a.svg b/app/static/images/tab_app_a.svg new file mode 100644 index 0000000..e2a1564 --- /dev/null +++ b/app/static/images/tab_app_a.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/static/images/tab_wd.svg b/app/static/images/tab_wd.svg deleted file mode 100644 index 801b441..0000000 --- a/app/static/images/tab_wd.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/tab_wd_a.svg b/app/static/images/tab_wd_a.svg deleted file mode 100644 index f4670d1..0000000 --- a/app/static/images/tab_wd_a.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/static/images/vip.png b/app/static/images/vip.png deleted file mode 100644 index c633c63..0000000 Binary files a/app/static/images/vip.png and /dev/null differ diff --git a/app/static/js/common/auth.js b/app/static/js/common/auth.js deleted file mode 100644 index 8b9db72..0000000 --- a/app/static/js/common/auth.js +++ /dev/null @@ -1,454 +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; - } - } - - 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() { - 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 { - 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 (e2) {} - 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) !== "/"; - } - - /** 注册账号:打开登录弹窗;无弹窗时跳首页登录入口 */ - function promptRegisteredRelogin() { - toast("登录已过期,请重新登录", "warning"); - var redirect = ""; - try { - redirect = (global.location.pathname || "/") + (global.location.search || ""); - } catch (e2) { - redirect = "/"; - } - var url = "/login/"; - if (isSafeRedirect(redirect) && redirect !== "/" && redirect.indexOf("/?auth=") !== 0) { - url = "/login/return" + (global.location.pathname || "/"); - } - if (typeof global.openLoginDialog === "function") { - try { - var opened = global.openLoginDialog(); - // dialogLoader 桩函数返回 Promise:加载失败时再走跳转兜底 - if (opened && typeof opened.then === "function") { - opened.catch(function () { - global.location.href = url; - }); - return; - } - return; - } catch (e) {} - } - global.location.href = url; - } - - /** - * 接口返回 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; - } - - global.AuthAPI = { - TOKEN_EXPIRED_CODE: TOKEN_EXPIRED_CODE, - register: register, - login: login, - 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/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 6588cfe..0e30f95 100644 --- a/app/static/js/common/request.js +++ b/app/static/js/common/request.js @@ -1,6 +1,6 @@ /** * 前端请求封装。 - * - 登录态走 cookie(credentials: include) + * - 游客会话走 cookie(credentials: include),会话失效由服务端重建 * - 浏览器无法改写 User-Agent;X-Real-IP 由服务端反代上游时注入 * - 直连上游时若需自定义头,请走本站 Go 代理接口 */ @@ -87,49 +87,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/pages/coinDialog.js b/app/static/js/pages/coinDialog.js deleted file mode 100644 index 97f1936..0000000 --- a/app/static/js/pages/coinDialog.js +++ /dev/null @@ -1,307 +0,0 @@ -(function () { - var mask = document.getElementById("coinDialog"); - if (!mask) return; - - var balanceEls = mask.querySelectorAll("[data-coin-balance]"); - var costEls = mask.querySelectorAll("[data-coin-cost]"); - var payBtn = mask.querySelector("[data-coin-dialog-pay]"); - var loading = false; - var paying = false; - - function formatCoins(n) { - var num = Number(n); - if (!Number.isFinite(num) || num < 0) num = 0; - return String(Math.floor(num)); - } - - function setBalance(n) { - var text = formatCoins(n); - balanceEls.forEach(function (el) { - el.textContent = text; - }); - } - - function setCost(n) { - var text = formatCoins(n); - costEls.forEach(function (el) { - el.textContent = text; - }); - } - - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - window.alert(message); - } - - function openLogin() { - var loginOpen = document.querySelector("[data-login-dialog-open]"); - if (loginOpen) loginOpen.click(); - } - - function goToCoinRecharge() { - window.location.href = "/deposit/coin/"; - } - - function isInsufficientBalance(res) { - if (!res) return false; - if (Number(res.code) === 20004) return true; - var message = String(res.tip || res.msg || ""); - return /(?:金币|余额).{0,6}不足|不足.{0,6}(?:金币|余额)/.test(message); - } - - function resolvePlayletId(trigger) { - if (trigger) { - var fromTrigger = Number(trigger.getAttribute("data-playlet-id") || 0); - if (fromTrigger > 0) return fromTrigger; - } - var fromMask = Number(mask.getAttribute("data-playlet-id") || 0); - if (fromMask > 0) return fromMask; - var playPage = - document.querySelector("[data-play-page]") || - document.querySelector(".play-page"); - if (playPage) { - var fromPage = Number(playPage.getAttribute("data-playlet-id") || 0); - if (fromPage > 0) return fromPage; - } - var player = document.querySelector("[data-play-player]"); - if (player) { - var fromPlayer = Number(player.getAttribute("data-playlet-id") || 0); - if (fromPlayer > 0) return fromPlayer; - } - var likeBtn = document.querySelector("[data-playlet-id]"); - if (likeBtn) { - var fromBtn = Number(likeBtn.getAttribute("data-playlet-id") || 0); - if (fromBtn > 0) return fromBtn; - } - return 0; - } - - function resolvePlayletSlug(trigger) { - if (trigger) { - var fromTrigger = String(trigger.getAttribute("data-playlet-slug") || "").trim(); - if (fromTrigger) return fromTrigger; - } - var fromMask = String(mask.getAttribute("data-playlet-slug") || "").trim(); - if (fromMask) return fromMask; - var playPage = - document.querySelector("[data-play-page]") || - document.querySelector(".play-page"); - if (playPage) { - var fromPage = String(playPage.getAttribute("data-playlet-slug") || "").trim(); - if (fromPage) return fromPage; - } - return ""; - } - - function buildPlayHref(playletId, chapterId) { - if (!playletId || !chapterId) return ""; - var slug = resolvePlayletSlug(null); - var key = slug || String(playletId); - return "/play/" + encodeURIComponent(key) + "/" + chapterId + "/"; - } - - function isOnPlayPage() { - return !!( - document.querySelector("[data-play-page]") || - document.querySelector(".play-page") - ); - } - - function afterUnlockSuccess(playletId, chapterId, data) { - var detail = { - playletId: playletId, - chapterId: chapterId, - balance: data && typeof data.balance === "number" ? data.balance : null, - canWatch: !!(data && data.canWatch), - unlocked: !!(data && data.unlocked), - }; - window.dispatchEvent( - new CustomEvent("play:chapter-unlocked", { detail: detail }) - ); - if ( - isOnPlayPage() && - typeof window.playAfterChapterUnlock === "function" - ) { - window.playAfterChapterUnlock(detail); - return; - } - var href = buildPlayHref(playletId, chapterId); - if (href) { - window.location.href = href; - } - } - - function setPaying(on) { - paying = !!on; - if (!payBtn) return; - payBtn.disabled = paying; - payBtn.classList.toggle("is-loading", paying); - payBtn.setAttribute("aria-busy", paying ? "true" : "false"); - } - - function fetchBalance() { - if (!window.HttpRequest) { - setBalance(0); - return Promise.resolve(0); - } - return window.HttpRequest.get("/api/auth/getInfo") - .then(function (res) { - if (!res || res.code !== 200 || !res.data) { - if (res && res.code === 401) { - openLogin(); - } - throw new Error((res && (res.tip || res.msg)) || "获取余额失败"); - } - var recharge = res.data.recharge; - setBalance(recharge); - if (window.CookieUtil && CookieUtil.setAuthSession) { - CookieUtil.setAuthSession(res.data); - } - return Number(recharge) || 0; - }) - .catch(function (err) { - setBalance(0); - toast((err && err.message) || "获取余额失败", "error"); - return 0; - }); - } - - function unlockAndPlay() { - if (paying) return; - if (!window.HttpRequest) { - toast("网络模块未就绪", "error"); - return; - } - - var chapterId = Number(mask.getAttribute("data-chapter-id") || 0); - var playletId = Number(mask.getAttribute("data-playlet-id") || 0); - var cost = Number(mask.getAttribute("data-single-coins") || 0); - var balance = Number( - (balanceEls[0] && balanceEls[0].textContent) || 0 - ); - - if (!chapterId || !playletId) { - toast("缺少剧集信息,请刷新后重试", "error"); - return; - } - - if (Number.isFinite(balance) && Number.isFinite(cost) && balance < cost) { - goToCoinRecharge(); - return; - } - - setPaying(true); - window.HttpRequest.post("/api/playlet/chapter/unlock", { - chapterId: chapterId, - playletId: playletId, - }) - .then(function (res) { - if (!res || res.code !== 200) { - if (res && res.code === 401) { - closeDialog(); - openLogin(); - return; - } - if (isInsufficientBalance(res)) { - goToCoinRecharge(); - return; - } - throw new Error((res && (res.tip || res.msg)) || "解锁失败"); - } - var data = res.data || {}; - if (typeof data.balance === "number") { - setBalance(data.balance); - if (window.CookieUtil && CookieUtil.setAuthSession) { - CookieUtil.setAuthSession({ recharge: data.balance }); - } - } - toast((res && res.tip) || "解锁成功", "success"); - closeDialog(); - afterUnlockSuccess( - Number(data.playletId) || playletId, - Number(data.chapterId) || chapterId, - data - ); - }) - .catch(function (err) { - toast((err && err.message) || "解锁失败,请稍后重试", "error"); - }) - .finally(function () { - setPaying(false); - }); - } - - function openDialog(event) { - if (event) event.preventDefault(); - var trigger = - event && event.target && event.target.closest - ? event.target.closest("[data-coin-dialog-open]") - : null; - var singleCoins = trigger - ? Number(trigger.getAttribute("data-single-coins") || 0) - : 0; - setCost(singleCoins); - if (trigger) { - mask.setAttribute( - "data-chapter-id", - trigger.getAttribute("data-chapter-id") || "" - ); - mask.setAttribute("data-single-coins", String(singleCoins || 0)); - mask.setAttribute( - "data-playlet-id", - String(resolvePlayletId(trigger) || "") - ); - mask.setAttribute("data-playlet-slug", resolvePlayletSlug(trigger)); - } else if (!mask.getAttribute("data-playlet-id")) { - mask.setAttribute("data-playlet-id", String(resolvePlayletId(null) || "")); - } - - mask.classList.add("is-open"); - document.body.style.overflow = "hidden"; - - if (loading) return; - loading = true; - fetchBalance().finally(function () { - loading = false; - }); - } - - function closeDialog() { - mask.classList.remove("is-open"); - document.body.style.overflow = ""; - } - - document.addEventListener("click", function (event) { - if (event.target.closest("[data-coin-dialog-open]")) { - openDialog(event); - return; - } - - if (event.target.closest("[data-coin-dialog-close]")) { - closeDialog(); - return; - } - - if (event.target.closest("[data-coin-dialog-pay]")) { - event.preventDefault(); - unlockAndPlay(); - return; - } - - if (event.target === mask) { - closeDialog(); - } - }); - - document.addEventListener("keydown", function (event) { - if (event.key === "Escape" && mask.classList.contains("is-open")) { - closeDialog(); - } - }); - - window.openCoinDialog = openDialog; - window.closeCoinDialog = closeDialog; -})(); diff --git a/app/static/js/pages/deposit.js b/app/static/js/pages/deposit.js deleted file mode 100644 index ec735f1..0000000 --- a/app/static/js/pages/deposit.js +++ /dev/null @@ -1,481 +0,0 @@ -/** - * 充值页:套餐 / 支付方式互斥选择,按套餐 payMethods 刷新;提交走 VIP/金币下单。 - * VIP: POST /api/vip/pay 金币: POST /api/coin/buy - * 支付方式严格使用接口返回的 payMethods,不造假默认渠道。 - */ -(function () { - var submitting = false; - var confirmMask = document.querySelector("[data-deposit-confirm]"); - var confirmMethods = document.querySelector("[data-deposit-confirm-methods]"); - var confirmSubmit = document.querySelector("[data-deposit-confirm-submit]"); - var bodyOverflowBeforeConfirm = ""; - - function toast(message, type) { - if (window.AuthAPI && typeof AuthAPI.toast === "function") { - AuthAPI.toast(message, type || "info"); - return; - } - if (typeof showToast === "function") showToast(message, type || "info"); - } - - function bindExclusive(root, selector, activeClass) { - var scope = root || document; - var nodes = scope.querySelectorAll(selector); - if (!nodes.length) return; - nodes.forEach(function (node) { - node.addEventListener("click", function () { - nodes.forEach(function (item) { - item.classList.remove(activeClass); - }); - node.classList.add(activeClass); - syncPayTypeAcross(node.getAttribute("data-pay-type") || ""); - }); - }); - } - - function syncPayTypeAcross(payType) { - if (!payType) return; - document.querySelectorAll("[data-pay-type]").forEach(function (el) { - var match = String(el.getAttribute("data-pay-type") || "") === String(payType); - if (el.classList.contains("deposit-pay-btn")) { - el.classList.toggle("deposit-pay-btn--active", match); - } - if (el.classList.contains("deposit-mobile-pay")) { - el.classList.toggle("deposit-mobile-pay--active", match); - } - if (el.classList.contains("deposit-confirm-method")) { - el.classList.toggle("is-active", match); - } - }); - } - - function parsePayMethods(raw) { - if (!raw) return []; - try { - var list = JSON.parse(raw); - if (!Array.isArray(list)) return []; - return list - .map(function (item) { - return { - type: String((item && item.type) || "").trim(), - typeName: String((item && (item.typeName || item.type)) || "").trim(), - }; - }) - .filter(function (item) { - return item.type || item.typeName; - }); - } catch (e) { - return []; - } - } - - function currentPayType() { - var active = - document.querySelector(".deposit-confirm-method.is-active[data-pay-type]") || - document.querySelector(".deposit-pay-btn--active[data-pay-type]") || - document.querySelector(".deposit-mobile-pay--active[data-pay-type]"); - return active ? String(active.getAttribute("data-pay-type") || "") : ""; - } - - function currentPackage() { - return ( - document.querySelector(".deposit-mobile-pkg--active[data-package-id]") || - document.querySelector(".deposit-package--active[data-package-id]") - ); - } - - function renderPayButtons(container, className, activeClass, methods, preferType) { - if (!container) return; - var list = Array.isArray(methods) ? methods : []; - if (!list.length) { - container.innerHTML = '
暂无可用支付方式
'; - return; - } - var prefer = preferType || ""; - var activeType = prefer; - if (!list.some(function (item) { return item.type === prefer; })) { - activeType = list[0] ? list[0].type : ""; - } - container.innerHTML = list - .map(function (item) { - var isActive = item.type === activeType; - return ( - '" - ); - }) - .join(""); - bindExclusive(container, "." + className.split(" ").join("."), activeClass); - } - - function escapeHtml(text) { - return String(text || "") - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """); - } - - function escapeAttr(text) { - return escapeHtml(text).replace(/'/g, "'"); - } - - function payMethodVisual(item) { - var value = String((item && (item.typeName || item.type)) || "").toLowerCase(); - if (/支付宝|alipay/.test(value)) { - return { kind: "alipay", icon: "/static/images/figma-dialogs/alipay.svg" }; - } - if (/微信|wechat|weixin|wxpay/.test(value)) { - return { kind: "wechat", icon: "/static/images/figma-dialogs/wechat.svg" }; - } - if (/usdt|tether/.test(value)) { - return { kind: "usdt", icon: "/static/images/figma-dialogs/usdt.svg" }; - } - return { kind: "other", icon: "" }; - } - - function renderConfirmMethods(methods, preferType) { - if (!confirmMethods) return ""; - var list = Array.isArray(methods) ? methods : []; - var activeType = preferType || (list[0] && list[0].type) || ""; - if (!list.some(function (item) { return item.type === activeType; })) { - activeType = (list[0] && list[0].type) || ""; - } - confirmMethods.innerHTML = list.map(function (item) { - var visual = payMethodVisual(item); - var icon = visual.icon - ? '' - : ''; - return ''; - }).join(""); - confirmMethods.querySelectorAll(".deposit-confirm-method").forEach(function (button) { - button.addEventListener("click", function () { - syncPayTypeAcross(button.getAttribute("data-pay-type") || ""); - }); - }); - syncPayTypeAcross(activeType); - return activeType; - } - - function hasPayMethods(el) { - var target = el || currentPackage(); - if (!target) return false; - return parsePayMethods(target.getAttribute("data-pay-methods")).length > 0; - } - - function syncPayMethods(el) { - var methods = parsePayMethods(el.getAttribute("data-pay-methods")); - var prefer = currentPayType(); - renderPayButtons( - document.querySelector("[data-deposit-pay-pc]"), - "deposit-pay-btn", - "deposit-pay-btn--active", - methods, - prefer - ); - renderPayButtons( - document.querySelector("[data-deposit-pay-mobile]"), - "deposit-mobile-pay", - "deposit-mobile-pay--active", - methods, - prefer - ); - } - - function syncPackageActive(kind, packageId) { - document.querySelectorAll(".deposit-package, .deposit-mobile-pkg").forEach(function (el) { - var match = - String(el.getAttribute("data-package-id") || "") === String(packageId) && - String(el.getAttribute("data-package-kind") || "") === String(kind); - el.classList.toggle("deposit-package--active", match && el.classList.contains("deposit-package")); - el.classList.toggle( - "deposit-mobile-pkg--active", - match && el.classList.contains("deposit-mobile-pkg") - ); - }); - } - - function updateSubmitButtons(price, loading) { - var canPay = hasPayMethods(currentPackage()); - var disabled = !!loading || !canPay; - var pkg = currentPackage(); - var packageName = pkg ? String(pkg.getAttribute("data-package-name") || "").trim() : ""; - var text; - if (loading) { - text = "支付中..."; - } else if (!canPay) { - text = "暂无支付方式"; - } else { - text = "立即支付 ¥" + (price || "0"); - } - document.querySelectorAll("[data-deposit-submit]").forEach(function (btn) { - btn.disabled = disabled; - btn.classList.toggle("is-loading", !!loading); - btn.classList.toggle("is-disabled", !canPay && !loading); - if (btn.classList.contains("deposit-pc-submit")) { - btn.textContent = loading ? "支付中..." : canPay ? "立即支付 ¥" + (price || "0") : "暂无支付方式"; - } else { - btn.textContent = loading ? "支付中..." : canPay ? "支付 ¥" + (price || "0") + " 充值金币" : text; - } - }); - document.querySelectorAll("[data-deposit-selected-note]").forEach(function (node) { - node.textContent = packageName - ? "已选择 " + packageName + " · 点击后选择支付方式" - : "请选择金币套餐"; - }); - } - - function onPackageClick(el) { - var kind = el.getAttribute("data-package-kind") || ""; - var id = el.getAttribute("data-package-id") || ""; - var price = el.getAttribute("data-price") || "0"; - syncPackageActive(kind, id); - syncPayMethods(el); - updateSubmitButtons(price, false); - } - - function openPayUrl(data) { - var url = data && data.payUrl ? String(data.payUrl).trim() : ""; - if (!url) { - toast("未获取到支付链接", "error"); - return; - } - var mode = String((data && data.payUrlMode) || "").toLowerCase(); - var openNew = - !!(data && data.isOpenNewBrowser) || - mode === "new" || - mode === "blank" || - mode === "external"; - if (openNew) { - var win = window.open(url, "_blank"); - if (!win) { - window.location.href = url; - } - return; - } - window.location.href = url; - } - - function ensureLoggedIn() { - if (window.CookieUtil && CookieUtil.isLoggedIn()) return true; - toast("请先登录后再支付", "error"); - var loginURL = "/login/return/deposit/"; - if (typeof window.openLoginDialog === "function") { - var opened = window.openLoginDialog(); - if (opened && typeof opened.then === "function") { - opened.catch(function () { - window.location.href = loginURL; - }); - } - } else { - window.location.href = loginURL; - } - return false; - } - - function closePayConfirm() { - if (!confirmMask) return; - confirmMask.classList.remove("is-open"); - confirmMask.setAttribute("aria-hidden", "true"); - document.body.style.overflow = bodyOverflowBeforeConfirm; - } - - function openPayConfirm() { - if (submitting || !confirmMask) return; - if (!ensureLoggedIn()) return; - var pkg = currentPackage(); - if (!pkg) { - toast("请选择套餐", "error"); - return; - } - var methods = parsePayMethods(pkg.getAttribute("data-pay-methods")); - if (!methods.length) { - toast("当前套餐暂无可用支付方式", "error"); - return; - } - var packageName = String(pkg.getAttribute("data-package-name") || "金币套餐").trim(); - var price = String(pkg.getAttribute("data-price") || "0").trim(); - var nameEl = confirmMask.querySelector("[data-deposit-confirm-name]"); - var priceEl = confirmMask.querySelector("[data-deposit-confirm-price]"); - if (nameEl) nameEl.textContent = packageName; - if (priceEl) priceEl.textContent = price; - renderConfirmMethods(methods, currentPayType()); - bodyOverflowBeforeConfirm = document.body.style.overflow; - document.body.style.overflow = "hidden"; - confirmMask.classList.add("is-open"); - confirmMask.setAttribute("aria-hidden", "false"); - } - - function submitPay() { - if (submitting) return; - if (!window.HttpRequest) { - toast("请求组件未加载", "error"); - return; - } - if (!ensureLoggedIn()) return; - - var pkg = currentPackage(); - if (!pkg) { - toast("请选择套餐", "error"); - return; - } - var packageId = Number(pkg.getAttribute("data-package-id") || 0); - var kind = String(pkg.getAttribute("data-package-kind") || ""); - var price = pkg.getAttribute("data-price") || "0"; - var payMode = currentPayType(); - if (!packageId || (kind !== "vip" && kind !== "coin")) { - toast("套餐无效,请重新选择", "error"); - return; - } - if (!payMode) { - toast("请选择支付方式", "error"); - return; - } - if (!hasPayMethods(pkg)) { - toast("当前套餐暂无可用支付方式", "error"); - return; - } - - var api = kind === "coin" ? "/api/coin/buy" : "/api/vip/pay"; - submitting = true; - updateSubmitButtons(price, true); - if (confirmSubmit) { - confirmSubmit.disabled = true; - confirmSubmit.textContent = "支付中..."; - } - - HttpRequest.post(api, { packageId: packageId, payMode: payMode }) - .then(function (res) { - if (!res || res.code !== 200 || !res.data) { - var err = new Error((res && (res.tip || res.msg)) || "发起支付失败"); - err.code = res && res.code; - throw err; - } - openPayUrl(res.data); - }) - .catch(function (err) { - if (err && err.code === 401) { - toast("请先登录后再支付", "error"); - if (typeof window.openLoginDialog === "function") { - var opened = window.openLoginDialog(); - if (opened && typeof opened.then === "function") { - opened.catch(function () { - window.location.href = "/login/return/deposit/"; - }); - } - } - return; - } - toast((err && err.message) || "发起支付失败", "error"); - }) - .finally(function () { - submitting = false; - updateSubmitButtons(price, false); - if (confirmSubmit) { - confirmSubmit.disabled = false; - confirmSubmit.textContent = "去支付"; - } - }); - } - - bindExclusive(document, ".deposit-pay-btn", "deposit-pay-btn--active"); - bindExclusive(document, ".deposit-mobile-pay", "deposit-mobile-pay--active"); - - document.querySelectorAll(".deposit-package[data-package-id]").forEach(function (node) { - node.addEventListener("click", function () { - onPackageClick(node); - }); - }); - - document.querySelectorAll(".deposit-mobile-pkg[data-package-id]").forEach(function (node) { - node.addEventListener("click", function () { - onPackageClick(node); - }); - }); - - document.querySelectorAll("[data-deposit-submit]").forEach(function (btn) { - btn.addEventListener("click", openPayConfirm); - }); - - if (confirmSubmit) confirmSubmit.addEventListener("click", submitPay); - document.querySelectorAll("[data-deposit-confirm-close]").forEach(function (button) { - button.addEventListener("click", closePayConfirm); - }); - if (confirmMask) { - confirmMask.addEventListener("click", function (event) { - if (event.target === confirmMask) closePayConfirm(); - }); - } - document.addEventListener("keydown", function (event) { - if (event.key === "Escape" && confirmMask && confirmMask.classList.contains("is-open")) { - closePayConfirm(); - } - }); - - - var active = - document.querySelector(".deposit-mobile-pkg--active[data-price]") || - document.querySelector(".deposit-package--active[data-price]"); - if (active) { - syncPayMethods(active); - updateSubmitButtons(active.getAttribute("data-price") || "0", false); - } else { - updateSubmitButtons("0", false); - } - - // /deposit/coin 时滚动到金币充值区域(套餐默认选中由 SSR 完成) - (function scrollPreferCoin() { - if (!/\/deposit\/coin\/?$/i.test(window.location.pathname || "")) return; - var isMobile = window.matchMedia("(max-width: 767px)").matches; - var target = isMobile - ? document.getElementById("depositCoinMobileTitle") - : document.getElementById("depositCoinGroup"); - if (!target) return; - setTimeout(function () { - target.scrollIntoView({ behavior: "smooth", block: "start" }); - }, 80); - })(); - - function formatRecharge(value) { - var n = Number(value); - if (!isFinite(n) || n < 0) n = 0; - return String(Math.floor(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ","); - } - - function syncDepositRecharge(value) { - var text = formatRecharge(value); - document.querySelectorAll("[data-deposit-recharge]").forEach(function (el) { - el.textContent = text; - }); - } - - function refreshDepositWallet() { - if (!window.CookieUtil || !CookieUtil.isLoggedIn() || !window.HttpRequest) { - return; - } - var session = CookieUtil.getAuthSession(); - if (session && session.recharge != null && session.recharge !== "") { - syncDepositRecharge(session.recharge); - } - HttpRequest.get("/api/auth/getInfo") - .then(function (res) { - if (!res || res.code !== 200 || !res.data) return; - if (CookieUtil.setAuthSession) CookieUtil.setAuthSession(res.data); - syncDepositRecharge(res.data.recharge); - }) - .catch(function () {}); - } - - refreshDepositWallet(); -})(); diff --git a/app/static/js/pages/detail.js b/app/static/js/pages/detail.js index fd03eab..9965ab2 100644 --- a/app/static/js/pages/detail.js +++ b/app/static/js/pages/detail.js @@ -120,239 +120,18 @@ })(); (function () { - // detail.js 同步执行早于 defer 的 request.js,需等 HttpRequest 就绪后再绑定 - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - window.alert(message); - } + // 付费剧集:不再站内解锁,统一引导下载 APP + var lockedEpisodes = document.querySelectorAll("[data-episode-locked]"); + if (!lockedEpisodes.length) return; - function bindLikeActions() { - var likeBtns = document.querySelectorAll("[data-detail-like]"); - if (!likeBtns.length) return; - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - if (!bindLikeActions._retried) { - bindLikeActions._retried = true; - window.addEventListener("load", bindLikeActions, { once: true }); + lockedEpisodes.forEach(function (el) { + el.addEventListener("click", function (e) { + e.preventDefault(); + if (typeof window.openDownloadApp === "function") { + window.openDownloadApp(); } - return; - } - if (bindLikeActions._bound) return; - bindLikeActions._bound = true; - - function applyLikeUI(isLike, countLike) { - var liked = Number(isLike) === 1; - likeBtns.forEach(function (btn) { - btn.classList.toggle("detail-action--active", liked); - btn.classList.toggle("detail-h5-top-btn--active", liked); - btn.classList.toggle("detail-h5-like--active", liked); - btn.classList.toggle("play-h5-side-item--active", liked); - btn.setAttribute("data-liked", liked ? "1" : "2"); - btn.setAttribute("aria-pressed", liked ? "true" : "false"); - if (countLike != null) { - btn.setAttribute("data-count-like", String(countLike)); - } - var label = btn.querySelector("[data-detail-like-label]"); - if (label) { - if (btn.getAttribute("data-like-label-mode") === "state") { - label.textContent = liked ? "已点赞" : "点赞"; - } else { - var n = countLike != null ? countLike : btn.getAttribute("data-count-like"); - label.textContent = String(n == null || n === "" ? 0 : n); - } - } - var h5Icon = btn.querySelector("[data-h5-like-icon]"); - if (h5Icon) { - h5Icon.src = liked - ? "/static/images/h5_liked.svg" - : "/static/images/like.svg"; - } - }); - } - - likeBtns.forEach(function (btn) { - btn.addEventListener("click", function () { - if (btn.getAttribute("data-liking") === "1") return; - var playletId = Number(btn.getAttribute("data-playlet-id") || 0); - if (!playletId) return; - - var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1; - var countNow = Number(btn.getAttribute("data-count-like") || 0); - if (Number.isNaN(countNow)) countNow = 0; - var nextLike = !likedNow; - var nextCount = Math.max(0, countNow + (nextLike ? 1 : -1)); - - likeBtns.forEach(function (item) { - item.setAttribute("data-liking", "1"); - item.disabled = true; - }); - applyLikeUI(nextLike ? 1 : 2, nextCount); - - window.HttpRequest.post("/api/playlet/like/set", { - id: playletId, - isLike: nextLike, - }) - .then(function (res) { - if (!res || res.code !== 200) { - if (res && res.code === 401) { - var loginOpen = document.querySelector( - "[data-login-dialog-open]" - ); - if (loginOpen) loginOpen.click(); - } - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyLikeUI( - data.isLike != null ? data.isLike : nextLike ? 1 : 2, - data.countLike != null ? data.countLike : nextCount - ); - }) - .catch(function (err) { - applyLikeUI(likedNow ? 1 : 2, countNow); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - likeBtns.forEach(function (item) { - item.removeAttribute("data-liking"); - item.disabled = false; - }); - }); - }); }); - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", bindLikeActions, { once: true }); - } else { - bindLikeActions(); - } -})(); - -(function () { - function toast(message, type) { - if (typeof window.showToast === "function") { - window.showToast(message, type); - return; - } - window.alert(message); - } - - function bindCollectActions() { - var collectBtns = document.querySelectorAll("[data-detail-collect]"); - if (!collectBtns.length) return; - if (!window.HttpRequest || typeof window.HttpRequest.post !== "function") { - if (!bindCollectActions._retried) { - bindCollectActions._retried = true; - window.addEventListener("load", bindCollectActions, { once: true }); - } - return; - } - if (bindCollectActions._bound) return; - bindCollectActions._bound = true; - - function applyCollectUI(isCollect, countCollect) { - var collected = Number(isCollect) === 1; - collectBtns.forEach(function (btn) { - btn.classList.toggle("detail-action--active", collected); - btn.classList.toggle("detail-h5-collect--active", collected); - btn.classList.toggle("detail-h5-top-btn--active", collected); - btn.classList.toggle("play-h5-side-item--active", collected); - btn.classList.toggle("is-collected", collected); - btn.setAttribute("data-collected", collected ? "1" : "2"); - btn.setAttribute("aria-pressed", collected ? "true" : "false"); - if (countCollect != null) { - btn.setAttribute("data-count-collect", String(countCollect)); - } - var label = btn.querySelector("[data-detail-collect-label]"); - if (label) { - if (btn.classList.contains("detail-h5-collect")) { - var n = countCollect != null - ? countCollect - : btn.getAttribute("data-count-collect"); - label.textContent = String(n == null || n === "" ? 0 : n); - } else { - label.textContent = collected ? "已收藏" : "收藏"; - } - } - var h5Icon = btn.querySelector("[data-h5-collect-icon]"); - if (h5Icon) { - if (btn.classList.contains("detail-h5-top-btn")) { - h5Icon.src = collected - ? "/static/images/h5_collected.svg?v=2" - : "/static/images/collect.svg"; - } else if (btn.classList.contains("play-h5-side-item")) { - h5Icon.src = collected - ? "/static/images/h5_collected.svg?v=2" - : "/static/images/collect.svg"; - } else { - h5Icon.src = collected - ? "/static/images/h5_detail_collected.svg" - : "/static/images/h5_detail_collect.svg"; - } - } - }); - } - - collectBtns.forEach(function (btn) { - btn.addEventListener("click", function () { - if (btn.getAttribute("data-collecting") === "1") return; - var playletId = Number(btn.getAttribute("data-playlet-id") || 0); - if (!playletId) return; - - var collectedNow = Number(btn.getAttribute("data-collected") || 2) === 1; - var countNow = Number(btn.getAttribute("data-count-collect") || 0); - if (Number.isNaN(countNow)) countNow = 0; - var nextCollect = !collectedNow; - var nextCount = Math.max(0, countNow + (nextCollect ? 1 : -1)); - - collectBtns.forEach(function (item) { - item.setAttribute("data-collecting", "1"); - item.disabled = true; - }); - applyCollectUI(nextCollect ? 1 : 2, nextCount); - - window.HttpRequest.post("/api/playlet/collect/set", { - id: playletId, - isCollect: nextCollect, - }) - .then(function (res) { - if (!res || res.code !== 200) { - if (res && res.code === 401) { - var loginOpen = document.querySelector( - "[data-login-dialog-open]" - ); - if (loginOpen) loginOpen.click(); - } - throw new Error((res && (res.tip || res.msg)) || "操作失败"); - } - var data = res.data || {}; - applyCollectUI( - data.isCollect != null ? data.isCollect : nextCollect ? 1 : 2, - data.countCollect != null ? data.countCollect : nextCount - ); - }) - .catch(function (err) { - applyCollectUI(collectedNow ? 1 : 2, countNow); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - collectBtns.forEach(function (item) { - item.removeAttribute("data-collecting"); - item.disabled = false; - }); - }); - }); - }); - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", bindCollectActions, { once: true }); - } else { - bindCollectActions(); - } + }); })(); (function () { @@ -593,15 +372,6 @@ return formatCommentTime(raw); } - function formatCompactCount(n) { - n = Number(n); - if (!n || n < 0 || Number.isNaN(n)) return "0"; - if (n < 10000) return String(n); - var v = n / 10000; - if (v >= 10) return Math.floor(v) + "w"; - return (Math.round(v * 10) / 10) + "w"; - } - function avatarURL(url) { url = String(url || "").trim(); return url || DEFAULT_AVATAR; @@ -614,10 +384,7 @@ } function renderDetailComment(item, playPcMode) { - var liked = Number(item.isLike) === 1; var avatar = avatarURL(item.userAvatar); - var commentId = item.id; - var parentsId = Number(item.parentsId || 0) || Number(commentId); var avatarHtml = item.userAvatar ? '' : '匿名用户头像'; - var likeIcon = liked - ? "/static/images/like_solid.svg" - : "/static/images/like_hollow.svg"; var textHtml = escapeHtml(item.text || ""); if (item.replyUserName) { textHtml = @@ -691,11 +455,7 @@ } return ( '
' + avatarHtml + '
' + @@ -709,27 +469,6 @@ '' + escapeHtml(formatCommentTime(item.createdAt)) + "" + - '" + - '' + "
" + repliesHtml + "
" @@ -737,10 +476,8 @@ } function renderPlayComment(item) { - var liked = Number(item.isLike) === 1; var avatar = avatarURL(item.userAvatar); var commentId = item.id; - var parentsId = Number(item.parentsId || 0) || Number(commentId); var children = Array.isArray(item.children) ? item.children : []; var replyCount = Number(item.commentCount || children.length || 0); var avatarHtml = item.userAvatar @@ -752,9 +489,6 @@ escapeHtml((item.userName || "匿名用户") + "的头像") + '" />' : '匿名用户头像'; - var likeIcon = liked - ? "/static/images/h5_liked.svg" - : "/static/images/like_hollow.svg"; var textHtml = escapeHtml(item.text || ""); if (item.replyUserName) { textHtml = @@ -807,10 +541,6 @@ return ( '
' + avatarHtml + '
' + @@ -825,30 +555,7 @@ '' + escapeHtml(formatRelativeTime(item.createdAt)) + "" + - '' + "
" + - '" + "
" + repliesHtml + "" @@ -887,18 +594,12 @@ var scope = sheetRoot || section; var listEl = section.querySelector("[data-comment-list]"); var moreBtn = section.querySelector("[data-comment-more]"); - var composerInput = - scope.querySelector(".play-comment-composer-input") || - scope.querySelector(".detail-comment-composer-input"); - var composerCount = scope.querySelector(".detail-comment-composer-count"); var titleEl = section.querySelector("[data-comment-title]"); - var sendBtn = scope.querySelector("[data-comment-send]"); var sortBtns = scope.querySelectorAll("[data-comment-sort]"); var sheetTitle = sheetRoot ? sheetRoot.querySelector("[data-play-comment-title]") : document.querySelector("[data-play-comment-title]"); var sideCount = document.querySelector("[data-play-comment-count]"); - var iconSend = !!(sendBtn && sendBtn.classList.contains("play-comment-composer-emoji")); if (section.getAttribute("data-comment-inited") === "1") return; section.setAttribute("data-comment-inited", "1"); @@ -910,169 +611,11 @@ var page = 0; var total = 0; var loading = false; - var sending = false; - var replyTarget = null; // { parentsId, replyCommentId, replyName } - var inlineSending = false; function getObjectId() { return Number(section.getAttribute("data-comment-object-id") || 0); } - function clearReplyTarget() { - replyTarget = null; - if (!scope) return; - var boxes = scope.querySelectorAll("[data-comment-inline-composer]"); - Array.prototype.forEach.call(boxes, function (el) { - el.remove(); - }); - } - - function setReplyTarget(parentsId, replyCommentId, replyName, anchorEl) { - parentsId = Number(parentsId || 0); - replyCommentId = Number(replyCommentId || 0); - if (!parentsId || !replyCommentId || !anchorEl) return; - - var root = - anchorEl.closest(commentItemSelector()) || - anchorEl.closest("[data-comment-id]"); - if (!root || !scope.contains(root)) return; - - // 再次点击同一条:关闭内联输入框 - var existingOnRoot = root.querySelector("[data-comment-inline-composer]"); - if ( - existingOnRoot && - replyTarget && - replyTarget.replyCommentId === replyCommentId - ) { - clearReplyTarget(); - return; - } - - clearReplyTarget(); - replyTarget = { - parentsId: parentsId, - replyCommentId: replyCommentId, - replyName: replyName || "用户", - }; - - var meta = - root.querySelector(".detail-comment-meta") || - root.querySelector(".play-comment-meta"); - var body = - root.querySelector(".detail-comment-body") || - root.querySelector(".play-comment-body") || - root; - var box = document.createElement("div"); - box.className = sheetMode - ? "play-comment-inline-composer" - : "detail-comment-inline-composer"; - box.setAttribute("data-comment-inline-composer", ""); - var placeholder = "回复 @" + replyTarget.replyName + "…"; - if (sheetMode) { - box.innerHTML = - '
' + - "回复 @" + - escapeHtml(replyTarget.replyName) + - "" + - '' + - "
" + - '
' + - '' + - '' + - "
"; - } else { - box.innerHTML = - '
' + - "回复 @" + - escapeHtml(replyTarget.replyName) + - "" + - '' + - "
" + - '' + - '"; - } - - if (meta && meta.parentNode === body) { - if (meta.nextSibling) { - body.insertBefore(box, meta.nextSibling); - } else { - body.appendChild(box); - } - } else { - body.appendChild(box); - } - - var inlineInput = box.querySelector("[data-comment-inline-input]"); - if (inlineInput) { - inlineInput.focus(); - } - } - - function sendInlineReply(box) { - if (!box || inlineSending || !replyTarget) return; - var objectId = getObjectId(); - if (!objectId) return; - var input = box.querySelector("[data-comment-inline-input]"); - var btn = box.querySelector("[data-comment-inline-send]"); - if (!input) return; - var text = String(input.value || "").trim(); - if (!text) { - toast("请输入评论内容", "error"); - return; - } - if (Array.from(text).length > 200) { - toast("评论最多 200 字", "error"); - return; - } - inlineSending = true; - if (btn) { - btn.disabled = true; - btn.classList.add("is-loading"); - } - input.disabled = true; - var payload = { - objectId: objectId, - objectType: objectType, - text: text, - parentsId: replyTarget.parentsId, - replyCommentId: - replyTarget.replyCommentId > 0 - ? replyTarget.replyCommentId - : replyTarget.parentsId, - }; - window.HttpRequest.post("/api/comment/send", payload) - .then(function (res) { - if (!res || res.code !== 200) { - if (res && res.code === 401) { - var loginOpen = document.querySelector( - "[data-login-dialog-open]" - ); - if (loginOpen) loginOpen.click(); - } - throw new Error((res && (res.tip || res.msg)) || "发表失败"); - } - clearReplyTarget(); - toast("发布成功,评论审核后才会展示", "success"); - }) - .catch(function (err) { - toast((err && err.message) || "发表失败,请稍后重试", "error"); - }) - .finally(function () { - inlineSending = false; - if (btn) { - btn.disabled = false; - btn.classList.remove("is-loading"); - } - if (input) input.disabled = false; - }); - } - function commentItemSelector() { return sheetMode ? ".play-comment-item" : ".detail-comment"; } @@ -1089,32 +632,6 @@ } } - function applyLikeUI(btn, isLike, countLike) { - var liked = Number(isLike) === 1; - btn.classList.toggle("detail-comment-like--active", liked); - btn.classList.toggle("play-comment-like--active", liked); - btn.setAttribute("data-liked", liked ? "1" : "2"); - var img = btn.querySelector("img"); - if (img) { - if (sheetMode) { - img.src = liked - ? "/static/images/h5_liked.svg" - : "/static/images/like_hollow.svg"; - } else { - img.src = liked - ? "/static/images/like_solid.svg" - : "/static/images/like_hollow.svg"; - } - } - var countEl = btn.querySelector("[data-like-count]"); - if (countEl && countLike != null) { - btn.setAttribute("data-like-raw", String(countLike)); - countEl.textContent = sheetMode - ? formatCompactCount(countLike) - : String(countLike); - } - } - function syncSortActive() { sortBtns.forEach(function (item) { var active = Number(item.getAttribute("data-comment-sort")) === sortOrder; @@ -1140,7 +657,6 @@ var emptyClass = sheetMode ? "section-empty play-comment-empty" : "section-empty"; - clearReplyTarget(); listEl.innerHTML = '
200) { - toast("评论最多 200 字", "error"); - return; - } - sending = true; - sendBtn.disabled = true; - sendBtn.classList.add("is-loading"); - if (!iconSend) sendBtn.textContent = "发表中"; - composerInput.disabled = true; - var payload = { - objectId: objectId, - objectType: objectType, - text: text, - parentsId: 0, - replyCommentId: 0, - }; - window.HttpRequest.post("/api/comment/send", payload) - .then(function (res) { - if (!res || res.code !== 200) { - if (res && res.code === 401) { - var loginOpen = document.querySelector( - "[data-login-dialog-open]" - ); - if (loginOpen) loginOpen.click(); - } - throw new Error((res && (res.tip || res.msg)) || "发表失败"); - } - composerInput.value = ""; - if (composerCount) composerCount.textContent = "0 / 200"; - toast("发布成功,评论审核后才会展示", "success"); - // 与 PC 一致:发表后不强制刷新列表(审核后才可见) - }) - .catch(function (err) { - toast((err && err.message) || "发表失败,请稍后重试", "error"); - }) - .finally(function () { - sending = false; - sendBtn.disabled = false; - sendBtn.classList.remove("is-loading"); - if (!iconSend) sendBtn.textContent = sendBtnDefaultText; - composerInput.disabled = false; - }); - }); - - if (sheetMode) { - composerInput.addEventListener("keydown", function (e) { - if (e.key === "Enter") { - e.preventDefault(); - sendBtn.click(); - } - }); - } - } - if (scope) { scope.addEventListener("click", function (e) { - var cancel = - e.target && e.target.closest - ? e.target.closest("[data-comment-reply-cancel]") - : null; - if (cancel) { - e.preventDefault(); - clearReplyTarget(); - return; - } - var inlineSend = - e.target && e.target.closest - ? e.target.closest("[data-comment-inline-send]") - : null; - if (inlineSend && scope.contains(inlineSend)) { - e.preventDefault(); - var box = inlineSend.closest("[data-comment-inline-composer]"); - sendInlineReply(box); - return; - } var repliesToggle = e.target && e.target.closest ? e.target.closest("[data-comment-replies-toggle]") : null; - if (repliesToggle && scope.contains(repliesToggle)) { - e.preventDefault(); - var repliesBox = repliesToggle.closest(".detail-comment-replies"); - if (!repliesBox) return; - var expanded = repliesToggle.getAttribute("aria-expanded") === "true"; - expanded = !expanded; - repliesToggle.setAttribute("aria-expanded", expanded ? "true" : "false"); - repliesToggle.textContent = replyToggleLabel( - expanded, - repliesToggle.getAttribute("data-reply-count") - ); - var hiddenItems = repliesBox.querySelectorAll("[data-reply-collapsed-item]"); - Array.prototype.forEach.call(hiddenItems, function (el) { - el.hidden = !expanded; - }); - return; - } - var replyBtn = - e.target && e.target.closest - ? e.target.closest("[data-comment-reply]") - : null; - if (!replyBtn || !scope.contains(replyBtn)) return; + if (!repliesToggle || !scope.contains(repliesToggle)) return; e.preventDefault(); - var replyCommentId = Number(replyBtn.getAttribute("data-comment-reply") || 0); - var parentsId = Number(replyBtn.getAttribute("data-parents-id") || 0); - var replyName = replyBtn.getAttribute("data-reply-name") || "用户"; - if (!parentsId) { - var root = - replyBtn.closest("[data-parents-id]") || - replyBtn.closest("[data-comment-id]"); - if (root) { - parentsId = Number( - root.getAttribute("data-parents-id") || - root.getAttribute("data-comment-id") || - 0 - ); - } - } - if (!replyCommentId) return; - if (!parentsId) parentsId = replyCommentId; - setReplyTarget(parentsId, replyCommentId, replyName, replyBtn); - }); - - scope.addEventListener("keydown", function (e) { - if (e.key !== "Enter") return; - var input = - e.target && e.target.closest - ? e.target.closest("[data-comment-inline-input]") - : null; - if (!input || !scope.contains(input)) return; - if (input.tagName === "TEXTAREA" && !e.ctrlKey && !e.metaKey) return; - e.preventDefault(); - var box = input.closest("[data-comment-inline-composer]"); - sendInlineReply(box); + var repliesBox = repliesToggle.closest(".detail-comment-replies"); + if (!repliesBox) return; + var expanded = repliesToggle.getAttribute("aria-expanded") === "true"; + expanded = !expanded; + repliesToggle.setAttribute("aria-expanded", expanded ? "true" : "false"); + repliesToggle.textContent = replyToggleLabel( + expanded, + repliesToggle.getAttribute("data-reply-count") + ); + var hiddenItems = repliesBox.querySelectorAll("[data-reply-collapsed-item]"); + Array.prototype.forEach.call(hiddenItems, function (el) { + el.hidden = !expanded; + }); }); } @@ -1403,70 +787,6 @@ }); } - if (listEl) { - listEl.addEventListener("click", function (e) { - var btn = - e.target && e.target.closest - ? e.target.closest("[data-comment-like]") - : null; - if (!btn || !listEl.contains(btn)) return; - var commentId = Number(btn.getAttribute("data-comment-like") || 0); - if (!commentId) return; - if (btn.getAttribute("data-liking") === "1") return; - - var likedNow = Number(btn.getAttribute("data-liked") || 2) === 1; - var nextLike = !likedNow; - var rawAttr = btn.getAttribute("data-like-raw"); - var countEl = btn.querySelector("[data-like-count]"); - var prevCount = Number( - rawAttr != null - ? rawAttr - : countEl && countEl.textContent - ? countEl.textContent - : 0 - ); - if (Number.isNaN(prevCount)) prevCount = 0; - - btn.setAttribute("data-liking", "1"); - btn.disabled = true; - applyLikeUI( - btn, - nextLike ? 1 : 2, - Math.max(0, prevCount + (nextLike ? 1 : -1)) - ); - - window.HttpRequest.post("/api/comment/like/set", { - commentId: commentId, - isLike: nextLike, - }) - .then(function (res) { - if (!res || res.code !== 200) { - if (res && res.code === 401) { - var loginOpen = document.querySelector( - "[data-login-dialog-open]" - ); - if (loginOpen) loginOpen.click(); - } - 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 : null - ); - }) - .catch(function (err) { - applyLikeUI(btn, likedNow ? 1 : 2, prevCount); - toast((err && err.message) || "操作失败,请稍后重试", "error"); - }) - .finally(function () { - btn.removeAttribute("data-liking"); - btn.disabled = false; - }); - }); - } - if (!listEl || !getObjectId()) { if (listEl) showEmpty("暂无评论"); return; diff --git a/app/static/js/pages/dialogLoader.js b/app/static/js/pages/dialogLoader.js index 8cbd0df..1c4c64d 100644 --- a/app/static/js/pages/dialogLoader.js +++ b/app/static/js/pages/dialogLoader.js @@ -7,20 +7,6 @@ js: "/static/js/pages/downloadAppDialog.js?v=4", open: "openDownloadAppDialog", }, - login: { - endpoint: "/ui/dialog/login", - elementId: "loginDialog", - css: "/static/css/loginDialog.css?v=2", - js: "/static/js/pages/loginDialog.js?v=3", - open: "openLoginDialog", - }, - register: { - endpoint: "/ui/dialog/register", - elementId: "registerDialog", - css: "/static/css/registerDialog.css?v=2", - js: "/static/js/pages/registerDialog.js?v=2", - open: "openRegisterDialog", - }, }; var pending = Object.create(null); var stubOpeners = Object.create(null); @@ -141,26 +127,6 @@ return pending[name]; } - function loginFallbackURL() { - var redirect = ""; - try { - redirect = global.location.pathname || "/"; - } catch (e) { - redirect = "/"; - } - var url = "/login/"; - if ( - typeof redirect === "string" && - redirect.charAt(0) === "/" && - redirect.charAt(1) !== "/" && - redirect !== "/" && - redirect.indexOf("/?auth=") !== 0 - ) { - url = "/login/return" + redirect; - } - return url; - } - function openDialog(name, value) { return ensureDialog(name) .then(function () { @@ -171,15 +137,6 @@ return opener(value); }) .catch(function (error) { - if (name === "login") { - try { - var params = new URLSearchParams(global.location.search || ""); - // 已在登录入口仍失败时避免死循环跳转 - if (params.get("auth") !== "login") { - global.location.href = loginFallbackURL(); - } - } catch (e) {} - } throw error; }); } @@ -196,12 +153,6 @@ function triggerName(target) { if (!target || !target.closest) return ""; if (target.closest("[data-download-app-open]")) return "download"; - if (target.closest("[data-register-dialog-open], a[href=\"#register\"], a[href=\"/register/\"]")) { - return "register"; - } - if (target.closest("[data-login-dialog-open], a[href=\"#login\"], a[href=\"/login/\"]")) { - return "login"; - } return ""; } @@ -222,22 +173,23 @@ openDialog(name).catch(function () {}); }, true); - function openFromLocation() { - var params = new URLSearchParams(global.location.search || ""); - var auth = params.get("auth"); - if (auth === "login" || auth === "register") { - openDialog(auth).catch(function () {}); - return; - } - if (global.location.hash === "#login") openDialog("login").catch(function () {}); - if (global.location.hash === "#register") openDialog("register").catch(function () {}); + function appDownloadURL() { + var el = document.querySelector("[data-app-download-url]"); + var url = el ? (el.getAttribute("data-app-download-url") || "").trim() : ""; + return url || "/download-app/"; } - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", openFromLocation, { once: true }); - } else { - openFromLocation(); + // 付费内容统一入口:移动端直接跳下载地址,PC 弹下载二维码。 + function openDownloadApp() { + if (isMobileViewport()) { + global.location.href = appDownloadURL(); + return Promise.resolve(); + } + return openDialog("download").catch(function () { + global.location.href = appDownloadURL(); + }); } global.ensureDialog = ensureDialog; + global.openDownloadApp = openDownloadApp; })(window); diff --git a/app/static/js/pages/editPassword.js b/app/static/js/pages/editPassword.js deleted file mode 100644 index dd3f29d..0000000 --- a/app/static/js/pages/editPassword.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * 修改密码:POST /api/auth/profile { oldPassword, newPassword } - */ -(function () { - var form = document.querySelector("[data-edit-password-form]"); - if (!form) return; - - var currentInput = form.querySelector("[data-edit-password-current]"); - var newInput = form.querySelector("[data-edit-password-new]"); - var confirmInput = form.querySelector("[data-edit-password-confirm]"); - var submitBtn = form.querySelector("[data-edit-password-submit]"); - - document.querySelectorAll("[data-password-toggle]").forEach(function (btn) { - btn.addEventListener("click", function () { - var id = btn.getAttribute("data-password-toggle"); - var input = id ? document.getElementById(id) : null; - if (!input) return; - var show = input.type === "password"; - input.type = show ? "text" : "password"; - btn.textContent = show ? "隐藏" : "显示"; - }); - }); - - function getField(name) { - return form.querySelector('[data-edit-password-field="' + name + '"]'); - } - - function setFieldError(name, message) { - var field = getField(name); - if (!field) return; - var error = field.querySelector("[data-edit-password-error]"); - field.classList.toggle("is-error", !!message); - if (error) { - if (message) { - error.hidden = false; - error.textContent = message; - } else { - error.hidden = true; - } - } - } - - function clearErrors() { - setFieldError("current", ""); - setFieldError("new", ""); - setFieldError("confirm", ""); - } - - function isValidLength(value) { - return value.length >= 4 && value.length <= 16; - } - - function toast(message, type) { - if (window.AuthAPI) AuthAPI.toast(message, type); - else if (typeof showToast === "function") showToast(message, type); - } - - function validate() { - clearErrors(); - var current = (currentInput && currentInput.value) || ""; - var next = (newInput && newInput.value) || ""; - var confirm = (confirmInput && confirmInput.value) || ""; - var ok = true; - - if (!current) { - setFieldError("current", "请输入当前密码"); - ok = false; - } else if (!isValidLength(current)) { - setFieldError("current", "当前密码需为 4–16 个字符"); - ok = false; - } - - if (!next) { - setFieldError("new", "请输入新密码"); - ok = false; - } else if (!isValidLength(next)) { - setFieldError("new", "新密码需为 4–16 个字符"); - ok = false; - } else if (next === current) { - setFieldError("new", "新密码不能与当前密码相同"); - ok = false; - } - - if (!confirm) { - setFieldError("confirm", "请再次输入新密码"); - ok = false; - } else if (confirm !== next) { - setFieldError("confirm", "两次输入的新密码不一致"); - ok = false; - } - - if (!ok) return null; - return { oldPassword: current, newPassword: next }; - } - - [currentInput, newInput, confirmInput].forEach(function (input) { - if (!input) return; - input.addEventListener("input", function () { - var field = input.closest("[data-edit-password-field]"); - if (!field) return; - var name = field.getAttribute("data-edit-password-field"); - setFieldError(name, ""); - }); - }); - - form.addEventListener("submit", function (event) { - event.preventDefault(); - var payload = validate(); - if (!payload) return; - if (!window.AuthAPI || !window.HttpRequest) { - return; - } - - if (submitBtn) submitBtn.disabled = true; - - AuthAPI.updateProfile(payload) - .then(function () { - toast("密码修改成功", "success"); - setTimeout(function () { - window.location.href = "/setting/"; - }, 400); - }) - .catch(function (err) { - var tip = (err && err.message) || "修改失败,请稍后重试"; - if (/旧|当前|原密码|old/i.test(tip)) { - setFieldError("current", tip); - return; - } - if (/新密码|new/i.test(tip)) { - setFieldError("new", tip); - return; - } - toast(tip, "error"); - }) - .finally(function () { - if (submitBtn) submitBtn.disabled = false; - }); - }); -})(); diff --git a/app/static/js/pages/editProfile.js b/app/static/js/pages/editProfile.js deleted file mode 100644 index f17b2cd..0000000 --- a/app/static/js/pages/editProfile.js +++ /dev/null @@ -1,68 +0,0 @@ -(function () { - var form = document.querySelector("[data-edit-profile-form]"); - var nickInput = document.querySelector("[data-edit-profile-nickname]"); - var bioInput = document.querySelector("[data-edit-profile-bio]"); - var submitBtn = document.querySelector("[data-edit-profile-submit]"); - if (!form) return; - - var originalNick = nickInput ? String(nickInput.value || "").trim() : ""; - var originalBio = bioInput ? String(bioInput.value || "").trim() : ""; - - function toast(message, type) { - if (window.AuthAPI) AuthAPI.toast(message, type); - else if (typeof showToast === "function") showToast(message, type); - } - - form.addEventListener("submit", function (event) { - event.preventDefault(); - if (!window.AuthAPI || !window.HttpRequest) { - return; - } - - var nick = nickInput ? String(nickInput.value || "").trim() : ""; - var bio = bioInput ? String(bioInput.value || "").trim() : ""; - if (!nick) { - toast("请输入昵称", "error"); - return; - } - if (nick.length > 20) { - toast("昵称最多 20 个字符", "error"); - return; - } - if (bio.length > 120) { - toast("个人简介最多 120 个字符", "error"); - return; - } - - var nickChanged = nick !== originalNick; - var bioChanged = bio !== originalBio; - if (!nickChanged && !bioChanged) { - toast("暂无修改", "info"); - return; - } - - var payload = {}; - if (nickChanged) payload.nickName = nick; - if (bioChanged) payload.introduction = bio; - if (!payload.nickName && !payload.introduction) { - toast(bioChanged ? "请填写个人简介或修改昵称" : "暂无修改", "error"); - return; - } - - if (submitBtn) submitBtn.disabled = true; - - AuthAPI.updateProfile(payload) - .then(function () { - toast("保存成功", "success"); - setTimeout(function () { - window.location.href = "/setting/"; - }, 400); - }) - .catch(function (err) { - toast((err && err.message) || "保存失败", "error"); - }) - .finally(function () { - if (submitBtn) submitBtn.disabled = false; - }); - }); -})(); diff --git a/app/static/js/pages/feedback.js b/app/static/js/pages/feedback.js index 73d0b0a..291683d 100644 --- a/app/static/js/pages/feedback.js +++ b/app/static/js/pages/feedback.js @@ -495,23 +495,6 @@ }); } - function requireLogin() { - if (window.CookieUtil && CookieUtil.isLoggedIn()) return true; - toast("请先登录后再提交反馈", "error"); - var loginURL = "/login/return/feedback/"; - if (typeof window.openLoginDialog === "function") { - var opened = window.openLoginDialog(); - if (opened && typeof opened.then === "function") { - opened.catch(function () { - window.location.href = loginURL; - }); - } - } else { - window.location.href = loginURL; - } - return false; - } - function uploadAllImages() { if (!selectedFiles.length) return Promise.resolve([]); if (!window.AuthAPI || typeof AuthAPI.uploadImage !== "function") { @@ -548,7 +531,6 @@ return; } if (submitBtn && submitBtn.classList.contains("is-loading")) return; - if (!requireLogin()) return; var activeTypeChip = document.querySelector("[data-feedback-type-option].is-active"); var type = parseInt( @@ -647,18 +629,6 @@ clearFiles(); }) .catch(function (err) { - if (err && err.code === 401) { - toast("请先登录后再提交反馈", "error"); - if (typeof window.openLoginDialog === "function") { - var opened = window.openLoginDialog(); - if (opened && typeof opened.then === "function") { - opened.catch(function () { - window.location.href = "/login/return/feedback/"; - }); - } - } - return; - } toast((err && err.message) || "提交失败", "error"); }) .finally(function () { diff --git a/app/static/js/pages/header.js b/app/static/js/pages/header.js index 3cd8770..33e0037 100644 --- a/app/static/js/pages/header.js +++ b/app/static/js/pages/header.js @@ -93,142 +93,12 @@ }); } - var DEFAULT_AVATAR = "/static/images/avatar.png"; - var PLACEHOLDER = "/static/images/placeholder.png?v=2"; - - 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"); - if (src && typeof shouldDecryptImage === "function" && shouldDecryptImage(src)) { - img.classList.add("lazy"); - img.src = PLACEHOLDER; - img.setAttribute("data-src", src); - } else { - img.classList.remove("lazy"); - img.removeAttribute("data-src"); - img.src = src || DEFAULT_AVATAR; - } - } - function refreshDecrypt(root) { if (typeof window.initLazyImages === "function") { window.initLazyImages(root || document); } } - /** 用 cookie 登录态同步 header-account */ - function parseExpireTime(value) { - if (value == null || value === "") return NaN; - if (typeof value === "number") { - return value < 1e12 ? value * 1000 : value; - } - var text = String(value).trim(); - if (!text) return NaN; - if (/^\d+$/.test(text)) { - var num = Number(text); - return num < 1e12 ? num * 1000 : num; - } - return Date.parse(text); - } - - // vipType≠0 且 currentVipExpire 未过期(与 91ly 一致) - function isVipActive(user) { - if (!user) return false; - var type = Number(user.vipType); - if (!type || type === 0) return false; - var expireMs = parseExpireTime(user.currentVipExpire); - if (!isFinite(expireMs) || expireMs <= 0) return false; - return expireMs > Date.now(); - } - - function syncHeaderVip(session) { - var active = isVipActive(session); - document.querySelectorAll("[data-header-vip], [data-header-vip-sep]").forEach(function (el) { - el.hidden = !active; - }); - } - - function syncHeaderAccount() { - var account = document.querySelector("[data-header-account]"); - if (!account || !window.CookieUtil) return; - - // 有 token 即展示个人信息(含游客) - var loggedIn = CookieUtil.isLoggedIn(); - var session = loggedIn ? CookieUtil.getAuthSession() : null; - var nameEl = account.querySelector("[data-header-username]"); - - if (loggedIn && session) { - var displayName = - session.nickName || session.accountNo || session.email || "用户"; - var avatar = usableAvatar(session.avatar); - - account.href = "/user-center/"; - account.removeAttribute("data-login-dialog-open"); - - var guest = account.querySelector("[data-header-guest-avatar]"); - if (guest) guest.remove(); - - var photo = account.querySelector("[data-header-avatar]"); - if (!photo) { - photo = document.createElement("img"); - photo.className = "account-avatar-photo"; - photo.setAttribute("data-header-avatar", ""); - photo.alt = displayName + "的头像"; - account.insertBefore(photo, nameEl || account.firstChild); - } - setAvatarImg(photo, avatar); - - if (nameEl) { - nameEl.textContent = displayName; - nameEl.classList.add("account-label--logged"); - } - - refreshDecrypt(account.parentElement || document); - syncHeaderVip(session); - return; - } - - account.href = "#login"; - account.setAttribute("data-login-dialog-open", ""); - - var loggedPhoto = account.querySelector("[data-header-avatar]"); - if (loggedPhoto) loggedPhoto.remove(); - if (!account.querySelector("[data-header-guest-avatar]")) { - var wrap = document.createElement("div"); - wrap.className = "account-avatar"; - wrap.setAttribute("data-header-guest-avatar", ""); - wrap.innerHTML = - '' + - '' + - ''; - account.insertBefore(wrap, nameEl || account.firstChild); - } - if (nameEl) { - nameEl.textContent = "未登录"; - nameEl.classList.remove("account-label--logged"); - } - - syncHeaderVip(null); - } - - syncHeaderAccount(); - window.syncHeaderAccount = syncHeaderAccount; - document.addEventListener("auth:login", function () { - syncHeaderAccount(); - }); - document.addEventListener("auth:logout", function () { - syncHeaderAccount(); - }); document.addEventListener( "click", diff --git a/app/static/js/pages/history.js b/app/static/js/pages/history.js deleted file mode 100644 index 68915d3..0000000 --- a/app/static/js/pages/history.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * 观看历史页:清空记录 - */ -(function () { - var clearBtns = document.querySelectorAll("[data-history-clear]"); - var confirmMask = document.querySelector("[data-history-confirm]"); - var confirmCancelBtns = document.querySelectorAll( - "[data-history-confirm-cancel]" - ); - var confirmOkBtn = document.querySelector("[data-history-confirm-ok]"); - var clearing = false; - - function toast(message, type) { - if (window.AuthAPI && typeof window.AuthAPI.toast === "function") { - window.AuthAPI.toast(message, type); - } - } - - function openClearConfirm() { - if (!confirmMask) return; - confirmMask.hidden = false; - confirmMask.removeAttribute("hidden"); - confirmMask.classList.add("is-open"); - document.body.style.overflow = "hidden"; - } - - function closeClearConfirm() { - if (!confirmMask) return; - confirmMask.classList.remove("is-open"); - confirmMask.hidden = true; - confirmMask.setAttribute("hidden", ""); - document.body.style.overflow = ""; - } - - function clearHistory() { - if (clearing) return; - if (!window.HttpRequest) { - toast("清空失败,请稍后重试", "error"); - return; - } - clearing = true; - window.HttpRequest.post("/api/playlet/history/clear") - .then(function (res) { - if (!res || res.code !== 200) { - throw new Error((res && (res.tip || res.msg)) || "清空失败"); - } - window.location.reload(); - }) - .catch(function (err) { - clearing = false; - toast((err && err.message) || "清空失败,请稍后重试", "error"); - }); - } - - clearBtns.forEach(function (btn) { - btn.addEventListener("click", function () { - openClearConfirm(); - }); - }); - - confirmCancelBtns.forEach(function (btn) { - btn.addEventListener("click", function () { - closeClearConfirm(); - }); - }); - - if (confirmMask) { - confirmMask.addEventListener("click", function (e) { - if (e.target === confirmMask) closeClearConfirm(); - }); - } - - if (confirmOkBtn) { - confirmOkBtn.addEventListener("click", function () { - closeClearConfirm(); - clearHistory(); - }); - } - - document.addEventListener("keydown", function (e) { - if (e.key !== "Escape") return; - if (!confirmMask || !confirmMask.classList.contains("is-open")) return; - closeClearConfirm(); - }); -})(); diff --git a/app/static/js/pages/loginDialog.js b/app/static/js/pages/loginDialog.js deleted file mode 100644 index d6feec8..0000000 --- a/app/static/js/pages/loginDialog.js +++ /dev/null @@ -1,236 +0,0 @@ -(function () { - var mask = document.getElementById("loginDialog"); - if (!mask) return; - - var form = mask.querySelector("[data-login-dialog-form]"); - var accountInput = mask.querySelector("[data-login-account]"); - var passwordInput = mask.querySelector("[data-login-password]"); - var submitBtn = mask.querySelector("[data-login-submit]"); - var defaultSubmitText = (submitBtn && submitBtn.textContent) || "登录"; - - function setSubmitting(loading) { - if (!submitBtn) return; - submitBtn.disabled = !!loading; - submitBtn.classList.toggle("is-loading", !!loading); - submitBtn.setAttribute("aria-busy", loading ? "true" : "false"); - submitBtn.textContent = loading ? "登录中..." : defaultSubmitText; - } - - function getField(name) { - return mask.querySelector('[data-login-field="' + name + '"]'); - } - - function setFieldError(name, message) { - var field = getField(name); - if (!field) return; - var control = field.querySelector("[data-login-control]"); - var error = field.querySelector("[data-login-error]"); - if (control) control.classList.add("is-error"); - if (error) { - error.textContent = message; - error.hidden = false; - } - } - - function clearFieldError(name) { - var field = getField(name); - if (!field) return; - var control = field.querySelector("[data-login-control]"); - var error = field.querySelector("[data-login-error]"); - if (control) control.classList.remove("is-error"); - if (error) error.hidden = true; - } - - function clearAllErrors() { - clearFieldError("account"); - clearFieldError("password"); - } - - function isValidLength(value) { - return value.length >= 4 && value.length <= 16; - } - - function validateForm() { - var account = (accountInput && accountInput.value.trim()) || ""; - var password = (passwordInput && passwordInput.value) || ""; - var valid = true; - - clearAllErrors(); - - if (!account) { - setFieldError("account", "账号格式不正确,请输入有效账号"); - valid = false; - } else if (!window.AuthAPI || !AuthAPI.isValidLoginAccount(account)) { - setFieldError("account", "账号格式不正确,请输入有效账号"); - valid = false; - } - - if (!password) { - setFieldError("password", "密码错误,请检查后重新输入"); - valid = false; - } else if (!isValidLength(password)) { - setFieldError("password", "密码错误,请检查后重新输入"); - valid = false; - } - - if (!valid) return null; - return { account: account, password: password }; - } - - function mapServerError(err) { - var tip = (err && err.message) || "登录失败,请稍后重试"; - if (/密码|password/i.test(tip)) { - setFieldError("password", tip); - return; - } - if (/账号|账户|用户|邮箱|account|email|不存在|错误/i.test(tip)) { - setFieldError("account", tip); - return; - } - if (window.AuthAPI) AuthAPI.toast(tip, "error"); - else if (typeof showToast === "function") showToast(tip, "error"); - } - - function openDialog(event) { - if (event) event.preventDefault(); - if (typeof window.closeRegisterDialog === "function") { - window.closeRegisterDialog(); - } - clearAllErrors(); - mask.classList.add("is-open"); - document.body.style.overflow = "hidden"; - return true; - } - - function closeDialog() { - mask.classList.remove("is-open"); - clearAllErrors(); - if (!document.getElementById("registerDialog") || !document.getElementById("registerDialog").classList.contains("is-open")) { - document.body.style.overflow = ""; - } - } - - function setLoginErrors(errors) { - clearAllErrors(); - if (!errors) return; - if (errors.account) setFieldError("account", errors.account); - if (errors.password) setFieldError("password", errors.password); - } - - if (form) { - form.addEventListener("submit", function (event) { - event.preventDefault(); - if (submitBtn && submitBtn.classList.contains("is-loading")) return; - var payload = validateForm(); - if (!payload) return; - if (!window.AuthAPI || !window.HttpRequest) { - return; - } - - setSubmitting(true); - - AuthAPI.login(payload) - .then(function () { - AuthAPI.toast("登录成功", "success"); - closeDialog(); - setTimeout(function () { - goAfterAuth(); - }, 400); - }) - .catch(function (err) { - setSubmitting(false); - mapServerError(err); - }); - }); - } - - function isSafeRedirect(path) { - return typeof path === "string" && path.charAt(0) === "/" && path.charAt(1) !== "/"; - } - - function goAfterAuth() { - var bodyRedirect = document.body && document.body.getAttribute("data-auth-redirect"); - if (isSafeRedirect(bodyRedirect)) { - window.location.href = bodyRedirect; - return; - } - var params = new URLSearchParams(window.location.search); - var redirect = params.get("redirect"); - if (isSafeRedirect(redirect)) { - window.location.href = redirect; - return; - } - window.location.reload(); - } - - function consumeAuthQuery(kind) { - var params = new URLSearchParams(window.location.search); - if (params.get("auth") !== kind) return false; - params.delete("auth"); - var qs = params.toString(); - var next = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash; - window.history.replaceState(null, "", next); - return true; - } - - if (accountInput) { - accountInput.addEventListener("input", function () { - clearFieldError("account"); - }); - } - - if (passwordInput) { - passwordInput.addEventListener("input", function () { - clearFieldError("password"); - }); - } - - document.addEventListener("click", function (event) { - var openEl = event.target.closest("[data-login-dialog-open]"); - if (openEl) { - openDialog(event); - return; - } - - var loginLink = event.target.closest('a[href="/login/"], a[href="#login"]'); - if (loginLink) { - openDialog(event); - return; - } - - if (event.target.closest("[data-login-dialog-close]")) { - closeDialog(); - return; - } - - if (event.target.closest("[data-login-dialog-eye]")) { - var btn = event.target.closest("[data-login-dialog-eye]"); - var input = document.getElementById("loginDialogPassword"); - var img = btn.querySelector("img"); - if (!input || !img) return; - var show = input.type === "password"; - input.type = show ? "text" : "password"; - img.src = show ? "/static/images/eye_open.svg" : "/static/images/eye_close.svg"; - btn.setAttribute("aria-label", show ? "隐藏密码" : "显示密码"); - return; - } - - if (event.target === mask) { - closeDialog(); - } - }); - - document.addEventListener("keydown", function (event) { - if (event.key === "Escape" && mask.classList.contains("is-open")) { - closeDialog(); - } - }); - - window.openLoginDialog = openDialog; - window.closeLoginDialog = closeDialog; - window.setLoginDialogErrors = setLoginErrors; - - if ((document.body && document.body.getAttribute("data-auth-mode") === "login") || consumeAuthQuery("login")) { - openDialog(); - } -})(); diff --git a/app/static/js/pages/play.js b/app/static/js/pages/play.js index 5072b38..0ab87e3 100644 --- a/app/static/js/pages/play.js +++ b/app/static/js/pages/play.js @@ -437,46 +437,6 @@ applyPlaySpeed(dp, currentPlaySpeed()); } - // 复用同一 meta 对象:bindVideoEvents 只绑一次,切集时就地更新字段即可生效。 - var playTrackerMeta = { - media_id: "", - video_id: "", - video_title: "", - video_type_id: "playlet", - video_type_name: "短剧", - video_content_type: "playlet", - recommend_trace_id: "", - video_tag_key: "", - video_tag_name: "", - }; - var playViewTracked = false; - - function syncPlayTrackerMeta() { - var root = document.querySelector("[data-play-page]"); - var ds = (root && root.dataset) || {}; - var player = document.querySelector("[data-play-player]"); - var pds = (player && player.dataset) || {}; - var episode = ds.playEpisode || ""; - playTrackerMeta.media_id = ds.playletId || ""; - playTrackerMeta.video_id = ds.playletId || ""; - playTrackerMeta.video_title = ds.playTitle || ""; - playTrackerMeta.video_type_id = "playlet"; - playTrackerMeta.video_type_name = ds.playTag || "短剧"; - playTrackerMeta.video_content_type = "playlet"; - playTrackerMeta.recommend_trace_id = ""; - playTrackerMeta.video_tag_key = pds.chapterId || ""; - playTrackerMeta.video_tag_name = episode ? "第" + episode + "集" : ""; - return playTrackerMeta; - } - - function bindPlayTracker(dp) { - if (!dp || !window.tracker || typeof window.tracker.bindVideoEvents !== "function") { - return; - } - syncPlayTrackerMeta(); - window.tracker.bindVideoEvents(dp, playTrackerMeta); - } - function nextEpisodeElement() { var active = document.querySelector(".play-ep-grid .play-ep--active"); if (!active) return null; @@ -607,7 +567,6 @@ var player = container.closest(".play-player"); bindPlayerPoster(playDp, player); bindH265Fallback(playDp, source); - bindPlayTracker(playDp); setupDesktopPlayerControls(playDp); return playDp; } catch (e) { @@ -681,7 +640,7 @@ if (id > 0 && next.getAttribute("data-play-switch") != null) { return id; } - // 付费/VIP 锁定集:返回 id 以便弹窗 + // 付费锁定集:返回 id 以便引导下载 APP if (id > 0) return id; } next = next.nextElementSibling; @@ -721,14 +680,17 @@ if (dp && typeof dp.on === "function") { dp.on("timeupdate", updateNextCountdown); dp.on("ended", function () { - markCurrentEpisodeWatched(); var chapterId = findNextChapterId(); setNextCountdownVisible(false, 3); - if (chapterId) switchToChapter(chapterId, { autoplay: true }); + if (!chapterId) return; + if (isLockedEpisode(chapterId)) { + openDownloadApp(); + return; + } + switchToChapter(chapterId, { autoplay: true }); }); dp.on("play", function () { applyPlaySpeed(dp, currentPlaySpeed()); - markCurrentEpisodeWatched(); updateNextCountdown(); }); dp.on("seeked", updateNextCountdown); @@ -770,26 +732,6 @@ (playPage && playPage.getAttribute("data-play-episode")) || 0 ); var switchingChapter = false; - var lastWatchReportKey = ""; - - function reportChapterWatch() { - if (!currentChapterId || !window.HttpRequest) return; - if (window.CookieUtil && !window.CookieUtil.isLoggedIn()) return; - var key = String(playletId || 0) + ":" + currentChapterId; - if (key === lastWatchReportKey) return; - lastWatchReportKey = key; - var body = { chapterId: currentChapterId }; - if (playletId) body.playletId = playletId; - window.HttpRequest.post("/api/playlet/chapter/watch/report", body).catch( - function () { - /* 静默失败:未登录/网络错误不打断播放 */ - } - ); - } - - function markCurrentEpisodeWatched() { - reportChapterWatch(); - } function padEpisode(n) { var num = Number(n) || 0; @@ -802,92 +744,42 @@ return "/play/" + encodeURIComponent(key) + "/" + chapterId + "/"; } - function resolvePayGate(chapter, el) { - if (chapter) { - if (chapter.canWatch || chapter.isUnlocked) return 0; - if (chapter.needUnlock) return 1; - if (chapter.needVip) return 2; - var payType = Number(chapter.payType || 0); - if (payType === 1 || payType === 2) return payType; - return 0; - } - if (el) { - if (el.getAttribute("data-can-watch") === "1") return 0; - if (el.getAttribute("data-need-unlock") === "1") return 1; - if (el.getAttribute("data-need-vip") === "1") return 2; - if (el.getAttribute("data-coin-dialog-open") != null) return 1; - if (el.getAttribute("data-vip-dialog-open") != null) return 2; - return Number(el.getAttribute("data-pay-type") || 0); - } - return 0; + function isChapterLocked(chapter) { + if (!chapter) return false; + if (chapter.canWatch || chapter.isUnlocked) return false; + if (chapter.needUnlock || chapter.needVip) return true; + // 与服务端 PlayletChapterData.AllowWatch 保持一致:仅免费集可直接播放。 + return Number(chapter.payType || 0) !== 0; } - function resolveUnlockCost(chapter, el) { - if (chapter) { - if (Number(chapter.costCoins) > 0) return Number(chapter.costCoins); - if (Number(chapter.singleCoins) > 0) return Number(chapter.singleCoins); - } - if (el) return Number(el.getAttribute("data-single-coins") || 0); - return 0; + function isLockedEpisode(chapterId) { + chapterId = Number(chapterId) || 0; + if (!chapterId) return false; + return !!document.querySelector( + '[data-play-locked][data-chapter-id="' + chapterId + '"]' + ); } - function openPayForChapter(el, chapter) { - var payGate = resolvePayGate(chapter, el); - if (payGate === 1) { - if (typeof window.openCoinDialog === "function") { - var coinTrigger = document.createElement("button"); - coinTrigger.setAttribute("data-coin-dialog-open", ""); - coinTrigger.setAttribute( - "data-chapter-id", - String((chapter && chapter.id) || (el && el.getAttribute("data-chapter-id")) || "") - ); - coinTrigger.setAttribute( - "data-playlet-id", - String( - (chapter && chapter.playletId) || - playletId || - (el && el.getAttribute("data-playlet-id")) || - "" - ) - ); - coinTrigger.setAttribute( - "data-single-coins", - String(resolveUnlockCost(chapter, el)) - ); - coinTrigger.style.display = "none"; - document.body.appendChild(coinTrigger); - coinTrigger.click(); - coinTrigger.remove(); - } - return true; + // 付费剧集统一引导下载 APP(移动端跳转下载地址,PC 弹出下载二维码)。 + function openDownloadApp() { + if (typeof window.openDownloadApp === "function") { + return window.openDownloadApp(); } - if (payGate === 2) { - var vipBtn = document.querySelector("[data-vip-dialog-open]"); - if (vipBtn) vipBtn.click(); - else if (typeof window.openVipDialog === "function") { - window.openVipDialog(); - } - return true; - } - return false; + return Promise.resolve(); } function updateEpisodeActive(chapterId) { document.querySelectorAll(".play-ep").forEach(function (el) { var isActive = Number(el.getAttribute("data-chapter-id") || 0) === chapterId; el.classList.toggle("play-ep--active", isActive); - var coin = el.querySelector(".play-ep-coin"); var lock = el.querySelector(".play-ep-lock"); - if (coin) coin.style.display = isActive ? "none" : ""; if (lock) lock.style.display = isActive ? "none" : ""; }); document.querySelectorAll(".play-ep-sheet-item").forEach(function (el) { var isActive = Number(el.getAttribute("data-chapter-id") || 0) === chapterId; el.classList.toggle("play-ep-sheet-item--active", isActive); - var coin = el.querySelector(".play-ep-sheet-coin"); var lock = el.querySelector(".play-ep-sheet-lock"); - if (coin) coin.hidden = isActive; if (lock) lock.hidden = isActive; }); syncDesktopNextControl(); @@ -897,7 +789,6 @@ var ep = Number(chapter.currentEpisode) || 0; currentEpisode = ep; currentChapterId = Number(chapter.id) || currentChapterId; - lastWatchReportKey = ""; if (playPage) { playPage.setAttribute("data-play-episode", String(ep)); } @@ -922,10 +813,6 @@ player.setAttribute("data-chapter-id", String(chapter.id || "")); player.setAttribute("data-playlet-id", String(playletId || chapter.playletId || "")); player.setAttribute("data-pay-type", String(chapter.payType || 0)); - player.setAttribute( - "data-single-coins", - String(resolveUnlockCost(chapter, null)) - ); player.setAttribute( "data-can-watch", chapter.canWatch || chapter.isUnlocked ? "1" : "0" @@ -935,10 +822,6 @@ chapter.needUnlock ? "1" : "0" ); player.setAttribute("data-need-vip", chapter.needVip ? "1" : "0"); - player.setAttribute( - "data-is-unlocked", - chapter.isUnlocked ? "1" : "0" - ); } var commentCount = document.querySelector("[data-play-comment-count]"); @@ -976,6 +859,8 @@ container.setAttribute("data-url", videoUrl || ""); if (playCover) container.setAttribute("data-cover", playCover); player.insertBefore(container, player.firstChild); + var lockedCta = player.querySelector("[data-play-locked-cta]"); + if (lockedCta) lockedCta.remove(); var playBtn = player.querySelector("[data-play-poster-play]"); if (!playBtn) { playBtn = document.createElement("button"); @@ -1016,24 +901,18 @@ throw new Error((res && (res.tip || res.msg)) || "加载失败"); } var chapter = res.data; - var payGate = resolvePayGate(chapter, null); - if (payGate !== 0 && !options.skipPayCheck) { - openPayForChapter(null, chapter); + if (isChapterLocked(chapter)) { + openDownloadApp(); return; } var videoUrl = resolvePlayUrl(chapter.videoUrl || ""); if (!videoUrl) { - if (payGate !== 0) { - openPayForChapter(null, chapter); - return; - } throw new Error("暂无播放地址"); } currentChapterId = Number(chapter.id) || chapterId; updateEpisodeActive(currentChapterId); updateChapterMeta(chapter); - markCurrentEpisodeWatched(); if (!options.skipHistory) { var href = buildPlayHref(currentChapterId); @@ -1049,8 +928,6 @@ if (playDp && typeof playDp.switchVideo === "function") { switchPlayerVideo(playDp, videoOption); - syncPlayTrackerMeta(); - bindPlayTracker(playDp); } else { ensurePlayerContainer(chapter.videoUrl || ""); playDp = initDPlayer(); @@ -1078,69 +955,6 @@ }); } - function markEpisodeUnlocked(chapterId) { - chapterId = Number(chapterId) || 0; - if (!chapterId) return; - var href = buildPlayHref(chapterId); - document - .querySelectorAll( - '.play-ep[data-chapter-id="' + - chapterId + - '"], .play-ep-sheet-item[data-chapter-id="' + - chapterId + - '"]' - ) - .forEach(function (el) { - el.removeAttribute("data-coin-dialog-open"); - el.removeAttribute("data-vip-dialog-open"); - el.removeAttribute("data-single-coins"); - el.setAttribute("data-play-switch", ""); - el.setAttribute("href", href || "javascript:;"); - var coin = el.querySelector( - ".play-ep-coin, .play-ep-sheet-coin" - ); - if (coin) coin.remove(); - var lock = el.querySelector( - ".play-ep-lock, .play-ep-sheet-lock" - ); - if (lock) lock.remove(); - }); - - var player = document.querySelector("[data-play-player]"); - if ( - player && - Number(player.getAttribute("data-chapter-id") || 0) === chapterId - ) { - player.setAttribute("data-pay-type", "0"); - player.setAttribute("data-can-watch", "1"); - player.setAttribute("data-need-unlock", "0"); - player.setAttribute("data-need-vip", "0"); - player.setAttribute("data-is-unlocked", "1"); - player.removeAttribute("data-single-coins"); - } - } - - function playAfterChapterUnlock(detail) { - detail = detail || {}; - var chapterId = Number(detail.chapterId) || 0; - if (!chapterId) return; - if (detail.playletId) { - playletId = Number(detail.playletId) || playletId; - if (playPage) { - playPage.setAttribute("data-playlet-id", String(playletId)); - } - } - markEpisodeUnlocked(chapterId); - switchToChapter(chapterId, { - force: true, - skipPayCheck: true, - autoplay: true, - replace: true, - }); - } - - window.playAfterChapterUnlock = playAfterChapterUnlock; - function bindEpisodeSwitch() { document.addEventListener("click", function (e) { var el = @@ -1154,12 +968,27 @@ switchToChapter(chapterId, { autoplay: true }); }); + document.addEventListener("click", function (e) { + var el = + e.target && e.target.closest + ? e.target.closest("[data-play-locked], [data-play-download-app]") + : null; + if (!el) return; + e.preventDefault(); + openDownloadApp(); + }); + window.addEventListener("popstate", function (e) { var match = String(window.location.pathname || "").match(/\/play\/[^/]+\/(\d+)\/?$/); var chapterId = (e.state && e.state.chapterId) || Number((match && match[1]) || 0); if (!chapterId) return; + if (isLockedEpisode(chapterId)) { + // 回退到付费集:交给服务端渲染下载 APP 引导,避免自动跳转。 + window.location.reload(); + return; + } switchToChapter(chapterId, { autoplay: true, skipHistory: true, @@ -1176,14 +1005,6 @@ } } - function maybeOpenPayDialog() { - var player = document.querySelector("[data-play-player]"); - if (!player) return; - var hasVideo = !!player.querySelector(".dplayer-video[data-url]"); - if (hasVideo) return; - openPayForChapter(player, null); - } - function bindSidebarRanges() { var ranges = document.querySelectorAll("[data-play-ep-ranges] .play-ep-range"); var episodes = document.querySelectorAll("[data-play-ep-grid] .play-ep"); @@ -1349,44 +1170,11 @@ window.alert(message); } - function trackPlayView() { - if (playViewTracked || !window.tracker || typeof window.tracker.videoEvent !== "function") return; - var meta = syncPlayTrackerMeta(); - if (!meta.video_id) return; - playViewTracked = true; - window.tracker.videoEvent({ - media_id: meta.media_id, - video_id: meta.video_id, - video_title: meta.video_title, - video_type_id: meta.video_type_id, - video_type_name: meta.video_type_name, - video_content_type: meta.video_content_type, - recommend_trace_id: meta.recommend_trace_id, - video_tag_key: meta.video_tag_key, - video_tag_name: meta.video_tag_name, - video_behavior_key: "video_view", - video_behavior_name: "视频展示", - }); - } - - function bindTrackingWhenReady(dp) { - function activateTracking() { - bindPlayTracker(dp || playDp); - trackPlayView(); - } - activateTracking(); - if (!window.__trackerReady) { - window.addEventListener("tracker:ready", activateTracking, { once: true }); - } - } - function boot() { var dp = initDPlayer(); bindH5PlayerUI(dp); bindSidebarRanges(); bindEpisodeSwitch(); - maybeOpenPayDialog(); - bindTrackingWhenReady(dp); if (typeof window.initLazyImages === "function") { window.initLazyImages(document.querySelector(".play-page") || document); } diff --git a/app/static/js/pages/registerDialog.js b/app/static/js/pages/registerDialog.js deleted file mode 100644 index 8e0f2ed..0000000 --- a/app/static/js/pages/registerDialog.js +++ /dev/null @@ -1,213 +0,0 @@ -(function () { - var mask = document.getElementById("registerDialog"); - if (!mask) return; - - var form = mask.querySelector("[data-register-dialog-form]"); - var accountInput = mask.querySelector("[data-register-account]"); - var passwordInput = mask.querySelector("[data-register-password]"); - var confirmInput = mask.querySelector("[data-register-password-confirm]"); - var submitBtn = mask.querySelector("[data-register-submit]"); - var defaultSubmitText = (submitBtn && submitBtn.textContent) || "立即注册"; - - function setSubmitting(loading) { - if (!submitBtn) return; - submitBtn.disabled = !!loading; - submitBtn.classList.toggle("is-loading", !!loading); - submitBtn.setAttribute("aria-busy", loading ? "true" : "false"); - submitBtn.textContent = loading ? "注册中..." : defaultSubmitText; - } - - function getField(name) { - return mask.querySelector('[data-register-field="' + name + '"]'); - } - - function setFieldError(name, message) { - var field = getField(name); - if (!field) return; - var control = field.querySelector("[data-register-control]"); - var error = field.querySelector("[data-register-error]"); - if (control) control.classList.add("is-error"); - if (error) { - error.textContent = message; - error.hidden = false; - } - } - - function clearFieldError(name) { - var field = getField(name); - if (!field) return; - var control = field.querySelector("[data-register-control]"); - var error = field.querySelector("[data-register-error]"); - if (control) control.classList.remove("is-error"); - if (error) error.hidden = true; - } - - function clearAllErrors() { - clearFieldError("account"); - clearFieldError("password"); - clearFieldError("password_confirm"); - } - - function isValidLength(value) { - return value.length >= 4 && value.length <= 16; - } - - function validateForm() { - var account = (accountInput && accountInput.value.trim()) || ""; - var password = (passwordInput && passwordInput.value) || ""; - var confirm = (confirmInput && confirmInput.value) || ""; - var valid = true; - - clearAllErrors(); - - if (!account) { - setFieldError("account", "请输入账号"); - valid = false; - } else if (!isValidLength(account)) { - setFieldError("account", "账号需为 4–16 个字符"); - valid = false; - } - - if (!password) { - setFieldError("password", "请输入密码"); - valid = false; - } else if (!isValidLength(password)) { - setFieldError("password", "密码需为 4–16 个字符"); - valid = false; - } - - if (!confirm) { - setFieldError("password_confirm", "请再次输入密码"); - valid = false; - } else if (confirm !== password) { - setFieldError("password_confirm", "两次输入的密码不一致"); - valid = false; - } - - if (!valid) return null; - return { accountNo: account, password: password }; - } - - 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; - } - if (window.AuthAPI) AuthAPI.toast(tip, "error"); - else if (typeof showToast === "function") showToast(tip, "error"); - } - - function openDialog(event) { - if (event) event.preventDefault(); - if (typeof window.closeLoginDialog === "function") { - window.closeLoginDialog(); - } - clearAllErrors(); - mask.classList.add("is-open"); - document.body.style.overflow = "hidden"; - return true; - } - - function closeDialog() { - mask.classList.remove("is-open"); - clearAllErrors(); - if (!document.getElementById("loginDialog") || !document.getElementById("loginDialog").classList.contains("is-open")) { - document.body.style.overflow = ""; - } - } - - if (form) { - form.addEventListener("submit", function (event) { - event.preventDefault(); - if (submitBtn && submitBtn.classList.contains("is-loading")) return; - var payload = validateForm(); - if (!payload) return; - if (!window.AuthAPI || !window.HttpRequest) { - return; - } - - setSubmitting(true); - - AuthAPI.register(payload) - .then(function () { - AuthAPI.toast("注册成功", "success"); - closeDialog(); - setTimeout(function () { - window.location.reload(); - }, 400); - }) - .catch(function (err) { - setSubmitting(false); - mapServerError(err); - }); - }); - } - - function consumeAuthQuery(kind) { - var params = new URLSearchParams(window.location.search); - if (params.get("auth") !== kind) return false; - params.delete("auth"); - var qs = params.toString(); - var next = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash; - window.history.replaceState(null, "", next); - return true; - } - - if (accountInput) { - accountInput.addEventListener("input", function () { - clearFieldError("account"); - }); - } - if (passwordInput) { - passwordInput.addEventListener("input", function () { - clearFieldError("password"); - }); - } - if (confirmInput) { - confirmInput.addEventListener("input", function () { - clearFieldError("password_confirm"); - }); - } - - document.addEventListener("click", function (event) { - var openEl = event.target.closest("[data-register-dialog-open]"); - if (openEl) { - openDialog(event); - return; - } - - var registerLink = event.target.closest('a[href="/register/"], a[href="#register"]'); - if (registerLink) { - openDialog(event); - return; - } - - if (event.target.closest("[data-register-dialog-close]")) { - closeDialog(); - return; - } - - if (event.target === mask) { - closeDialog(); - } - }); - - document.addEventListener("keydown", function (event) { - if (event.key === "Escape" && mask.classList.contains("is-open")) { - closeDialog(); - } - }); - - window.openRegisterDialog = openDialog; - window.closeRegisterDialog = closeDialog; - - if ((document.body && document.body.getAttribute("data-auth-mode") === "register") || consumeAuthQuery("register")) { - openDialog(); - } -})(); diff --git a/app/static/js/pages/setting.js b/app/static/js/pages/setting.js deleted file mode 100644 index 4226ad6..0000000 --- a/app/static/js/pages/setting.js +++ /dev/null @@ -1,34 +0,0 @@ -(function () { - document.querySelectorAll(".setting-switch").forEach(function (btn) { - btn.addEventListener("click", function () { - var on = btn.classList.toggle("setting-switch--on"); - btn.setAttribute("aria-pressed", on ? "true" : "false"); - }); - }); - - var logoutBtn = document.querySelector("[data-auth-logout]"); - if (logoutBtn) { - logoutBtn.addEventListener("click", function () { - function done() { - if (window.AuthAPI && typeof AuthAPI.markGuestLogout === "function") { - AuthAPI.markGuestLogout(); - } - if (window.CookieUtil && typeof CookieUtil.clearAuth === "function") { - CookieUtil.clearAuth(); - } - try { - document.dispatchEvent(new CustomEvent("auth:logout")); - } catch (e) {} - window.location.href = "/"; - } - - if (window.HttpRequest) { - HttpRequest.post("/api/auth/logout", {}) - .catch(function () {}) - .then(done); - } else { - done(); - } - }); - } -})(); diff --git a/app/static/js/pages/userCenter.js b/app/static/js/pages/userCenter.js deleted file mode 100644 index 486af3a..0000000 --- a/app/static/js/pages/userCenter.js +++ /dev/null @@ -1,161 +0,0 @@ -(function () { - function parseExpireTime(value) { - if (value == null || value === "") return NaN; - if (typeof value === "number") { - return value < 1e12 ? value * 1000 : value; - } - var text = String(value).trim(); - if (!text) return NaN; - if (/^\d+$/.test(text)) { - var num = Number(text); - return num < 1e12 ? num * 1000 : num; - } - return Date.parse(text); - } - - function isVipActive(user) { - if (!user) return false; - var type = Number(user.vipType); - if (!type || type === 0) return false; - var expireMs = parseExpireTime(user.currentVipExpire); - if (!isFinite(expireMs) || expireMs <= 0) return false; - return expireMs > Date.now(); - } - - function vipTypeLabel(vipType) { - var type = Number(vipType); - if (type === 1) return "月卡会员"; - if (type === 2) return "年卡会员"; - return "普通会员"; - } - - function vipDisplayLabel(user) { - var name = user && String(user.showCardName || "").trim(); - if (name) return name; - return vipTypeLabel(user && user.vipType); - } - - function vipExpireText(raw) { - var ms = parseExpireTime(raw); - if (!isFinite(ms) || ms <= 0) return ""; - var d = new Date(ms); - var y = d.getFullYear(); - var m = String(d.getMonth() + 1).padStart(2, "0"); - var day = String(d.getDate()).padStart(2, "0"); - return "有效期至 " + y + "/" + m + "/" + day; - } - - function formatRecharge(value) { - var n = Number(value); - if (!isFinite(n) || n < 0) n = 0; - return String(Math.floor(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ","); - } - - function syncUserCenterRecharge(value) { - var text = formatRecharge( - value != null - ? value - : CookieUtil.getAuthSession() && CookieUtil.getAuthSession().recharge, - ); - var pc = document.querySelector("[data-user-center-recharge]"); - if (pc) pc.textContent = text; - var hint = document.querySelector("[data-user-center-recharge-hint]"); - if (hint) hint.textContent = text + "金币"; - } - - function syncUserCenterID(value) { - var id = String(value == null ? "" : value).trim(); - if (!id) return; - document.querySelectorAll("[data-user-center-id]").forEach(function (el) { - el.textContent = el.getAttribute("data-id-layout") === "pc" - ? "ID:" + id - : "ID " + id; - }); - } - - function refreshUserCenterWallet() { - if (!window.CookieUtil || !CookieUtil.isLoggedIn()) return; - syncUserCenterRecharge(); - if (!window.HttpRequest) return; - HttpRequest.get("/api/auth/getInfo") - .then(function (res) { - if (!res || res.code !== 200 || !res.data) return; - if (CookieUtil.setAuthSession) CookieUtil.setAuthSession(res.data); - syncUserCenterID(res.data.userId); - syncUserCenterRecharge(res.data.recharge); - syncUserCenterVip(); - }) - .catch(function () {}); - } - - function syncUserCenterVip() { - if (!window.CookieUtil || !CookieUtil.isLoggedIn()) return; - var session = CookieUtil.getAuthSession(); - var active = isVipActive(session); - var label = active ? vipDisplayLabel(session) : ""; - var expire = active - ? vipExpireText(session && session.currentVipExpire) - : ""; - - var pc = document.querySelector("[data-user-center-vip]"); - if (pc) { - pc.classList.toggle("user-center-vip-card--active", active); - var status = pc.querySelector("[data-vip-status]"); - var expireEl = pc.querySelector("[data-vip-expire]"); - var action = pc.querySelector("[data-vip-action]"); - if (status) { - status.textContent = active ? "尊享 VIP 会员" : "尚未开通会员"; - } - if (expireEl) { - if (active && expire) { - expireEl.hidden = false; - expireEl.style.display = ""; - expireEl.textContent = expire; - } else { - expireEl.hidden = true; - expireEl.style.display = "none"; - expireEl.textContent = ""; - } - } - if (action) { - if (active) { - action.hidden = true; - action.style.display = "none"; - } else { - action.hidden = false; - action.style.display = ""; - action.textContent = "立即开通 →"; - } - } - } - - var mobile = document.querySelector("[data-user-center-mobile-vip]"); - if (mobile) { - mobile.classList.toggle("user-center-mobile-vip--active", active); - var title = mobile.querySelector("[data-mobile-vip-title]"); - var desc = mobile.querySelector("[data-mobile-vip-desc]"); - var btn = mobile.querySelector("[data-mobile-vip-btn]"); - if (title) title.textContent = active ? label : "开通黄果短剧会员"; - if (desc) { - desc.textContent = active - ? expire || "全场免费看 · 热门短剧抢先看" - : "全场免费看 · 热门短剧抢先看"; - } - if (btn) { - btn.hidden = false; - btn.style.display = ""; - if (active) { - btn.textContent = "续费会员"; - } else { - btn.textContent = "立即开通"; - } - } - } - } - - syncUserCenterVip(); - refreshUserCenterWallet(); - window.syncUserCenterVip = syncUserCenterVip; - window.syncUserCenterID = syncUserCenterID; - window.syncUserCenterRecharge = syncUserCenterRecharge; -})(); diff --git a/app/static/js/pages/vipDialog.js b/app/static/js/pages/vipDialog.js deleted file mode 100644 index 0a1dc92..0000000 --- a/app/static/js/pages/vipDialog.js +++ /dev/null @@ -1,52 +0,0 @@ -(function () { - var mask = document.getElementById("vipDialog"); - if (!mask) return; - - function openDialog(event) { - if (event) event.preventDefault(); - mask.classList.add("is-open"); - document.body.style.overflow = "hidden"; - } - - function closeDialog() { - mask.classList.remove("is-open"); - document.body.style.overflow = ""; - } - - function isVipLockedEpisode(target) { - var episode = target.closest( - ".detail-episode, .play-ep, .play-ep-sheet-item, .detail-h5-ep-item" - ); - if (!episode) return false; - // 金币解锁走 coinDialog,不在此打开 VIP 弹窗 - if (episode.hasAttribute("data-coin-dialog-open")) return false; - return !!episode.querySelector( - ".detail-episode-lock[src*='vip'], .detail-episode-vip, .play-ep-lock[src*='vip'], .play-ep-sheet-lock[src*='vip'], .detail-h5-ep-lock[src*='vip']" - ); - } - - document.addEventListener("click", function (event) { - if (event.target.closest("[data-vip-dialog-open]") || isVipLockedEpisode(event.target)) { - openDialog(event); - return; - } - - if (event.target.closest("[data-vip-dialog-close]")) { - closeDialog(); - return; - } - - if (event.target === mask) { - closeDialog(); - } - }); - - document.addEventListener("keydown", function (event) { - if (event.key === "Escape" && mask.classList.contains("is-open")) { - closeDialog(); - } - }); - - window.openVipDialog = openDialog; - window.closeVipDialog = closeDialog; -})(); diff --git a/app/static/js/tracker.js b/app/static/js/tracker.js deleted file mode 100644 index 60b96e8..0000000 --- a/app/static/js/tracker.js +++ /dev/null @@ -1,1483 +0,0 @@ -/** - * 数据上报 SDK 接入封装 (91porn / JHA-1029) - * 文档: http://34.146.148.117:8033/web/getting-started.html - * 参考: ttrgw-server / mdcom_web 项目 tracker.js - * - * 使用方式: 在 中先引入 web-sdk-vX.X.X.js,再引入本文件 tracker.js, - * 然后通过 trackerInit 注入配置。模板做法见 base/trackerInit.html。 - */ -(function (global) { - "use strict"; - - // 是否已经初始化(避免页面切换/重复 init) - var __inited = false; - // 当前页面信息 - var __pageInfo = { page_key: "", page_name: "" }; - // 来源页面信息 - var __referrerPageInfo = { page_key: "", page_name: "" }; - // 广告已上报展示去重 Set (key = slot+ids) - var __adShownSet = {}; - // 页面加载开始时间 - var __pageStart = (global.performance && global.performance.now) - ? global.performance.now() - : Date.now(); - - // ------- 工具方法 ------- - function isReady() { - return typeof global.WebSDK !== "undefined" && global.WebSDK && global.WebSDK.track; - } - function safeCall(fn) { - if (!isReady()) { - // 等待 SDK 加载完成后再执行 - var timer = setInterval(function () { - if (isReady()) { - clearInterval(timer); - try { fn(); } catch (e) { logError(e); } - } - }, 50); - return; - } - try { fn(); } catch (e) { logError(e); } - } - // sid 是必填公共字段。确认一次存在后置位,后续事件直接同步上报。 - var __sidKnownPresent = false; - // 发送事件前确保 sid 存在:能取到则上报;为空则尝试补齐(notifyPageLoad); - // 仍无法生成则丢弃该事件(满足"无法生成 sid 时丢弃或补齐"的要求)。 - function sendTrack(send) { - safeCall(function () { - if (__sidKnownPresent || !global.WebSDK.getCommonField) { send(); return; } - global.WebSDK.getCommonField("sid").then(function (sid) { - if (sid) { __sidKnownPresent = true; send(); return; } - // 补齐:强制重建会话生成 sid - try { global.WebSDK.notifyPageLoad && global.WebSDK.notifyPageLoad(); } catch (e) {} - global.WebSDK.getCommonField("sid").then(function (sid2) { - if (sid2) { __sidKnownPresent = true; send(); } - else { logError(new Error("sid 无法生成,事件已丢弃")); } // 丢弃 - }).catch(function () { __sidKnownPresent = true; send(); }); - }).catch(function () { __sidKnownPresent = true; send(); }); - }); - } - function logError() {} - // 必填非空字符串字段的安全默认值 - function nonEmpty(v, fallback) { - var s = (v == null) ? "" : String(v); - return s.length ? s : fallback; - } - // 把数值限制在 [min, max] 区间(用于点击百分比 0-100) - function clamp(v, min, max) { - var n = parseInt(v, 10); - if (isNaN(n)) n = 0; - if (n < min) n = min; - if (n > max) n = max; - return n; - } - // Web SDK 文档约定:无标签时 tag_key=default,tag_name=默认标签 - var TAG_KEY_DEFAULT = "default"; - var TAG_NAME_DEFAULT = "默认标签"; - function tagKeyOrDefault(v) { - return nonEmpty(v, TAG_KEY_DEFAULT); - } - function tagNameOrDefault(v) { - return nonEmpty(v, TAG_NAME_DEFAULT); - } - - var __deviceInfo = null; - function parseDeviceInfoFromUA(ua) { - ua = ua || (typeof navigator !== "undefined" ? navigator.userAgent : "") || ""; - var brand = ""; - var model = ""; - var systemName = ""; - var systemVersion = ""; - - if (/iPhone|iPad|iPod/i.test(ua)) { - systemName = "iOS"; - brand = "Apple"; - var ios = ua.match(/OS (\d+[_\d]*) like Mac OS X/i); - systemVersion = ios ? ios[1].replace(/_/g, ".") : ""; - if (/iPad/i.test(ua)) model = "iPad"; - else if (/iPod/i.test(ua)) model = "iPod"; - else model = "iPhone"; - } else if (/Android/i.test(ua)) { - systemName = "Android"; - var and = ua.match(/Android ([\d.]+)/i); - systemVersion = and ? and[1] : ""; - var andModel = ua.match(/Android\s+[\d.]+;\s*([^;)]+?)(?:\s+Build\/|\))/i); - if (andModel) model = andModel[1].trim(); - if (/HUAWEI|HW-|HONOR/i.test(ua)) brand = "HUAWEI"; - else if (/Xiaomi|Redmi|MI\s/i.test(ua)) brand = "Xiaomi"; - else if (/OPPO/i.test(ua)) brand = "OPPO"; - else if (/vivo/i.test(ua)) brand = "vivo"; - else if (/Samsung|SM-/i.test(ua)) brand = "Samsung"; - else if (/OnePlus/i.test(ua)) brand = "OnePlus"; - } else if (/Windows/i.test(ua)) { - systemName = "Windows"; - brand = "PC"; - model = "PC"; - var win = ua.match(/Windows NT ([\d.]+)/i); - systemVersion = win ? win[1] : ""; - } else if (/Mac OS X|Macintosh/i.test(ua)) { - systemName = "macOS"; - brand = "Apple"; - model = "Mac"; - var mac = ua.match(/Mac OS X ([\d_]+)/i); - systemVersion = mac ? mac[1].replace(/_/g, ".") : ""; - } else if (/Linux/i.test(ua)) { - systemName = "Linux"; - brand = "PC"; - model = "PC"; - } else { - brand = "PC"; - model = "PC"; - systemName = "Other"; - } - - return { - deviceBrand: brand, - deviceModel: model, - systemName: systemName, - systemVersion: systemVersion, - }; - } - function getDeviceInfo() { - if (__deviceInfo) return __deviceInfo; - __deviceInfo = parseDeviceInfoFromUA(); - return __deviceInfo; - } - function applyDeviceContextToSdk(info) { - if (!info || !global.WebSDK || !global.WebSDK.setContext) return; - global.WebSDK.setContext({ - deviceBrand: info.deviceBrand || "", - deviceModel: info.deviceModel || "", - systemName: info.systemName || "", - systemVersion: info.systemVersion || "", - }); - } - function enrichDeviceInfoAsync() { - if (typeof navigator === "undefined" || !navigator.userAgentData || - !navigator.userAgentData.getHighEntropyValues) return; - navigator.userAgentData.getHighEntropyValues(["platform", "platformVersion", "model", "brands"]) - .then(function (hints) { - hints = hints || {}; - var info = getDeviceInfo(); - if (hints.platform) { - var p = String(hints.platform).toLowerCase(); - if (p.indexOf("win") >= 0) info.systemName = "Windows"; - else if (p.indexOf("mac") >= 0) info.systemName = "macOS"; - else if (p.indexOf("android") >= 0) info.systemName = "Android"; - else if (p === "ios") info.systemName = "iOS"; - else info.systemName = hints.platform; - } - if (hints.platformVersion) info.systemVersion = String(hints.platformVersion); - if (hints.model) info.deviceModel = String(hints.model); - if (hints.brands && hints.brands.length) { - for (var i = hints.brands.length - 1; i >= 0; i--) { - var b = hints.brands[i]; - if (b && b.brand && b.brand !== "Not)A;Brand" && b.brand !== "Chromium") { - info.deviceBrand = b.brand; - break; - } - } - } - __deviceInfo = info; - applyDeviceContextToSdk(info); - }) - .catch(function () {}); - } - - - var REFERRER_SPECIAL_NAMES = { - direct: "直接访问", - external: "外部站点", - unknown: "未知来源", - }; - - function normalizeReferrer(info) { - info = info || {}; - var pk = nonEmpty(info.page_key, ""); - var pn = nonEmpty(info.page_name, ""); - if (!pk) { - return { page_key: "direct", page_name: "直接访问" }; - } - if (REFERRER_SPECIAL_NAMES[pk]) { - return { - page_key: pk, - page_name: pn || REFERRER_SPECIAL_NAMES[pk], - }; - } - return { page_key: pk, page_name: pn || pk }; - } - - function getReferrerFromURLHint() { - try { - var search = (global.location && global.location.search) || ""; - if (!search) return null; - var p = new URLSearchParams(search); - var src = p.get("utm_source") || p.get("from") || p.get("source") || ""; - src = String(src).trim(); - if (src) { - return { page_key: "external", page_name: src.slice(0, 128) }; - } - } catch (e) {} - return null; - } - /** - * 获取来源页面信息. 来源优先级: - * 1) sessionStorage 里上一次 trackPageView 时存的 {page_key, page_name} - * (同一 tab 内站内跳转最准, 能保留 page_name 中文等) - * 2) document.referrer 解析: - * - 与当前页同源: 用 URL pattern 反推 page_key / page_name - * - 不同源: page_key="external", page_name=referrer 的 host - * 3) 无 referrer 且 sessionStorage 空: page_key="direct", page_name="直接访问" - * (典型场景: 用户从书签 / 地址栏 / 二维码 / app 内嵌外跳直接进站) - * - * 经此调整后 referrer_page_key / referrer_page_name 不再出现空字符串, - * 数据分析侧可按 "direct" / "external" / 具体业务 key 区分来路。 - */ - function getReferrerPageInfo() { - // 1) sessionStorage (站内跳转, 最准) - try { - var raw = sessionStorage.getItem("__tracker_last_page"); - if (raw) { - var obj = JSON.parse(raw); - if (obj && obj.page_key) { - return normalizeReferrer({ - page_key: String(obj.page_key), - page_name: String(obj.page_name || ""), - }); - } - } - } catch (e) {} - - // 2) document.referrer(含外链点击进入) - var ref = ""; - try { ref = document.referrer || ""; } catch (e) {} - if (ref) { - try { - var u = new URL(ref, location.href); - if (u.host === location.host) { - // 同源, 按 URL pattern 推断 page_key / page_name - var hit = inferPageFromPath(u.pathname); - if (hit && hit.page_key) { - return normalizeReferrer(hit); - } - return normalizeReferrer({ - page_key: "internal_other", - page_name: "站内其他页面", - }); - } - return normalizeReferrer({ - page_key: "external", - page_name: u.host || "外部站点", - }); - } catch (e) {} - } - - // 3) URL 投放参数(referrer 为空但属外站导流) - var hint = getReferrerFromURLHint(); - if (hint) return normalizeReferrer(hint); - - // 4) 无来源:书签 / 地址栏 / 二维码 / 外链 noreferrer 等 - return normalizeReferrer({ page_key: "direct", page_name: "直接访问" }); - } - - /** - * 根据 URL pathname 反推 page_key / page_name. - * 顺序需要与各页面模板里 window.__SDK_PAGE__ / trackerInit pageKey 保持一致, - * 后续模板新增页面时记得在这里同步登记。 - */ - function inferPageFromPath(p) { - p = String(p || ""); - if (p === "" || p === "/") { - return { page_key: "home", page_name: "首页" }; - } - // 与 app/api/web/tracker_page.go、各页 trackerInit pageKey 保持一致 - // 专题列表与专题详情共用 /topic 前缀,必须先匹配列表分页。 - if (/^\/(?:topic|more)(?:\/page\/\d+)?\/?$/.test(p)) { - return { page_key: "theme_index", page_name: "专题列表" }; - } - var RULES = [ - [/^\/list(\/|$)/, "list", "短剧列表"], - [/^\/series(\/|$)/, "series", "AI成人剧集"], - [/^\/(?:tag|tag-list)(\/|$)/, "tag_list", "标签详情"], - [/^\/rank(\/|$)/, "rank", "排行榜"], - [/^\/today-update(\/|$)/, "today_update", "今日更新"], - [/^\/category(\/|$)/, "category_detail", "分类详情"], - [/^\/(?:topic|more)(\/|$)/, "topic", "专题详情"], - [/^\/download-app(\/|$)/, "download_app", "下载APP"], - [/^\/application(\/|$)/, "application", "应用推荐"], - [/^\/home-road(\/|$)/, "home_road", "回家的路"], - [/^\/detail(\/|$)/, "detail", "短剧详情"], - [/^\/play(\/|$)/, "play", "播放页"], - [/^\/login(\/|$)/, "login", "登录"], - [/^\/register(\/|$)/, "register", "注册"], - [/^\/user-center(\/|$)/, "user_center", "个人中心"], - [/^\/deposit(\/|$)/, "deposit", "充值中心"], - [/^\/collect(\/|$)/, "collect", "我的收藏"], - [/^\/history(\/|$)/, "history", "观看历史"], - [/^\/setting(\/|$)/, "setting", "设置"], - [/^\/edit-profile(\/|$)/, "edit_profile", "编辑资料"], - [/^\/edit-password(\/|$)/, "edit_password", "修改密码"], - [/^\/search(\/|$)/, "search", "搜索结果页"], - [/^\/about-us(\/|$)/, "about_us", "关于我们"], - [/^\/user-agreement(\/|$)/, "user_agreement", "用户协议"], - [/^\/privacy-policy(\/|$)/, "privacy_policy", "隐私政策"], - [/^\/help-center(\/|$)/, "help_center", "帮助中心"], - [/^\/richtext(\/|$)/, "rich_text", "富文本页"], - [/^\/feedback(\/|$)/, "feedback", "问题反馈"], - [/^\/(?:nav|dh)(\/|$)/, "nav", "官方导航"] - ]; - for (var i = 0; i < RULES.length; i++) { - if (RULES[i][0].test(p)) { - return { page_key: RULES[i][1], page_name: RULES[i][2] }; - } - } - return null; - } - function savePageInfo(info) { - try { - var pk = nonEmpty(info.page_key, ""); - if (!pk) return; - sessionStorage.setItem( - "__tracker_last_page", - JSON.stringify({ page_key: pk, page_name: nonEmpty(info.page_name, "") }) - ); - } catch (e) {} - } - - function normalizeReferrer(info) { - info = info || {}; - var pk = nonEmpty(info.page_key, ""); - var pn = nonEmpty(info.page_name, ""); - if (!pk) { - return { page_key: "direct", page_name: "直接访问" }; - } - if (REFERRER_SPECIAL_NAMES[pk]) { - return { - page_key: pk, - page_name: pn || REFERRER_SPECIAL_NAMES[pk], - }; - } - return { page_key: pk, page_name: pn || pk }; - } - - // 修正无效 navigation_key(如 "0"、cat_、subcat_) - function normalizeNavigationKey(navKey, navName, href) { - var k = String(navKey || "").trim(); - var invalid = !k || k === "0" || k === "cat_" || k === "subcat_" || - /^cat_0$/.test(k) || /^subcat_0$/.test(k); - if (!invalid) return k; - if (href) { - try { - var u = new URL(href, location.href); - var pathKey = u.pathname.replace(/^\/+|\/+$/g, "").replace(/[^\w]+/g, "_"); - if (pathKey) return "nav_path_" + pathKey; - } catch (e) {} - } - var slug = String(navName || "").trim().replace(/\s+/g, "_").replace(/[^\w一-龥]+/g, "_"); - if (slug) return "nav_name_" + slug.slice(0, 48); - return "nav_other"; - } - - // ------- 公共字段补齐:device_brand / device_model / system_name / system_version ------- - // 背景:web-sdk 仅上报 device(Android/iOS/PC) 与 user_agent, - // device_brand / device_model 被 SDK 硬编码为空,且不含 system_name / system_version, - // 也无公开 API 注入顶层公共字段。这里基于 user_agent 解析并在上报前补齐这四个字段。 - function guessAndroidBrand(model, ua) { - var s = (model || "") + " " + (ua || ""); - var map = [ - [/Build\/HONOR|\bHONOR\b/i, "Honor"], - [/Build\/HUAWEI|HUAWEI|HW-|HMA-|ELS-|ANA-|\bJAD-|\bNOH-/i, "Huawei"], - // 华为/荣耀机型代号:XXX-AL00 / -AN00 / -TL00 / -AN90 等 - [/\b[A-Z]{2,4}-(AL|AN|TL|EM|DL)\d{2}\b/, "Huawei"], - [/SM-|samsung|Galaxy/i, "Samsung"], - [/Pixel/i, "Google"], - [/Redmi|POCO|XiaoMi|\bMi\b|Mi\s?\d/i, "Xiaomi"], - // 小米/红米数字机型代号:2xxxxxxx / 2xxxxAA 等(以 2 开头的全大写数字混合码) - [/\b2[0-9]{3,4}[A-Z][A-Z0-9]{1,}\b/, "Xiaomi"], - [/OPPO|\bCPH\d{3,4}|\bPB\w{2}|\bP[A-Z]{2}\d{2}\b/, "OPPO"], - [/vivo|\bV\d{4}[A-Z]?\b/i, "vivo"], - [/OnePlus|ONEPLUS|\bGM19|\bHD19|\bKB20|\bLE21/i, "OnePlus"], - [/realme|\bRMX\d{3,4}/i, "realme"], - [/Meizu/i, "Meizu"], - [/Lenovo/i, "Lenovo"], - [/\bZTE\b/i, "ZTE"], - [/Nokia/i, "Nokia"], - [/Sony|\bSO-|\bXQ-/i, "Sony"], - [/Motorola|\bmoto\b/i, "Motorola"], - [/asus/i, "ASUS"] - ]; - for (var i = 0; i < map.length; i++) { - if (map[i][0].test(s)) return map[i][1]; - } - return ""; - } - - // 爬虫/机器人识别:这类 UA 无真实设备/系统,统一标记 system_name="Bot", - // 避免设备字段为空(满足"无法解析需补充特殊情况说明")。 - function parseBotInfo(ua) { - var known = [ - [/bingbot/i, "Bing", "bingbot"], - [/googlebot/i, "Google", "Googlebot"], - [/baiduspider/i, "Baidu", "Baiduspider"], - [/yandex(bot|\.com\/bots)/i, "Yandex", "YandexBot"], - [/duckduckbot/i, "DuckDuckGo", "DuckDuckBot"], - [/bytespider/i, "ByteDance", "Bytespider"], - [/petalbot/i, "Huawei", "PetalBot"], - [/applebot/i, "Apple", "Applebot"], - [/sogou.{0,6}spider/i, "Sogou", "Sogou Spider"], - [/360spider/i, "360", "360Spider"], - [/semrushbot/i, "Semrush", "SemrushBot"], - [/ahrefsbot/i, "Ahrefs", "AhrefsBot"], - [/facebookexternalhit/i, "Facebook", "facebookexternalhit"] - ]; - var ver = (ua.match(/(?:bot|spider)[\/ ]v?([\d.]+)/i) || [])[1] || ""; - for (var i = 0; i < known.length; i++) { - if (known[i][0].test(ua)) { - return { device_brand: known[i][1], device_model: known[i][2], system_name: "Bot", system_version: ver }; - } - } - // 通用:"bot/x.y" 或 "spider/x.y"(正常浏览器不含该模式,避免误判) - var gm = ua.match(/([A-Za-z][A-Za-z]*bot)\/([\d.]+)/) || ua.match(/([A-Za-z][A-Za-z]*spider)\/?([\d.]*)/i); - if (gm) return { device_brand: "", device_model: gm[1], system_name: "Bot", system_version: gm[2] || "" }; - return null; - } - - // 低熵 Client Hints(同步可得),用于 UA 为空时兜底 - function getClientHintsInfo() { - var out = { system_name: "", mobile: null, brands: "" }; - try { - var uad = (typeof navigator !== "undefined") && navigator.userAgentData; - if (uad) { - if (uad.platform) out.system_name = String(uad.platform); // Windows/macOS/Android/iOS/Linux/Chrome OS... - if (typeof uad.mobile === "boolean") out.mobile = uad.mobile; - if (Array.isArray(uad.brands)) { - out.brands = uad.brands.map(function (b) { return b.brand + "/" + b.version; }).join(" "); - } - } - } catch (e) {} - return out; - } - - // user_agent 兜底:navigator.userAgent 为空时,用 Client Hints / navigator 信息拼一个非空值, - // 保证 PC/Web 场景 user_agent 至少有值(要求:无法取到时也不能为空)。 - function buildUAFallback() { - try { - var nav = (typeof navigator !== "undefined") ? navigator : null; - if (nav && nav.userAgent) return nav.userAgent; - var ch = getClientHintsInfo(); - var parts = []; - if (ch.system_name) parts.push(ch.system_name); - if (ch.brands) parts.push(ch.brands); - if (nav && nav.platform) parts.push(nav.platform); - if (nav && nav.appVersion) parts.push(nav.appVersion); - if (nav && nav.language) parts.push(nav.language); - var s = parts.filter(Boolean).join("; ").replace(/\s+/g, " ").trim(); - return s || "unknown"; - } catch (e) { return "unknown"; } - } - - // 提取安卓/鸿蒙机型:设备代号(如 ADY-AL10) 优先;其次 "; Build/";再次 "; )"。 - // 过滤掉操作系统/SDK/语言等非机型 token,避免把 "OpenHarmony 6.1"、"HMSCore x.y" 当成机型。 - function extractAndroidModel(ua) { - // 1) 设备代号:3 段式(XXX-AL00 / ADY-AL10 等) - var code = ua.match(/;\s*([A-Z][A-Z0-9]{1,5}-[A-Z0-9]{2,})/); - if (code) return code[1].trim(); - // 2) "; Build/" 或 "; )" - var mm = ua.match(/;\s*([^;()]+?)\s+Build\//) || - ua.match(/;\s*([^;()]+?)\)\s*(?:AppleWebKit|Gecko|$)/); - var model = mm ? mm[1].trim() : ""; - model = model.replace(/\bwv\b/i, "").replace(/^U\s+/i, "") - .replace(/\bzh[-_][A-Za-z-]+\b/gi, "").replace(/\s+/g, " ").trim(); - // 丢弃系统/SDK/无效 token - if (/^(rv:|Mobile$|Tablet$)/i.test(model) || - /OpenHarmony|HarmonyOS|HMSCore|^Android\b|^Linux\b/i.test(model) || - !/[A-Za-z0-9]/.test(model)) model = ""; - return model; - } - - function parseUAClientInfo(ua) { - ua = ua || ((typeof navigator !== "undefined" && navigator.userAgent) || ""); - var out = { device_brand: "", device_model: "", system_name: "", system_version: "" }; - try { - // 0) 爬虫/机器人优先识别 - var bot = parseBotInfo(ua); - if (bot) return bot; - var m; - if (/iPhone|iPad|iPod/.test(ua)) { - out.device_brand = "Apple"; - out.device_model = /iPad/.test(ua) ? "iPad" : (/iPod/.test(ua) ? "iPod" : "iPhone"); - out.system_name = "iOS"; - m = ua.match(/OS (\d+[_.]\d+(?:[_.]\d+)?)/); - if (m) out.system_version = m[1].replace(/_/g, "."); - } else if (/OpenHarmony|HarmonyOS|ArkWeb/i.test(ua)) { - // 鸿蒙(含 "Android x; HarmonyOS" 兼容标识) - out.system_name = "HarmonyOS"; - m = ua.match(/(?:OpenHarmony|HarmonyOS)\s+([\d.]+)/i) || ua.match(/Android\s+([\d.]+)/); - if (m) out.system_version = m[1]; - out.device_model = extractAndroidModel(ua) || (/Tablet/i.test(ua) ? "Tablet" : "Phone"); - out.device_brand = guessAndroidBrand(out.device_model, ua); - } else if (/Android/.test(ua)) { - out.system_name = "Android"; - m = ua.match(/Android\s+([\d.]+)/); - if (m) out.system_version = m[1]; - var model = extractAndroidModel(ua); - // 部分浏览器(Firefox 等)不带机型,回退为 Mobile/Tablet,避免空值 - if (!model) model = /Tablet/i.test(ua) ? "Tablet" : "Mobile"; - out.device_model = model; - out.device_brand = guessAndroidBrand(model, ua); - } else if (/Windows NT/.test(ua)) { - out.system_name = "Windows"; - m = ua.match(/Windows NT ([\d.]+)/); - var winMap = { "10.0": "10", "6.3": "8.1", "6.2": "8", "6.1": "7" }; - if (m) out.system_version = winMap[m[1]] || m[1]; - out.device_model = "PC"; - } else if (/Windows/i.test(ua)) { - // 非标准 Windows UA(如 "X11; Windows" 的 HeyTap/Oppo 浏览器) - out.system_name = "Windows"; - out.device_model = "PC"; - } else if (/CrOS/.test(ua)) { - // ChromeOS - out.system_name = "Chrome OS"; - out.device_model = "PC"; - m = ua.match(/CrOS\s+\S+\s+([\d.]+)/); - if (m) out.system_version = m[1]; - } else if (/Macintosh|Mac OS X/.test(ua)) { - // iPadOS 13+ 的 Safari 桌面模式会伪装成 Macintosh,需用触点数纠正为 iPad - var isTouchIPad = false; - try { - isTouchIPad = (typeof navigator !== "undefined") && - navigator.maxTouchPoints && navigator.maxTouchPoints > 1 && - /Macintosh/.test(ua); - } catch (e) {} - if (isTouchIPad) { - out.system_name = "iOS"; - out.device_brand = "Apple"; - out.device_model = "iPad"; - m = ua.match(/Version\/(\d+[_.]\d+(?:[_.]\d+)?)/); - if (m) out.system_version = m[1].replace(/_/g, "."); - } else { - out.system_name = "macOS"; - out.device_brand = "Apple"; - out.device_model = "Mac"; - m = ua.match(/Mac OS X (\d+[_.]\d+(?:[_.]\d+)?)/); - if (m) out.system_version = m[1].replace(/_/g, "."); - } - } else if (/Linux/.test(ua)) { - out.system_name = "Linux"; - out.device_model = "PC"; - } - // UA 为空或未解析出时,用 Client Hints(navigator.userAgentData) 兜底系统名/机型 - if (!out.system_name || !out.device_model) { - var ch = getClientHintsInfo(); - if (!out.system_name && ch.system_name) out.system_name = ch.system_name; - if (!out.device_model) { - if (ch.mobile === false) out.device_model = "PC"; - else if (ch.mobile === true) out.device_model = "Mobile"; - } - if (!out.device_brand && ch.system_name === "macOS") out.device_brand = "Apple"; - } - // 最终兜底:保证 system_name / system_version 不为空(需求:不能为空) - if (!out.system_name) { - // 依据设备类型给出系统名,最差为 Unknown - var dev = guessDeviceTypeFromUA(ua); - out.system_name = dev === "Android" ? "Android" - : dev === "iOS" ? "iOS" - : (dev === "PC" ? "PC" : "Unknown"); - } - if (!out.system_version) out.system_version = "0.0.0"; - if (!out.device_model) out.device_model = "Unknown"; - } catch (e) {} - return out; - } - - // 设备类型(Android/iOS/PC)推断,与 web-sdk 内部口径一致,UA 兜底 - function guessDeviceTypeFromUA(ua) { - ua = ua || ((typeof navigator !== "undefined" && navigator.userAgent) || ""); - if (/Android/i.test(ua)) return "Android"; - if (/iPhone|iPad|iPod/i.test(ua)) return "iOS"; - try { - // iPadOS 桌面模式 - if (/Macintosh/.test(ua) && typeof navigator !== "undefined" && - navigator.maxTouchPoints > 1) return "iOS"; - } catch (e) {} - return "PC"; - } - - // 设备指纹兜底:当 SDK 未能生成 device_fingerprint 时,基于稳定特征生成一个非空指纹。 - function buildFingerprintFallback() { - try { - var nav = (typeof navigator !== "undefined") ? navigator : {}; - var scr = (typeof screen !== "undefined") ? screen : {}; - var parts = [ - nav.userAgent || "", nav.platform || "", nav.language || "", - (scr.width || "") + "x" + (scr.height || ""), - scr.colorDepth || "", - (new Date()).getTimezoneOffset(), - nav.hardwareConcurrency || "", nav.deviceMemory || "" - ].join("||"); - // 简易 FNV-1a 32 位哈希 - var h = 2166136261 >>> 0; - for (var i = 0; i < parts.length; i++) { - h ^= parts.charCodeAt(i); - h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0; - } - var hex = h.toString(16); - while (hex.length < 8) hex = "0" + hex; - return "fp_fb_" + hex; - } catch (e) { - return "fp_fb_unknown"; - } - } - - // 安装上报请求拦截,在 SDK 发送批量上报前为每条记录补齐公共字段: - // device_brand / device_model / system_name / system_version(基于 UA / Client Hints) - // user_agent(为空时用兜底值,保证非空) - // 仅对上报端点(eventTracking/batchReport)生效;全程 try/catch,任何异常都回退原始请求,不影响上报。 - function installCommonFieldPatch() { - if (global.__trackerCFPatch) return; - global.__trackerCFPatch = true; - var info = parseUAClientInfo(); - var uaFallback = buildUAFallback(); // 始终非空(最差为 "unknown") - var deviceFallback = guessDeviceTypeFromUA(); // Android / iOS / PC - var fpFallback = buildFingerprintFallback(); // device_fingerprint 兜底(非空) - var SDK_VERSION_FALLBACK = "1.1.8"; // 与所引入的 web-sdk 版本一致 - var FP_VERSION_FALLBACK = "1.0.0"; - var APP_VERSION_FALLBACK = "1.0.0"; - // 始终安装:即便设备字段解析为空,也要保证公共字段非空 - - function isReportUrl(u) { - try { return /eventTracking\/batchReport/i.test(String(u || "")); } catch (e) { return false; } - } - function augmentBody(body) { - try { - if (typeof body !== "string" || !body) return null; - var arr = JSON.parse(body); - if (!Array.isArray(arr) || !arr.length) return null; - var changed = false; - for (var i = 0; i < arr.length; i++) { - var r = arr[i]; - if (!r || typeof r !== "object") continue; - if (!r.device_brand && info.device_brand) { r.device_brand = info.device_brand; changed = true; } - if (!r.device_model && info.device_model) { r.device_model = info.device_model; changed = true; } - if (!r.system_name && info.system_name) { r.system_name = info.system_name; changed = true; } - if (!r.system_version && info.system_version) { r.system_version = info.system_version; changed = true; } - // 以下字段强制保证非空(需求:不能为空) - if (!r.device) { r.device = deviceFallback; changed = true; } - if (!r.system_name) { r.system_name = "Unknown"; changed = true; } - if (!r.system_version) { r.system_version = "0.0.0"; changed = true; } - if (!r.device_fingerprint) { r.device_fingerprint = fpFallback; changed = true; } - if (!r.fp_version) { r.fp_version = FP_VERSION_FALLBACK; changed = true; } - if (!r.sdk_version) { r.sdk_version = SDK_VERSION_FALLBACK; changed = true; } - if (!r.app_version) { r.app_version = APP_VERSION_FALLBACK; changed = true; } - // user_agent 为空(含 undefined/null/空串)时补齐,保证非空 - if (!r.user_agent && uaFallback) { r.user_agent = uaFallback; changed = true; } - } - return changed ? JSON.stringify(arr) : null; - } catch (e) { return null; } - } - - // fetch(SDK 默认走 fetch) - if (typeof global.fetch === "function") { - var origFetch = global.fetch; - global.fetch = function (input, init) { - try { - var url = (typeof input === "string") ? input : (input && input.url); - if (isReportUrl(url) && init && typeof init.body === "string") { - var nb = augmentBody(init.body); - if (nb != null) init.body = nb; - } - } catch (e) {} - return origFetch.apply(this, arguments); - }; - } - // XHR 兜底 - if (typeof XMLHttpRequest !== "undefined") { - var op = XMLHttpRequest.prototype.open; - var os = XMLHttpRequest.prototype.send; - XMLHttpRequest.prototype.open = function (method, url) { - try { this.__trackerReportUrl = isReportUrl(url); } catch (e) {} - return op.apply(this, arguments); - }; - XMLHttpRequest.prototype.send = function (body) { - try { - if (this.__trackerReportUrl && typeof body === "string") { - var nb = augmentBody(body); - if (nb != null) return os.call(this, nb); - } - } catch (e) {} - return os.apply(this, arguments); - }; - } - // sendBeacon(默认关闭,仅兜底字符串入参) - try { - if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") { - var ob = navigator.sendBeacon.bind(navigator); - navigator.sendBeacon = function (url, data) { - try { - if (isReportUrl(url) && typeof data === "string") { - var nb = augmentBody(data); - if (nb != null) data = nb; - } - } catch (e) {} - return ob(url, data); - }; - } - } catch (e) {} - } - - // ------- 初始化 ------- - function trackerInit(opts) { - opts = opts || {}; - // 0) 安装公共字段补齐(须在 SDK 发出任何上报前完成) - installCommonFieldPatch(); - // 设置当前页面信息(即便 SDK 尚未加载也先保存,后续点击事件能用到) - var inferredPage = inferPageFromPath((global.location && global.location.pathname) || "") || { - page_key: "internal_other", - page_name: "站内其他页面", - }; - var configuredPageKey = nonEmpty(opts.pageKey, ""); - var configuredPageName = nonEmpty(opts.pageName, ""); - __pageInfo.page_key = (!configuredPageKey || configuredPageKey === "unknown") - ? inferredPage.page_key - : configuredPageKey; - __pageInfo.page_name = (!configuredPageName || configuredPageName === "未知页面") - ? inferredPage.page_name - : configuredPageName; - __referrerPageInfo = getReferrerPageInfo(); - - // 1) 先注册全局点击监听(document 一定存在,不依赖 DOM 完成) - bindClickListeners(); - - safeCall(function () { - if (!__inited) { - global.WebSDK.init({ - appId: opts.appId, - uid: opts.uid || "", - channel: opts.channel || "", - encryptedConfig: opts.encryptedConfig || "", - appVersion: opts.appVersion || "1.0.1", - // 项目会手动上报带业务页面字段的 PV/点击,关闭 SDK 自动事件避免重复上报。 - disableAutoEvents: ["app_page_view", "page_click"], - }); - // 补齐 sid:会话开始即强制生成并持久化 sid(必填公共字段), - // 避免首个事件因 sid 尚未生成而上报空 sid。 - try { global.WebSDK.notifyPageLoad && global.WebSDK.notifyPageLoad(); } catch (e) {} - // 预热并缓存 sid 状态,确认存在后后续事件无需再异步校验 - try { - if (global.WebSDK.getCommonField) { - global.WebSDK.getCommonField("sid").then(function (sid) { - if (sid) __sidKnownPresent = true; - }).catch(function () {}); - } - } catch (e) {} - __inited = true; - } - // 2) 页面展示:等 DOM 解析完再上报,避免 SDK 还在做域名 ping 时事件丢失 - whenDomReady(function () { - trackPageView(); - // 保存当前页面信息以便下一次跳转作为 referrer - savePageInfo(__pageInfo); - if (!global.__trackerReady) { - global.__trackerReady = true; - try { - global.dispatchEvent(new CustomEvent("tracker:ready", { detail: currentPageInfo() })); - } catch (e) { - try { - var readyEvent = document.createEvent("Event"); - readyEvent.initEvent("tracker:ready", false, false); - global.dispatchEvent(readyEvent); - } catch (ignored) {} - } - } - }); - // 3) 广告展示观察必须等到 DOM 解析完 - whenDomReady(function () { - bindAdImpressionObserver(); - bindAdMutationObserver(); - if (document.readyState === "complete") { - rescanAds(); - } else { - global.addEventListener("load", rescanAds); - } - }); - }); - } - - // 等 DOMContentLoaded - function whenDomReady(cb) { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", function () { cb(); }, { once: true }); - } else { - cb(); - } - } - - // ------- 公共上报方法 ------- - - // 1. 导航路径行为 - function trackNavigation(navKey, navName, href) { - var k = normalizeNavigationKey(navKey, navName, href); - var n = nonEmpty(navName, k); - if (!k || k === "0") return; - sendTrack(function () { - global.WebSDK.track.navigation({ - navigation_key: k, - navigation_name: n, - }); - }); - } - - // 2. 应用页面展示 - function trackPageView(extra) { - extra = extra || {}; - var loadTime = 0; - try { - var nav = (global.performance && typeof global.performance.getEntriesByType === "function") - ? (global.performance.getEntriesByType("navigation")[0] || null) - : null; - if (nav && nav.loadEventEnd > 0 && nav.startTime != null) { - loadTime = Math.max(0, Math.round(nav.loadEventEnd - nav.startTime)); - } else { - var now = (global.performance && global.performance.now) - ? global.performance.now() - : Date.now(); - loadTime = Math.max(0, Math.round(now - __pageStart)); - } - } catch (e) {} - var pi = currentPageInfo(); - var pageKey = pi.page_key; - var pageName = pi.page_name; - var ref = normalizeReferrer(getReferrerPageInfo()); - sendTrack(function () { - global.WebSDK.track.appPageView({ - user_type: nonEmpty(extra.user_type, "guest"), - page_key: pageKey, - page_name: pageName, - referrer_page_key: ref.page_key, - referrer_page_name: ref.page_name, - current_page_key: pageKey, - current_page_name: pageName, - page_load_time: parseInt(extra.page_load_time != null ? extra.page_load_time : loadTime, 10) || 0, - recommend_trace_id: extra.recommend_trace_id || "", - }); - }); - } - - // 3. 应用点击事件 - // 注意(需求): click_x_percent / click_y_percent = 点击位置横/纵坐标百分比(0-100)。 - // SDK 1.1.8 起 click_page_x/click_page_y 使用视口坐标(与 clientX/clientY 一致), - // 百分比也按视口尺寸计算;screen.width/height 仅作为独立的屏幕分辨率字段上报。 - function trackPageClick(e) { - if (!isReady()) return; - try { - var sw = (global.screen && global.screen.width) || 0; // 屏幕分辨率(单独字段上报) - var sh = (global.screen && global.screen.height) || 0; - var docEl = document.documentElement || {}; - var viewportW = global.innerWidth || docEl.clientWidth || 0; - var viewportH = global.innerHeight || docEl.clientHeight || 0; - // 点击的视口坐标(不含页面滚动距离) - var x = e.clientX != null ? e.clientX : 0; - var y = e.clientY != null ? e.clientY : 0; - // 横/纵坐标百分比(0-100) - var xPct = viewportW > 0 ? clamp((x / viewportW) * 100, 0, 100) : 0; - var yPct = viewportH > 0 ? clamp((y / viewportH) * 100, 0, 100) : 0; - var pi = currentPageInfo(); - sendTrack(function () { - global.WebSDK.track.pageClick({ - page_key: pi.page_key, - page_name: pi.page_name, - click_page_x: parseInt(x, 10) || 0, - click_page_y: parseInt(y, 10) || 0, - click_x_percent: xPct, - click_y_percent: yPct, - screen_width: parseInt(sw, 10) || 0, - screen_height: parseInt(sh, 10) || 0, - recommend_trace_id: "", - }); - }); - } catch (err) { - logError(err); - } - } - - // 4. 视频事件(播放、暂停) - function trackVideoEvent(params) { - params = params || {}; - var video_id = nonEmpty(params.video_id, ""); - if (!video_id) return; - sendTrack(function () { - global.WebSDK.track.videoEvent({ - media_id: String(params.media_id || video_id), - video_id: video_id, - video_title: nonEmpty(params.video_title, "未命名视频"), - video_type_id: nonEmpty(params.video_type_id, "0"), - video_type_name: nonEmpty(params.video_type_name, "未分类"), - video_content_type: String(params.video_content_type || "video"), - recommend_trace_id: String(params.recommend_trace_id || ""), - video_tag_key: tagKeyOrDefault(params.video_tag_key), - video_tag_name: tagNameOrDefault(params.video_tag_name), - video_duration: parseInt(params.video_duration || 0, 10) || 0, - play_duration: parseInt(params.play_duration || 0, 10) || 0, - play_progress: clamp(params.play_progress || 0, 0, 100), - video_behavior_key: nonEmpty(params.video_behavior_key, "video_view"), - video_behavior_name: nonEmpty(params.video_behavior_name, "视频行为"), - }); - }); - } - - // 阅读进度百分比限制在 0-100 - function clampProgress(n) { - var v = parseInt(n || 0, 10); - if (!isFinite(v) || v < 0) return 0; - if (v > 100) return 100; - return v; - } - - // 4.1 小说事件 novel_event(按埋点文档对齐) - // 上报来源:客户端;必填 novel_id/novel_title/novel_type_id/novel_type_name/ - // novel_behavior_key/novel_behavior_name;必填可空 novel_tag_key/novel_tag_name; - // 整数 read_progress(0-100)/page_no(从 1 开始;列表页 0)。 - // chapter_id / chapter_name 没有章节传 "";recommend_trace_id 未对接推荐引擎传 ""。 - // novel_behavior_key 取值:view(展示) / page_next(下一页) / page_prev(上一页) / complete(读完)。 - function trackNovelEvent(params) { - params = params || {}; - var novel_id = nonEmpty(params.novel_id, ""); - if (!novel_id) return; - sendTrack(function () { - global.WebSDK.track.novelEvent({ - media_id: String(params.media_id || novel_id), - novel_id: novel_id, - novel_title: nonEmpty(params.novel_title, "未命名小说"), - chapter_id: String(params.chapter_id || ""), - chapter_name: String(params.chapter_name || ""), - novel_type_id: nonEmpty(params.novel_type_id, "default"), - novel_type_name: nonEmpty(params.novel_type_name, "默认分类"), - recommend_trace_id: String(params.recommend_trace_id || ""), - novel_tag_key: tagKeyOrDefault(params.novel_tag_key), - novel_tag_name: tagNameOrDefault(params.novel_tag_name), - read_progress: clampProgress(params.read_progress), - page_no: parseInt(params.page_no || 0, 10) || 0, - novel_behavior_key: nonEmpty(params.novel_behavior_key, "view"), - novel_behavior_name: nonEmpty(params.novel_behavior_name, "展示"), - }); - }); - } - - // 4.2 漫画事件 comic_event(按埋点文档对齐,字段口径同 novel_event) - // comic_behavior_key 取值:view(展示) / page_next(下一页) / page_prev(上一页) / complete(读完)。 - function trackComicEvent(params) { - params = params || {}; - var comic_id = nonEmpty(params.comic_id, ""); - if (!comic_id) return; - sendTrack(function () { - global.WebSDK.track.comicEvent({ - media_id: String(params.media_id || comic_id), - comic_id: comic_id, - comic_title: nonEmpty(params.comic_title, "未命名漫画"), - chapter_id: String(params.chapter_id || ""), - chapter_name: String(params.chapter_name || ""), - comic_type_id: nonEmpty(params.comic_type_id, "default"), - comic_type_name: nonEmpty(params.comic_type_name, "默认分类"), - recommend_trace_id: String(params.recommend_trace_id || ""), - comic_tag_key: tagKeyOrDefault(params.comic_tag_key), - comic_tag_name: tagNameOrDefault(params.comic_tag_name), - read_progress: clampProgress(params.read_progress), - page_no: parseInt(params.page_no || 0, 10) || 0, - comic_behavior_key: nonEmpty(params.comic_behavior_key, "view"), - comic_behavior_name: nonEmpty(params.comic_behavior_name, "展示"), - }); - }); - } - - // 5. 关键词搜索事件 - function trackKeywordSearch(params) { - params = params || {}; - var keyword = nonEmpty(params.keyword, ""); - if (!keyword) return; - sendTrack(function () { - global.WebSDK.track.keywordSearch({ - keyword: keyword, - search_result_count: parseInt(params.search_result_count || 0, 10) || 0, - search_content_type: String(params.search_content_type || ""), - search_trace_id: String(params.search_trace_id || ""), - search_id: String(params.search_id || ""), - }); - }); - } - - // 6. 关键词搜索结果点击事件 - function trackKeywordClick(params) { - params = params || {}; - var keyword = nonEmpty(params.keyword, ""); - var itemId = nonEmpty(params.click_item_id, ""); - if (!keyword || !itemId) return; - sendTrack(function () { - global.WebSDK.track.keywordClick({ - keyword: keyword, - click_item_id: itemId, - click_item_type_key: nonEmpty(params.click_item_type_key, "video"), - click_item_type_name: nonEmpty(params.click_item_type_name, "视频"), - click_position: parseInt(params.click_position || 0, 10) || 0, - search_trace_id: String(params.search_trace_id || ""), - }); - }); - } - - // 9. 广告页面点击事件 (event_type: click/close/show) - function trackAdvertising(params) { - params = params || {}; - var id = nonEmpty(params.advertising_id, ""); - if (!id) return; - sendTrack(function () { - global.WebSDK.track.advertising({ - event_type: nonEmpty(params.event_type, "click"), - advertising_key: nonEmpty(params.advertising_key, id), - advertising_name: nonEmpty(params.advertising_name, "广告_" + id), - advertising_id: id, - }); - }); - } - - // 7. 广告展示日志 - function trackAdImpression(params) { - params = params || {}; - var ad_id = nonEmpty(params.ad_id, ""); - var pi = currentPageInfo(); - var ad_slot_key = nonEmpty(params.ad_slot_key, pi.page_key + "_ad"); - var ad_slot_name = nonEmpty(params.ad_slot_name, pi.page_name + "广告位"); - if (!ad_id) return; - var dedupKey = [ad_slot_key, ad_id, params.creative_id || ""].join("|"); - if (__adShownSet[dedupKey]) return; - __adShownSet[dedupKey] = true; - var payload = { - page_key: nonEmpty(params.page_key, pi.page_key), - page_name: nonEmpty(params.page_name, pi.page_name), - ad_slot_key: ad_slot_key, - ad_slot_name: ad_slot_name, - ad_id: ad_id, - creative_id: String(params.creative_id || ad_id), - ad_type: nonEmpty(params.ad_type, "banner"), - }; - // 强制保证关键字段非空(需求:page_key/page_name/ad_type/creative_id 不能为空) - if (!payload.page_key) payload.page_key = "internal_other"; - if (!payload.page_name) payload.page_name = "站内其他页面"; - if (!payload.ad_type) payload.ad_type = "banner"; - if (!payload.creative_id) payload.creative_id = ad_id; - sendTrack(function () { - global.WebSDK.track.adImpression(payload); - }); - } - - // 点击前补发展示,保证 ad_impression.client_ts <= ad_click.client_ts - function ensureAdImpressionBeforeClick(ad) { - if (!ad || !ad.ad_id) return; - var ad_slot_key = nonEmpty(ad.ad_slot_key, currentPageInfo().page_key + "_ad"); - var dedupKey = [ad_slot_key, ad.ad_id, ad.creative_id || ""].join("|"); - if (!__adShownSet[dedupKey]) { - trackAdImpression(ad); - } - } - - // 8. 广告点击日志 - // 注意(需求): page_key、page_name、ad_type、creative_id 不能为空,缺省取当前页面值 / banner / ad_id - function trackAdClick(params) { - params = params || {}; - var ad_id = nonEmpty(params.ad_id, ""); - if (!ad_id) return; - ensureAdImpressionBeforeClick(params); - var pi = currentPageInfo(); - var payload = { - page_key: nonEmpty(params.page_key, pi.page_key), - page_name: nonEmpty(params.page_name, pi.page_name), - ad_slot_key: nonEmpty(params.ad_slot_key, pi.page_key + "_ad"), - ad_slot_name: nonEmpty(params.ad_slot_name, pi.page_name + "广告位"), - ad_id: ad_id, - creative_id: String(params.creative_id || ad_id), - ad_type: nonEmpty(params.ad_type, "banner"), - }; - // 强制保证关键字段非空(需求 1:page_key/page_name/ad_type/creative_id 不能为空) - if (!payload.page_key) payload.page_key = "internal_other"; - if (!payload.page_name) payload.page_name = "站内其他页面"; - if (!payload.ad_type) payload.ad_type = "banner"; - if (!payload.creative_id) payload.creative_id = ad_id; - sendTrack(function () { - global.WebSDK.track.adClick(payload); - }); - } - - // ------- 自动收集逻辑 ------- - - // 通过 data-* 属性的 DOM 收集广告展示(IntersectionObserver) - function bindAdImpressionObserver() { - if (!global.IntersectionObserver) return; - if (global.__trackerAdObserver) return; - global.__trackerAdObserver = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - if (entry.isIntersecting) { - var el = entry.target; - var ad = parseAdDataset(el); - if (ad && ad.ad_id) { - trackAdImpression(ad); - global.__trackerAdObserver.unobserve(el); - } - } - }); - }, { threshold: 0.1 }); - rescanAds(); - fireVisibleAdsSync(); - } - - // 同步上报首屏可见广告 - function fireVisibleAdsSync() { - var vw = global.innerWidth || (document.documentElement && document.documentElement.clientWidth) || 0; - var vh = global.innerHeight || (document.documentElement && document.documentElement.clientHeight) || 0; - var nodes = document.querySelectorAll("[data-ad-id]"); - for (var i = 0; i < nodes.length; i++) { - var el = nodes[i]; - if (el.__trackerSyncFired) continue; - if (!isElementInViewport(el, vw, vh)) continue; - var ad = parseAdDataset(el); - if (!ad || !ad.ad_id) continue; - trackAdImpression(ad); - el.__trackerSyncFired = true; - try { global.__trackerAdObserver && global.__trackerAdObserver.unobserve(el); } catch (e) {} - } - } - - function isElementInViewport(el, vw, vh) { - try { - var r = el.getBoundingClientRect(); - if (r.width <= 0 || r.height <= 0) return false; - var horizOk = r.right > 0 && r.left < vw; - var vertOk = r.bottom > 0 && r.top < vh; - return horizOk && vertOk; - } catch (e) { return false; } - } - - // 重新扫描所有 [data-ad-id] 元素并注册到 observer - function rescanAds() { - if (!global.__trackerAdObserver) return; - var nodes = document.querySelectorAll("[data-ad-id]"); - for (var i = 0; i < nodes.length; i++) { - var el = nodes[i]; - if (el.__trackerObserved) continue; - el.__trackerObserved = true; - try { global.__trackerAdObserver.observe(el); } catch (e) {} - } - } - - // 监听 DOM 动态插入广告元素(懒加载、弹窗显示、swiper clone 等) - function bindAdMutationObserver() { - if (typeof MutationObserver === "undefined") return; - if (global.__trackerMutationObserver) return; - global.__trackerMutationObserver = new MutationObserver(function (mutations) { - var needRescan = false; - for (var i = 0; i < mutations.length; i++) { - var m = mutations[i]; - if (m.addedNodes && m.addedNodes.length) { needRescan = true; break; } - if (m.type === "attributes" && m.attributeName === "data-ad-id") { needRescan = true; break; } - } - if (needRescan) rescanAds(); - }); - try { - global.__trackerMutationObserver.observe(document.body || document.documentElement, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ["data-ad-id"], - }); - } catch (e) {} - } - - // 当前页面信息:优先 trackerInit 设置的值;为空则按 URL 推断;最后归为站内其他页面。 - // 保证 page_key / page_name 取到真实当前页面值,不会为空。 - function currentPageInfo() { - var pk = nonEmpty(__pageInfo.page_key, ""); - var pn = nonEmpty(__pageInfo.page_name, ""); - if (pk) return { page_key: pk, page_name: nonEmpty(pn, pk) }; - var hit = inferPageFromPath((global.location && global.location.pathname) || ""); - if (hit && hit.page_key) { - return { page_key: hit.page_key, page_name: nonEmpty(hit.page_name, hit.page_key) }; - } - return { page_key: "internal_other", page_name: "站内其他页面" }; - } - - function parseAdDataset(el) { - if (!el || !el.dataset) return null; - var pi = currentPageInfo(); - // 槽位兜底:优先 data-ad-slot-key;缺失时用 data-ad-location 推导(旧版脚本只写了 location), - // 保证不再出现 unknown_slot。 - var slotKey = el.dataset.adSlotKey || ""; - var slotName = el.dataset.adSlotName || ""; - if (!slotKey && el.dataset.adLocation != null && el.dataset.adLocation !== "") { - slotKey = "location_" + String(el.dataset.adLocation).replace(/[^\w]+/g, "_"); - slotName = slotName || ("广告位置" + el.dataset.adLocation); - } - if (!slotKey) { - slotKey = pi.page_key + "_ad"; - slotName = slotName || (pi.page_name + "广告位"); - } - return { - ad_id: el.dataset.adId || "", - creative_id: el.dataset.creativeId || el.dataset.adId || "", - ad_slot_key: slotKey, - ad_slot_name: slotName, - ad_type: el.dataset.adType || "banner", - page_key: pi.page_key, - page_name: pi.page_name, - }; - } - - // href 是否为可跳转链接(排除空、#、javascript: 等不跳转的情况) - function hrefIsNavigable(href) { - if (!href) return false; - href = String(href).trim(); - if (!href || href === "#") return false; - if (/^javascript:/i.test(href)) return false; - return true; - } - - // 判断本次广告点击是否会真正“跳转”。 - // 需求:ad_click 只有广告跳转才上报;滑动/拖拽、关闭等不上报。 - // 命中条件(任一): - // 1) 广告元素自身或祖先是带有效 href 的 (同窗或新开都算跳转) - // 2) clickDD 跳转模式:元素链上带有效的 data-link - function isNavigatingAdClick(el) { - try { - var a = el.closest ? el.closest("a[href]") : null; - if (a && hrefIsNavigable(a.getAttribute("href"))) return true; - } catch (e) {} - var node = el; - while (node && node !== document) { - if (node.dataset && hrefIsNavigable(node.dataset.link)) return true; - node = node.parentNode; - } - return false; - } - - // 关闭广告弹窗(event_type=close)。从 [data-ad-close] 元素读取广告信息。 - function parseAdvDataset(el, eventType) { - if (!el || !el.dataset) return null; - var id = el.dataset.adId || el.dataset.advertisingId || ""; - if (!id) return null; - return { - event_type: eventType, - advertising_id: id, - advertising_key: el.dataset.advertisingKey || el.dataset.adSlotKey || id, - advertising_name: el.dataset.advertisingName || el.dataset.adSlotName || ("广告_" + id), - }; - } - - // 搜索结果点击:从 [data-search-item] 读取信息 - function trackSearchResultClick(linkEl) { - if (!linkEl || !linkEl.getAttribute) return; - var itemId = linkEl.getAttribute("data-search-item") || linkEl.getAttribute("data-id") || ""; - if (!itemId) return; - var keyword = linkEl.getAttribute("data-search-keyword") || currentSearchKeyword() || ""; - if (!keyword) return; - trackKeywordClick({ - keyword: keyword, - click_item_id: itemId, - click_item_type_key: linkEl.getAttribute("data-search-item-type-key") || "video", - click_item_type_name: linkEl.getAttribute("data-search-item-type-name") || "视频", - click_position: parseInt(linkEl.getAttribute("data-search-position") || 0, 10) || 0, - search_trace_id: linkEl.getAttribute("data-search-trace-id") || "", - }); - } - - // 从 URL 推断当前搜索词(/search/关键词/ 或查询参数)。 - function currentSearchKeyword() { - try { - var q = new URLSearchParams(location.search || ""); - var queryKeyword = q.get("q") || q.get("title") || q.get("keyword") || q.get("key") || ""; - if (queryKeyword) return queryKeyword; - var match = String(location.pathname || "").match(/^\/search\/([^/]+)(?:\/|$)/); - return match ? decodeURIComponent(match[1]) : ""; - } catch (e) { return ""; } - } - - // 全局点击收集 - 必须能在 执行阶段就挂载(document 一定存在) - function bindClickListeners() { - if (global.__trackerClickBound) return; - global.__trackerClickBound = true; - - // 拖拽/滑动检测:按下后位移超过阈值即视为"滑动/拖拽",本次产生的 click 不算广告点击。 - // 同时覆盖触摸(touch)、鼠标(mouse)与指针(pointer),因为 Swiper 等轮播在桌面端是 - // 鼠标拖拽、移动端是触摸/指针拖拽;仅监听 touch 会漏掉桌面端拖拽(如浮动广告滑动)。 - var DRAG_THRESHOLD = 6; - var __dragStart = null; - function __onDragStart(x, y) { - __dragStart = { x: x, y: y }; - global.__suppressAdClick = false; // 新手势开始,清除上一次标记 - } - function __onDragMove(x, y) { - if (!__dragStart) return; // 仅在按下拖动时判断(pointermove/mousemove 悬停也会触发) - if (Math.abs(x - __dragStart.x) > DRAG_THRESHOLD || - Math.abs(y - __dragStart.y) > DRAG_THRESHOLD) { - global.__suppressAdClick = true; - } - } - function __onDragEnd() { __dragStart = null; } - - // 触摸(移动端) - document.addEventListener("touchstart", function (e) { - if (e.touches && e.touches[0]) __onDragStart(e.touches[0].clientX, e.touches[0].clientY); - }, { passive: true, capture: true }); - document.addEventListener("touchmove", function (e) { - if (e.touches && e.touches[0]) __onDragMove(e.touches[0].clientX, e.touches[0].clientY); - }, { passive: true, capture: true }); - document.addEventListener("touchend", __onDragEnd, { passive: true, capture: true }); - document.addEventListener("touchcancel", __onDragEnd, { passive: true, capture: true }); - - // 指针(鼠标/触控笔/触摸,覆盖桌面端 Swiper 拖拽);不支持则用鼠标事件兜底 - if (typeof global.PointerEvent !== "undefined") { - document.addEventListener("pointerdown", function (e) { __onDragStart(e.clientX, e.clientY); }, { passive: true, capture: true }); - document.addEventListener("pointermove", function (e) { __onDragMove(e.clientX, e.clientY); }, { passive: true, capture: true }); - document.addEventListener("pointerup", __onDragEnd, { passive: true, capture: true }); - document.addEventListener("pointercancel", __onDragEnd, { passive: true, capture: true }); - } else { - document.addEventListener("mousedown", function (e) { __onDragStart(e.clientX, e.clientY); }, { passive: true, capture: true }); - document.addEventListener("mousemove", function (e) { __onDragMove(e.clientX, e.clientY); }, { passive: true, capture: true }); - document.addEventListener("mouseup", __onDragEnd, { passive: true, capture: true }); - } - - document.addEventListener("click", function (e) { - trackPageClick(e); - - // 搜索结果点击 - var searchLink = e.target && e.target.closest - ? e.target.closest("[data-search-item]") - : null; - if (searchLink) { - trackSearchResultClick(searchLink); - } - - // 广告关闭弹窗 → advertising(event_type=close) - var closeEl = e.target && e.target.closest - ? e.target.closest("[data-ad-close]") - : null; - if (closeEl) { - var advClose = parseAdvDataset(closeEl, "close"); - if (advClose) trackAdvertising(advClose); - } - - // 关闭/禁止上报区域不算广告点击 - var isCloseClick = false; - try { - isCloseClick = !!(e.target && e.target.closest && e.target.closest( - '[data-ad-close], [data-no-ad-click], .closeBtn, .nineClose' - )); - } catch (_) { isCloseClick = false; } - var suppressAd = !!global.__suppressAdClick || isCloseClick; - if (global.__suppressAdClick) { - global.__suppressAdClick = false; - } - - // 自动捕获广告点击 (data-ad-id) 和导航点击 (data-nav-key) - var target = e.target; - var adReported = false; - while (target && target !== document) { - if (target.dataset) { - // ad_click 仅在“广告跳转”时上报;滑动/拖拽(suppressAd)、关闭、无跳转的点击均不上报 - if (target.dataset.adId && !suppressAd && !adReported && isNavigatingAdClick(target)) { - var ad = parseAdDataset(target); - trackAdClick(ad); - adReported = true; - } - if (target.dataset.navKey) { - var href = (target.getAttribute && target.getAttribute("href")) || ""; - trackNavigation( - target.dataset.navKey, - target.dataset.navName || target.textContent || "", - href - ); - } - } - target = target.parentNode; - } - }, true); - } - - // ------- 视频自动绑定 ------- - // 适用于原生 - -{{end}} diff --git a/app/templates/base/contentExtras.html b/app/templates/base/contentExtras.html index 6934fc5..c1fa744 100644 --- a/app/templates/base/contentExtras.html +++ b/app/templates/base/contentExtras.html @@ -38,7 +38,7 @@ {{if .appConfig.DetailBannerList}}
{{range $ad := .appConfig.DetailBannerList}} - + {{$ad.Title}} {{end}} diff --git a/app/templates/base/dialogAd.html b/app/templates/base/dialogAd.html index a588279..b12d47e 100644 --- a/app/templates/base/dialogAd.html +++ b/app/templates/base/dialogAd.html @@ -13,18 +13,14 @@ data-dialog-ad-item data-dialog-ad-index="{{$index}}" data-dialog-ad-id="{{$dialog.ID}}" - data-ad-id="{{$dialog.ID}}" data-creative-id="{{$dialog.ID}}" - data-ad-slot-key="dialog_ad" - data-ad-slot-name="弹窗广告" - data-ad-type="popup" role="dialog" aria-modal="true" aria-label="{{if $dialog.Title}}{{$dialog.Title}}{{else}}广告{{end}}" hidden > {{if $dialog.Link}} - + {{$dialog.Title}} {{else}} @@ -32,7 +28,7 @@ {{$dialog.Title}}
{{end}} -
diff --git a/app/templates/base/downloadAppDialog.html b/app/templates/base/downloadAppDialog.html index 48e305b..812bddd 100644 --- a/app/templates/base/downloadAppDialog.html +++ b/app/templates/base/downloadAppDialog.html @@ -8,8 +8,8 @@
-{{end}} diff --git a/app/templates/base/registerDialog.html b/app/templates/base/registerDialog.html deleted file mode 100644 index 47d4dbf..0000000 --- a/app/templates/base/registerDialog.html +++ /dev/null @@ -1,80 +0,0 @@ -{{define "registerDialog"}} -
-
- - -
-
-{{end}} diff --git a/app/templates/base/seoCommon.html b/app/templates/base/seoCommon.html index 930c77b..2e702e4 100644 --- a/app/templates/base/seoCommon.html +++ b/app/templates/base/seoCommon.html @@ -37,23 +37,21 @@ {{if .paginationNextURL}}{{end}} - - - - + + + + {{/* 首屏壳层:合并 base+header+footer+eightPop,减少弱网 RTT(改源文件后跑 scripts/build-shell-css.sh) */}} -{{template "trackerInit" .}} +{{template "analytics" .}} -{{/* PC 保留原 defer;H5 不在此拉取,由下方脚本空闲时注入 */}} - - - - - - - - -{{end}} diff --git a/app/templates/base/vipDialog.html b/app/templates/base/vipDialog.html deleted file mode 100644 index 6f1c241..0000000 --- a/app/templates/base/vipDialog.html +++ /dev/null @@ -1,24 +0,0 @@ -{{define "vipDialog"}} - -
- -
- -{{end}} diff --git a/app/templates/page/collect.html b/app/templates/page/collect.html deleted file mode 100644 index fd7bd78..0000000 --- a/app/templates/page/collect.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - {{template "seoCommon" .}} - {{if .seoInfo.Title}}{{.seoInfo.Title}}{{else if .title}}{{.title}} - 黄果短剧{{else}}我的收藏 - 黄果短剧{{end}} - - - - - - - - - - - - - -{{template "header" .}} -
-
- -
-

我的收藏

-

继续关注喜欢的内容

-
-
- -
- - 返回 - -
我的收藏
- -
- - -
-{{template "footer" .}} - - diff --git a/app/templates/page/deposit.html b/app/templates/page/deposit.html deleted file mode 100644 index 17f989a..0000000 --- a/app/templates/page/deposit.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - {{template "seoCommon" .}} - {{if .title}}{{.title}} - 黄果短剧{{else}}金币充值 - 黄果短剧{{end}} - - - - - - - {{template "header" .}} -
-
- - -
-
WALLET
-

金币充值

-
当前余额 {{if .rechargeText}}{{.rechargeText}}{{else}}0{{end}} 金币
-
- -
-
钱包充值
-

选支付 · 立即到账

-
支持多种支付方式,支付成功后即时到账。
-
- -

购买套餐

-
-
金币充值按集解锁,自由使用
-
- {{range .coinList}} - - {{else}}
暂无金币套餐
{{end}} -
-
- - -

请选择金币套餐

- -
-

交易明细

-
- {{range .txList}} -
-
-
{{if .Title}}{{.Title}}{{else if .TranTypeName}}{{.TranTypeName}}{{else if .Remark}}{{.Remark}}{{else}}金币变动{{end}}
- {{if .CreatedAt}}
{{formatShortDateTime .CreatedAt}}
{{end}} -
-
- {{if eq .ChangeAmount 0}}{{if eq .FlowDir 1}}+{{else}}-{{end}}{{absInt64 .Amount}}{{else}}{{if eq .FlowDir 1}}+{{else}}-{{end}}{{absInt64 .ChangeAmount}}{{end}} -
-
- {{else}}
暂无交易记录
{{end}} -
-
-
- -
- - -
-
金币余额 (金币)
-
{{if .rechargeText}}{{.rechargeText}}{{else}}000.00{{end}}
-
- -
-
请选择充值金额首次充值赠送10%
-
- {{range .coinList}} - - {{else}}
暂无金币套餐
{{end}} -
-
- -
-

金币怎么用?

-
-
解锁单集内容按剧集实际定价消耗金币
-
奖励金币优先使用解锁时系统自动优先扣除任务/签到获得的奖励金币
-
金币畅看范围内不消耗金币会员权益仍优先于金币,避免重复付费
-
-
-
- -
- -

如遇充值问题请咨询 在线客服

-
- - -
- - {{template "footer" .}} - - - diff --git a/app/templates/page/detail.html b/app/templates/page/detail.html index c118c76..c208821 100644 --- a/app/templates/page/detail.html +++ b/app/templates/page/detail.html @@ -20,7 +20,7 @@ {{end}} - + @@ -37,10 +37,6 @@ 返回
- @@ -95,20 +91,6 @@
立即播放
- - - -
- {{if $.isLoggedIn}} -
- {{if $.avatar}} - 我的头像 - {{else}} - 默认用户头像 - {{end}} -
- - -
-
- {{else}} - - {{end}}
评论加载中…
@@ -226,10 +173,6 @@
- 开始播放
{{end}} @@ -240,8 +183,6 @@ {{end}} {{template "footer" .}} -{{template "vipDialog" .}} -{{template "coinDialog" .}} - + diff --git a/app/templates/page/editPassword.html b/app/templates/page/editPassword.html deleted file mode 100644 index 74f45f8..0000000 --- a/app/templates/page/editPassword.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - {{template "seoCommon" .}} - 修改登录密码 - 黄果短剧 - - - - - - - - - - {{template "header" .}} -
-
- -
-

修改登录密码

-
- 建议使用 8–20 位字母、数字和符号组合 -
-
-
- -
-
- - 返回 - -
修改登录密码
- -
- -
-
-
设置新的登录密码
-
- 建议使用 8–20 位字母、数字和符号组合 -
-
- -
- -
- - -
- -
- -
- -
- - -
- -
- -
- -
- - -
- -
- - -
-
-
- {{template "footer" .}} - - - diff --git a/app/templates/page/editProfile.html b/app/templates/page/editProfile.html deleted file mode 100644 index 62134d9..0000000 --- a/app/templates/page/editProfile.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - {{template "seoCommon" .}} - 编辑个人资料 - 黄果短剧 - - - - - - - - - - {{template "header" .}} -
-

编辑个人资料

-
- -
- -
-
- - 返回 - -
编辑个人资料
- -
- -
-
- - 个人头像 - - - -
- - - - - - -
-
-
- {{template "footer" .}} - - - diff --git a/app/templates/page/helpCenter.html b/app/templates/page/helpCenter.html index e57a116..a0f862c 100644 --- a/app/templates/page/helpCenter.html +++ b/app/templates/page/helpCenter.html @@ -7,14 +7,14 @@ 帮助中心 - 黄果短剧 {{if .jsonLD}} - - diff --git a/app/templates/page/homeRoad.html b/app/templates/page/homeRoad.html index 12fa979..f98dfb3 100644 --- a/app/templates/page/homeRoad.html +++ b/app/templates/page/homeRoad.html @@ -23,7 +23,7 @@
官方权威发布 · 永久有效入口
- +

黄果短剧 · 官方最新地址发布页

实时同步最新可用线路与备用入口,帮助你在不同网络环境下快速返回。

diff --git a/app/templates/page/index.html b/app/templates/page/index.html index 3e21036..c6dd8be 100644 --- a/app/templates/page/index.html +++ b/app/templates/page/index.html @@ -10,10 +10,10 @@ - + - + {{if .jsonLD}} {{end}} @@ -27,7 +27,7 @@ {{if $heroSrc}}{{end}} {{end}}{{end}} - + {{template "header" .}}

{{if .seoInfo.Title}}{{.seoInfo.Title}}{{else if .appConfig.SeoInfo.Title}}{{.appConfig.SeoInfo.Title}}{{else if .title}}{{.title}}{{else}}黄果短剧 - 热门短剧在线看{{end}}

diff --git a/app/templates/page/list.html b/app/templates/page/list.html index 38cdf9a..d34c70c 100644 --- a/app/templates/page/list.html +++ b/app/templates/page/list.html @@ -64,7 +64,7 @@ {{if .appConfig.BottomBanner}}
{{range $ad := .appConfig.BottomBanner}} - + {{$ad.Title}} {{end}} diff --git a/app/templates/page/nav.html b/app/templates/page/nav.html index 090718e..abf5119 100644 --- a/app/templates/page/nav.html +++ b/app/templates/page/nav.html @@ -10,10 +10,10 @@ - + - + {{if .jsonLD}} {{end}} @@ -26,7 +26,7 @@

{{if .appConfig.SeoNavInfo.DhSiteName}}{{.appConfig.SeoNavInfo.DhSiteName}}{{else if .appConfig.SeoNavInfo.DhTitle}}{{.appConfig.SeoNavInfo.DhTitle}}{{else if .title}}{{.title}}{{else}}黄果短剧 · 官方导航{{end}}

-
- {{if $.isLoggedIn}} -
- - -
- {{else}} - - {{end}} -
{{end}} {{else}} @@ -489,9 +393,7 @@ {{end}} {{template "footer" .}} -{{template "vipDialog" .}} -{{template "coinDialog" .}} - - + + diff --git a/app/templates/page/rank.html b/app/templates/page/rank.html index 20989a1..d475b69 100644 --- a/app/templates/page/rank.html +++ b/app/templates/page/rank.html @@ -69,7 +69,7 @@ {{if .appConfig.BottomBanner}}
{{range $ad := .appConfig.BottomBanner}} - + {{$ad.Title}} {{end}} diff --git a/app/templates/page/search.html b/app/templates/page/search.html index 49910bf..bd70436 100644 --- a/app/templates/page/search.html +++ b/app/templates/page/search.html @@ -151,22 +151,5 @@ {{template "footer" .}} - diff --git a/app/templates/page/series.html b/app/templates/page/series.html index 6f1c3eb..ad87a9b 100644 --- a/app/templates/page/series.html +++ b/app/templates/page/series.html @@ -60,7 +60,7 @@ {{if .appConfig.BottomBanner}}
{{range $ad := .appConfig.BottomBanner}} - + {{$ad.Title}} {{end}} diff --git a/app/templates/page/setting.html b/app/templates/page/setting.html deleted file mode 100644 index 9d16615..0000000 --- a/app/templates/page/setting.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - {{template "seoCommon" .}} - 账号设置 - 黄果短剧 - - - - - - - - - - {{template "header" .}} -
-
- -
-

账号设置

-

管理个人资料与登录状态

-
-
- -
- - 返回 - -
账号设置
- -
- -
-
-

个人资料

-
- {{if .setting.Avatar}} - {{if .setting.DisplayName}}{{.setting.DisplayName}}{{else}}用户{{end}}的头像 - {{else}} - {{if .setting.DisplayName}}{{.setting.DisplayName}}{{else}}用户{{end}}的默认头像 - {{end}} -
-
{{.setting.DisplayName}}
-
-
-
- - -
-
- 个人简介 -
- {{if - .setting.Introduction}}{{.setting.Introduction}}{{else}}未设置简介{{end}} -
-
- 保存修改 -
- -
- - -
- - -
-
- {{template "footer" .}} - - - diff --git a/app/templates/page/themeIndex.html b/app/templates/page/themeIndex.html index 172beb7..30a7c03 100644 --- a/app/templates/page/themeIndex.html +++ b/app/templates/page/themeIndex.html @@ -52,7 +52,7 @@ {{if .appConfig.BottomBanner}}
{{range $ad := .appConfig.BottomBanner}} - + {{$ad.Title}} {{end}} diff --git a/app/templates/page/userCenter.html b/app/templates/page/userCenter.html deleted file mode 100644 index c00af1e..0000000 --- a/app/templates/page/userCenter.html +++ /dev/null @@ -1,318 +0,0 @@ - - - - - - {{template "seoCommon" .}} - 个人中心 - 黄果短剧 - - - - - - - - - - {{template "header" .}} -
-
-
-
ACCOUNT
-
-
-

- 个人中心 -

- {{if and .isLoggedIn (not .isRegistered)}} - 游客注册 - {{end}} -
- -
- {{if .isLoggedIn}} -
欢迎回来,继续你的追剧旅程
- {{else}} -
管理观看数据、收藏和金币余额
- {{end}} -
- - {{template "squarePromo" dict "Ads" .appConfig.DetailTopList "SlotKey" "user_center_detail_top" "SlotName" "个人中心方块广告" "Label" "推广应用"}} - - {{if .isLoggedIn}} -
- {{if .avatar}} - {{if .userName}}{{.userName}}{{else}}用户{{end}}的头像 - {{else}} - {{if .userName}}{{.userName}}{{else}}用户{{end}}的默认头像 - {{end}} -
-
- {{if .userName}}{{.userName}}{{else}}用户{{end}} -
-
- ID:{{if .userId}}{{.userId}}{{else}}--{{end}} -
- {{if .email}} -
{{.email}}
- {{end}} -
-
- -
-
金币余额 (金币)
-
- {{if .rechargeText}}{{.rechargeText}}{{else}}000.00{{end}} -
-
- 金币充值 -
-
- {{else}} - - {{end}} - - -
- -
- {{if .isLoggedIn}} -
- {{if .avatar}} - {{if .userName}}{{.userName}}{{else}}用户{{end}}的头像 - {{else}} - {{if .userName}}{{.userName}}{{else}}用户{{end}}的默认头像 - {{end}} -
-
- {{if .userName}}{{.userName}}{{else}}用户{{end}} -
-
- {{if .userId}}ID {{.userId}}{{else}}ID --{{end}} -
-
- {{if not .isRegistered}} - 游客注册 - {{end}} -
- - - {{else}} -
- 未登录用户头像 -
-
未登录
-
登录后同步收藏与观看历史
-
- -
- -
- -
-
金币余额 (金币)
-
000.00
-
- 金币充值 -
- - {{template "squarePromo" dict "Ads" .appConfig.DetailTopList "SlotKey" "user_center_detail_top" "SlotName" "个人中心方块广告" "Label" "推广应用"}} - - -
- {{end}} -
-
- {{template "footer" .}} - - - diff --git a/pub/pubdef/pubdef.go b/pub/pubdef/pubdef.go index b113e1b..f0f3fd9 100644 --- a/pub/pubdef/pubdef.go +++ b/pub/pubdef/pubdef.go @@ -11,11 +11,6 @@ const ( AppId = 1038 ) -// TrackerAppId 数据上报 Web SDK 的业务 appId。 -func TrackerAppId() string { - return "JHA-1043" -} - const PageSize int64 = 40 type SeoValue struct {