kaizheng(郑凯) 32da76af09 fix:bug修复
2025-04-25 13:30:14 +08:00

772 lines
17 KiB
Vue

<template>
<div class="msg-content">
<ChatFrame
ref="ChatFrameRef"
:informMsgList="
detail.interactType === 1
? store.state.interactMsgObj.userInteractMsgList
: store.state.interactMsgObj.tgInteractMsgList
"
:detail="detail"
:isTg="detail.interactType === 2"
@optShare="optShare" />
<div class="operate-wrap">
<div class="operate" @click="showInteractAction">
<img src="@/assets/images/msg-icon.png" alt="" />
</div>
<div
class="operate"
@click="changeMsgTip"
v-show="detail.interactType === 2">
<img :src="msgTipIcon[msgTipIndex]" alt="" />
</div>
<ul
class="new-msg-list"
v-show="msgTipIndex === 1"
v-if="detail.interactType === 2">
<li
v-for="(item, index) in newMsgTree"
:key="index"
@click="showInteractAction">
<div class="content">
<p>{{ item.content }}</p>
<img :src="item.imgUrl" />
</div>
</li>
</ul>
</div>
<!-- 活动 -->
<div
class="recommend-img"
v-show="showRecommend"
@click="toActivityPage(1)">
<img :src="activityObj.imgUrl" alt="" srcset="" />
<i @click.stop="closeActivity"></i>
</div>
<!-- 推送产品 -->
<div class="recommend-img" v-show="showCarPush" @click="toActivityPage(2)">
<img :src="carPushObj.coverImgUrl" alt="" srcset="" />
<i @click.stop="closeCarPush"></i>
</div>
</div>
<div class="input-area">
<input
type="text"
maxlength="200"
:placeholder="
detail.playType === 2 || detail.liveStatus === $liveStatusObj.FinishPlay
? '直播结束,暂不支持互动哟~'
: isSpeak
? '互动已关闭...'
: '说点什么...'
"
v-model.trim="text"
@focus="inputMsg"
:disabled="
detail.playType === 2 ||
isSpeak ||
[$liveStatusObj.FinishPlay].includes(detail.liveStatus)
"
@keyup.enter="sendMsg(1, { text })" />
<button
class="send"
:disabled="!text || sendTextLoading"
@click="sendMsg(1, { text })">
发送
</button>
<button
class="share"
@click="sendMsg(5)"
v-if="
terminalType === 'Browser' ||
(terminalType === 'App' && system === 'android')
"></button>
<button
class="star-btn"
v-if="detail.liveStatus !== $liveStatusObj.NotStart">
<div
@click.stop="sendMsg(4)"
:class="[
'star',
favorUserCountObj.isFavor === 1 ? 'active' : '',
]"></div>
<span v-if="favorUserCountObj.favorUserCount">{{
bigNumberTransform(favorUserCountObj.favorUserCount)
}}</span>
</button>
</div>
<div
class="discounts-num"
v-if="
couponDetail &&
couponDetail.sendTotalNumber - couponDetail.sendGottenNumber > 0
"
@click="showDiscountCoupon">
<!-- <p>
仅剩{{ couponDetail.sendTotalNumber - couponDetail.sendGottenNumber }}份
</p> -->
</div>
<Share ref="shareRef" :detail="detail" />
<QuestionnairePopup ref="questionnairePopupRef" :questionId="questionId" />
<van-overlay :show="showGuide" @click="showGuide = false">
<img src="@/assets/images/guide1.png" />
<p>点击右上角进行分享,点击屏幕关闭分享指引</p>
</van-overlay>
<Interact
:detail="detail"
ref="InteractRef"
:isSpeak="isSpeak"
:optShare="optShare"
@bsRefresh="bsRefresh" />
</template>
<script setup>
import {
ref,
defineProps,
watch,
defineEmits,
onBeforeUnmount,
reactive,
computed,
defineExpose,
} from "vue"
import { useRoute } from "vue-router"
import { showToast } from "vant"
import Share from "@/components/Share"
// import { likeVideo, queryLiveCoupon } from "@/api/video";
import { likeVideo } from "@/api/video"
import emitter from "@/utils/emitter"
import useGetLiveStatusObj from "@/hooks/useGetLiveStatusObj"
import QuestionnairePopup from "../components/QuestionnairePopup.vue"
import { useStore } from "vuex"
import ChatFrame from "./ChatFrame.vue"
import Interact from "./Interact.vue"
import { terminalType } from "@/utils/index"
import { getSystem } from "@/utils/index"
import useContinuousClickLive from "@/hooks/useContinuousClickLive"
// import TgChatFrame from "./TgChatFrame.vue";
// import MainInteract from "./MainInteract.vue";
import { queryCartRead } from "@/api/video"
const $liveStatusObj = useGetLiveStatusObj()
const store = useStore()
const system = getSystem()
const props = defineProps({
detail: {
// 视频详情
ype: Object,
default: () => ({}),
},
activityObj: {
// 活动内容
type: Object,
default: () => ({}),
},
favorUserCountObj: {
// 关注用户信息
type: Object,
default: () => ({}),
},
isSpeak: {
// 是否关闭互动
type: Boolean,
default: false,
},
carPushObj: {
// 推送产品内容
type: Object,
default: () => ({}),
},
})
const msgTipIcon = {
1: require("@/assets/images/open-msg-tip.png"),
2: require("@/assets/images/close-msg-tip.png"),
}
const showRecommend = ref(false) // 展示活动模块
const route = useRoute()
watch(
() => props.activityObj,
() => {
if (props.activityObj && Object.keys(props.activityObj).length) {
showRecommend.value = true
}
}
)
const newMsgTree = computed(() => {
return store.state.interactMsgObj.newTreeMsg.slice(-3)
})
const showCarPush = ref(false) // 展示推送产品
watch(
() => props.carPushObj,
() => {
if (props.carPushObj && Object.keys(props.carPushObj).length) {
showCarPush.value = true
} else {
showCarPush.value = false
}
}
)
onBeforeUnmount(() => {
emitter.off("getCouponDetail")
emitter.off("informUserInRoom")
})
async function toActivityPage(type) {
if (type === 1) {
location.href = props.activityObj.url
} else {
await queryCartRead({
productId: props.carPushObj.productId,
productType: props.carPushObj.productType,
videoId: route.query.id,
saleUserId: route.query.saleUserId,
})
location.href = window.config.getCarProductLink({
token: store.state.userInfo.token,
refreshToken: store.state.userInfo.refreshToken,
activityId: props.carPushObj.url,
staffNo: props.detail.saleUserWorkNo || props.detail.advisorBasic.workNo,
})
}
}
const text = ref()
const shareRef = ref()
const sendTextLoading = ref(false)
const emit = defineEmits(["sendMsg", "closeActivity", "closeCarPush"])
const showGuide = ref(false)
const sendMsg = (type, params = {}) => {
if (![1, 4].includes(type)) {
// 不是聊天互动
emit("sendMsg", type, params)
}
switch (type) {
case 1:
if (text.value) {
sendTextLoading.value = true
emit("sendMsg", type, {
...params,
callBack: () => {
sendTextLoading.value = false
text.value = ""
},
errorBack: () => {
sendTextLoading.value = false
},
})
} else {
showToast("请输入互动内容!")
}
break
case 4:
dblclickLive()
break
case 5:
optShare()
break
default:
break
}
}
const optShare = () => {
// 点击分享
if (
typeof WeixinJSBridge === "object" &&
typeof window.WeixinJSBridge.invoke === "function"
) {
showGuide.value = true
} else {
shareRef.value.showPopup = true
}
}
const ChatFrameRef = ref()
function inputMsg() {
let bs = ChatFrameRef.value.bs
bs && bs.stop()
bs && bs.scrollTo(0, bs.maxScrollY, 0)
}
function bsRefresh() {
ChatFrameRef.value && ChatFrameRef.value.bs && ChatFrameRef.value.bs.refresh()
}
// 关闭活动弹窗
const closeActivity = () => {
showRecommend.value = false
emit("closeActivity")
}
// 关闭推送产品
const closeCarPush = () => {
showCarPush.value = false
emit("closeCarPush")
}
let liveIndex = 0
// const liveIcon = [
// require("@/assets/images/liveIcon/icon1.png"),
// require("@/assets/images/liveIcon/icon2.png"),
// require("@/assets/images/liveIcon/icon3.png"),
// require("@/assets/images/liveIcon/icon4.png"),
// ]
const favorUserCountObj = reactive({
favorUserCount: props.detail.favorUserCount,
isFavor: props.detail.isFavor,
})
const sendLikeVideo = async (liveNum) => {
const ret = await likeVideo({
id: props.detail.id,
option: 1,
num: liveNum,
})
if (ret.code === 0) {
favorUserCountObj.isFavor = 1
favorUserCountObj.favorUserCount = ret.data.count
emitter.emit("updateVideoDetail", favorUserCountObj)
}
}
let sendLiveTime = null
const addContinuousClickLive = useContinuousClickLive()
console.log(addContinuousClickLive)
const dblclickLive = () => {
// const img = document.createElement("img")
// img.setAttribute("src", liveIcon[liveIndex % 4])
// img.setAttribute("class", "live-icon")
// const starBox = document.querySelector(".star-btn")
// starBox.appendChild(img)
// liveIndex++
// ;((img) => {
// setTimeout(() => {
// starBox && starBox.removeChild(img)
// }, 2000)
// })(img)
addContinuousClickLive(".star-btn")
clearTimeout(sendLiveTime)
sendLiveTime = setTimeout(() => {
sendLikeVideo(liveIndex)
liveIndex = 0
}, 1000)
}
function bigNumberTransform(value) {
let param = {}
let k = 10000,
sizes = ["", "万", "亿", "万亿"],
i
if (value < k) {
param.value = value
param.unit = ""
} else {
i = Math.floor(Math.log(value) / Math.log(k))
param.value = parseInt(value / Math.pow(k, i))
param.unit = `${sizes[i]}+`
}
let number = param.value + param.unit
return number
}
const couponDetail = ref()
// 获取最新的优惠券对象
emitter.on("getCouponDetail", (value) => {
couponDetail.value = value
})
const showDiscountCoupon = () => {
emitter.emit("showDiscountCoupon", couponDetail.value)
}
// const getLiveCoupon = async () => {
// let ret = await queryLiveCoupon({
// liveId: props.detail.id,
// });
// if (ret.code === 0 && ret.data && ret.data.length) {
// couponDetail.value = ret.data[0];
// couponDetail.value.couponType = Number(couponDetail.value.couponType);
// couponDetail.value.sendCouponId = couponDetail.value.id;
// }
// };
// 获取优惠券
// getLiveCoupon();
let tipLook = false
const userInTip = (name) => {
try {
const span = document.createElement("span")
span.innerHTML = `${name} 来了`
span.setAttribute("class", "user-in-tip")
document.querySelector(".msg-content").appendChild(span)
;((span) => {
setTimeout(() => {
let star = document.querySelector(".msg-content")
star && star.removeChild(span)
tipLook = false
}, 3000)
})(span)
} catch (error) {
tipLook = false
}
}
const InteractRef = ref()
const showInteractAction = () => {
InteractRef.value.show = true
}
const msgTipIndex = ref(1)
const changeMsgTip = () => {
msgTipIndex.value = msgTipIndex.value === 1 ? 2 : 1
}
emitter.on("informUserInRoom", (name) => {
if (!tipLook) {
tipLook = true
userInTip(name)
}
})
defineExpose({
bsRefresh,
})
</script>
<style scoped lang="scss">
@import url("@/assets/css/live.css");
::v-deep .user-in-tip {
position: absolute;
top: 40px;
left: 0;
transform: translateX(-100%);
background: rgba(0, 0, 0, 0.6);
animation: tipmove 3s;
-webkit-animation: tipmove 3s; /*Safari and Chrome*/
font-size: 24px;
line-height: 24px;
padding: 12px 24px;
border-radius: 24px;
color: #fff;
}
@keyframes tipmove {
0% {
transform: translateX(-100%);
opacity: 1;
}
50% {
transform: translateX(40px);
opacity: 1;
}
100% {
transform: translateX(40px);
opacity: 0;
}
}
@-webkit-keyframes tipmove /*Safari and Chrome*/ {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.input-area {
display: flex;
align-items: center;
background: #fff;
padding: 20px 20px;
input {
height: 64px;
background: #9aa4b61a;
border-radius: 32px;
font-weight: 500;
font-size: 24px;
letter-spacing: 0;
line-height: 24px;
padding: 0 16px;
flex: 1;
color: #666;
margin: 0 20px;
}
input::placeholder,
input::-webkit-input-placeholder {
color: #666; /* 设置颜色为灰色 */
}
button.send {
height: 64px;
padding: 0 20px;
font-size: 28px;
border-radius: 32px;
color: #fff;
background-image: linear-gradient(270deg, #2e78fa 0%, #45acff 100%);
&[disabled] {
opacity: 0.3;
}
}
.share {
width: 64px;
height: 64px;
margin-left: 20px;
background: url(../../../assets/images/share3.png) no-repeat center;
background-size: contain;
border-radius: 50%;
}
.star-btn {
width: 64px;
height: 64px;
position: relative;
background: none;
margin-right: 10px;
.star {
width: 100%;
height: 100%;
background: url(../../../assets/images/h-like.png) no-repeat center;
background-size: 52px 52px;
margin-left: 20px;
&.active {
background: url(../../../assets/images/h-like1.png) no-repeat center;
background-size: 52px 52px;
}
}
span {
position: absolute;
top: 0;
right: 0;
min-width: 20px;
transform: translateX(18px) translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
flex-direction: column;
border-radius: 13px 13px 13px 4px;
background: linear-gradient(
128deg,
#fff2dc -25.5%,
#ffd9a7 89.14%,
#605a52 89.14%
);
color: rgb(142, 81, 5);
font-size: 18px;
font-style: normal;
font-weight: 500;
}
}
}
.msg-content {
position: relative;
background: #f5f6fa;
height: calc(100% - 144px);
overflow: hidden;
.recommend-img {
position: absolute;
right: 20px;
bottom: 20px;
width: 208px;
height: 280px;
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
i {
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
top: 0;
right: 0;
transform: translateX(50%) translateY(-50%);
background: url(../../../assets/images/close-icon.png) no-repeat center;
background-size: 100%;
}
}
}
.top-tip {
color: #1b2330;
font-size: 24px;
text-align: center;
line-height: 28px;
border-radius: 20px;
// background: rgba(154, 164, 182, 0.1);
padding: 8px 8px;
animation-delay: 2s;
}
::v-deep .live-icon {
position: absolute;
left: 50%;
top: 0;
width: 60px;
height: auto;
z-index: 2;
transform: translateX(-50%) translateY(-75%);
animation: mymove 2.5s;
-webkit-animation: mymove 2.5s; /*Safari and Chrome*/
}
@keyframes mymove {
from {
top: 0;
opacity: 1;
}
to {
top: -200px;
opacity: 0;
}
}
@-webkit-keyframes mymove /*Safari and Chrome*/ {
from {
top: 0;
opacity: 1;
}
to {
top: -200px;
opacity: 0;
}
}
.discounts-num {
position: absolute;
top: 120px;
right: 40px;
width: 96px;
height: 96px;
flex-shrink: 0;
border-radius: 24px;
background-color: rgba(0, 0, 0, 0.3);
background-image: url(../../../assets/images/discounts.png);
background-position: top 10px center;
background-size: 72px 64px;
background-repeat: no-repeat;
overflow: hidden;
p {
position: absolute;
bottom: 0;
width: 100%;
background: rgba(0, 0, 0, 0.3);
color: rgb(255, 255, 255);
text-align: center;
font-size: 18px;
font-weight: 400;
line-height: 40px;
}
}
.question {
background: #fff;
border-radius: 8px;
color: #000;
padding: 16px;
width: 280px;
h5 {
font-size: 30px;
margin-bottom: 20px;
}
span {
color: #ff3d36;
font-size: 28px;
}
}
.pulldown-wrapper {
position: absolute;
width: 100%;
padding: 20px;
box-sizing: border-box;
transform: translateY(-100%) translateZ(0);
text-align: center;
color: #999;
font-size: 24px;
}
.van-overlay {
z-index: 3000;
text-align: right;
img {
width: 200px;
margin: 60px 40px 40px 0;
}
p {
font-size: 32px;
color: #fff;
text-align: center;
}
}
.operate-wrap {
position: absolute;
display: flex;
align-items: center;
right: 0px;
bottom: 60px;
padding: 8px 24px 8px 16px;
border-radius: 28px 0 0 28px;
background: #fff;
& > div,
& > div img {
width: 40px;
height: 40px;
}
& > div img {
display: block;
}
& > div {
display: flex;
align-items: center;
}
& > div:first-child {
margin-right: 20px;
}
& > div:last-child {
border-left: 1px solid #cccccc;
padding-left: 20px;
}
.new-msg-list {
position: absolute;
right: 20px;
bottom: 70px;
li {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 10px;
.content {
display: inline-flex;
align-items: center;
background: rgba(0, 0, 0, 0.4);
padding: 4px 4px 4px 16px;
border-radius: 20px;
}
img {
width: 36px;
height: 36px;
border-radius: 50%;
margin-left: 8px;
}
p {
font-size: 24px;
white-space: nowrap;
color: #fff;
max-width: 260px;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
</style>