Files
hgmj_h5/src/components/CommentBox/index.vue
T
2026-10-08 21:20:52 +07:00

1229 lines
36 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<van-popup
v-if="props.renderType === 'popup'"
v-model:show="isShow"
position="bottom"
z-index="2001"
teleport="body"
:overlay="!props.overlayTransparent"
:overlay-style="props.overlayTransparent ? { background: 'transparent' } : undefined"
@close="emits('close')"
>
<div ref="CommentBox" class="comment-wrap"></div>
</van-popup>
<div v-else ref="CommentBox" class="comment-wrap"></div>
<Teleport v-if="CommentBox" :to="CommentBox">
<div class="wrapper" :class="[props.theme, props.renderType || '', { 'is-peek-video': props.overlayTransparent }]">
<div class="headerBox">
<div class="horizontalLineBlock"></div>
<div class="title">{{ state.lfCount }}条评论</div>
</div>
<div class="main overflow-y-auto">
<template v-if="!state.loading">
<PullRefresh :params="moreParams" @onLoad="onLoad" @onRefresh="onRefresh">
<div class="commentBox" v-for="(item, index) in state.list" :key="item.id">
<div class="goldComment" v-if="item.isGodComment"></div>
<LazyImg
:src="item.userPortrait"
class="avatar"
:key="item.userPortrait"
@click.stop="router.push({ path: '/userInfo', query: { uid: item.userID } })"
/>
<div class="commentInfoBox">
<div class="name flex-vcenter">
<span class="ellipsis">
{{ item.userName || '未知用户' }}
</span>
<!-- <div class="vip" v-if="item.vipName">{{ item.vipName }}</div> -->
<SvgIcon name="vipLabel" class="vipLabel" v-if="item.vipLevel" />
</div>
<div class="content">
<!-- {{ item.content }} -->
<HighlightText
:text="item.content"
:keyword="item.searchKeyword"
:linkStr="item.linkStr"
:linkType="item.linkType"
></HighlightText>
<lazyImg v-if="item.image" class="image" :src="item.image" @lz-image-click="toPicPage"></lazyImg>
</div>
<!-- 置顶评论下方:评论区 Banner 轮播 -->
<div v-if="index === 0 && commentBanners.length" class="commentTopBanner">
<van-swipe :autoplay="4000" :show-indicators="commentBanners.length > 1" lazy-render>
<van-swipe-item
v-for="banner in commentBanners"
:key="banner.id"
class="commentTopBannerItem"
style="width: 291px; height: 61px"
@click="handleCommentBannerClick(banner)"
>
<LazyImg :src="banner.imageUrl" :key="banner.imageUrl" class="bannerImg" />
</van-swipe-item>
</van-swipe>
</div>
<div class="commentInfo flex-center-between">
<div class="time">{{ moment(item.createdAt).format('YYYY-MM-DD HH:mm') }}</div>
<div v-if="![1, 2].includes(item.linkType)" class="optBox flex-vcenter">
<div class="zanBox">
<SvgIcon @click="clickLike(item)" v-if="item.isLike" name="movieZan" />
<SvgIcon @click="clickLike(item)" v-else name="movieUnZan" />
{{ watchCount(item.likeCount) }}
</div>
<SvgIcon :name="props.theme ? 'msg2' : 'msg'" @click="reply(item, undefined, undefined)" />
</div>
</div>
<!-- 子评论区域 -->
<div class="subCommentArea">
<div class="subShow" v-if="item.Info.length">
<div class="subCommentBox" v-for="subItem in item.Info" :key="subItem.id">
<LazyImg :src="subItem.userPortrait" class="avatar" :key="subItem.userPortrait" />
<div class="commentInfoBox">
<div class="nameBox flex-vcenter">
<div class="name">{{ subItem.userName || '未知用户' }}</div>
<!-- <span
class="privateLetter"
@click.stop="router.push({ path: '/privateLetter', query: { uid: subItem.userID } })"
v-if="userInfo.uid != subItem.userID"
>
私信TA
</span> -->
<!-- <span v-if="subItem.toUserName">回复了</span> -->
<SvgIcon v-if="subItem.toUserName" name="rightArrow3" />
<div class="name light-color-3">{{ subItem.toUserName }}</div>
</div>
<div class="content">
<!-- {{ subItem.content }} -->
<HighlightText
:text="subItem.content"
:keyword="subItem.searchKeyword"
:linkStr="subItem.linkStr"
:linkType="subItem.linkType"
></HighlightText>
<lazyImg
v-if="subItem.image"
class="image"
:src="subItem.image"
@lz-image-click="toPicPage"
></lazyImg>
</div>
<div class="commentInfo flex-center-between">
<div class="time">{{ moment(subItem.createdAt).format('YYYY-MM-DD HH:mm') }}</div>
<div v-if="![1, 2].includes(subItem.linkType)" class="optBox flex-vcenter">
<div class="zanBox">
<SvgIcon @click="clickLike(subItem)" v-if="subItem.isLike" name="movieZan" />
<SvgIcon @click="clickLike(subItem)" v-else name="movieUnZan" />
{{ watchCount(subItem.likeCount) }}
</div>
<SvgIcon
:name="props.theme ? 'msg2' : 'msg'"
@click="reply(subItem, subItem.userID, item.id)"
/>
</div>
</div>
<!-- <div class="horizontalLine" v-if="item.subList && item.subList.length"></div> -->
</div>
</div>
</div>
<div class="subShow pdunset" v-if="item.subShow">
<div class="subCommentBox" v-for="subItem in item.subList" :key="subItem.id">
<LazyImg
:src="subItem.userPortrait"
class="avatar"
:key="subItem.userPortrait"
@click.stop="router.push({ path: '/userInfo', query: { uid: subItem.userID } })"
/>
<div class="commentInfoBox">
<div class="nameBox flex-vcenter">
<div class="name">{{ subItem.userName || '未知用户' }}</div>
<!-- <span
class="privateLetter"
@click.stop="router.push({ path: '/privateLetter', query: { uid: subItem.userID } })"
v-if="userInfo.uid != subItem.userID && !subItem.toUserName"
>
私信TA
</span> -->
<!-- <span v-if="subItem.toUserName">回复了</span> -->
<SvgIcon v-if="subItem.toUserName" name="rightArrow3" />
<div class="name light-color-3">{{ subItem.toUserName }}</div>
</div>
<div class="content">
<!-- {{ subItem.content }} -->
<HighlightText
:text="subItem.content"
:keyword="subItem.searchKeyword"
:linkStr="subItem.linkStr"
:linkType="subItem.linkType"
></HighlightText>
<lazyImg
v-if="subItem.image"
class="image"
:src="subItem.image"
@lz-image-click="toPicPage"
></lazyImg>
</div>
<div class="commentInfo flex-center-between">
<div class="time">{{ moment(subItem.createdAt).format('YYYY-MM-DD HH:mm') }}</div>
<div v-if="![1, 2].includes(subItem.linkType)" class="optBox flex-vcenter">
<div class="zanBox">
<SvgIcon @click="clickLike(subItem)" v-if="subItem.isLike" name="movieZan" />
<SvgIcon @click="clickLike(subItem)" v-else name="movieUnZan" />
{{ watchCount(subItem.likeCount) }}
</div>
<SvgIcon
:name="props.theme ? 'msg2' : 'msg'"
@click="reply(subItem, subItem.userID, item.id)"
/>
</div>
</div>
<!-- <div class="horizontalLine" v-if="index != item.subList.length - 1"></div> -->
</div>
</div>
<Loading v-if="item.subLoading" />
</div>
<div class="expandBox flex-vcenter" v-if="item.commCount - 1 > 0 && item.Info.length > 0">
<div
class="expandMore flex-vcenter"
@click.stop="expandComment(item)"
v-if="!(item.subList && item.subList.length) && !item.subHasNext"
>
<div class="flex-vcenter">
—— 展开{{ item.commCount - 1 }}条回复
<van-icon name="arrow-down" class="comment-expand" />
</div>
</div>
<template v-else>
<div class="expandMore flex-vcenter" @click.stop="loadMore(item)" v-if="!item.subHasNext">
<div class="flex-vcenter expand">
—— 查看更多
<van-icon name="arrow-down" class="comment-expand" />
</div>
</div>
<div
class="closeBox"
@click.stop="expandComment(item)"
v-if="item.subList && item.subList.length"
>
<div class="close flex-center-center">
收起
<van-icon name="arrow-up" class="comment-expand" />
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</PullRefresh>
</template>
<Loading v-else />
</div>
<div class="mask" v-if="state.childrenCommentObj" @click.stop="cancelReply"></div>
<!-- 评论 -->
<div id="comment-bottom-box" ref="sendBox" class="bottomBox" :class="[props.theme]">
<!-- <div class="send-img" v-if="state.img">
<img class="close" @click="delImg" src="@/assets/images/png/close1.png" alt="" />
<DecryptImg class="img" :src="state.img" />
</div> -->
<div class="commentBtnBox flex-center-between" :class="[props.theme]">
<div class="commentInput flex-vcenter">
<van-field
ref="contentRef"
v-model="state.content"
:placeholder="state.placeholder"
input-align="left"
:clearable="false"
:border="false"
@blur="blur"
/>
</div>
<UploadImgMess
:maxCount="1"
:isCover="false"
@uploadSuccess="uploadSuccess"
style="margin-left: 0"
:key="imgKey"
ref="uploadFileRef"
:commentType="props.commentType"
/>
<template v-if="props.commentType == 'short'">
<div class="post_item flex-vcenter" @click.stop="sendBtn">
<SvgIcon class="icon" name="post_send" />
</div>
</template>
<template v-if="props.commentType == 'normal'">
<div class="flex-vcenter comment_post">
<div class="post_item flex-vcenter" @click.stop="clickCollect" v-if="showCollected">
<SvgIcon class="icon" v-if="state.videoInfo.vidStatus?.hasCollected" name="post_followin" />
<SvgIcon class="icon" v-else name="post_follow" />
</div>
<div class="post_item flex-vcenter" @click.stop="sendBtn">
<SvgIcon class="icon" name="post_send" />
</div>
</div>
</template>
</div>
</div>
</div>
</Teleport>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, ref, PropType, unref, watch } from 'vue';
import { showToast } from 'vant';
import useLoading from '@/hooks/loading';
import moment from 'moment';
import { LikeType } from '@/enums/app';
import CommunityApi from '@/service/community';
import AppApi from '@/service/app';
import VideoApi from '@/service/video';
import { watchCount, compatibleAppStoreJump, innerJump } from '@/utils/index';
import { useUserStore } from '@/stores/user';
import { useRouter } from 'vue-router';
const router = useRouter();
const userStore = useUserStore();
const { userInfo } = userStore;
const imgKey = ref(1);
const CommentBox = ref();
const uploadFileRef = ref(null);
/** 评论区置顶 Banner */
const commentBanners = ref<Types.App.CommentBannerItem[]>([]);
const props = defineProps({
isOpen: {
type: Boolean,
default: false,
},
objType: {
type: String,
default: 'video',
},
renderType: {
type: String as PropType<'popup' | 'normal'>, //popup弹窗形式,normal正常布局流
default: 'popup',
},
theme: {
type: String as PropType<'black' | ''>,
default: 'black',
},
SendBtnTeleportEl: {
type: String,
default: '',
},
commentType: {
type: String as PropType<'normal' | 'short'>, //normal默认底部样式,short短视频底部样式
default: 'normal',
},
collectType: {
type: String,
default: LikeType.SP,
},
showCollected: {
type: Boolean,
default: () => true,
},
/** 短剧等场景:遮罩透明,顶部露出全宽播放(对齐抖音评论) */
overlayTransparent: {
type: Boolean,
default: false,
},
});
const sendBox = ref();
watch(sendBox, (el) => {
if (!el) return;
if (!props.SendBtnTeleportEl) return;
document.querySelector(props.SendBtnTeleportEl)?.appendChild(el);
});
const { openLoading, closeLoading } = useLoading();
const isShow = computed(() => {
return props.isOpen;
});
const emits = defineEmits(['addComment', 'close', 'getTotal']);
const contentRef = ref<HTMLElement>();
const state = reactive({
content: '',
list: <Array<any>>[], // 评论列表
loading: true,
pageNumber: 1, // 评论页码
pageSize: 15, // 评论一页的数量
subPageNumber: 1, // 子评论页码
subPageSize: 15, // 子评论一页的数量
videoInfo: <any>{},
lfCount: 0,
placeholder: '请输入评论',
childrenCommentObj: <any>null,
img: '',
});
// 上拉加载及下拉刷新参数
const moreParams = reactive({
loading: false,
refreshing: false,
finished: false,
isNoData: false,
error: false,
isHigehtMax: false,
});
// 获取评论数据
const getList = async (type: string) => {
const requestPage = state.pageNumber;
try {
let req = {
objID: String(state.videoInfo.id),
objType: props.objType,
curTime: new Date().toISOString(),
pageNumber: String(requestPage),
pageSize: String(state.pageSize),
};
let res: any = await CommunityApi.getCommentList(req);
state.loading = false;
state.lfCount = res.total || 0;
emits('getTotal', unref(state.lfCount));
moreParams.loading = false;
moreParams.refreshing = false;
const { list } = res;
if (type === 'refresh' || requestPage === 1) {
state.list = list || [];
} else if (list && list.length) {
state.list = state.list.concat(list);
}
if (requestPage == 1 && state.list.length == 0) {
moreParams.isNoData = true;
}
if (!res.hasNext) {
moreParams.finished = true;
}
// 用请求页码判断,避免 onLoad 抢先 pageNumber++ 后漏拉置顶 Banner
if (requestPage === 1) {
void loadCommentTopBanners();
}
} catch (e) {
moreParams.loading = false;
moreParams.error = true;
}
};
/** 拉取评论区置顶 Banner(COMMENT_TOP) */
const loadCommentTopBanners = async () => {
try {
const res = await AppApi.getBannerList({ scene: 'COMMENT_TOP' });
const list = (res?.list || []).slice().sort((a, b) => (b.sort || 0) - (a.sort || 0));
commentBanners.value = list;
} catch (e) {
console.log(e);
commentBanners.value = [];
}
};
const handleCommentBannerClick = (banner: Types.App.CommentBannerItem) => {
if (!banner || banner.linkType === 'NONE' || !banner.linkValue) return;
const linkValue = banner.linkValue.trim();
if (banner.linkType === 'EXTERNAL') {
compatibleAppStoreJump(linkValue);
return;
}
// INTERNAL
if (linkValue.startsWith('yinseinner://')) {
innerJump(linkValue.substring('yinseinner://'.length));
return;
}
if (linkValue.startsWith('video://')) {
const path = linkValue.slice('video://'.length);
const queryIndex = path.indexOf('?');
const routeName = queryIndex === -1 ? path : path.slice(0, queryIndex);
const query = queryIndex === -1 ? '' : path.slice(queryIndex + 1);
const idMatch = query.match(/(?:^|&)id=([^&]*)/);
const id = idMatch ? decodeURIComponent(idMatch[1]) : '';
if ((routeName === 'detail' || routeName === 'video_page') && id) {
router.push({
path: '/movieDetails',
query: { vid: id },
});
}
return;
}
if (/^https?:\/\//i.test(linkValue)) {
compatibleAppStoreJump(linkValue);
return;
}
innerJump(linkValue);
};
const onLoad = () => {
moreParams.loading = true;
state.pageNumber++;
getList('');
};
const onRefresh = () => {
state.pageNumber = 1;
moreParams.refreshing = true;
moreParams.finished = false;
moreParams.isNoData = false;
getList('refresh');
};
// 评论成功后增加评论数据
const addComment = (res: any, childrenCommentObj: any) => {
moreParams.isNoData = false;
if (childrenCommentObj) {
state.list.map((comment: any) => {
if (comment.id == childrenCommentObj.id) {
if (!comment.subList) {
comment.subLoading = false;
comment.subList = [];
comment.subHasNext = true;
comment.subShow = true;
}
comment.Info.unshift(res);
}
return comment;
});
} else {
// 插入都到置顶评论后
state.list.splice(1, 0, res);
}
};
// 点赞
const clickLike = async (comment: any) => {
try {
if (!comment.isLike) {
let req = {
type: LikeType.COMMENT,
objID: comment.id,
};
await CommunityApi.zan(req);
comment.isLike = true;
comment.likeCount++;
showToast('点赞成功');
} else {
let req = {
type: LikeType.COMMENT,
objIDs: [comment.id],
};
await CommunityApi.cancleZan(req);
comment.isLike = false;
comment.likeCount--;
showToast('取消成功');
}
} catch (e) {
console.log(e);
}
};
// 收藏
const clickCollect = async () => {
let hasCollected = state.videoInfo.vidStatus.hasCollected;
let req = {
objID: state.videoInfo.id,
isCollect: !hasCollected,
type: props.collectType,
};
CommunityApi.collect(req).then(() => {
state.videoInfo.vidStatus.hasCollected = !hasCollected;
if (hasCollected) {
showToast('取消成功');
state.videoInfo.collectCount--;
} else {
showToast('收藏成功');
state.videoInfo.collectCount++;
}
});
};
// 回复二级评论
const reply = (comment: any, toUserID: any, parentId: any) => {
comment.toUserID = toUserID ? toUserID : undefined;
comment.rid = toUserID ? comment.id : undefined;
comment.id = toUserID ? parentId : comment.id;
state.childrenCommentObj = comment;
state.placeholder = `回复@${comment.userName}`;
contentRef.value?.focus();
};
// 展开评论
const expandComment = async (comment: any) => {
if (!comment.subShow) {
comment.subShow = true;
comment.subHasNext = true; // 子评论是否还有下一页数据
comment.subList = []; // 子评论列表
comment.subLoading = true; // 子评论加载loading
state.subPageNumber = 1;
const resObj: any = await getSubList(comment);
comment.subLoading = false;
comment.subHasNext = resObj.subHasNext;
if (resObj.list.length) {
if (!comment.subList) {
comment.subList = [];
}
comment.subList = comment.subList.concat(resObj.list);
} else {
comment.commCount = comment.commCount - 1;
}
} else {
comment.subLoading = false;
comment.subList = []; // 清空子评论列表
comment.subHasNext = false; // 子评论是否还有下一页数据
comment.subShow = false;
}
};
// 获取更多评论
const loadMore = async (comment: any) => {
comment.subLoading = true;
state.subPageNumber++;
const resObj: any = await getSubList(comment);
comment.subLoading = false;
comment.subList = comment.subList.concat(resObj.list);
comment.subHasNext = resObj.subHasNext;
};
// 获取二级评论
const getSubList = async (comment: any) => {
let resObj = {
subHasNext: true,
list: <any>[],
};
let req = {
objID: comment.objID,
cmtId: comment.id,
fstID: comment.Info[0].id, // 第一条评论的ID
curTime: new Date().toISOString(),
pageNumber: state.subPageNumber,
pageSize: state.subPageSize,
};
let res: any = await CommunityApi.getSecCommentList(req);
if (res.list && res.list.length > 0) {
resObj.list = res.list;
}
if (res.hasNext) {
resObj.subHasNext = false;
}
return resObj;
};
const sendBtn = async () => {
if (!state.content) return showToast('请输入评论内容');
try {
let req = {};
if (!state.childrenCommentObj) {
req = {
objID: state.videoInfo.id,
objType: props.objType,
level: 1,
content: state.content,
image: state.img,
};
} else {
// 二级评论参数
req = {
objID: state.videoInfo.id,
objType: props.objType,
level: 2,
content: state.content,
cid: state.childrenCommentObj.id,
userID: state.childrenCommentObj.userID,
rid: state.childrenCommentObj.toUserID ? state.childrenCommentObj.rid : undefined,
toUserID: state.childrenCommentObj.toUserID,
image: state.img,
};
}
openLoading();
let res: any = await VideoApi.sendComment(req);
state.content = ''; // 评论输入框内容清空
state.lfCount++;
if (typeof res === 'object') {
addComment(res, state.childrenCommentObj);
}
emits('addComment');
cancelReply();
showToast({
message: '已提交,审核通过后将展示',
className: 'comment-review-toast',
});
(uploadFileRef.value as any).reset();
state.img = '';
} catch (e) {
console.log(e);
} finally {
closeLoading();
}
};
// 回复一级评论
const cancelReply = () => {
state.childrenCommentObj = <any>null;
state.placeholder = '请输入评论';
state.content = '';
};
const getCommentList = () => {
state.pageNumber = 1;
moreParams.loading = false;
moreParams.finished = false;
moreParams.isNoData = false;
state.list = <Array<Types.Video.CommentItem>>[];
// 置顶 Banner 单独接口,打开评论区时先拉,不依赖评论分页状态
void loadCommentTopBanners();
getList('refresh');
};
const blur = () => {
window.scrollTo(0, 0);
};
defineExpose({
getCommentList,
moreParams,
state,
getList,
});
const uploadSuccess = (url: string) => {
state.img = url;
};
const delImg = () => {
state.img = '';
imgKey.value++;
};
const toPicPage = (img: string) => {
router.push({
path: '/picturePreview',
query: {
imgs: [img],
index: 0,
},
});
};
</script>
<style lang="scss" scoped>
:deep(.van-popup) {
background: transparent !important;
}
.wrapper {
height: 70vh;
background: #0f0f0f;
.headerBox {
background: #0f0f0f;
border-radius: 20px 20px 0 0;
padding: 18px 16px 0;
box-sizing: border-box;
z-index: 2;
top: 1px;
.horizontalLineBlock {
height: 5px;
width: 36px;
background: rgba(255, 255, 255, 0.2);
border-radius: 100px;
margin: 0 auto;
}
.title {
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: rgba(255, 255, 255, 0.9);
margin-top: 10px;
padding: 0;
}
}
.main {
height: 55vh;
padding: 18px 16px;
background: #0f0f0f;
color: rgba(255, 255, 255, 0.9);
box-sizing: border-box;
.commentBox {
display: flex;
gap: 12px;
margin-bottom: 18px;
padding-bottom: 0;
position: relative;
.goldComment {
position: absolute;
top: 0px;
right: -5px;
height: 67px;
width: 83px;
background: url('@/assets/images/png/home/goldComment.png') no-repeat;
background-size: 100% 100%;
}
.avatar {
height: 40px;
width: 40px;
border-radius: 50%;
overflow: hidden;
position: relative;
flex-shrink: 0;
transform: translateZ(0px);
:deep(.loading) {
background-size: 18px 18px;
}
}
.commentInfoBox {
flex: 1;
min-width: 0;
margin-left: 0;
font-size: 14px;
display: flex;
flex-direction: column;
gap: 10px;
.name {
color: rgba(255, 255, 255, 0.55);
font-weight: 500;
font-size: 14px;
line-height: 20px;
height: auto;
flex: 1;
gap: 4px;
span {
max-width: 240px;
}
.vipLabel {
width: 22px;
height: 14px;
margin-left: 0;
}
.vip {
height: 14px;
width: 30px;
margin-left: 8px;
color: black;
font-size: 10px;
font-weight: 500;
background-color: rgba(255, 211, 0, 1);
border-radius: 14px;
text-align: center;
line-height: 15px;
}
}
.content {
margin-top: 0;
line-height: 20px;
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
.image {
width: 120px;
max-height: 160px;
border-radius: 10px;
margin: 4px 0 0;
overflow: hidden;
}
:deep(.highlighted) {
font-size: 14px;
line-height: 20px;
color: rgba(255, 255, 255, 0.9);
span {
color: rgba(246, 136, 4, 1);
}
}
}
.commentTopBanner {
margin-top: 0;
width: 291px;
max-width: 100%;
height: 61px;
border-radius: 12px;
overflow: hidden;
:deep(.van-swipe) {
width: 291px;
max-width: 100%;
height: 61px;
overflow: hidden;
}
:deep(.van-swipe__track) {
height: 100%;
}
:deep(.van-swipe-item) {
width: 291px !important;
height: 61px;
overflow: hidden;
}
.commentTopBannerItem {
width: 291px;
height: 61px;
overflow: hidden;
}
.bannerImg {
display: block;
width: 291px !important;
max-width: 100%;
height: 61px !important;
overflow: hidden;
:deep(.laz_com),
:deep(.lazy_placeholder),
:deep(img) {
width: 100% !important;
height: 100% !important;
object-fit: cover;
}
}
:deep(.van-swipe__indicators) {
bottom: 4px;
display: flex;
justify-content: center;
gap: 4px;
width: 100%;
padding: 0;
}
:deep(.van-swipe__indicator) {
width: 4px;
height: 4px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.5);
opacity: 1;
margin: 0;
}
:deep(.van-swipe__indicator--active) {
width: 8px;
height: 4px;
border-radius: 3px;
background: #fff;
}
}
.commentInfo {
margin-top: 0;
color: rgba(255, 255, 255, 0.55);
.time {
font-size: 12px;
line-height: 18px;
color: rgba(255, 255, 255, 0.55);
}
.optBox {
color: rgba(255, 255, 255, 0.9);
gap: 12px;
svg {
width: 20px;
height: 20px;
}
.zanBox {
margin-right: 0;
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
line-height: 18px;
color: rgba(255, 255, 255, 0.9);
}
}
}
}
.subCommentArea {
border-radius: 12px;
margin-top: 0;
.subShow {
padding: 0;
.subCommentBox {
display: flex;
gap: 12px;
margin: 12px 0 0;
.avatar {
height: 36px;
width: 36px;
border-radius: 50%;
overflow: hidden;
position: relative;
transform: translateZ(0px);
:deep(.loading) {
background-size: 13px 13px;
}
}
.commentInfoBox {
flex: 1;
margin-left: 0;
font-size: 14px;
gap: 10px;
.nameBox {
.name {
height: auto;
color: rgba(255, 255, 255, 0.55);
font-weight: 500;
font-size: 14px;
line-height: 20px;
flex: unset;
}
> svg {
width: 18px;
height: 18px;
}
span {
color: rgba(255, 255, 255, 0.55);
margin: 0 5px;
}
}
.content {
margin-top: 0;
line-height: 20px;
font-size: 14px;
}
.commentInfo {
margin-top: 0;
.time {
font-size: 12px;
color: rgba(255, 255, 255, 0.55);
}
}
.horizontalLine {
height: 1px;
width: 100%;
background-color: rgba(255, 255, 255, 0.1);
margin: 12px 0 0;
}
}
}
}
.pdunset {
padding-top: unset;
}
.expandBox {
color: #656565;
font-size: 12px;
font-weight: 500;
line-height: 18px;
flex: 1;
padding: 0;
justify-content: space-between;
.expandMore {
gap: 6px;
}
.comment-expand {
margin-left: 0;
font-size: 12px;
}
.closeBox {
width: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
.close {
height: 22px;
width: 55px;
border-radius: 22px;
}
}
}
}
}
}
.mask {
height: 100vh;
width: 100vw;
position: fixed;
bottom: 0;
right: 0;
z-index: 88;
background-color: rgba(0, 0, 0, 0.4);
}
&.black {
.headerBox {
background: #0f0f0f;
color: rgba(255, 255, 255, 0.9);
.horizontalLineBlock {
background: rgba(255, 255, 255, 0.2);
}
}
.main {
background: #0f0f0f;
color: rgba(255, 255, 255, 0.9);
}
}
/**
* 短剧等顶部露视频:弹层高度锁死为 70dvh,内部 flex,避免 header+main(55vh)+输入栏撑破后上溢盖住视频。
* 与 DramaPlayPage --drama-peek-h: 30dvh 对齐(30+70=100dvh)。
*/
&.is-peek-video {
height: 70dvh;
max-height: 70dvh;
overflow: hidden;
display: flex;
flex-direction: column;
box-sizing: border-box;
background: #0f0f0f;
.headerBox {
flex-shrink: 0;
}
.main {
flex: 1 1 auto;
min-height: 0;
height: auto;
}
.bottomBox,
#comment-bottom-box {
position: relative !important;
flex-shrink: 0;
bottom: auto !important;
right: auto !important;
padding-bottom: env(safe-area-inset-bottom);
background: #0f0f0f;
box-sizing: border-box;
}
}
}
.wrapper.popup {
border-radius: 20px 20px 0 0;
overflow: hidden;
}
</style>
<style lang="scss">
#comment-bottom-box {
width: 100%;
position: absolute;
bottom: 0;
right: 0;
z-index: 99;
background: #0f0f0f;
.send-img {
width: 60px;
height: 60px;
margin-left: 12px;
position: relative;
.close {
width: 20px;
height: 20px;
position: absolute;
top: -10px;
right: -10px;
z-index: 1;
}
.img {
width: 60px;
height: 60px;
border-radius: 7px;
overflow: hidden;
margin: 10px 0;
}
}
.commentBtnBox {
height: 58px;
width: 100%;
padding: 14px 16px;
box-sizing: border-box;
background: #0f0f0f;
gap: 12px;
.commentInput {
border: unset;
border-radius: 16px;
background: rgba(255, 255, 255, 0.1);
height: 32px;
position: relative;
padding: 7px 16px;
flex: 1;
overflow: hidden;
box-sizing: border-box;
.van-cell {
padding: 0;
height: 18px;
min-height: 18px;
background: transparent;
align-items: center;
::placeholder {
font-size: 12px;
line-height: 18px;
color: #bdbdbd;
}
.van-field__control {
font-size: 12px;
line-height: 18px;
color: rgba(255, 255, 255, 0.9);
}
}
}
.post_item {
color: rgba(255, 255, 255, 0.9);
margin-left: 0;
flex-shrink: 0;
.icon {
width: 20px;
height: 20px;
}
}
.comment_post {
font-size: 14px;
gap: 12px;
}
&.black {
.commentInput {
background: rgba(255, 255, 255, 0.1);
.van-cell {
background: transparent;
::placeholder {
color: #bdbdbd;
}
.van-field__control {
color: rgba(255, 255, 255, 0.9);
}
}
}
}
}
&.black {
.commentBtnBox {
background: #0f0f0f;
}
}
}
</style>