yys hai 5 días
pai
achega
e8728d40cc

+ 0 - 11
src/views/live/live/index.vue

@@ -1,17 +1,6 @@
 <template>
 <template>
   <div class="app-container">
   <div class="app-container">
     <el-row :gutter="10" class="mb8">
     <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-          type="primary"
-          plain
-          icon="el-icon-plus"
-          size="mini"
-          @click="handleAdd"
-          v-hasPermi="['live:live:add']"
-        >新增</el-button>
-      </el-col>
-
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
     </el-row>
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
     <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">

+ 632 - 397
src/views/live/liveConsole/LiveConsole.vue

@@ -2,230 +2,189 @@
   <div class="console">
   <div class="console">
 
 
     <div class="left-panel">
     <div class="left-panel">
-      <h2>学员列表</h2>
-      <div class="search">
-        <input type="text" placeholder="搜索用户昵称" v-model="searchKeyword">
-        <button @click="searchUsers()">搜索</button>
+      <h2 class="panel-title">学员列表</h2>
+      <div class="search-bar">
+        <el-input
+          v-model="searchKeyword"
+          placeholder="搜索用户昵称"
+          size="small"
+          clearable
+          @keyup.enter.native="searchUsers"
+        />
+        <el-button type="primary" size="small" @click="searchUsers">搜索</el-button>
       </div>
       </div>
-      <el-tabs class="live-console-tab-right" v-model="tabLeft.activeName" @tab-click="handleUserClick" :stretch="true">
+      <el-tabs class="console-tabs" v-model="tabLeft.activeName" @tab-click="handleUserClick" :stretch="true">
         <el-tab-pane :label="alLabel" name="al">
         <el-tab-pane :label="alLabel" name="al">
-          <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_al" style="height: 800px; width: 100%;">
-            <el-row class='scrollbar-demo-item' v-for="u in alDisplayList" :key="u.userId">
-              <el-col :span="20">
-                <el-row type="flex" align="middle">
-                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
-                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
-                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
-                </el-row>
-              </el-col>
-              <el-col :span="4" >
-                <el-popover
-                  width="100"
-                  trigger="click">
-                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
-                  <i class="el-icon-more" slot="reference"></i>
-                </el-popover>
-              </el-col>
-            </el-row>
+          <el-scrollbar class="panel-scroll" ref="manageLeftRef_al">
+            <div class="user-list-item" v-for="u in alDisplayList" :key="u.userId">
+              <el-avatar :size="36" :src="u.avatar" class="user-avatar" />
+              <div class="user-meta">
+                <div class="user-name">{{ u.nickName }}</div>
+                <div class="user-id">{{ u.userId }}</div>
+              </div>
+              <el-popover width="120" trigger="click" popper-class="user-action-popover">
+                <a class="action-link" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                <a class="action-link" @click="blockUser(u)">拉黑</a>
+                <i class="el-icon-more user-more-btn" slot="reference"></i>
+              </el-popover>
+            </div>
           </el-scrollbar>
           </el-scrollbar>
         </el-tab-pane>
         </el-tab-pane>
         <el-tab-pane :label="onlineLabel" name="online">
         <el-tab-pane :label="onlineLabel" name="online">
-          <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_online" style="height: 800px; width: 100%;">
-            <el-row class='scrollbar-demo-item' v-for="u in onlineDisplayList" :key="u.userId">
-              <el-col :span="20">
-                <el-row type="flex" align="middle">
-                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
-                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
-                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
-                </el-row>
-              </el-col>
-              <el-col :span="4" >
-                <el-popover
-                  width="100"
-                  trigger="click">
-                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
-                  <i class="el-icon-more" slot="reference"></i>
-                </el-popover>
-              </el-col>
-            </el-row>
+          <el-scrollbar class="panel-scroll" ref="manageLeftRef_online">
+            <div class="user-list-item" v-for="u in onlineDisplayList" :key="u.userId">
+              <el-avatar :size="36" :src="u.avatar" class="user-avatar" />
+              <div class="user-meta">
+                <div class="user-name">{{ u.nickName }}</div>
+                <div class="user-id">{{ u.userId }}</div>
+              </div>
+              <el-popover width="120" trigger="click" popper-class="user-action-popover">
+                <a class="action-link" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                <a class="action-link" @click="blockUser(u)">拉黑</a>
+                <i class="el-icon-more user-more-btn" slot="reference"></i>
+              </el-popover>
+            </div>
           </el-scrollbar>
           </el-scrollbar>
         </el-tab-pane>
         </el-tab-pane>
         <el-tab-pane :label="offlineLabel" name="offline">
         <el-tab-pane :label="offlineLabel" name="offline">
-          <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_offline" style="height: 800px; width: 100%;">
-            <el-row class='scrollbar-demo-item' v-for="u in offlineDisplayList" :key="u.userId">
-              <el-col :span="20">
-                <el-row type="flex" align="middle">
-                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
-                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
-                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
-                </el-row>
-              </el-col>
-              <el-col :span="4" >
-                <el-popover
-                  width="100"
-                  trigger="click">
-                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                  <i class="el-icon-more" slot="reference"></i>
-                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
-                </el-popover>
-              </el-col>
-            </el-row>
+          <el-scrollbar class="panel-scroll" ref="manageLeftRef_offline">
+            <div class="user-list-item" v-for="u in offlineDisplayList" :key="u.userId">
+              <el-avatar :size="36" :src="u.avatar" class="user-avatar" />
+              <div class="user-meta">
+                <div class="user-name">{{ u.nickName }}</div>
+                <div class="user-id">{{ u.userId }}</div>
+              </div>
+              <el-popover width="120" trigger="click" popper-class="user-action-popover">
+                <a class="action-link" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                <a class="action-link" @click="blockUser(u)">拉黑</a>
+                <i class="el-icon-more user-more-btn" slot="reference"></i>
+              </el-popover>
+            </div>
           </el-scrollbar>
           </el-scrollbar>
         </el-tab-pane>
         </el-tab-pane>
         <el-tab-pane :label="silencedUserLabel" name="silenced">
         <el-tab-pane :label="silencedUserLabel" name="silenced">
-          <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_silenced" style="height: 800px; width: 100%;">
-            <el-row class='scrollbar-demo-item' v-for="u in silencedDisplayList" :key="u.userId">
-              <el-col :span="20">
-                <el-row type="flex" align="middle">
-                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
-                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
-                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
-                </el-row>
-              </el-col>
-              <el-col :span="4" >
-                <el-popover
-                  width="100"
-                  trigger="click">
-                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
-                  <i class="el-icon-more" slot="reference"></i>
-                </el-popover>
-              </el-col>
-            </el-row>
+          <el-scrollbar class="panel-scroll" ref="manageLeftRef_silenced">
+            <div class="user-list-item" v-for="u in silencedDisplayList" :key="u.userId">
+              <el-avatar :size="36" :src="u.avatar" class="user-avatar" />
+              <div class="user-meta">
+                <div class="user-name">{{ u.nickName }}</div>
+                <div class="user-id">{{ u.userId }}</div>
+              </div>
+              <el-popover width="120" trigger="click" popper-class="user-action-popover">
+                <a class="action-link" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                <a class="action-link" @click="blockUser(u)">拉黑</a>
+                <i class="el-icon-more user-more-btn" slot="reference"></i>
+              </el-popover>
+            </div>
           </el-scrollbar>
           </el-scrollbar>
         </el-tab-pane>
         </el-tab-pane>
       </el-tabs>
       </el-tabs>
     </div>
     </div>
 
 
     <div class="middle-panel">
     <div class="middle-panel">
-      <h2>消息管理</h2>
-
-
-      <div class="discussion-messages">
-        <h3>讨论区消息</h3>
-        <div class="message-settings">
-          <label>
-            <input type="checkbox" v-model="globalVisible" @change="globalVisibleChange">
-            全局用户自见
-          </label>
+      <div class="panel-card live-player-card">
+        <h3 class="section-title">直播观看</h3>
+        <div class="live-player-wrapper">
+          <div v-if="showLivePlaceholder" class="live-placeholder">
+            <span class="live-placeholder-text">{{ livePlaceholderText }}</span>
+          </div>
+          <LivePlayer v-else ref="livePlayer" :videoParam="videoParam" />
         </div>
         </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-if="m.userId !== userId" style="margin-top: 5px" type="flex" align="top" >
-              <el-col :span="3" style="margin-left: 10px"><el-avatar :src="m.avatar"/></el-col>
-              <el-col :span="15">
-                <el-row style="margin-left: 10px">
-                  <el-col><div style="font-size: 12px; color: #999; margin-bottom: 3px;">{{ m.nickName }}</div></el-col>
-                  <el-col :span="24" style="max-width: 200px;">
-                    <div style="white-space: normal; word-wrap: break-word;background-color: #f0f2f5; padding: 8px; border-radius: 5px;font-size: 14px;width: 100%;">
-                      {{ m.msg }}
-                    </div>
-                  </el-col>
-                  <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-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="deleteMsg(m)">删除</a>
-                  </el-col>
-                </el-row>
-              </el-col>
-            </el-row>
-            <el-row v-if="m.userId === userId" 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">
-                  <div style="font-size: 12px; color: #999; margin-bottom: 3px;">{{ m.nickName }}</div>
-                  <div style="white-space: normal; word-wrap: break-word;width: 100%; background-color: #e6f7ff; padding: 8px; border-radius: 5px;font-size: 14px;">{{ m.msg }}</div>
+      </div>
+
+      <div class="panel-card ops-panel">
+        <h3 class="section-title">运营自动化</h3>
+        <div class="automation-panel">
+          <p class="section-subtitle">时间轴设置</p>
+          <div class="automation-body">
+            <div class="timeline-items">
+              <div class="timeline-item" v-for="item in timelineItems.slice(0, 2)" :key="item.id || item.absValue">
+                <div class="timeline-info">
+                  <div class="timeline-time">{{ formatDate(item.absValue) }}</div>
+                  <div class="timeline-action">{{ item.taskName }}</div>
                 </div>
                 </div>
-                <el-avatar :src="m.avatar" style="margin-left: 10px; margin-right: 10px;"/>
+                <el-button type="text" size="mini" class="action-link" @click="removeTimelineItem(item)">删除</el-button>
               </div>
               </div>
-            </el-row>
-          </el-row>
-          <!-- 底部留白 -->
-          <div style="height: 20px;"></div>
-          </el-scrollbar>
-          <!-- 加载最新消息按钮 -->
-          <el-button
-            v-if="showLoadLatestBtn"
-            class="load-latest-btn"
-            type="primary"
-            size="small"
-            @click.stop="loadLatestMessages"
-            :disabled="isLoadingLatest"
-            :loading="isLoadingLatest"
-            icon="el-icon-refresh">
-            加载最新消息
-          </el-button>
-        </div>
-        <!--        <div class="message-list">-->
-        <!--          <div class="message-item" v-for="msg in msgList" :key="msg.id">-->
-        <!--            <div class="message-avatar">-->
-        <!--              <img :src="msg.avatar" alt="用户头像">-->
-        <!--            </div>-->
-        <!--            <div class="message-content">-->
-        <!--              <div class="message-user">{{ msg.user }}</div>-->
-        <!--              <div class="message-text">{{ msg.text }}</div>-->
-        <!--            </div>-->
-        <!--            <div class="message-actions">-->
-        <!--&lt;!&ndash;              <button @click="toggleVisible(msg)">&ndash;&gt;-->
-        <!--&lt;!&ndash;                {{ msg.isVisible ? '仅用户自见' : '全局可见' }}&ndash;&gt;-->
-        <!--&lt;!&ndash;              </button>&ndash;&gt;-->
-        <!--              <button @click="deleteMessage(msg)">删除</button>-->
-        <!--            </div>-->
-        <!--          </div>-->
-        <!--        </div>-->
-      </div>
-      <div class="system-messages">
-        <h3>系统消息</h3>
-        <textarea placeholder="输入系统消息" v-model="newMsg"></textarea>
-        <div class="message-actions">
-          <button @click="sendMessage">发送消息</button>
-          <button @click="sendPopMessage">弹窗消息</button>
-          <button @click="showTopMsgDialog">顶部消息</button>
+              <el-empty v-if="!timelineItems.length" description="暂无任务" :image-size="48" />
+            </div>
+          </div>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
 
 
     <div class="right-panel">
     <div class="right-panel">
-      <h2>运营工具</h2>
-      <div class="live-player">
-        <h3>直播观看</h3>
-        <LivePlayer ref="livePlayer" :videoParam="videoParam" />
-      </div>
-
-      <div class="automation">
-        <h3>运营自动化</h3>
-        <div class="timeline">
-          <h4>时间轴设置</h4>
-          <div class="timeline-items">
-            <div class="timeline-item" v-for="item in timelineItems.slice(0,2)" :key="item.time">
-              <div class="time">{{ formatDate(item.absValue) }}</div>
-              <div class="action">{{ item.taskName }}</div>
-              <button class="delete" @click="removeTimelineItem(item)">删除</button>
+      <div class="discussion-messages">
+        <div class="discussion-header">
+          <h3 class="discussion-title">讨论</h3>
+          <div class="discussion-header-line"></div>
+        </div>
+        <div class="discussion-toolbar">
+          <el-checkbox v-model="globalVisible" @change="globalVisibleChange">全局用户自见</el-checkbox>
+        </div>
+        <div class="message-container" @click="handleMessageBoxClick">
+          <el-scrollbar class="msg-scroll" ref="manageRightRef">
+            <div v-for="(m, index) in msgList" :key="getMsgKey(m, index)" class="msg-item">
+              <div v-if="isSelfMessage(m)" class="msg-row msg-row--self">
+                <div class="msg-content msg-content--self">
+                  <div class="msg-nickname">{{ m.nickName }}</div>
+                  <div class="msg-bubble msg-bubble--self">{{ m.msg }}</div>
+                </div>
+                <el-avatar :size="32" :src="m.avatar" class="msg-avatar" />
+              </div>
+              <div v-else class="msg-row">
+                <el-avatar :size="32" :src="m.avatar" class="msg-avatar" />
+                <div class="msg-content">
+                  <div class="msg-nickname">{{ m.nickName }}</div>
+                  <div class="msg-bubble">{{ m.msg }}</div>
+                  <div class="msg-actions">
+                    <a class="action-link" @click.stop="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                    <a class="action-link" @click.stop="blockUser(m)">拉黑</a>
+                    <a class="action-link" @click.stop="singleVisible(m)">{{ isSingleVisible(m) ? '解除用户自见' : '用户自见' }}</a>
+                    <a class="action-link" @click.stop="deleteMsg(m)">删除</a>
+                  </div>
+                </div>
+              </div>
             </div>
             </div>
+            <div class="scroll-bottom-space"></div>
+          </el-scrollbar>
+        </div>
+        <div class="discussion-input">
+          <el-input
+            type="textarea"
+            v-model="newMsg"
+            placeholder="请输入消息..."
+            :rows="4"
+            resize="none"
+          />
+          <div class="discussion-input-actions">
+            <el-button
+              v-if="showLoadLatestBtn"
+              class="discussion-action-btn"
+              size="small"
+              @click="loadLatestMessages"
+              :disabled="isLoadingLatest"
+              :loading="isLoadingLatest"
+            >加载最新</el-button>
+            <el-button class="discussion-send-btn" type="primary" size="small" @click="sendMessage">发送</el-button>
           </div>
           </div>
-          <!--          <button class="add" @click="addTimelineItem">添加时间节点</button>-->
         </div>
         </div>
       </div>
       </div>
 
 
-      <div class="watermark">
-        <h3>直播氛围自动化</h3>
-        <div class="watermark-settings">
-          <textarea :disabled="autoWatermark" v-model="watermarkTemplate" placeholder="水军弹幕内容模板,每行一条"></textarea>
-          <div class="watermark-options">
-            <label>
-              发送间隔:
-              <input type="number" :disabled="autoWatermark" v-model.number="interval" min="1">
-              秒
-            </label>
-            <label>
-              <input type="checkbox" v-model="autoWatermark" @change="changeAutoWatermark">
-              启用水军自动化
-            </label>
-          </div>
+      <div class="panel-card watermark">
+        <h3 class="section-title">直播氛围自动化</h3>
+        <el-input
+          type="textarea"
+          :rows="3"
+          :disabled="autoWatermark"
+          v-model="watermarkTemplate"
+          placeholder="水军弹幕内容模板,每行一条"
+          resize="none"
+        />
+        <div class="watermark-options">
+          <span class="toolbar-label">发送间隔</span>
+          <el-input-number v-model="interval" :min="1" :disabled="autoWatermark" size="mini" controls-position="right" />
+          <span class="toolbar-label">秒</span>
+          <el-checkbox v-model="autoWatermark" @change="changeAutoWatermark">启用水军自动化</el-checkbox>
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
@@ -285,6 +244,9 @@ export default {
       watermarkList:[],
       watermarkList:[],
       watermarkTemplate: '',
       watermarkTemplate: '',
       liveInfo: {},
       liveInfo: {},
+      isAudit: false,
+      liveStatus: 0,
+      loading: false,
       videoParam:{
       videoParam:{
         startTime:'',
         startTime:'',
         livingUrl: '',
         livingUrl: '',
@@ -415,7 +377,8 @@ export default {
         pageNum: 1,
         pageNum: 1,
         pageSize: 30,
         pageSize: 30,
         liveId: null
         liveId: null
-      }
+      },
+      chatScrollTop: 0
     };
     };
   },
   },
   computed: {
   computed: {
@@ -436,6 +399,24 @@ export default {
     },
     },
     silencedUserLabel() {
     silencedUserLabel() {
       return `禁言(${this.userTotal.silenced})`;
       return `禁言(${this.userTotal.silenced})`;
+    },
+    showLivePlaceholder() {
+      if (!this.isAudit) {
+        return true;
+      }
+      if (this.liveStatus === 4) {
+        return !this.videoParam.videoUrl;
+      }
+      if (this.liveStatus !== 2) {
+        return true;
+      }
+      if (this.videoParam.liveType === 1) {
+        return !this.videoParam.livingUrl;
+      }
+      return !this.videoParam.videoUrl;
+    },
+    livePlaceholderText() {
+      return '直播未开启';
     }
     }
   },
   },
   created() {
   created() {
@@ -456,6 +437,29 @@ export default {
     this.initScrollListeners();
     this.initScrollListeners();
   },
   },
   methods: {
   methods: {
+    isSameUser(id1, id2) {
+      if (id1 == null || id2 == null) return false;
+      return String(id1) === String(id2);
+    },
+    isSelfMessage(m) {
+      return this.isSameUser(m.userId, this.userId);
+    },
+    isSingleVisible(m) {
+      return Number(m.singleVisible) === 1;
+    },
+    getMsgKey(m, index) {
+      if (m.msgId != null && m.msgId !== '') {
+        return m.msgId;
+      }
+      return `msg-${index}-${m.userId || 'unknown'}`;
+    },
+    ensureSocket() {
+      if (!this.socket || !this.socket.ws || this.socket.ws.readyState !== WebSocket.OPEN) {
+        this.$message.error('WebSocket 未连接,请稍后重试');
+        return false;
+      }
+      return true;
+    },
     // 点击消息框
     // 点击消息框
     handleMessageBoxClick() {
     handleMessageBoxClick() {
       // 点击消息框时,停止自动滚动
       // 点击消息框时,停止自动滚动
@@ -496,7 +500,7 @@ export default {
           // 强制滚动或启用自动滚动时,直接滚动到底部并隐藏按钮
           // 强制滚动或启用自动滚动时,直接滚动到底部并隐藏按钮
           if (forceScroll || this.isAutoScrollEnabled) {
           if (forceScroll || this.isAutoScrollEnabled) {
             this.showLoadLatestBtn = false;
             this.showLoadLatestBtn = false;
-            this.$refs.manageRightRef.wrap.scrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
+            wrap.scrollTop = wrap.scrollHeight - wrap.clientHeight;
           }
           }
         });
         });
       }
       }
@@ -517,18 +521,22 @@ export default {
       this.loadMsgList();
       this.loadMsgList();
     },
     },
     singleVisible(m){
     singleVisible(m){
-      // 过滤当前所有消息 找到userId的相同的消息 更改他们的自可见状态
-      m.singleVisible= m.singleVisible === 1 ? 0 : 1
-      this.msgList.forEach(m1 => {m1.singleVisible = m1.userId === m.userId ? m.singleVisible : !m.singleVisible})
-      // 消息自可见
-      let msg = {
+      const newStatus = this.isSingleVisible(m) ? 0 : 1;
+      m.singleVisible = newStatus;
+      this.msgList.forEach(m1 => {
+        if (this.isSameUser(m1.userId, m.userId)) {
+          m1.singleVisible = newStatus;
+        }
+      });
+      if (!this.ensureSocket()) return;
+      const msg = {
         liveId: this.liveId,
         liveId: this.liveId,
         userId: m.userId,
         userId: m.userId,
         userType: 0,
         userType: 0,
         cmd: 'singleVisible',
         cmd: 'singleVisible',
-        status:m.singleVisible
-      }
-      this.socket.send(JSON.stringify(msg))
+        status: newStatus
+      };
+      this.socket.send(JSON.stringify(msg));
     },
     },
     deleteMsg(m){
     deleteMsg(m){
       // 1. 弹出确认对话框
       // 1. 弹出确认对话框
@@ -547,9 +555,10 @@ export default {
             let msg = {
             let msg = {
               liveId: this.liveId,
               liveId: this.liveId,
               userId: m.userId,
               userId: m.userId,
-              msg: m.msgId, // 关键:将消息ID发送给后台
+              msg: m.msgId,
               cmd: 'deleteMsg',
               cmd: 'deleteMsg',
             };
             };
+            if (!this.ensureSocket()) return;
             this.socket.send(JSON.stringify(msg));
             this.socket.send(JSON.stringify(msg));
             // 可以在这里给用户一个删除成功的提示
             // 可以在这里给用户一个删除成功的提示
             this.$message({
             this.$message({
@@ -566,16 +575,19 @@ export default {
         });
         });
       });
       });
     },
     },
-    globalVisibleChange( val){
-      // 消息自可见
-      let msg = {
+    globalVisibleChange(){
+      if (!this.ensureSocket()) {
+        this.globalVisible = !this.globalVisible;
+        return;
+      }
+      const msg = {
         liveId: this.liveId,
         liveId: this.liveId,
         userId: '9999',
         userId: '9999',
         userType: 0,
         userType: 0,
         cmd: 'globalVisible',
         cmd: 'globalVisible',
-        status:this.globalVisible ? 1 :0
-      }
-      this.socket.send(JSON.stringify(msg))
+        status: this.globalVisible ? 1 : 0
+      };
+      this.socket.send(JSON.stringify(msg));
     },
     },
     changeAutoWatermark( val){
     changeAutoWatermark( val){
       this.watermarkList = this.watermarkTemplate
       this.watermarkList = this.watermarkTemplate
@@ -616,6 +628,7 @@ export default {
       if (curMsg.trim() === '') {
       if (curMsg.trim() === '') {
         return;
         return;
       }
       }
+      if (!this.ensureSocket()) return;
       let msg = {
       let msg = {
         msg: curMsg,
         msg: curMsg,
         liveId: this.liveId,
         liveId: this.liveId,
@@ -685,10 +698,10 @@ export default {
       this.$message.success('顶部消息发送成功');
       this.$message.success('顶部消息发送成功');
     },
     },
     sendMessage() {
     sendMessage() {
-      // 发送前简单校验
       if (this.newMsg.trim() === '') {
       if (this.newMsg.trim() === '') {
         return;
         return;
       }
       }
+      if (!this.ensureSocket()) return;
 
 
       let msg = {
       let msg = {
         msg: this.newMsg,
         msg: this.newMsg,
@@ -711,12 +724,13 @@ export default {
         if (cmd === 'sendMsg') {
         if (cmd === 'sendMsg') {
           let message = JSON.parse(data.data)
           let message = JSON.parse(data.data)
 
 
-          let user = this.alDisplayList.find(u => u.userId === message.userId)
+          let user = this.alDisplayList.find(u => this.isSameUser(u.userId, message.userId))
           if (user) {
           if (user) {
             message.msgStatus = user.msgStatus
             message.msgStatus = user.msgStatus
           } else {
           } else {
             message.msgStatus = 0
             message.msgStatus = 0
           }
           }
+          message.singleVisible = message.singleVisible == null ? 0 : Number(message.singleVisible)
           delete message.params
           delete message.params
           if(this.msgList.length > 50){
           if(this.msgList.length > 50){
             this.msgList.shift()
             this.msgList.shift()
@@ -781,6 +795,24 @@ export default {
 
 
         } else if (cmd === 'live_end') {
         } else if (cmd === 'live_end') {
 
 
+        } else if (cmd === 'deleteMsg') {
+          const msgId = data.msg || data.msgId;
+          if (msgId != null) {
+            const index = this.msgList.findIndex(item => String(item.msgId) === String(msgId));
+            if (index !== -1) {
+              this.msgList.splice(index, 1);
+            }
+          }
+        } else if (cmd === 'singleVisible') {
+          const targetUserId = data.userId;
+          const status = Number(data.status) === 1 ? 1 : 0;
+          this.msgList.forEach(m1 => {
+            if (this.isSameUser(m1.userId, targetUserId)) {
+              m1.singleVisible = status;
+            }
+          });
+        } else if (cmd === 'globalVisible') {
+          this.globalVisible = Number(data.status) === 1 || data.status === true;
         }
         }
       }
       }
     },
     },
@@ -793,7 +825,7 @@ export default {
             return
             return
           }
           }
           this.isAudit = true
           this.isAudit = true
-          this.status = res.data.status
+          this.liveStatus = res.data.status
           this.videoParam.startTime = new Date(res.data.startTime).getTime()
           this.videoParam.startTime = new Date(res.data.startTime).getTime()
           if(res.data.status == 4){
           if(res.data.status == 4){
             this.videoParam.liveType = 3
             this.videoParam.liveType = 3
@@ -807,21 +839,16 @@ export default {
             if (res.data.liveType == 1) {
             if (res.data.liveType == 1) {
               this.videoParam.livingUrl = res.data.flvHlsUrl
               this.videoParam.livingUrl = res.data.flvHlsUrl
               this.videoParam.livingUrl = this.videoParam.livingUrl.replace("flv","m3u8")
               this.videoParam.livingUrl = this.videoParam.livingUrl.replace("flv","m3u8")
-              // this.$nextTick(() => {
-              //   this.initPlayer()
-              // })
-              // this.processInterval = setInterval(this.updateLiveProgress, 1000);
             } else {
             } else {
               this.videoParam.liveType = 2
               this.videoParam.liveType = 2
               this.videoParam.videoUrl = res.data.videoUrl;
               this.videoParam.videoUrl = res.data.videoUrl;
-              // this.$nextTick(() => {
-              //   this.initVideoPlayer(res.data.startTime)
-              // })
             }
             }
           }
           }
           this.$nextTick(() => {
           this.$nextTick(() => {
-            this.globalVisible = res.data.globalVisible
-            this.$refs.livePlayer.initPlayer()
+            this.globalVisible = res.data.globalVisible === 1 || res.data.globalVisible === true
+            if (this.$refs.livePlayer) {
+              this.$refs.livePlayer.initPlayer()
+            }
           })
           })
           this.loading = false
           this.loading = false
         } else {
         } else {
@@ -836,9 +863,12 @@ export default {
         liveWsUrl: this.liveWsUrl,
         liveWsUrl: this.liveWsUrl,
         liveId: this.liveId,
         liveId: this.liveId,
         userId: this.userId
         userId: this.userId
+      }).then((ws) => {
+        this.socket = ws || this.$store.state.liveWs[this.liveId]
+        if (this.socket) {
+          this.socket.onmessage = (event) => this.handleWsMessage(event)
+        }
       })
       })
-      this.socket = this.$store.state.liveWs[this.liveId]
-      this.socket.onmessage = (event) => this.handleWsMessage(event)
     },
     },
     changeUserState(u) {
     changeUserState(u) {
       const displayList = this[`${this.currentTab}DisplayList`];
       const displayList = this[`${this.currentTab}DisplayList`];
@@ -1166,12 +1196,13 @@ export default {
           let totalPage = (total % this.msgParams.pageSize == 0) ? Math.floor(total / this.msgParams.pageSize) : Math.floor(total / this.msgParams.pageSize + 1);
           let totalPage = (total % this.msgParams.pageSize == 0) ? Math.floor(total / this.msgParams.pageSize) : Math.floor(total / this.msgParams.pageSize + 1);
           rows.forEach(row => {
           rows.forEach(row => {
             if (!this.msgList.some(m => m.msgId === row.msgId)) {
             if (!this.msgList.some(m => m.msgId === row.msgId)) {
-              let user = this.alDisplayList.find(u => u.userId === row.userId)
+              let user = this.alDisplayList.find(u => this.isSameUser(u.userId, row.userId))
               if (user) {
               if (user) {
                 row.msgStatus = user.msgStatus
                 row.msgStatus = user.msgStatus
               } else {
               } else {
                 row.msgStatus = 0
                 row.msgStatus = 0
               }
               }
+              row.singleVisible = row.singleVisible == null ? 0 : Number(row.singleVisible)
               this.msgList.push(row)
               this.msgList.push(row)
             }
             }
           })
           })
@@ -1179,7 +1210,7 @@ export default {
           this.msgList.reverse()
           this.msgList.reverse()
           // 同步更新消息列表中相同用户的状态
           // 同步更新消息列表中相同用户的状态
           this.alDisplayList.forEach(u => {
           this.alDisplayList.forEach(u => {
-            this.msgList.filter(m => m.userId === u.userId).forEach(m => m.msgStatus = u.msgStatus)
+            this.msgList.filter(m => this.isSameUser(m.userId, u.userId)).forEach(m => m.msgStatus = u.msgStatus)
           })
           })
 
 
           // 所有消息加载完成后,根据自动滚动状态决定是否滚动
           // 所有消息加载完成后,根据自动滚动状态决定是否滚动
@@ -1490,296 +1521,500 @@ export default {
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
+/* 布局 */
 .console {
 .console {
   display: flex;
   display: flex;
-  height: 100vh;
+  height: 100%;
+  overflow: hidden;
+  background: #f5f7fa;
 }
 }
 
 
-.left-panel, .middle-panel, .right-panel {
-  padding: 20px;
+.left-panel,
+.middle-panel,
+.right-panel {
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+  padding: 12px;
   box-sizing: border-box;
   box-sizing: border-box;
+  overflow: hidden;
 }
 }
 
 
 .left-panel {
 .left-panel {
-  width: 30%;
-  background: #f8fafc;
-  border-right: 1px solid #e2e8f0;
+  width: 22%;
+  border-right: 1px solid #ebeef5;
 }
 }
 
 
 .middle-panel {
 .middle-panel {
-  width: 40%;
-  background: #f8fafc;
-  border-right: 1px solid #e2e8f0;
+  width: 48%;
+  border-right: 1px solid #ebeef5;
+  gap: 12px;
+  overflow: hidden;
 }
 }
 
 
 .right-panel {
 .right-panel {
   width: 30%;
   width: 30%;
-  background: #f8fafc;
+  gap: 8px;
+  overflow: hidden;
 }
 }
 
 
-.search {
-  margin: 10px 0;
+/* 通用标题与卡片 */
+.panel-title {
+  margin: 0 0 8px;
+  flex-shrink: 0;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  line-height: 1.4;
 }
 }
 
 
-.search input {
-  width: 70%;
-  padding: 8px;
-  border: 1px solid #cbd5e1;
-  border-radius: 4px;
+.section-title {
+  margin: 0;
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
 }
 }
 
 
-.search button {
-  padding: 8px 15px;
-  background: #3b82f6;
-  color: #fff;
-  border: none;
+.section-subtitle {
+  margin: 0 0 8px;
+  font-size: 13px;
+  color: #909399;
+}
+
+.panel-card {
+  background: #fff;
+  border: 1px solid #ebeef5;
   border-radius: 4px;
   border-radius: 4px;
+  padding: 12px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
+}
+
+.toolbar-label {
+  font-size: 13px;
+  color: #606266;
+}
+
+.action-link {
+  display: inline-block;
+  margin-right: 10px;
+  font-size: 12px;
+  color: #f56c6c;
   cursor: pointer;
   cursor: pointer;
 }
 }
 
 
-.tabs {
+.action-link:hover {
+  opacity: 0.85;
+}
+
+/* 学员列表 */
+.search-bar {
   display: flex;
   display: flex;
-  margin: 10px 0;
+  gap: 8px;
+  margin-bottom: 10px;
 }
 }
 
 
-.tabs button {
-  padding: 8px 15px;
-  border: 1px solid #e2e8f0;
-  background: #fff;
-  cursor: pointer;
+.search-bar .el-input {
+  flex: 1;
 }
 }
 
 
-.tabs button.active {
-  background: #3b82f6;
-  color: #fff;
-  border-color: #3b82f6;
+.console-tabs {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
 }
 }
 
 
-.user-list {
-  max-height: 600px;
-  overflow-y: auto;
+.console-tabs ::v-deep .el-tabs__content {
+  flex: 1;
+  min-height: 0;
 }
 }
 
 
-.user-item {
+.console-tabs ::v-deep .el-tab-pane {
+  height: 100%;
+}
+
+.panel-scroll {
+  height: 100%;
+}
+
+.panel-scroll ::v-deep .el-scrollbar {
+  height: 100%;
+}
+
+.user-list-item {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
-  padding: 10px;
-  border-bottom: 1px solid #e2e8f0;
+  gap: 10px;
+  padding: 10px 8px;
+  border-bottom: 1px solid #f2f6fc;
 }
 }
 
 
-.user-item img {
-  width: 40px;
-  height: 40px;
-  border-radius: 50%;
-  margin-right: 10px;
+.user-list-item:hover {
+  background: #f5f7fa;
 }
 }
 
 
-.user-info {
+.user-meta {
   flex: 1;
   flex: 1;
+  min-width: 0;
 }
 }
 
 
 .user-name {
 .user-name {
-  font-weight: bold;
+  font-size: 13px;
+  color: #303133;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 }
 
 
-.user-status {
+.user-id {
+  margin-top: 2px;
   font-size: 12px;
   font-size: 12px;
-  color: #64748b;
+  color: #909399;
 }
 }
 
 
-.online {
-  color: #10b981;
+.user-more-btn {
+  color: #909399;
+  cursor: pointer;
+  padding: 4px;
 }
 }
 
 
-.offline {
-  color: #94a3b8;
+.user-more-btn:hover {
+  color: #409eff;
 }
 }
 
 
-.user-actions {
+/* 中间直播画面 */
+.live-player-card {
+  flex: 3;
+  min-height: 0;
   display: flex;
   display: flex;
+  flex-direction: column;
+  margin: 0;
+  overflow: hidden;
+  padding: 10px 12px;
 }
 }
 
 
-.user-actions button {
-  padding: 5px 10px;
-  margin-left: 5px;
-  border: none;
-  border-radius: 4px;
-  cursor: pointer;
+.live-player-card .section-title {
+  flex-shrink: 0;
+  margin-bottom: 6px;
 }
 }
 
 
-.block {
-  background: #ef4444;
-  color: #fff;
-}
-
-.unblock {
-  background: #10b981;
-  color: #fff;
+.live-player-wrapper {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
 }
 }
 
 
-.mute {
-  background: #f59e0b;
-  color: #fff;
+.live-placeholder {
+  flex: 1;
+  min-height: 0;
+  max-height: 100%;
+  background: #000;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 }
 
 
-.unmute {
-  background: #3b82f6;
+.live-placeholder-text {
   color: #fff;
   color: #fff;
+  font-size: 26px;
+  font-weight: 700;
+  letter-spacing: 3px;
+  user-select: none;
 }
 }
 
 
-.system-messages, .discussion-messages {
-  margin: 20px 0;
-  background: #fff;
-  padding: 15px;
-  border-radius: 8px;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+.live-player-card ::v-deep .live-player {
+  flex: 1;
+  min-height: 0;
+  max-height: 100%;
+  height: 100%;
+  margin-bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #000;
+  border-radius: 4px;
 }
 }
 
 
-.system-messages textarea {
+.live-player-card ::v-deep .player {
   width: 100%;
   width: 100%;
-  height: 100px;
-  border: 1px solid #e2e8f0;
+  height: 100%;
+  max-height: 100%;
+  min-height: 0;
   border-radius: 4px;
   border-radius: 4px;
-  padding: 8px;
+  object-fit: contain;
+}
+
+/* 运营自动化 */
+.ops-panel {
+  flex: 2;
+  min-height: 0;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  margin: 0;
+  padding: 8px 10px 10px;
   box-sizing: border-box;
   box-sizing: border-box;
 }
 }
 
 
-.message-actions {
-  margin-top: 10px;
+.ops-panel .section-title {
+  flex-shrink: 0;
+  margin-bottom: 8px;
 }
 }
 
 
-.message-actions button {
-  padding: 5px 10px;
-  margin-right: 5px;
-  background: #3b82f6;
-  color: #fff;
-  border: none;
+.automation-panel {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+}
+
+.automation-body {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+}
+
+.timeline-items {
+  margin-top: 4px;
+}
+
+.timeline-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 8px;
+  padding: 8px 0;
+  border-bottom: 1px solid #f2f6fc;
+}
+
+.timeline-time {
+  font-size: 12px;
+  color: #909399;
+}
+
+.timeline-action {
+  margin-top: 2px;
+  font-size: 13px;
+  color: #303133;
+}
+
+/* 讨论区 */
+.discussion-messages {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  border: 1px solid #ebeef5;
   border-radius: 4px;
   border-radius: 4px;
-  cursor: pointer;
+  overflow: hidden;
+}
+
+.discussion-header {
+  flex-shrink: 0;
+  padding: 12px 16px 0;
 }
 }
 
 
-.message-list {
-  max-height: 300px;
-  overflow-y: auto;
+.discussion-title {
+  margin: 0;
+  text-align: center;
+  font-size: 16px;
+  font-weight: 600;
+  color: #36cfc9;
+  line-height: 1.4;
+}
+
+.discussion-header-line {
   margin-top: 10px;
   margin-top: 10px;
+  height: 2px;
+  background: #36cfc9;
 }
 }
 
 
-.message-item {
+.discussion-toolbar {
+  flex-shrink: 0;
   display: flex;
   display: flex;
-  margin-bottom: 10px;
-  padding-bottom: 10px;
-  border-bottom: 1px solid #e2e8f0;
+  justify-content: flex-end;
+  padding: 8px 16px 0;
 }
 }
 
 
-.message-avatar img {
-  width: 30px;
-  height: 30px;
-  border-radius: 50%;
-  margin-right: 10px;
+.message-container {
+  position: relative;
+  flex: 1;
+  min-height: 0;
+  padding: 8px 16px 0;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
 }
 }
 
 
-.message-content {
+.msg-scroll {
   flex: 1;
   flex: 1;
+  min-height: 0;
+  height: auto;
 }
 }
 
 
-.message-user {
-  font-weight: bold;
+.msg-scroll ::v-deep .el-scrollbar {
+  height: 100%;
 }
 }
 
 
-.message-text {
-  font-size: 14px;
-  color: #64748b;
+.msg-scroll ::v-deep .el-scrollbar__wrap {
+  height: 100% !important;
+  max-height: 100%;
+}
+
+.msg-item + .msg-item {
+  margin-top: 8px;
+}
+
+.msg-row {
+  display: flex;
+  align-items: flex-start;
+  gap: 8px;
+}
+
+.msg-row--self {
+  justify-content: flex-end;
+}
+
+.msg-content {
+  max-width: 72%;
+}
+
+.msg-content--self {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
 }
 }
 
 
-.message-actions button {
-  padding: 3px 8px;
+.msg-nickname {
+  margin-bottom: 4px;
   font-size: 12px;
   font-size: 12px;
-  background: #3b82f6;
-  color: #fff;
-  border: none;
+  color: #909399;
+}
+
+.msg-bubble {
+  padding: 8px 10px;
   border-radius: 4px;
   border-radius: 4px;
-  cursor: pointer;
+  background: #f4f4f5;
+  font-size: 13px;
+  line-height: 1.5;
+  color: #303133;
+  word-break: break-word;
+}
+
+.msg-bubble--self {
+  background: #ecf5ff;
+}
+
+.msg-actions {
+  margin-top: 6px;
+}
+
+.msg-avatar {
+  flex-shrink: 0;
 }
 }
 
 
-.live-player, .automation, .watermark {
-  margin: 20px 0;
+.scroll-bottom-space {
+  height: 16px;
+}
+
+.discussion-input {
+  flex-shrink: 0;
+  padding: 12px 16px 16px;
+  border-top: 1px solid #f0f0f0;
   background: #fff;
   background: #fff;
-  padding: 15px;
-  border-radius: 8px;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
 }
 }
 
 
-.timeline-items {
-  margin: 10px 0;
+.discussion-input ::v-deep .el-textarea__inner {
+  background: #f5f5f5;
+  border: none;
+  border-radius: 6px;
+  padding: 12px;
+  font-size: 13px;
+  color: #303133;
 }
 }
 
 
-.timeline-item {
+.discussion-input ::v-deep .el-textarea__inner:focus {
+  border: none;
+  box-shadow: none;
+}
+
+.discussion-input-actions {
   display: flex;
   display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 8px 0;
-  border-bottom: 1px solid #e2e8f0;
+  justify-content: flex-end;
+  gap: 8px;
+  margin-top: 10px;
 }
 }
 
 
-.delete {
-  background: #ef4444;
+.discussion-action-btn {
+  background: #36cfc9;
+  border-color: #36cfc9;
   color: #fff;
   color: #fff;
-  border: none;
-  border-radius: 4px;
-  padding: 3px 8px;
-  cursor: pointer;
 }
 }
 
 
-.add {
-  background: #10b981;
+.discussion-action-btn:hover,
+.discussion-action-btn:focus {
+  background: #2eb8ab;
+  border-color: #2eb8ab;
   color: #fff;
   color: #fff;
-  border: none;
-  border-radius: 4px;
-  padding: 8px 15px;
-  cursor: pointer;
 }
 }
 
 
-.watermark-settings textarea {
-  width: 100%;
-  height: 100px;
-  border: 1px solid #e2e8f0;
-  border-radius: 4px;
-  padding: 8px;
-  box-sizing: border-box;
+.discussion-send-btn {
+  background: #36cfc9;
+  border-color: #36cfc9;
+}
+
+.discussion-send-btn:hover,
+.discussion-send-btn:focus {
+  background: #2eb8ab;
+  border-color: #2eb8ab;
+}
+
+/* 右侧氛围自动化 */
+.watermark {
+  flex-shrink: 0;
+  margin: 0;
 }
 }
 
 
 .watermark-options {
 .watermark-options {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 8px;
   margin-top: 10px;
   margin-top: 10px;
 }
 }
 
 
-.watermark-options label {
-  display: block;
-  margin-bottom: 5px;
-}
-/* 隐藏 el-scrollbar 的横向滚动条 */
-.el-scrollbar__wrap {
-  overflow-x: hidden !important;
+/* 隐藏滚动条,保留滚动能力 */
+.console ::v-deep .el-scrollbar__bar {
+  display: none !important;
 }
 }
-.custom-scrollbar .el-scrollbar__wrap {
+
+.console ::v-deep .el-scrollbar__wrap {
   overflow-x: hidden !important;
   overflow-x: hidden !important;
+  scrollbar-width: none;
+  -ms-overflow-style: none;
 }
 }
-.scrollbar-demo-item{
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  height: 50px;
-  margin: 10px;
-  text-align: center;
-  border-radius: 4px;
+
+.console ::v-deep .el-scrollbar__wrap::-webkit-scrollbar {
+  width: 0;
+  height: 0;
+  display: none;
 }
 }
-.message-container {
-  position: relative;
+</style>
+
+<style>
+.user-action-popover .action-link {
+  display: block;
+  margin: 0 0 8px;
+  font-size: 13px;
+  color: #f56c6c;
+  cursor: pointer;
 }
 }
-.load-latest-btn {
-  position: absolute;
-  bottom: 20px;
-  right: 20px;
-  z-index: 10;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+.user-action-popover .action-link:last-child {
+  margin-bottom: 0;
 }
 }
 </style>
 </style>

+ 28 - 2
src/views/live/liveConsole/index.vue

@@ -1,10 +1,12 @@
 <template>
 <template>
-  <div id="app">
+  <div class="live-console-app">
     <div class="nav">
     <div class="nav">
       <button @click="currentView = 'dashboard'">实时大屏</button>
       <button @click="currentView = 'dashboard'">实时大屏</button>
       <button @click="currentView = 'console'">中控台</button>
       <button @click="currentView = 'console'">中控台</button>
     </div>
     </div>
-    <component :is="currentView" :liveId="liveId"></component>
+    <div class="live-console-main">
+      <component :is="currentView" :liveId="liveId"></component>
+    </div>
   </div>
   </div>
 </template>
 </template>
 
 
@@ -34,10 +36,34 @@ export default {
 body {
 body {
   margin: 0;
   margin: 0;
   font-family: 'Arial', sans-serif;
   font-family: 'Arial', sans-serif;
+  overflow: hidden;
+}
+
+.live-console-app {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  overflow: hidden;
+}
+
+.live-console-main {
+  flex: 1 1 0;
+  min-height: 0;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.live-console-main > * {
+  flex: 1 1 auto;
+  min-height: 0;
+  width: 100%;
+  height: 100%;
 }
 }
 
 
 .nav {
 .nav {
   display: flex;
   display: flex;
+  flex-shrink: 0;
   background: #1e3a8a;
   background: #1e3a8a;
   color: white;
   color: white;
 }
 }