Parcourir la source

直播优化代码

yuhongqi il y a 2 jours
Parent
commit
cfe0b98c01

+ 10 - 10
src/views/life/video/index.vue

@@ -106,8 +106,8 @@
           <el-input v-model="form.fileName" placeholder="上传文件名或标识"/>
         </el-form-item>
         <el-form-item label="封面" prop="coverUrl">
-          <image-upload v-model="form.coverUrl" :limit="1" :file-size="2" aspect-ratio="16:9"/>
-          <div style="color:#909399;font-size:12px;line-height:1.4;margin-top:4px">比例 16:9,大小不超过 2MB</div>
+          <image-upload v-model="form.coverUrl" :limit="1" :file-size="2"/>
+          <div style="color:#909399;font-size:12px;line-height:1.4;margin-top:4px">大小不超过 2MB</div>
         </el-form-item>
         <video-upload
           :type="3"
@@ -144,12 +144,12 @@
             <el-radio :label="0">下架</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="今日主推" prop="isHot">
-          <el-radio-group v-model="form.isHot">
-            <el-radio :label="1">是</el-radio>
-            <el-radio :label="0">否</el-radio>
-          </el-radio-group>
-        </el-form-item>
+<!--        <el-form-item label="今日主推" prop="isHot">-->
+<!--          <el-radio-group v-model="form.isHot">-->
+<!--            <el-radio :label="1">是</el-radio>-->
+<!--            <el-radio :label="0">否</el-radio>-->
+<!--          </el-radio-group>-->
+<!--        </el-form-item>-->
         <el-form-item label="是否关联商品" prop="isProduct">
           <el-radio-group v-model="form.isProduct" @change="onIsProductChange">
             <el-radio :label="1">是</el-radio>
@@ -208,8 +208,8 @@
               </el-select>
             </el-form-item>
             <el-form-item label="封面" required>
-              <image-upload v-model="row.coverUrl" :limit="1" :file-size="2" aspect-ratio="16:9"/>
-              <div style="color:#909399;font-size:12px">比例 16:9,大小不超过 2MB</div>
+              <image-upload v-model="row.coverUrl" :limit="1" :file-size="2"/>
+              <div style="color:#909399;font-size:12px">大小不超过 2MB</div>
             </el-form-item>
             <video-upload
               :type="3"

+ 66 - 1
src/views/live/live/index.vue

@@ -553,7 +553,7 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item label="直播马甲号" prop="vestId">
-          <el-select v-model="form.vestId" filterable clearable placeholder="请选择马甲号" style="width:100%">
+          <el-select v-model="form.vestId" filterable clearable placeholder="请选择马甲号" style="width:100%" @change="onVestChange">
             <el-option
               v-for="item in lifeVestOptions"
               :key="item.id"
@@ -563,6 +563,23 @@
           </el-select>
           <div style="color:#909399;font-size:12px;line-height:1.4;margin-top:4px">公域直播必须绑定马甲号;私域可不选</div>
         </el-form-item>
+        <el-form-item label="马甲号视频" prop="lifeVideoId" v-if="form.vestId && form.liveType == 2">
+          <el-select
+            v-model="form.lifeVideoId"
+            filterable
+            clearable
+            placeholder="可选:选用该马甲的短视频(自动带入时长)"
+            style="width:100%"
+            @change="onLifeVideoSelect"
+          >
+            <el-option
+              v-for="item in lifeVideoOptions"
+              :key="item.videoId"
+              :label="(item.title || item.fileName || ('视频'+item.videoId)) + (item.duration ? (' / ' + secondsToTime(item.duration)) : '')"
+              :value="item.videoId"
+            />
+          </el-select>
+        </el-form-item>
         <el-form-item label="直播标签配置" prop="liveTagList">
           <el-table
             :data="form.liveTagList"
@@ -802,6 +819,7 @@ import {
 import { exportLiveMsgComments } from "@/api/live/liveMsg";
 import { listLiveGroupType, addLiveGroupType } from "@/api/live/liveGroupType";
 import { listLifeVest } from "@/api/life/vest";
+import { listLifeVideo } from "@/api/life/video";
 import Editor from "@/components/Editor/wang";
 import user from "@/store/modules/user";
 import VideoUpload from "@/components/LiveVideoUpload/single.vue";
@@ -929,6 +947,7 @@ export default {
       comapnyDropList: [],
       markTypeDropList: [],
       lifeVestOptions: [],
+      lifeVideoOptions: [],
       queryTagParams:{
         pageNum: 1,
         pageSize: 5,
@@ -1264,8 +1283,10 @@ export default {
         liveGroupType: null,
         visibleScope: 0,
         vestId: null,
+        lifeVideoId: null,
       };
       this.videoUrl = "";
+      this.lifeVideoOptions = [];
       this.resetForm("form");
     },
     /** 搜索按钮操作 */
@@ -1302,6 +1323,9 @@ export default {
         if (this.form.duration) {
           this.form.durationTime = this.secondsToTime(this.form.duration);
         }
+        if (this.form.vestId) {
+          this.loadLifeVideoOptions(this.form.vestId);
+        }
         //初始化
         if (
           this.form.liveTagList == undefined ||
@@ -1339,6 +1363,9 @@ export default {
         if (this.form.duration) {
           this.form.durationTime = this.secondsToTime(this.form.duration);
         }
+        if (this.form.vestId) {
+          this.loadLifeVideoOptions(this.form.vestId);
+        }
         //初始化
         if (
           this.form.liveTagList == undefined ||
@@ -1402,6 +1429,44 @@ export default {
         this.lifeVestOptions = [];
       });
     },
+    onVestChange(vestId) {
+      this.form.lifeVideoId = null;
+      this.lifeVideoOptions = [];
+      if (vestId) {
+        this.loadLifeVideoOptions(vestId);
+      }
+    },
+    loadLifeVideoOptions(vestId) {
+      if (!vestId) {
+        this.lifeVideoOptions = [];
+        return;
+      }
+      listLifeVideo({ pageNum: 1, pageSize: 200, vestId, isShow: 1 }).then((res) => {
+        this.lifeVideoOptions = res.rows || [];
+      }).catch(() => {
+        this.lifeVideoOptions = [];
+      });
+    },
+    onLifeVideoSelect(lifeVideoId) {
+      if (!lifeVideoId) {
+        return;
+      }
+      const video = (this.lifeVideoOptions || []).find((v) => v.videoId === lifeVideoId);
+      if (!video) {
+        return;
+      }
+      this.form.videoUrl = video.videoUrl;
+      this.videoUrl = video.videoUrl || "";
+      this.form.fileName = video.fileName;
+      if (video.duration) {
+        this.form.duration = Number(video.duration);
+        this.form.durationTime = this.secondsToTime(this.form.duration);
+        this.timeChange();
+      }
+      if (!this.form.liveImgUrl && video.coverUrl) {
+        this.form.liveImgUrl = video.coverUrl;
+      }
+    },
     /** 删除按钮操作 */
     handleDelete(row) {
       const liveIds = row.liveId || this.ids;

+ 68 - 8
src/views/live/liveConsole/LiveConsole.vue

@@ -113,7 +113,7 @@
         </div>
         <div class="message-container" @click="handleMessageBoxClick">
           <el-scrollbar class="custom-scrollbar" style="height: 500px; width: 100%;" ref="manageRightRef">
-            <el-row v-for="m in msgList" :key="m.msgId">
+            <el-row v-for="(m, msgIndex) in msgList" :key="msgRowKey(m, msgIndex)">
             <el-row v-if="m.userId === userId && m.msgId == null" style="padding: 8px 0" type="flex" align="top" justify="end">
               <div style="display: flex;justify-content: flex-end">
                 <div style="display: flex;justify-content: flex-end;flex-direction: column;max-width: 200px;align-items: flex-end">
@@ -136,7 +136,7 @@
                   <el-col>
                     <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
                     <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="blockUser(m)">拉黑</a>
-                    <a v-if="m.singleVisible === 1" style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">解除用户自见</a>
+                    <a v-if="isUserSingleVisible(m)" style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">解除用户自见</a>
                     <a v-else style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">用户自见</a>
                     <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="replyUser(m)">回复</a>
                     <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="deleteMsg(m)">删除</a>
@@ -294,6 +294,8 @@ export default {
         videoUrl: '',
       },
       msgList:[],
+      // 按用户维度维护自见状态,避免消息行共享/重渲染导致按钮状态串台
+      userSingleVisibleMap: {},
       currentTab: 'al',
       searchKeyword: '',
       globalVisible: false,
@@ -522,17 +524,46 @@ export default {
       // loadMsgList 中会自动滚动到底部,因为 isAutoScrollEnabled 已经是 true
       this.loadMsgList();
     },
+    msgRowKey(m, index) {
+      if (m && m.msgId != null && m.msgId !== '') {
+        return 'msg-' + m.msgId
+      }
+      return 'msg-tmp-' + (m && m.userId != null ? m.userId : 'x') + '-' + (m && m.createTime ? m.createTime : '') + '-' + index
+    },
+    isUserSingleVisible(m) {
+      if (!m || m.userId == null) {
+        return false
+      }
+      const uid = String(m.userId)
+      if (Object.prototype.hasOwnProperty.call(this.userSingleVisibleMap, uid)) {
+        return Number(this.userSingleVisibleMap[uid]) === 1
+      }
+      return Number(m.singleVisible) === 1
+    },
+    applyUserSingleVisible(userId, status) {
+      if (userId == null) {
+        return
+      }
+      const uid = String(userId)
+      const next = Number(status) === 1 ? 1 : 0
+      this.$set(this.userSingleVisibleMap, uid, next)
+      this.msgList.forEach(m1 => {
+        if (String(m1.userId) === uid) {
+          this.$set(m1, 'singleVisible', next)
+        }
+      })
+    },
     singleVisible(m){
-      // 过滤当前所有消息 找到userId的相同的消息 更改他们的自可见状态
-      m.singleVisible= m.singleVisible === 1 ? 0 : 1
-      this.msgList.forEach(m1 => {m1.singleVisible = m1.userId === m.userId ? m.singleVisible : !m.singleVisible})
+      // 仅更新同一用户的消息自见状态,不影响其他用户
+      const status = this.isUserSingleVisible(m) ? 0 : 1
+      this.applyUserSingleVisible(m.userId, status)
       // 消息自可见
       let msg = {
         liveId: this.liveId,
         userId: m.userId,
         userType: 0,
         cmd: 'singleVisible',
-        status:m.singleVisible
+        status
       }
       this.socket.send(JSON.stringify(msg))
     },
@@ -715,14 +746,15 @@ export default {
         return;
       }
 
+      // userId 使用专用 fs_user(400001),与后端入库账号一致
       let msg = {
         msg: this.newMsg,
         liveId: this.liveId,
-        userId: this.userId,
+        userId: 400001,
         userType: 1,
         cmd: 'sendMsg',
         avatar: this.$store.state.user.user.avatar,
-        nickName: this.$store.state.user.user.nickName,
+        nickName: this.$store.state.user.user.nickName || '直播管理员',
       }
 
       this.socket.send(JSON.stringify(msg))
@@ -753,6 +785,14 @@ export default {
             message.msgStatus = 0
           }
           delete message.params
+          if (message.userId != null) {
+            const uid = String(message.userId)
+            if (Object.prototype.hasOwnProperty.call(this.userSingleVisibleMap, uid)) {
+              message.singleVisible = Number(this.userSingleVisibleMap[uid]) === 1 ? 1 : 0
+            } else {
+              this.$set(this.userSingleVisibleMap, uid, Number(message.singleVisible) === 1 ? 1 : 0)
+            }
+          }
           if(this.msgList.length > 50){
             this.msgList.shift()
           }
@@ -812,6 +852,8 @@ export default {
             this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
           }
 
+        } else if (cmd === 'singleVisible') {
+          this.applyUserSingleVisible(data.userId, data.status)
         } else if (cmd === 'live_start') {
 
         } else if (cmd === 'live_end') {
@@ -1207,6 +1249,15 @@ export default {
                 row.msgStatus = 0
               }
               this.msgList.push(row)
+              if (row.userId != null) {
+                const uid = String(row.userId)
+                if (!Object.prototype.hasOwnProperty.call(this.userSingleVisibleMap, uid)) {
+                  this.$set(this.userSingleVisibleMap, uid, Number(row.singleVisible) === 1 ? 1 : 0)
+                } else {
+                  // 本地已有操作结果时,以本地为准回写到消息行
+                  this.$set(row, 'singleVisible', Number(this.userSingleVisibleMap[uid]) === 1 ? 1 : 0)
+                }
+              }
             }
           })
 
@@ -1215,6 +1266,15 @@ export default {
           this.alDisplayList.forEach(u => {
             this.msgList.filter(m => m.userId === u.userId).forEach(m => m.msgStatus = u.msgStatus)
           })
+          // 用用户维度自见状态统一刷消息行,避免翻页/重载串台
+          Object.keys(this.userSingleVisibleMap).forEach(uid => {
+            const status = Number(this.userSingleVisibleMap[uid]) === 1 ? 1 : 0
+            this.msgList.forEach(m => {
+              if (String(m.userId) === uid) {
+                this.$set(m, 'singleVisible', status)
+              }
+            })
+          })
           // 所有消息加载完成后,根据自动滚动状态决定是否滚动
           this.$nextTick(() => {
             setTimeout(() => {