123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707 |
- <template>
- <cover-view style="background-color: red;">
- <template v-if="openCommentStatus==2">
- <!-- <text v-for="(item, index) in activeDanmus" :key="item.commentId" class="danmu-item danmuMove"
- :style="{
- top: item.top + 'px',
- ...item.style,
- 'animation-duration': '8s'
- }" @animationend="animationend(item,index)">
- {{ item.content }}
- </text> -->
- </template>
- <view class="container-body" id="msglist" v-if="openCommentStatus==1">
- <view class="listbox" v-for="(item, index) in msgs" :key="index" :id="'view' + index">
- <text :class="userId&&item.userId == userId?'list-name my':'list-name'">
- {{userId&&item.userId == userId ? '我' : item.nickName||'--'}}:
- </text>
- <text class="list-con">{{item.content||''}}</text>
- </view>
- <view class="empty" v-if="msgs&&msgs.length==0">暂无评论~</view>
- </view>
- </cover-view>
- </template>
- <script>
- import { saveMsg,revokeMsg,getComments} from "@/api/course.js"
- export default {
- props: {
- height:{
- type: String,
- default:'0px'
- },
- urlOption:{
- type: Object,
- default:{}
- },
- time:{
- type: [String,Number],
- default: 0
- },
- viewCommentNum:{
- type: [String,Number],
- default: 200
- },
- openCommentStatus:{
- type: [String,Number],
- default: 3
- },
- // 用户自己开启关闭弹幕展示 1,展示弹幕,0 关闭的弹幕
- showDanmu:{
- type: [String,Number],
- default: 1
- },
- },
- data() {
- return {
- statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
- scrollTop: 0,
- inputText:"",
- isSocketOpen:false,
- socket:null,
- isSend:true,
- commentList:[],
- msgs: [],
- pageNum: 1,
- pageSize: 10,
- userInfo: {},
- userId: '',
- pingpangTimes:null,
- // 弹幕
- danmuList: [],
- tracks:[],
- activeDanmus:[],
- flagTime: 0,
- danmuItemStyle:{
- color: '#ffffff',
- fontSize: '16px',
- border: 'solid 1px #ffffff',
- borderRadius: '5px',
- padding: '2px 2px',
- backgroundColor: 'rgba(255, 255, 255, 0.1)'
- },
- ctx: null,
- danmuIndex:{}
- }
- },
- mounted() {
- this.getComments()
- this.getUser();
- this.initTracks()
- if(!this.socket || !this.isSocketOpen) {
- this.initSocket()
- }
- },
- methods: {
- back() {
- uni.navigateBack()
- },
- getUser() {
- const userInfo = uni.getStorageSync('userInfo');
- if(userInfo&&JSON.stringify(userInfo)!='{}') {
- this.userInfo = JSON.parse(userInfo)
- this.userId = this.userInfo.userId || ''
- }else {
- this.userInfo = {}
- this.userId = ''
- }
- },
- getComments() {
- let that = this
- getComments({
- pageNum: this.openCommentStatus==2 ? 1 : this.pageNum,
- pageSize: this.openCommentStatus==2 ? this.viewCommentNum : this.pageSize,
- courseId: this.urlOption.courseId,
- videoId: this.urlOption.videoId,
- openCommentStatus: this.openCommentStatus
- }).then(res=>{
- if(res.code==200){
- if(this.openCommentStatus==2) {
- this.danmuList = res.data.list.map(item=>({
- commentId: item.commentId,
- content: item.content,
- time: item.time || this.time,
- color: "#FFFFFF",
- mode: item.mode || "scroll",
- top: null,
- style: {
- color: item.isColor==1 ? item.color || this.danmuItemStyle.color : this.danmuItemStyle.color,//是否彩色1是0否
- fontSize: item.fontSize || this.danmuItemStyle.fontSize,
- padding: this.danmuItemStyle.padding,
- border:this.userInfo.userId ==item.userId ? item.color ? `solid 1px ${item.color}`: this.danmuItemStyle.border : 'none',
- borderRadius: this.userInfo.userId==item.userId ? this.danmuItemStyle.borderRadius : 0,
- backgroundColor: this.userInfo.userId==item.userId ? this.danmuItemStyle.backgroundColor : 'transparent'
- },
- }))
- this.initDanmuIndex()
- that.$emit('getMore',0)
- } else if(this.openCommentStatus==1) {
- this.danmuList = []
- this.activeDanmus = []
- this.danmuIndex = {};
- let list = res.data.list.reverse()
- if (that.pageNum == 1) {
- that.commentList = list;
- that.handleScrollBottom();
- } else {
- that.commentList = that.commentList.concat(list);
- }
- that.msgs = [...list,...that.msgs]
- if(that.commentList.length >= res.data.total || that.commentList.length >= Number(this.viewCommentNum||200)) {
- that.$emit('getMore',1)
- } else {
- this.pageNum++
- that.$emit('getMore',0)
- }
- } else {
- that.danmuList = []
- that.activeDanmus = []
- that.danmuIndex = {};
- that.commentList = [];
- that.msgs = that.msgs;
- that.$emit('getMore',0);
- }
- }
- else{
- that.danmuList = []
- that.danmuIndex = {};
- that.commentList = [];
- that.msgs = that.msgs;
- that.$emit('getMore',0);
- }
- })
- },
- saveMsg() {
- if (this.inputText == "" || this.inputText.trim() == "") {
- uni.showToast({
- title: '请输入评论',
- icon: "none"
- })
- return;
- }
- if (!this.isSend) {
- return;
- }
- const param = {
- userId: this.userId || '',
- userType: 2, // 1-管理员,2-用户
- courseId: this.urlOption.courseId,
- videoId: this.urlOption.videoId,
- type:1, // 评论类型 1:评论,2:回复,目前没有回复,默认传1就行了
- content: this.inputText,
- time: this.time,
- fontSize: '16px',
- mode: "scroll",
- color: "#ffffff",
- }
- saveMsg(param).then(res=>{
- if(res.code == 200) {
- const status = res.status ? 0 : 1
- this.sendMsg(param,status);
- } else {
- uni.showToast({
- title: res.msg,
- icon: "none"
- })
- }
- })
- },
- handleInput(val) {
- this.inputText = val
- console.log("====")
- this.addMsg({msg: this.inputText,time: this.time},2)
- // if(!this.isSocketOpen) {
- // // 重新发起会话
- // this.initSocket('reStart')
- // } else {
- // this.saveMsg();
- // }
- },
- handleScrollBottom() {
- setTimeout(() => {
- const query = uni.createSelectorQuery().in(this);
- query.select('#msglist')
- .boundingClientRect((res) => {
- if(res) {
- const scrollH = res.height;
- this.scrollTop = res.height;
- this.$emit('getScrollTop',this.scrollTop)
- }
- }).exec();
- },500);
- },
- initSocket(type) {
- //创建一个socket连接
- var userId = this.userInfo.userId;
- var that = this;
- if (this.socket) {
- this.socket.close()
- this.socket = null;
- }
- this.socket = uni.connectSocket({
- url: getApp().globalData.wsUrl + "/app/webSocket/" + userId,
- multiple: true,
- success: res => {
- console.log('WebSocket连接已打开1!');
- that.isSocketOpen = true
- // 保持心跳
- if(that.pingpangTimes) {
- clearInterval(that.pingpangTimes)
- that.pingpangTimes= null
- }
- that.pingpangTimes=setInterval(()=>{
- let data={
- userId: that.userId || '',
- userType: 2, // 1-管理员,2-用户
- courseId: that.urlOption.courseId,
- videoId: that.urlOption.videoId,
- type:1, // 评论类型 1:评论,2:回复,目前没有回复,默认传1就行了
- // msg: that.inputText,
- cmd:'heartbeat'
- };
- that.socket.send({
- data: JSON.stringify(data),
- success: () => {
- // console.log('WebSocket发送心条数据!');
- },
- fail: () => {
- that.isSocketOpen=false
- }
- });
- },15000)
- },
- error: res => {
- console.log(res)
- },
- })
- this.socket.onMessage((res) => {
- // console.log("收到消息parse",JSON.parse(res.data))
- const redata = JSON.parse(res.data);
- if(redata.cmd=="heartbeat"){
- //心跳
- // console.log("heartbeat")
- }else if(redata.cmd=="sendMsg"){
- that.isSend=true;
- that.addMsg(redata);
- }
- })
- //监听socket打开
- this.socket.onOpen(() => {
- console.log('WebSocket连接已打开2!');
- that.isSocketOpen = true
- that.isSend = true;
- if(type=='reStart') {
- // 重连的时候重新发消息
- this.saveMsg()
- }
- })
- //监听socket关闭
- this.socket.onClose(() => {
- that.isSocketOpen = false
- that.socket = null
- console.log('WebSocket连接已关闭!');
- if(that.pingpangTimes) {
- clearInterval(that.pingpangTimes)
- that.pingpangTimes= null
- }
- })
- //监听socket错误
- this.socket.onError((err) => {
- console.log("socket err:",err)
- that.isSocketOpen = false
- that.socket = null
- if(that.pingpangTimes) {
- clearInterval(that.pingpangTimes)
- that.pingpangTimes= null
- }
- })
- },
- sendMsg(param,status) {
- if(status == 1) {
- this.addMsg({msg: param.content,time: param.time},2)
- return
- }
- if (this.isSocketOpen) {
- var userId = this.userInfo.userId;
- var data = {
- userId: this.userId || '',
- userType: 2, // 1-管理员,2-用户
- courseId: this.urlOption.courseId,
- videoId: this.urlOption.videoId,
- type:1, // 评论类型 1:评论,2:回复,目前没有回复,默认传1就行了
- msg: param.content,
- cmd: 'sendMsg',
- time: param.time,
- fontSize: '16px',
- mode: "scroll",
- color: "#ffffff",
- };
- this.socket.send({
- data: JSON.stringify(data),
- success: () => {
- console.log("发送成功")
- this.isSend = false;
- },
- fail: () => {
- console.log("发送失败")
- }
- });
-
- }
-
- },
- addMsg(data,type) {
- console.log("弹幕===")
- let obj = {}
- if (type==2) {
- obj = {
- content: data.msg,
- courseId: this.urlOption.courseId,
- type: 1,
- userId: this.userId,
- userType: 2,
- videoId: this.urlOption.videoId,
- nickName: '',
- time: data.time,
- fontSize: data.fontSize,
- mode: data.mode,
- color: data.color,
- }
- } else {
- obj = {
- content: data.msg,
- courseId: this.urlOption.courseId,
- type: data.type,
- userId: data.userId,
- userType: data.userType,
- videoId: this.urlOption.videoId,
- nickName: data.nickName,
- time: data.time,
- fontSize: data.fontSize,
- mode: data.mode,
- color: data.color,
- }
- }
- if(this.openCommentStatus == 1){
- this.msgs.push(obj)
- this.handleScrollBottom();
- } else if(this.openCommentStatus == 2) {
- this.addDanmuMsg(obj)
- }
- this.inputText = ""
- this.$emit("setInputText")
- },
- addDanmuMsg(content) {
- const id = content.userId +'_' + new Date().getTime()
- const mystyle = {
- color: content.color || this.danmuItemStyle.color,
- fontSize: content.fontSize || this.danmuItemStyle.fontSize,
- border: content.color ? `solid 1px ${content.color}`: this.danmuItemStyle.border,
- borderRadius: this.danmuItemStyle.borderRadius,
- padding: this.danmuItemStyle.padding,
- backgroundColor: this.danmuItemStyle.backgroundColor
- }
- const otherstyle = {
- color: content.color || this.danmuItemStyle.color,
- fontSize: content.fontSize || this.danmuItemStyle.fontSize,
- padding: this.danmuItemStyle.padding,
- }
- const mode = content.mode || "scroll"
- const obj = {
- commentId: content.commentId || id,
- userId: content.userId,
- content: content.content,
- time: this.flagTime + 1,
- color: content.color || this.danmuItemStyle.color,
- style: this.userInfo.userId == content.userId ? mystyle : otherstyle,
- top: null
- }
- if(this.showDanmu == 0) return
- // 如果danmuList超过最大大小,移除旧的弹幕
- const maxDanmuListSize = 10000; // 设置最大大小
- if (this.danmuList.length >= maxDanmuListSize) {
- this.danmuList.shift(); // 移除最旧的弹幕
- }
- this.danmuList.push(obj);
-
- // 更新索引
- if (!this.danmuIndex[obj.time]) {
- this.danmuIndex[obj.time] = [];
- }
- this.danmuIndex[obj.time].push(obj);
- },
- closeWSocket() {
- if(this.socket!=null){
- this.socket.close()
- }
- if(this.pingpangTimes) {
- clearInterval(this.pingpangTimes)
- this.pingpangTimes= null
- }
- },
- initTracks() {
- this.tracks = [];
- const trackHeight = 22; // 每行高度
- const trackCount = 3;
- for (let i = 0; i < trackCount; i++) {
- this.tracks.push({
- top: i * trackHeight + 10,
- isFree: true,
- releaseTime: 0 // 轨道释放时间
- });
- }
- },
- // 获取字体高度
- getTextWidth(content) {
- if (!this.ctx) {
- this.ctx = uni.createCanvasContext('myCanvas')
- }
- const metrics = this.ctx.measureText(content)
- return Math.ceil(metrics.width)
- },
- // 分配轨道
- getFreeTrack(item) {
- const screenWidth = uni.getSystemInfoSync().screenWidth;
- const width = this.getTextWidth(item.content);
- const passWidth = width + screenWidth;
- const duration = 8; // 持续时间(秒)
- const currentTime = Date.now();
-
- for (let i = 0; i < this.tracks.length; i++) {
- if (this.tracks[i].isFree || this.tracks[i].releaseTime <= currentTime) {
- this.tracks[i].isFree = false;
- this.tracks[i].releaseTime = currentTime + Math.ceil(duration * 1000 / passWidth * width) + 1000;
- return this.tracks[i].top;
- }
- }
-
- // 无可用轨道
- if (this.userInfo.userId && item.userId == this.userInfo.userId) {
- // console.log("自己发的弹幕");
- let trackHeight = this.tracks[this.tracks.length - 1].top;
- return Math.random() * trackHeight + 16; // 自己发的弹幕随机高度
- } else {
- // console.log("无可用轨道");
- return 'abandon';
- }
- },
- // 初始化时建立索引
- initDanmuIndex() {
- this.danmuIndex = {};
- this.danmuList.forEach((item) => {
- if (!this.danmuIndex[item.time]) {
- this.danmuIndex[item.time] = [];
- }
- this.danmuIndex[item.time].push(item);
- });
- },
- // 检测并激活弹幕
- checkDanmu(flagTime) {
- this.flagTime = flagTime;
- if(this.showDanmu == 0) return;
- const newDanmus = this.danmuList.filter((item) => Math.abs(item.time - this.flagTime) < 1);
- // 分配轨道高度
- const aliveNewDanmus = newDanmus.map((item) => {
- if (!item.top) {
- item.top = this.getFreeTrack(item);
- }
- return item;
- }).filter((item) => item.top !== 'abandon');
- // 添加到活跃列表
- this.activeDanmus = [...this.activeDanmus, ...aliveNewDanmus];
- this.$emit("getActiveDanmus",this.activeDanmus)
- },
- animationend(moveItem, i) {
- // 移除动画结束的弹幕(性能优化)
- this.activeDanmus = this.activeDanmus.filter((item) => item.commentId !== moveItem.commentId)
- this.$emit("getActiveDanmus",this.activeDanmus)
- },
- },
- beforeDestroy() {
- if(this.socket!=null){
- this.socket.close()
- }
- if(this.pingpangTimes) {
- clearInterval(this.pingpangTimes)
- this.pingpangTimes= null
- }
- }
- }
- </script>
- <style scoped lang="scss">
- @mixin u-flex($flexD, $alignI, $justifyC) {
- display: flex;
- flex-direction: $flexD;
- align-items: $alignI;
- justify-content: $justifyC;
- }
- .empty {
- @include u-flex(row, center, center);
- padding: 24rpx 50rpx;
- color: #999999;
- }
- .listbox {
- white-space: pre-wrap;
- letter-spacing: 1px;
- margin-bottom: 16rpx;
- }
- .list-name {
- flex-shrink: 0;
- font-family: PingFang SC, PingFang SC;
- font-weight: 600;
- font-size: 28rpx;
- color: #222222;
- margin-right: 16rpx;
- }
- .my {
- color: #FF5C03;
- }
- .list-con {
- font-family: PingFang SC, PingFang SC;
- font-size: 28rpx;
- color: #222222;
- }
- .nav-bar {
- position: fixed;
- z-index: 9999;
- top: 0;
- left: 0;
- width: 100%;
- overflow: hidden;
- .nav-bg {
- width: 100%;
- height: 100%;
- position: absolute;
- left: 0;
- top: 0;
- z-index: 1;
- background-color: #fff;
- }
- &-box {
- position: relative;
- padding: 0 24rpx;
- @include u-flex(row, center, flex-start);
- height: 88rpx;
- box-sizing: border-box;
- z-index: 3;
- }
- &-left {
- width: 100%;
- @include u-flex(row, center, flex-start);
- overflow: hidden;
- image {
- flex-shrink: 0;
- width: 64rpx;
- height: 64rpx;
- border-radius: 12rpx 12rpx 12rpx 12rpx;
- }
- }
- &-name {
- font-family: PingFang SC, PingFang SC;
- font-weight: 600;
- font-size: 28rpx;
- color: #222222;
- }
- &-head {
- flex: 1;
- overflow: hidden;
- margin-left: 22rpx;
- margin-right: 22rpx;
- font-family: PingFang SC, PingFang SC;
- font-weight: 400;
- font-size: 23rpx;
- color: #999999;
- }
- }
- .page-bg {
- position: absolute;
- top: 0;
- left: 0;
- }
- .container-body {
- padding: 32rpx 30rpx;
- box-sizing: border-box;
- }
- .TUI-message-list {
- width: 100%;
- box-sizing: border-box;
- }
- .chatinput {
- position: fixed;
- left: 32rpx;
- right: 32rpx;
- z-index: 999;
- bottom: calc(var(--window-bottom) + 24rpx);
- height: 96rpx;
- background-color: green;
- background: #FFFFFF;
- box-shadow: 0rpx 8rpx 21rpx 0rpx rgba(0, 0, 0, 0.1);
- border-radius: 24rpx 24rpx 24rpx 24rpx;
- @include u-flex(row, center, center);
- padding: 0 24rpx;
- box-sizing: border-box;
- .uni-input {
- flex: 1;
- margin-right: 32rpx;
- font-size: 30rpx;
- }
-
- .send {
- font-family: PingFang SC, PingFang SC;
- font-weight: 400;
- font-size: 28rpx;
- color: #FFFFFF !important;
- flex-shrink: 0;
- padding: 0 20rpx;
- height: 72rpx;
- background: #FF5C03 !important;
- border-radius: 8rpx 8rpx 8rpx 8rpx;
- &::after {
- border: none;
- }
- }
- }
- // .danmu-item {
- // position: absolute;
- // top: 0;
- // white-space: nowrap;
- // font-size: 16px;
- // height: 20px;
- // display: inline-flex;
- // box-sizing: border-box;
- // align-items: center;
- // }
- // .danmuMove {
- // // animation: mymove 8s linear forwards;
- // // animation-duration: 8s;
- // animation-timing-function: linear;
- // animation-delay: 0s;
- // animation-iteration-count: 1;
- // animation-direction: normal;
- // animation-fill-mode: forwards;
- // animation-play-state: running;
- // animation-name: mymove;
- // will-change: transform;
- // }
-
- // @keyframes mymove {
- // from {
- // transform: translateX(100vw);
- // }
-
- // to {
- // transform: translateX(-100%);
- // }
- // }
- </style>
|