详情页评论改推荐列表
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"videoweb/app/service/commentser"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CommentList BFF:代理 GET /api/app/comment/list。
|
||||
// 文档:http://172.104.181.99:25319/swagger/index.html#/评论APP/get_api_app_comment_list
|
||||
func CommentList(c *gin.Context) {
|
||||
objectID, _ := strconv.ParseInt(c.Query("objectId"), 10, 64)
|
||||
objectType, _ := strconv.Atoi(c.Query("objectType"))
|
||||
pageNum, _ := strconv.Atoi(c.Query("pageNum"))
|
||||
pageSize, _ := strconv.Atoi(c.Query("pageSize"))
|
||||
sortOrder, _ := strconv.Atoi(c.Query("sortOrder"))
|
||||
if objectID <= 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"code": 400,
|
||||
"msg": "objectId required",
|
||||
"tip": "缺少评论对象",
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
data := commentser.FetchListOrEmpty(c, commentser.ListQuery{
|
||||
ObjectID: objectID,
|
||||
ObjectType: objectType,
|
||||
PageNum: pageNum,
|
||||
PageSize: pageSize,
|
||||
SortOrder: sortOrder,
|
||||
})
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"tip": "成功",
|
||||
"data": data,
|
||||
})
|
||||
}
|
||||
|
||||
// CommentSend BFF:代理 POST /api/app/comment/send。
|
||||
+4
-6
@@ -10,14 +10,12 @@ import (
|
||||
func Help(c *gin.Context) {
|
||||
HTMLOK(c, "help.html", gin.H{
|
||||
"faqItems": []seoser.FAQItem{
|
||||
{Question: "黄果短剧网页版可以看全集吗?", Answer: "可以。免费集直接观看,付费集可用金币按集解锁,解锁后永久有效。"},
|
||||
{Question: "解锁过的剧集还需要再付费吗?", Answer: "不需要。已解锁的剧集可在登录后继续观看,无需重复付费。"},
|
||||
{Question: "如何充值金币?", Answer: "登录后进入个人中心,选择金币充值,即可查看可用的充值方式。"},
|
||||
{Question: "支持哪些支付方式?", Answer: "可用的支付方式会在充值页面列出,请以页面显示为准。"},
|
||||
{Question: "忘记密码怎么办?", Answer: "请通过意见反馈联系我们,提交账号信息后由客服协助处理。"},
|
||||
{Question: "黄果短剧网页版可以看全集吗?", Answer: "免费集可直接在网页观看;标记为 App 专享的剧集,下载黄果短剧 App 即可免费观看完整内容。"},
|
||||
{Question: "网页版需要注册账号吗?", Answer: "不需要。网页版无需注册或登录,打开即可浏览和观看。"},
|
||||
{Question: "如何下载黄果短剧 App?", Answer: "点击页面顶部或播放页的「下载 App」按钮,按提示完成安装即可。"},
|
||||
{Question: "网站打不开怎么办?", Answer: "请收藏页脚的最新地址、备用地址与海外永久地址,任一地址均可访问。"},
|
||||
{Question: "内容多久更新一次?", Answer: "黄果短剧持续更新内容,欢迎关注首页的新剧和最近上新。"},
|
||||
{Question: "如何联系客服或洽谈合作?", Answer: "请发送邮件至 support@huanguoai.net,我们会尽快回复。"},
|
||||
{Question: "如何注销账号?", Answer: "请通过意见反馈或客服邮箱提交注销申请。"},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+52
-118
@@ -2,7 +2,6 @@ package web
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"videoweb/app/service/commentser"
|
||||
"videoweb/app/service/playletser"
|
||||
"videoweb/app/service/videoser"
|
||||
|
||||
@@ -76,20 +75,6 @@ func Play(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
commentObjectID := playlet.ID
|
||||
commentObjectType := commentser.ObjectTypePlaylet
|
||||
if chapterID > 0 {
|
||||
commentObjectID = chapterID
|
||||
commentObjectType = commentser.ObjectTypeChapter
|
||||
}
|
||||
comments := commentser.FetchListOrEmpty(c, commentser.ListQuery{
|
||||
ObjectID: commentObjectID,
|
||||
ObjectType: commentObjectType,
|
||||
PageNum: 1,
|
||||
PageSize: 4,
|
||||
SortOrder: commentser.SortHot,
|
||||
})
|
||||
|
||||
canWatch := false
|
||||
episode := 0
|
||||
if chapter != nil {
|
||||
@@ -119,45 +104,36 @@ func Play(c *gin.Context) {
|
||||
}
|
||||
|
||||
data := gin.H{
|
||||
"title": title,
|
||||
"navActive": "list",
|
||||
"seoInfo": seo,
|
||||
"playlet": playlet,
|
||||
"chapter": chapter,
|
||||
"chapterID": chapterID,
|
||||
"episode": episode,
|
||||
"canWatch": canWatch,
|
||||
"mainURL": mainURL,
|
||||
"previewURL": previewURL,
|
||||
"hasPreview": previewURL != "",
|
||||
"nextChapterID": playletser.NextChapterID(playlet, chapterID),
|
||||
"advitise": playlet.Advitise,
|
||||
"related": playlet.RelatedRecommendData,
|
||||
"guessLike": guessLike.List,
|
||||
"guessPage": guessLike.PageNum,
|
||||
"guessPageSize": guessLike.PageSize,
|
||||
"guessTotal": guessLike.TotalCount,
|
||||
"hotList": hotList,
|
||||
"hotMoreURL": playletser.AllListURL("all", 0, 0, 2, 1),
|
||||
"comments": comments.List,
|
||||
"commentTotal": comments.TotalCount,
|
||||
"commentPage": comments.PageNum,
|
||||
"commentSize": comments.PageSize,
|
||||
"commentObjectID": commentObjectID,
|
||||
"commentObjectType": commentObjectType,
|
||||
"title": title,
|
||||
"navActive": "list",
|
||||
"seoInfo": seo,
|
||||
"playlet": playlet,
|
||||
"chapter": chapter,
|
||||
"chapterID": chapterID,
|
||||
"episode": episode,
|
||||
"canWatch": canWatch,
|
||||
"mainURL": mainURL,
|
||||
"previewURL": previewURL,
|
||||
"hasPreview": previewURL != "",
|
||||
"nextChapterID": playletser.NextChapterID(playlet, chapterID),
|
||||
"advitise": playlet.Advitise,
|
||||
"related": playlet.RelatedRecommendData,
|
||||
"guessLike": guessLike.List,
|
||||
"guessPage": guessLike.PageNum,
|
||||
"guessPageSize": guessLike.PageSize,
|
||||
"guessTotal": guessLike.TotalCount,
|
||||
"hotList": hotList,
|
||||
"hotMoreURL": playletser.AllListURL("all", 0, 0, 2, 1),
|
||||
"playResponse": gin.H{
|
||||
"playlet": playlet,
|
||||
"chapter": chapter,
|
||||
"chapterID": chapterID,
|
||||
"canWatch": canWatch,
|
||||
"mainURL": mainURL,
|
||||
"previewURL": previewURL,
|
||||
"hasPreview": previewURL != "",
|
||||
"guessLike": guessLike,
|
||||
"hotList": hotList,
|
||||
"comments": comments,
|
||||
"commentObjectID": commentObjectID,
|
||||
"commentObjectType": commentObjectType,
|
||||
"playlet": playlet,
|
||||
"chapter": chapter,
|
||||
"chapterID": chapterID,
|
||||
"canWatch": canWatch,
|
||||
"mainURL": mainURL,
|
||||
"previewURL": previewURL,
|
||||
"hasPreview": previewURL != "",
|
||||
"guessLike": guessLike,
|
||||
"hotList": hotList,
|
||||
},
|
||||
}
|
||||
data["canonicalPath"] = playletser.PlayPath(playlet.Slug, playlet.ID)
|
||||
@@ -192,27 +168,6 @@ func SeriesPlay(c *gin.Context) {
|
||||
videoURL := strings.TrimSpace(video.Video.URL)
|
||||
canWatch := videoURL != ""
|
||||
|
||||
commentObjectID := video.ID
|
||||
// 视频评论:objectType=20(swagger:10-短剧 11-短剧剧集 20-视频)
|
||||
commentObjectType := commentser.ObjectTypeVideo
|
||||
comments := commentser.FetchListOrEmpty(c, commentser.ListQuery{
|
||||
ObjectID: commentObjectID,
|
||||
ObjectType: commentObjectType,
|
||||
PageNum: 1,
|
||||
PageSize: 4,
|
||||
SortOrder: commentser.SortHot,
|
||||
})
|
||||
if comments.PageNum <= 0 {
|
||||
comments.PageNum = 1
|
||||
}
|
||||
if comments.PageSize <= 0 {
|
||||
comments.PageSize = 4
|
||||
}
|
||||
commentTotal := comments.TotalCount
|
||||
if commentTotal <= 0 && video.CountComment > 0 {
|
||||
commentTotal = video.CountComment
|
||||
}
|
||||
|
||||
hot := playletser.FetchAllListOrEmpty(c, playletser.AllListQuery{
|
||||
PageNum: 1,
|
||||
PageSize: 8,
|
||||
@@ -245,51 +200,30 @@ func SeriesPlay(c *gin.Context) {
|
||||
}
|
||||
|
||||
data := gin.H{
|
||||
"title": title,
|
||||
"navActive": "aiSeries",
|
||||
"seoInfo": video.SeoInfo,
|
||||
"video": video,
|
||||
"videoID": video.ID,
|
||||
"videoSlug": video.Slug,
|
||||
"cover": cover,
|
||||
"videoURL": videoURL,
|
||||
"canWatch": canWatch,
|
||||
"guessLike": guessLike.List,
|
||||
"guessPage": guessLike.PageNum,
|
||||
"guessPageSize": guessLike.PageSize,
|
||||
"guessTotal": guessLike.TotalCount,
|
||||
"guessPlayletID": guessPlayletID,
|
||||
"advitise": video.Advitise,
|
||||
"comments": comments.List,
|
||||
"commentTotal": commentTotal,
|
||||
"commentPage": comments.PageNum,
|
||||
"commentSize": comments.PageSize,
|
||||
"commentObjectID": commentObjectID,
|
||||
"commentObjectType": commentObjectType,
|
||||
"hotList": hotList,
|
||||
"hotMoreURL": playletser.AllListURL("all", 0, 0, 2, 1),
|
||||
"title": title,
|
||||
"navActive": "aiSeries",
|
||||
"seoInfo": video.SeoInfo,
|
||||
"video": video,
|
||||
"videoID": video.ID,
|
||||
"videoSlug": video.Slug,
|
||||
"cover": cover,
|
||||
"videoURL": videoURL,
|
||||
"canWatch": canWatch,
|
||||
"guessLike": guessLike.List,
|
||||
"guessPage": guessLike.PageNum,
|
||||
"guessPageSize": guessLike.PageSize,
|
||||
"guessTotal": guessLike.TotalCount,
|
||||
"guessPlayletID": guessPlayletID,
|
||||
"advitise": video.Advitise,
|
||||
"hotList": hotList,
|
||||
"hotMoreURL": playletser.AllListURL("all", 0, 0, 2, 1),
|
||||
"videoResponse": gin.H{
|
||||
"video": video,
|
||||
"videoURL": videoURL,
|
||||
"canWatch": canWatch,
|
||||
"comments": comments,
|
||||
"hotList": hotList,
|
||||
"guessLike": guessLike,
|
||||
"guessPlayletID": guessPlayletID,
|
||||
"commentObjectID": commentObjectID,
|
||||
"commentObjectType": commentObjectType,
|
||||
},
|
||||
"commentDebug": gin.H{
|
||||
"path": "/api/app/comment/list",
|
||||
"objectId": commentObjectID,
|
||||
"objectType": commentObjectType, // 20-视频
|
||||
"pageNum": comments.PageNum,
|
||||
"pageSize": comments.PageSize,
|
||||
"totalCount": commentTotal,
|
||||
"list": comments.List,
|
||||
"apis": []string{
|
||||
"GET /api/app/comment/list",
|
||||
},
|
||||
"video": video,
|
||||
"videoURL": videoURL,
|
||||
"canWatch": canWatch,
|
||||
"hotList": hotList,
|
||||
"guessLike": guessLike,
|
||||
"guessPlayletID": guessPlayletID,
|
||||
},
|
||||
"playDebug": gin.H{
|
||||
"canWatch": canWatch,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
echo "开始"
|
||||
# 本机 Go 装在 ~/sdk/go,未加入 PATH 时在这里补上
|
||||
command -v go >/dev/null 2>&1 || export PATH="$HOME/sdk/go/bin:$PATH"
|
||||
rm -rf ./web_huangguo2_h5 ./web_huangguo2_h5.tar.gz
|
||||
GOOS=linux GOARCH=amd64 go build -o web_huangguo2_h5
|
||||
tar zvcf web_huangguo2_h5.tar.gz \
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"strings"
|
||||
"videoweb/app/api/web"
|
||||
"videoweb/app/appg"
|
||||
"videoweb/app/service/commentser"
|
||||
"videoweb/app/service/playletser"
|
||||
"videoweb/app/service/seoser"
|
||||
"videoweb/app/service/translationser"
|
||||
@@ -62,9 +61,6 @@ func setGinMode() {
|
||||
|
||||
func setupEngine(engine *gin.Engine) {
|
||||
tplFuncs := playletser.TemplateFuncs()
|
||||
for name, fn := range commentser.TemplateFuncs() {
|
||||
tplFuncs[name] = fn
|
||||
}
|
||||
tplFuncs["InJSONScript"] = inJSONScript
|
||||
tplFuncs["navHref"] = translationser.NavHref
|
||||
tplFuncs["navIsDownload"] = translationser.NavIsDownload
|
||||
@@ -209,7 +205,6 @@ func registerRoutes(r *gin.Engine) {
|
||||
|
||||
// 播放页 BFF
|
||||
r.GET("/api/playlet/guess-like/list", web.GuessLikeList)
|
||||
r.GET("/api/comment/list", web.CommentList)
|
||||
r.GET("/api/web/search/suggest", web.SearchSuggest)
|
||||
|
||||
// 搜索引擎抓取、发现与站点所有权验证。
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
package commentser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"videoweb/app/service/upstream"
|
||||
"videoweb/pub/common/log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
listPath = "/api/app/comment/list"
|
||||
defaultCommentSize = 10
|
||||
)
|
||||
|
||||
// ListQuery 评论列表查询参数。
|
||||
type ListQuery struct {
|
||||
ObjectID int64
|
||||
ObjectType int
|
||||
PageNum int
|
||||
PageSize int
|
||||
SortOrder int
|
||||
}
|
||||
|
||||
// Normalize 校正分页与排序默认值。
|
||||
func (q *ListQuery) Normalize() {
|
||||
if q.PageNum <= 0 {
|
||||
q.PageNum = 1
|
||||
}
|
||||
if q.PageSize <= 0 {
|
||||
q.PageSize = defaultCommentSize
|
||||
}
|
||||
if q.PageSize > 50 {
|
||||
q.PageSize = 50
|
||||
}
|
||||
q.ObjectType = NormalizeObjectType(q.ObjectType)
|
||||
if q.SortOrder != SortLatest {
|
||||
q.SortOrder = SortHot
|
||||
}
|
||||
}
|
||||
|
||||
// FetchList 分页查询评论。
|
||||
func FetchList(c *gin.Context, q ListQuery) (*GetCommentListResp, error) {
|
||||
q.Normalize()
|
||||
if q.ObjectID <= 0 {
|
||||
return nil, fmt.Errorf("comment list invalid objectId")
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Set("objectId", strconv.FormatInt(q.ObjectID, 10))
|
||||
params.Set("objectType", strconv.Itoa(q.ObjectType))
|
||||
params.Set("pageNum", strconv.Itoa(q.PageNum))
|
||||
params.Set("pageSize", strconv.Itoa(q.PageSize))
|
||||
params.Set("sortOrder", strconv.Itoa(q.SortOrder))
|
||||
|
||||
requestPath := listPath + "?" + params.Encode()
|
||||
env, err := fetchListOnce(c, requestPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if env.Code != 200 {
|
||||
return nil, fmt.Errorf("comment list code=%d msg=%s", env.Code, env.Msg)
|
||||
}
|
||||
return &env.Data, nil
|
||||
}
|
||||
|
||||
func fetchListOnce(c *gin.Context, path string) (*APIEnvelope[GetCommentListResp], error) {
|
||||
var env APIEnvelope[GetCommentListResp]
|
||||
if err := upstream.GetJSON(c, path, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
// FetchListOrEmpty 失败时返回空列表。
|
||||
func FetchListOrEmpty(c *gin.Context, q ListQuery) *GetCommentListResp {
|
||||
data, err := FetchList(c, q)
|
||||
if err != nil {
|
||||
log.Error("fetch comment list failed", log.E(err))
|
||||
return &GetCommentListResp{
|
||||
ObjectID: q.ObjectID,
|
||||
PageNum: q.PageNum,
|
||||
PageSize: q.PageSize,
|
||||
}
|
||||
}
|
||||
if data == nil {
|
||||
return &GetCommentListResp{
|
||||
ObjectID: q.ObjectID,
|
||||
PageNum: q.PageNum,
|
||||
PageSize: q.PageSize,
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// ParseSortOrder 解析站点评论排序(1 热门 / 2 最新)。
|
||||
func ParseSortOrder(s string) int {
|
||||
n, err := strconv.Atoi(strings.TrimSpace(s))
|
||||
if err != nil || n != SortLatest {
|
||||
return SortHot
|
||||
}
|
||||
return SortLatest
|
||||
}
|
||||
|
||||
// FormatCreatedAt 展示用时间格式化(非字段映射)。
|
||||
func FormatCreatedAt(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
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.Local().Format("2006/1/2 15:04")
|
||||
}
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// AvatarOrDefault 头像空时回退默认图。
|
||||
func AvatarOrDefault(avatar string) string {
|
||||
avatar = strings.TrimSpace(avatar)
|
||||
if avatar == "" {
|
||||
return "/static/images/profile/avatar.png"
|
||||
}
|
||||
return avatar
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package commentser
|
||||
|
||||
import "html/template"
|
||||
|
||||
// TemplateFuncs 评论展示辅助。
|
||||
func TemplateFuncs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"formatCommentTime": FormatCreatedAt,
|
||||
"commentAvatar": AvatarOrDefault,
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package commentser
|
||||
|
||||
// CommentSendReq 对应 swagger proto.CommentSendReq
|
||||
// parentsId 为 0 发一级评论,大于 0 发二级评论;replyCommentId 为实际被回复的评论。
|
||||
type CommentSendReq struct {
|
||||
ObjectID int64 `json:"objectId"`
|
||||
ObjectType int `json:"objectType"`
|
||||
Text string `json:"text"`
|
||||
ParentsID int64 `json:"parentsId"`
|
||||
ReplyCommentID int64 `json:"replyCommentId"`
|
||||
}
|
||||
|
||||
// CommentLikeSetReq 对应 swagger proto.CommentLikeSetReq
|
||||
type CommentLikeSetReq struct {
|
||||
CommentID int64 `json:"commentId"`
|
||||
IsLike bool `json:"isLike"`
|
||||
}
|
||||
|
||||
// CommentLikeSetResp 对应 swagger proto.CommentLikeSetResp
|
||||
type CommentLikeSetResp struct {
|
||||
CommentID int64 `json:"commentId"`
|
||||
CountLike int64 `json:"countLike"`
|
||||
IsLike int `json:"isLike"`
|
||||
}
|
||||
|
||||
// CommentInfo 对应 swagger proto.CommentInfo
|
||||
type CommentInfo struct {
|
||||
ID int64 `json:"id"`
|
||||
ObjectID int64 `json:"objectId"`
|
||||
ObjectType int `json:"objectType"`
|
||||
Text string `json:"text"`
|
||||
UserID int64 `json:"userId"`
|
||||
UserName string `json:"userName"`
|
||||
UserAvatar string `json:"userAvatar"`
|
||||
CountLike int64 `json:"countLike"`
|
||||
CommentCount int64 `json:"commentCount"`
|
||||
IsLike int `json:"isLike"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
Level int `json:"level,omitempty"`
|
||||
ParentsID int64 `json:"parentsId,omitempty"`
|
||||
ReplyCommentID int64 `json:"replyCommentId,omitempty"`
|
||||
ReplyUserName string `json:"replyUserName,omitempty"`
|
||||
Children []CommentInfo `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// GetCommentListResp 对应 swagger proto.GetCommentListResp
|
||||
type GetCommentListResp struct {
|
||||
List []CommentInfo `json:"list"`
|
||||
ObjectID int64 `json:"objectId"`
|
||||
PageNum int `json:"pageNum"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
}
|
||||
|
||||
// APIEnvelope 上游统一响应壳
|
||||
type APIEnvelope[T any] struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Tip string `json:"tip"`
|
||||
Data T `json:"data"`
|
||||
}
|
||||
|
||||
const (
|
||||
// ObjectTypePlaylet 短剧评论
|
||||
ObjectTypePlaylet = 10
|
||||
// ObjectTypeChapter 短剧剧集评论
|
||||
ObjectTypeChapter = 11
|
||||
// ObjectTypeVideo 视频评论
|
||||
ObjectTypeVideo = 20
|
||||
|
||||
SortHot = 1 // 热门
|
||||
SortLatest = 2 // 最新
|
||||
)
|
||||
|
||||
// NormalizeObjectType 校正评论对象类型:10-短剧 11-短剧剧集 20-视频。
|
||||
func NormalizeObjectType(objectType int) int {
|
||||
switch objectType {
|
||||
case ObjectTypeChapter, ObjectTypeVideo:
|
||||
return objectType
|
||||
default:
|
||||
return ObjectTypePlaylet
|
||||
}
|
||||
}
|
||||
@@ -1857,3 +1857,32 @@ a.play-synopsis__tag:hover {
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 猜你喜欢放在左栏(原评论位置),右栏为热门榜单 */
|
||||
.play-dual > .play-like {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.play-dual .play-like__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 20px 16px;
|
||||
}
|
||||
|
||||
.play-dual .card--play {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.play-dual > .play-like {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.play-dual .play-like__grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
/* 播放页评论区(含输入框、列表、内联回复) */
|
||||
|
||||
.play-comments {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.play-comments__head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.play-comments__title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.play-comments__count {
|
||||
color: var(--text-dim);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.play-composer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.play-composer__avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #232328;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.play-composer__avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.play-composer__field {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.play-composer__input {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.play-composer__input::placeholder {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.play-composer__count {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-dim);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.play-composer__login,
|
||||
.play-composer__send {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.play-composer__send:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.play-comment-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.play-comment {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid var(--line-soft);
|
||||
}
|
||||
|
||||
.play-comment:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.play-comment__avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 999px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.play-comment__body {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.play-comment__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.play-comment__name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.play-comment__time {
|
||||
color: var(--text-dim);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.play-comment__text {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.play-comment--empty {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.play-comment--empty .play-comment__text {
|
||||
padding: 12px 0;
|
||||
color: var(--muted, #888);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.play-comment__reply {
|
||||
padding: 10px 14px;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.play-comment__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.play-comment__action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--text-dim);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.play-comment__action:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.play-comment__action.is-active {
|
||||
color: var(--brand, #e9c977);
|
||||
}
|
||||
|
||||
.play-comment__action.is-active .icon {
|
||||
opacity: 1;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.play-comment__action .icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.play-comment__inline {
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid var(--line-soft);
|
||||
border-radius: var(--radius-sm);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.play-comment__inline-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
color: var(--text-dim);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.play-comment__inline-hint button {
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--brand, #e9c977);
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.play-comment__inline-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.play-comment__inline-input {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
height: 36px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.play-comment__inline-input::placeholder {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.play-comment__inline-input:focus,
|
||||
.play-comment__inline-input:focus-visible {
|
||||
border-color: rgba(233, 201, 119, 0.55);
|
||||
}
|
||||
|
||||
.play-comment__inline-send {
|
||||
flex-shrink: 0;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
background: var(--brand, #e9c977);
|
||||
color: #141417;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.play-comment__inline-send:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.play-comments__more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding-top: 12px;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.play-comments__more[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.play-comments__more:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.play-comments__more .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.play-comments {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.play-comments__head {
|
||||
gap: 14px;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.play-comments__head::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.play-comments__title {
|
||||
flex-shrink: 0;
|
||||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.play-comments__count {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.play-composer {
|
||||
width: 100%;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.play-composer__avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.play-composer__field {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.play-composer__login,
|
||||
.play-composer__send {
|
||||
flex-shrink: 0;
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.play-comment-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.play-comments__more {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -702,7 +702,7 @@
|
||||
if (btn.getAttribute("data-play-bound") === "1") return;
|
||||
btn.setAttribute("data-play-bound", "1");
|
||||
btn.addEventListener("click", function (event) {
|
||||
// 预告片免登录 / 免解锁;仅正片未解锁时拦充值
|
||||
// 预告片直接播放;仅正片为 App 专享时弹下载提示
|
||||
if (
|
||||
btn.hasAttribute("data-play-coin-open") &&
|
||||
!isPreviewMode()
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
// 播放页评论:只读列表 + 「查看更多评论」追加翻页
|
||||
(function () {
|
||||
var commentsRoot = document.querySelector("[data-play-comments]");
|
||||
if (!commentsRoot) return;
|
||||
|
||||
var listEl = document.querySelector("[data-comment-list]");
|
||||
var totalEl = document.querySelector("[data-comment-total-label]");
|
||||
var moreBtn = document.querySelector("[data-comment-more]");
|
||||
var loadingMore = false;
|
||||
var PAGE_SIZE = 4;
|
||||
|
||||
function toast(message, type) {
|
||||
if (typeof window.showToast === "function") {
|
||||
window.showToast(message, type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function toInt(v, fallback) {
|
||||
var n = parseInt(v, 10);
|
||||
return isNaN(n) ? fallback || 0 : n;
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s == null ? "" : s)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function getPageSize() {
|
||||
return Math.max(
|
||||
1,
|
||||
toInt(commentsRoot.getAttribute("data-comment-size"), PAGE_SIZE)
|
||||
);
|
||||
}
|
||||
|
||||
function getPage() {
|
||||
return Math.max(
|
||||
1,
|
||||
toInt(commentsRoot.getAttribute("data-comment-page"), 1)
|
||||
);
|
||||
}
|
||||
|
||||
function getLoaded() {
|
||||
if (!listEl) return 0;
|
||||
return listEl.querySelectorAll(".play-comment:not(.play-comment--empty)").length;
|
||||
}
|
||||
|
||||
function setPagerState(page, loaded, total) {
|
||||
if (page != null) commentsRoot.setAttribute("data-comment-page", String(page));
|
||||
if (loaded != null) commentsRoot.setAttribute("data-comment-loaded", String(loaded));
|
||||
if (total != null) commentsRoot.setAttribute("data-comment-total", String(total));
|
||||
syncMoreButton(total != null ? total : toInt(commentsRoot.getAttribute("data-comment-total"), 0));
|
||||
}
|
||||
|
||||
function syncMoreButton(total) {
|
||||
if (!moreBtn) return;
|
||||
var loaded = getLoaded();
|
||||
var hasMore = total > loaded;
|
||||
if (hasMore) {
|
||||
moreBtn.hidden = false;
|
||||
moreBtn.removeAttribute("hidden");
|
||||
} else {
|
||||
moreBtn.hidden = true;
|
||||
moreBtn.setAttribute("hidden", "");
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(raw) {
|
||||
var s = String(raw || "").trim();
|
||||
if (!s) return "刚刚";
|
||||
var d = new Date(s);
|
||||
if (isNaN(d.getTime())) return s;
|
||||
return (
|
||||
d.getFullYear() +
|
||||
"/" +
|
||||
(d.getMonth() + 1) +
|
||||
"/" +
|
||||
d.getDate() +
|
||||
" " +
|
||||
String(d.getHours()).padStart(2, "0") +
|
||||
":" +
|
||||
String(d.getMinutes()).padStart(2, "0")
|
||||
);
|
||||
}
|
||||
|
||||
function renderCommentItem(item) {
|
||||
item = item || {};
|
||||
var avatar = String(item.userAvatar || "").trim();
|
||||
var name = escapeHtml(item.userName || "匿名用户");
|
||||
var text = escapeHtml(item.text || "");
|
||||
var time = escapeHtml(formatTime(item.createdAt));
|
||||
var like = toInt(item.countLike, 0);
|
||||
var avatarHtml = avatar
|
||||
? '<img class="play-comment__avatar lazy" src="/static/images/placeholder.png?v=2" data-src="' +
|
||||
escapeHtml(avatar) +
|
||||
'" alt="" loading="lazy" decoding="async">'
|
||||
: '<img class="play-comment__avatar" src="/static/images/placeholder.png?v=2" data-src="/static/images/profile/avatar.png" alt="" loading="lazy" decoding="async">';
|
||||
var children = Array.isArray(item.children) ? item.children : [];
|
||||
var replies = children
|
||||
.map(function (child) {
|
||||
var cName = escapeHtml(child.userName || "用户");
|
||||
var replyTo = child.replyUserName
|
||||
? " 回复 " + escapeHtml(child.replyUserName)
|
||||
: "";
|
||||
return (
|
||||
'<div class="play-comment__reply">' +
|
||||
cName +
|
||||
replyTo +
|
||||
":" +
|
||||
escapeHtml(child.text || "") +
|
||||
"</div>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
var commentId = escapeHtml(item.id || "");
|
||||
return (
|
||||
'<li class="play-comment" data-comment-id="' +
|
||||
commentId +
|
||||
'">' +
|
||||
avatarHtml +
|
||||
'<div class="play-comment__body">' +
|
||||
'<div class="play-comment__meta">' +
|
||||
'<span class="play-comment__name">' +
|
||||
name +
|
||||
"</span>" +
|
||||
'<span class="play-comment__time">' +
|
||||
time +
|
||||
"</span>" +
|
||||
"</div>" +
|
||||
'<p class="play-comment__text">' +
|
||||
text +
|
||||
"</p>" +
|
||||
replies +
|
||||
'<div class="play-comment__actions">' +
|
||||
'<span class="play-comment__action">' +
|
||||
'<img class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/heart.svg" alt="" loading="lazy" decoding="async">' +
|
||||
"<span>" +
|
||||
escapeHtml(like) +
|
||||
"</span>" +
|
||||
"</span>" +
|
||||
"</div></div></li>"
|
||||
);
|
||||
}
|
||||
|
||||
function getLoadedIds() {
|
||||
var ids = Object.create(null);
|
||||
if (!listEl) return ids;
|
||||
listEl.querySelectorAll(".play-comment[data-comment-id]").forEach(function (el) {
|
||||
var id = String(el.getAttribute("data-comment-id") || "").trim();
|
||||
if (id) ids[id] = true;
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function updateTotal(n) {
|
||||
if (!totalEl) return;
|
||||
totalEl.textContent = n > 0 ? n + " 条" : "暂无评论";
|
||||
}
|
||||
|
||||
// 下一页追加到现有列表底部,不替换已展示评论。
|
||||
function appendList(list, totalCount) {
|
||||
if (!listEl) return;
|
||||
list = Array.isArray(list) ? list : [];
|
||||
var total =
|
||||
typeof totalCount === "number"
|
||||
? totalCount
|
||||
: toInt(commentsRoot.getAttribute("data-comment-total"), 0);
|
||||
if (typeof totalCount === "number") {
|
||||
updateTotal(totalCount);
|
||||
}
|
||||
if (!list.length) {
|
||||
setPagerState(getPage(), getLoaded(), total);
|
||||
return;
|
||||
}
|
||||
|
||||
var empty = listEl.querySelector(".play-comment--empty");
|
||||
if (empty) empty.remove();
|
||||
|
||||
var existing = getLoadedIds();
|
||||
var fresh = list.filter(function (item) {
|
||||
var id = String((item && item.id) || "").trim();
|
||||
return id && !existing[id];
|
||||
});
|
||||
if (!fresh.length) {
|
||||
setPagerState(getPage(), getLoaded(), total);
|
||||
return;
|
||||
}
|
||||
|
||||
var wrap = document.createElement("ul");
|
||||
wrap.innerHTML = fresh.map(renderCommentItem).join("");
|
||||
var nodes = [];
|
||||
while (wrap.firstChild) {
|
||||
nodes.push(wrap.firstChild);
|
||||
listEl.appendChild(wrap.firstChild);
|
||||
}
|
||||
if (typeof window.initLazyImages === "function") {
|
||||
nodes.forEach(function (node) {
|
||||
if (node && node.nodeType === 1) window.initLazyImages(node);
|
||||
});
|
||||
window.initLazyImages(listEl);
|
||||
}
|
||||
setPagerState(getPage(), getLoaded(), total);
|
||||
}
|
||||
|
||||
function fetchCommentPage(pageNum) {
|
||||
if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") {
|
||||
return Promise.reject(new Error("评论服务未就绪"));
|
||||
}
|
||||
var objectId = toInt(commentsRoot.getAttribute("data-object-id"), 0);
|
||||
var objectType = toInt(commentsRoot.getAttribute("data-object-type"), 10);
|
||||
if (objectId <= 0) return Promise.reject(new Error("缺少评论对象"));
|
||||
var pageSize = getPageSize();
|
||||
return window.HttpRequest.get("/api/comment/list", {
|
||||
params: {
|
||||
objectId: objectId,
|
||||
objectType: objectType,
|
||||
pageNum: pageNum,
|
||||
pageSize: pageSize,
|
||||
sortOrder: 1,
|
||||
},
|
||||
}).then(function (res) {
|
||||
if (!res || Number(res.code) !== 200 || !res.data) {
|
||||
throw new Error((res && (res.tip || res.msg)) || "加载失败");
|
||||
}
|
||||
return res.data;
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoreComments() {
|
||||
if (loadingMore || !moreBtn) return;
|
||||
var nextPage = getPage() + 1;
|
||||
loadingMore = true;
|
||||
moreBtn.disabled = true;
|
||||
fetchCommentPage(nextPage)
|
||||
.then(function (data) {
|
||||
var list = data.list || [];
|
||||
var total = toInt(data.totalCount, 0);
|
||||
commentsRoot.setAttribute("data-comment-page", String(nextPage));
|
||||
// 只追加,绝不 renderList 整表替换
|
||||
appendList(list, total);
|
||||
})
|
||||
.catch(function (err) {
|
||||
toast((err && err.message) || "加载失败,请稍后重试", "error");
|
||||
})
|
||||
.finally(function () {
|
||||
loadingMore = false;
|
||||
if (moreBtn) moreBtn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
if (moreBtn) {
|
||||
moreBtn.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
loadMoreComments();
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -1,69 +0,0 @@
|
||||
{{define "playComments"}}
|
||||
<link rel="stylesheet" href="/static/css/playComments.css">
|
||||
{{/* 播放页评论区:首屏 4 条,超出显示「查看更多评论」客户端翻页 */}}
|
||||
{{$c := .chapter}}
|
||||
{{$pageSize := .commentSize}}
|
||||
{{$total := .commentTotal}}
|
||||
{{$shown := len .comments}}
|
||||
<div
|
||||
class="play-comments"
|
||||
data-play-comments
|
||||
data-comment-page="{{if .commentPage}}{{.commentPage}}{{else}}1{{end}}"
|
||||
data-comment-size="{{if gt $pageSize 0}}{{$pageSize}}{{else}}4{{end}}"
|
||||
data-comment-total="{{$total}}"
|
||||
data-comment-loaded="{{$shown}}"
|
||||
data-object-id="{{.commentObjectID}}"
|
||||
data-object-type="{{.commentObjectType}}"
|
||||
>
|
||||
<div class="play-comments__head">
|
||||
<h2 class="play-comments__title">评论</h2>
|
||||
<span class="play-comments__count" data-comment-total-label>{{if gt $total 0}}{{$total}} 条{{else if and $c (gt $c.CountComment 0)}}{{$c.CountComment}} 条{{else if and .video (gt .video.CountComment 0)}}{{.video.CountComment}} 条{{else}}暂无评论{{end}}</span>
|
||||
</div>
|
||||
|
||||
<ul class="play-comment-list" data-comment-list>
|
||||
{{range .comments}}
|
||||
<li class="play-comment" data-comment-id="{{.ID}}">
|
||||
{{$avatar := commentAvatar .UserAvatar}}
|
||||
{{if .UserAvatar}}
|
||||
<img decoding="async" loading="lazy" class="play-comment__avatar lazy" src="/static/images/placeholder.png?v=2" data-src="{{$avatar}}" alt="评论用户头像">
|
||||
{{else}}
|
||||
<img decoding="async" loading="lazy" class="play-comment__avatar" src="/static/images/placeholder.png?v=2" data-src="{{$avatar}}" alt="评论用户头像">
|
||||
{{end}}
|
||||
<div class="play-comment__body">
|
||||
<div class="play-comment__meta">
|
||||
<span class="play-comment__name">{{if .UserName}}{{.UserName}}{{else}}匿名用户{{end}}</span>
|
||||
<span class="play-comment__time">{{formatCommentTime .CreatedAt}}</span>
|
||||
</div>
|
||||
<p class="play-comment__text">{{.Text}}</p>
|
||||
{{range .Children}}
|
||||
<div class="play-comment__reply">
|
||||
{{if .UserName}}{{.UserName}}{{else}}用户{{end}}{{if .ReplyUserName}} 回复 {{.ReplyUserName}}{{end}}:{{.Text}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="play-comment__actions">
|
||||
<span class="play-comment__action">
|
||||
<img decoding="async" loading="lazy" class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/heart.svg" alt="点赞数">
|
||||
<span>{{.CountLike}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{else}}
|
||||
<li class="play-comment play-comment--empty">
|
||||
<p class="play-comment__text">暂无评论</p>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
class="play-comments__more"
|
||||
type="button"
|
||||
data-comment-more
|
||||
{{if le $total $shown}}hidden{{end}}
|
||||
>
|
||||
查看更多评论
|
||||
<img decoding="async" loading="lazy" class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/chevron-right.svg" alt="前往下一项">
|
||||
</button>
|
||||
</div>
|
||||
<script defer src="/static/js/pages/playComments.js?v=5"></script>
|
||||
{{end}}
|
||||
@@ -49,10 +49,6 @@
|
||||
<summary>如何联系客服或洽谈合作?<span class="help-faq__chevron" aria-hidden="true"></span></summary>
|
||||
<p>请发送邮件至 support@huanguoai.net,我们会尽快回复。</p>
|
||||
</details>
|
||||
<details class="help-faq__item">
|
||||
<summary>如何注销账号?<span class="help-faq__chevron" aria-hidden="true"></span></summary>
|
||||
<p>请通过意见反馈或客服邮箱提交注销申请。</p>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<aside class="help-aside">
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<script src="/static/js/common/compat.js?v=4"></script>
|
||||
<link rel="stylesheet" href="/static/css/base.css?v=2">
|
||||
<link rel="stylesheet" href="/static/common/DPlayer.min.css">
|
||||
<link rel="stylesheet" href="/static/css/play.css?v=26">
|
||||
<link rel="stylesheet" href="/static/css/play.css?v=27">
|
||||
<script defer src="/static/common/hls.min.js"></script>
|
||||
<script defer src="/static/common/DPlayer.min.js?v=2"></script>
|
||||
</head>
|
||||
@@ -331,8 +331,40 @@
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<section class="play-dual" aria-label="评论与热榜">
|
||||
{{template "playComments" .}}
|
||||
<section class="play-dual" aria-label="推荐与热榜">
|
||||
{{if .guessLike}}
|
||||
<section class="play-like" aria-label="猜你喜欢">
|
||||
<div class="play-like__head">
|
||||
<div class="play-like__titles">
|
||||
<h2 class="play-like__title">猜你喜欢</h2>
|
||||
<span class="play-like__desc only-pc">根据本剧题材推荐</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="play-like__refresh"
|
||||
data-guess-refresh
|
||||
data-playlet-id="{{.playlet.ID}}"
|
||||
data-page="{{.guessPage}}"
|
||||
data-page-size="{{if .guessPageSize}}{{.guessPageSize}}{{else}}6{{end}}"
|
||||
data-total="{{.guessTotal}}"
|
||||
>
|
||||
换一批
|
||||
<img decoding="async" loading="lazy" class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/chevron-right.svg" alt="前往下一项">
|
||||
</button>
|
||||
</div>
|
||||
<div class="play-like__grid" id="play-like" data-guess-grid>
|
||||
{{range .guessLike}}
|
||||
<article class="card card--play">
|
||||
<a class="card__cover lazy" href="{{playURL .Slug .ID}}" style="{{cssCoverPlaceholder}}" data-src="{{.Cover}}">
|
||||
{{if .TotalEpisode}}<span class="card__ep">全 {{.TotalEpisode}} 集</span>{{else}}<span class="card__ep">{{updateStatusText .UpdateStatus}}</span>{{end}}
|
||||
</a>
|
||||
<h3 class="card__title"><a href="{{playURL .Slug .ID}}">{{playletDisplayName .PlayName .Title}}</a></h3>
|
||||
<p class="card__meta">{{range $ti, $tag := cardTags .}}{{if $ti}} {{end}}{{if $tag.URL}}<a href="{{$tag.URL}}" {{externalLinkAttrs $tag.URL}}>{{$tag.Name}}</a>{{else}}{{$tag.Name}}{{end}}{{end}}</p>
|
||||
</article>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<aside class="play-hot" aria-label="热门短剧">
|
||||
<div class="play-hot__head">
|
||||
@@ -362,46 +394,12 @@
|
||||
</div>
|
||||
</li>
|
||||
{{else}}
|
||||
<li><p class="play-comment__text" style="padding:12px 0;color:var(--muted, #888)">暂无热榜数据</p></li>
|
||||
<li><p class="play-hot__empty" style="margin:0;padding:12px 0;color:var(--text-dim)">暂无热榜数据</p></li>
|
||||
{{end}}
|
||||
</ol>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
{{if .guessLike}}
|
||||
<section class="play-like" aria-label="猜你喜欢">
|
||||
<div class="play-like__head">
|
||||
<div class="play-like__titles">
|
||||
<h2 class="play-like__title">猜你喜欢</h2>
|
||||
<span class="play-like__desc only-pc">根据本剧题材推荐</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="play-like__refresh"
|
||||
data-guess-refresh
|
||||
data-playlet-id="{{.playlet.ID}}"
|
||||
data-page="{{.guessPage}}"
|
||||
data-page-size="{{if .guessPageSize}}{{.guessPageSize}}{{else}}6{{end}}"
|
||||
data-total="{{.guessTotal}}"
|
||||
>
|
||||
换一批
|
||||
<img decoding="async" loading="lazy" class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/chevron-right.svg" alt="前往下一项">
|
||||
</button>
|
||||
</div>
|
||||
<div class="play-like__grid" id="play-like" data-guess-grid>
|
||||
{{range .guessLike}}
|
||||
<article class="card card--play">
|
||||
<a class="card__cover lazy" href="{{playURL .Slug .ID}}" style="{{cssCoverPlaceholder}}" data-src="{{.Cover}}">
|
||||
{{if .TotalEpisode}}<span class="card__ep">全 {{.TotalEpisode}} 集</span>{{else}}<span class="card__ep">{{updateStatusText .UpdateStatus}}</span>{{end}}
|
||||
</a>
|
||||
<h3 class="card__title"><a href="{{playURL .Slug .ID}}">{{playletDisplayName .PlayName .Title}}</a></h3>
|
||||
<p class="card__meta">{{range $ti, $tag := cardTags .}}{{if $ti}} {{end}}{{if $tag.URL}}<a href="{{$tag.URL}}" {{externalLinkAttrs $tag.URL}}>{{$tag.Name}}</a>{{else}}{{$tag.Name}}{{end}}{{end}}</p>
|
||||
</article>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<div class="play-promo play-promo--bottom">
|
||||
{{if .advitise}}{{template "ads" (adSlice .advitise.DetailBannerList 2 2)}}{{end}}
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<script src="/static/js/common/compat.js?v=4"></script>
|
||||
<link rel="stylesheet" href="/static/css/base.css?v=2">
|
||||
<link rel="stylesheet" href="/static/common/DPlayer.min.css">
|
||||
<link rel="stylesheet" href="/static/css/play.css?v=26">
|
||||
<link rel="stylesheet" href="/static/css/play.css?v=27">
|
||||
<script defer src="/static/common/hls.min.js"></script>
|
||||
<script defer src="/static/common/DPlayer.min.js?v=2"></script>
|
||||
</head>
|
||||
@@ -181,8 +181,40 @@
|
||||
{{if .advitise}}{{template "ads" (adSlice .advitise.DetailBannerList 0 2)}}{{end}}
|
||||
</div>
|
||||
|
||||
<section class="play-dual" aria-label="评论与热榜">
|
||||
{{template "playComments" .}}
|
||||
<section class="play-dual" aria-label="推荐与热榜">
|
||||
{{if .guessLike}}
|
||||
<section class="play-like" aria-label="猜你喜欢">
|
||||
<div class="play-like__head">
|
||||
<div class="play-like__titles">
|
||||
<h2 class="play-like__title">猜你喜欢</h2>
|
||||
<span class="play-like__desc only-pc">根据本剧题材推荐</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="play-like__refresh"
|
||||
data-guess-refresh
|
||||
data-playlet-id="{{.guessPlayletID}}"
|
||||
data-page="{{.guessPage}}"
|
||||
data-page-size="{{if .guessPageSize}}{{.guessPageSize}}{{else}}6{{end}}"
|
||||
data-total="{{.guessTotal}}"
|
||||
>
|
||||
换一批
|
||||
<img decoding="async" loading="lazy" class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/chevron-right.svg" alt="前往下一项">
|
||||
</button>
|
||||
</div>
|
||||
<div class="play-like__grid" id="play-like" data-guess-grid>
|
||||
{{range .guessLike}}
|
||||
<article class="card card--play">
|
||||
<a class="card__cover lazy" href="{{playURL .Slug .ID}}" style="{{cssCoverPlaceholder}}" data-src="{{.Cover}}">
|
||||
{{if .TotalEpisode}}<span class="card__ep">全 {{.TotalEpisode}} 集</span>{{else}}<span class="card__ep">{{updateStatusText .UpdateStatus}}</span>{{end}}
|
||||
</a>
|
||||
<h3 class="card__title"><a href="{{playURL .Slug .ID}}">{{playletDisplayName .PlayName .Title}}</a></h3>
|
||||
<p class="card__meta">{{range $ti, $tag := cardTags .}}{{if $ti}} {{end}}{{if $tag.URL}}<a href="{{$tag.URL}}" {{externalLinkAttrs $tag.URL}}>{{$tag.Name}}</a>{{else}}{{$tag.Name}}{{end}}{{end}}</p>
|
||||
</article>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<aside class="play-hot" aria-label="热门短剧">
|
||||
<div class="play-hot__head">
|
||||
@@ -212,46 +244,12 @@
|
||||
</div>
|
||||
</li>
|
||||
{{else}}
|
||||
<li><p class="play-comment__text" style="padding:12px 0;color:var(--muted, #888)">暂无热榜数据</p></li>
|
||||
<li><p class="play-hot__empty" style="margin:0;padding:12px 0;color:var(--text-dim)">暂无热榜数据</p></li>
|
||||
{{end}}
|
||||
</ol>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
{{if .guessLike}}
|
||||
<section class="play-like" aria-label="猜你喜欢">
|
||||
<div class="play-like__head">
|
||||
<div class="play-like__titles">
|
||||
<h2 class="play-like__title">猜你喜欢</h2>
|
||||
<span class="play-like__desc only-pc">根据本剧题材推荐</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="play-like__refresh"
|
||||
data-guess-refresh
|
||||
data-playlet-id="{{.guessPlayletID}}"
|
||||
data-page="{{.guessPage}}"
|
||||
data-page-size="{{if .guessPageSize}}{{.guessPageSize}}{{else}}6{{end}}"
|
||||
data-total="{{.guessTotal}}"
|
||||
>
|
||||
换一批
|
||||
<img decoding="async" loading="lazy" class="icon" src="/static/images/placeholder.png?v=2" data-src="/static/images/chevron-right.svg" alt="前往下一项">
|
||||
</button>
|
||||
</div>
|
||||
<div class="play-like__grid" id="play-like" data-guess-grid>
|
||||
{{range .guessLike}}
|
||||
<article class="card card--play">
|
||||
<a class="card__cover lazy" href="{{playURL .Slug .ID}}" style="{{cssCoverPlaceholder}}" data-src="{{.Cover}}">
|
||||
{{if .TotalEpisode}}<span class="card__ep">全 {{.TotalEpisode}} 集</span>{{else}}<span class="card__ep">{{updateStatusText .UpdateStatus}}</span>{{end}}
|
||||
</a>
|
||||
<h3 class="card__title"><a href="{{playURL .Slug .ID}}">{{playletDisplayName .PlayName .Title}}</a></h3>
|
||||
<p class="card__meta">{{range $ti, $tag := cardTags .}}{{if $ti}} {{end}}{{if $tag.URL}}<a href="{{$tag.URL}}" {{externalLinkAttrs $tag.URL}}>{{$tag.Name}}</a>{{else}}{{$tag.Name}}{{end}}{{end}}</p>
|
||||
</article>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<div class="play-promo play-promo--bottom">
|
||||
{{if .advitise}}{{template "ads" (adSlice .advitise.DetailBannerList 2 2)}}{{end}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user