Переглянути джерело

1、优化直播中控台排版

yys 1 тиждень тому
батько
коміт
2f98a394fc

+ 521 - 165
src/views/live/liveConsole/LiveConsole.vue

@@ -82,54 +82,18 @@
     </div>
 
     <div class="middle-panel">
-      <h2 class="panel-title">消息管理</h2>
-
-      <div class="panel-card discussion-messages">
-        <div class="section-header">
-          <h3 class="section-title">讨论区消息</h3>
-          <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 in msgList" :key="m.msgId" class="msg-item">
-              <div v-if="m.userId === userId && m.msgId == null" 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)">{{ m.singleVisible === 1 ? '解除用户自见' : '用户自见' }}</a>
-                    <a class="action-link" @click.stop="deleteMsg(m)">删除</a>
-                  </div>
-                </div>
-              </div>
-            </div>
-            <div class="scroll-bottom-space"></div>
-          </el-scrollbar>
-          <el-button
-            v-if="showLoadLatestBtn"
-            class="load-latest-btn"
-            type="primary"
-            size="mini"
-            @click.stop="loadLatestMessages"
-            :disabled="isLoadingLatest"
-            :loading="isLoadingLatest"
-            icon="el-icon-refresh"
-          >加载最新消息</el-button>
+      <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="panel-card ops-panel">
-        <el-tabs v-model="opsTabActive" class="ops-tabs">
+        <el-tabs v-model="opsTabActive" class="ops-tabs" tab-position="left">
           <el-tab-pane label="营销" name="marketing">
             <div class="marketing-panel" v-loading="marketingLoading">
               <div class="marketing-toolbar">
@@ -143,14 +107,15 @@
                   show-icon
                 />
               </div>
-              <el-radio-group v-model="marketingCategory" size="mini" class="marketing-categories" @change="clearMarketingSelection">
+              <el-radio-group v-model="marketingCategory" size="mini" class="marketing-categories" @change="handleMarketingCategoryChange">
                 <el-radio-button v-for="cat in marketingCategories" :key="cat.key" :label="cat.key">{{ cat.label }}</el-radio-button>
               </el-radio-group>
-              <el-scrollbar class="marketing-scroll">
+              <div class="marketing-content" :class="{ 'has-marketing-pagination': marketingListTotal > 0 }">
+              <div class="marketing-body">
                 <!-- 商品:图片卡片 -->
-                <div class="marketing-cards" v-if="marketingCategory === 'goods' && currentMarketingList.length">
+                <div class="marketing-cards" v-if="marketingCategory === 'goods' && paginatedMarketingList.length">
                   <div
-                    v-for="item in currentMarketingList"
+                    v-for="item in paginatedMarketingList"
                     :key="getMarketingItemKey(item)"
                     class="marketing-card"
                     :class="{ selected: isMarketingItemSelected(item) }"
@@ -170,16 +135,14 @@
                     </div>
                     <div class="card-info">
                       <div class="card-title" :title="item.productName">{{ item.productName }}</div>
-                      <div class="card-bottom">
-                        <span class="card-price" v-if="item.price">¥{{ item.price }}</span>
-                      </div>
+                      <div class="card-price" v-if="item.price">¥{{ item.price }}</div>
                     </div>
                   </div>
                 </div>
                 <!-- 优惠券 / 核销券 / 红包 / 抽奖:列表 -->
-                <div class="marketing-list" v-else-if="marketingCategory !== 'goods' && currentMarketingList.length">
+                <div class="marketing-list" v-else-if="marketingCategory !== 'goods' && paginatedMarketingList.length">
                   <div
-                    v-for="item in currentMarketingList"
+                    v-for="item in paginatedMarketingList"
                     :key="getMarketingItemKey(item)"
                     class="marketing-list-item"
                     :class="{ selected: isMarketingItemSelected(item) }"
@@ -218,24 +181,37 @@
                   </div>
                 </div>
                 <el-empty v-else description="暂无数据" :image-size="60" />
-              </el-scrollbar>
-              <div class="panel-footer">
-                <el-button size="mini" @click="handleActivityRecord">活动记录</el-button>
-                <el-button size="mini" @click="handleDistributionRecord">发放记录</el-button>
+              </div>
+              <pagination
+                v-if="marketingListTotal > 0"
+                class="marketing-pagination"
+                :total="marketingListTotal"
+                :page.sync="marketingPageNum"
+                :limit.sync="marketingPageSize"
+                layout="total, prev, pager, next"
+                :pager-count="3"
+                :background="false"
+                small
+                :auto-scroll="false"
+                @pagination="handleMarketingPagination"
+              />
               </div>
             </div>
           </el-tab-pane>
-          <el-tab-pane label="系统消息" name="systemMsg">
-            <div class="system-msg-panel">
-              <el-input
-                type="textarea"
-                :rows="3"
-                placeholder="输入系统消息"
-                v-model="newMsg"
-                resize="none"
-              />
-              <div class="panel-footer">
-                <el-button type="primary" size="mini" @click="sendMessage">发送消息</el-button>
+          <el-tab-pane label="运营自动化" name="automation">
+            <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>
+                    <el-button type="text" size="mini" class="action-link" @click="removeTimelineItem(item)">删除</el-button>
+                  </div>
+                  <el-empty v-if="!timelineItems.length" description="暂无任务" :image-size="48" />
+                </div>
               </div>
             </div>
           </el-tab-pane>
@@ -244,24 +220,60 @@
     </div>
 
     <div class="right-panel">
-      <h2 class="panel-title">运营工具</h2>
-      <div class="panel-card live-player">
-        <h3 class="section-title">直播观看</h3>
-        <LivePlayer ref="livePlayer" :videoParam="videoParam" />
-      </div>
-
-      <div class="panel-card automation">
-        <h3 class="section-title">运营自动化</h3>
-        <p class="section-subtitle">时间轴设置</p>
-        <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 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 in msgList" :key="m.msgId" class="msg-item">
+              <div v-if="m.userId === userId && m.msgId == null" 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)">{{ m.singleVisible === 1 ? '解除用户自见' : '用户自见' }}</a>
+                    <a class="action-link" @click.stop="deleteMsg(m)">删除</a>
+                  </div>
+                </div>
+              </div>
             </div>
-            <el-button type="text" size="mini" class="action-link" @click="removeTimelineItem(item)">删除</el-button>
+            <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>
-          <el-empty v-if="!timelineItems.length" description="暂无任务" :image-size="48" />
         </div>
       </div>
 
@@ -269,7 +281,7 @@
         <h3 class="section-title">直播氛围自动化</h3>
         <el-input
           type="textarea"
-          :rows="4"
+          :rows="3"
           :disabled="autoWatermark"
           v-model="watermarkTemplate"
           placeholder="水军弹幕内容模板,每行一条"
@@ -335,6 +347,8 @@ export default {
       watermarkList:[],
       watermarkTemplate: '',
       liveInfo: {},
+      isAudit: false,
+      liveStatus: 0,
       videoParam:{
         startTime:'',
         livingUrl: '',
@@ -471,6 +485,8 @@ export default {
         { key: 'lottery', label: '抽奖' }
       ],
       marketingLoading: false,
+      marketingPageNum: 1,
+      marketingPageSize: 3,
       showCart: true,
       goodsLiveList: [],
       couponLiveList: [],
@@ -504,6 +520,24 @@ export default {
     silencedUserLabel() {
       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 '直播未开启';
+    },
     currentMarketingList() {
       if (this.marketingCategory === 'goods') {
         return this.goodsLiveList;
@@ -518,6 +552,13 @@ export default {
         return this.couponLiveList.filter(item => !this.isVerifyCouponType(item));
       }
       return this.couponLiveList.filter(item => this.isVerifyCouponType(item));
+    },
+    marketingListTotal() {
+      return this.currentMarketingList.length;
+    },
+    paginatedMarketingList() {
+      const start = (this.marketingPageNum - 1) * this.marketingPageSize;
+      return this.currentMarketingList.slice(start, start + this.marketingPageSize);
     }
   },
   created() {
@@ -585,6 +626,14 @@ export default {
     clearMarketingSelection() {
       this.selectedMarketingItem = null;
     },
+    handleMarketingCategoryChange() {
+      this.clearMarketingSelection();
+      this.marketingPageNum = 1;
+      this.marketingPageSize = 3;
+    },
+    handleMarketingPagination() {
+      this.selectedMarketingItem = null;
+    },
     isVerifyCouponType(item) {
       const label = this.selectDictLabel(this.couponTypeOptions, item.type) || '';
       return label.includes('核销') || label.includes('代金券');
@@ -817,17 +866,6 @@ export default {
         }
       });
     },
-    handleActivityRecord() {
-      if (!this.timelineItems.length) {
-        this.$message.info('暂无活动记录');
-        return;
-      }
-      const names = this.timelineItems.map(item => `${this.formatDate(item.absValue)} ${item.taskName}`).join('\n');
-      this.$alert(names, '活动记录', { confirmButtonText: '确定' });
-    },
-    handleDistributionRecord() {
-      this.$message.info('发放记录请在直播配置-优惠券/红包配置中查看');
-    },
     // 点击消息框
     handleMessageBoxClick() {
       // 点击消息框时,停止自动滚动
@@ -1135,7 +1173,7 @@ export default {
             return
           }
           this.isAudit = true
-          this.status = res.data.status
+          this.liveStatus = res.data.status
           this.videoParam.startTime = new Date(res.data.startTime).getTime()
           if(res.data.status == 4){
             this.videoParam.liveType = 3
@@ -1164,7 +1202,9 @@ export default {
           this.$nextTick(() => {
             this.globalVisible = res.data.globalVisible
             this.showCart = res.data.showCart == null || res.data.showCart === 1
-            this.$refs.livePlayer.initPlayer()
+            if (this.$refs.livePlayer) {
+              this.$refs.livePlayer.initPlayer()
+            }
           })
           this.loading = false
         } else {
@@ -1836,7 +1876,7 @@ export default {
 /* 布局 */
 .console {
   display: flex;
-  height: 100vh;
+  height: 100%;
   overflow: hidden;
   background: #f5f7fa;
 }
@@ -1846,31 +1886,34 @@ export default {
 .right-panel {
   display: flex;
   flex-direction: column;
-  padding: 16px;
+  min-height: 0;
+  padding: 12px;
   box-sizing: border-box;
   overflow: hidden;
 }
 
 .left-panel {
-  width: 28%;
+  width: 22%;
   border-right: 1px solid #ebeef5;
 }
 
 .middle-panel {
-  width: 42%;
+  width: 48%;
   border-right: 1px solid #ebeef5;
   gap: 12px;
+  overflow: hidden;
 }
 
 .right-panel {
   width: 30%;
-  overflow-y: auto;
-  gap: 12px;
+  gap: 8px;
+  overflow: hidden;
 }
 
 /* 通用标题与卡片 */
 .panel-title {
-  margin: 0 0 12px;
+  margin: 0 0 8px;
+  flex-shrink: 0;
   font-size: 16px;
   font-weight: 600;
   color: #303133;
@@ -1946,10 +1989,13 @@ export default {
 .console-tabs {
   flex: 1;
   min-height: 0;
+  display: flex;
+  flex-direction: column;
 }
 
 .console-tabs ::v-deep .el-tabs__content {
-  height: calc(100% - 40px);
+  flex: 1;
+  min-height: 0;
 }
 
 .console-tabs ::v-deep .el-tab-pane {
@@ -1957,7 +2003,11 @@ export default {
 }
 
 .panel-scroll {
-  height: calc(100vh - 180px);
+  height: 100%;
+}
+
+.panel-scroll ::v-deep .el-scrollbar {
+  height: 100%;
 }
 
 .user-list-item {
@@ -2001,23 +2051,122 @@ export default {
   color: #409eff;
 }
 
-/* 讨论区消息 */
+/* 中间直播画面 */
+.live-player-card {
+  flex: 3;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+  margin: 0;
+  overflow: hidden;
+  padding: 10px 12px;
+}
+
+.live-player-card .section-title {
+  flex-shrink: 0;
+  margin-bottom: 6px;
+}
+
+.live-player-wrapper {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+}
+
+.live-placeholder {
+  flex: 1;
+  min-height: 0;
+  max-height: 100%;
+  background: #000;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.live-placeholder-text {
+  color: #fff;
+  font-size: 26px;
+  font-weight: 700;
+  letter-spacing: 3px;
+  user-select: none;
+}
+
+.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;
+}
+
+.live-player-card ::v-deep .player {
+  width: 100%;
+  height: 100%;
+  max-height: 100%;
+  min-height: 0;
+  border-radius: 4px;
+  object-fit: contain;
+}
+
+/* 讨论区 */
 .discussion-messages {
   flex: 1;
   min-height: 0;
   display: flex;
   flex-direction: column;
+  background: #fff;
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  overflow: hidden;
+}
+
+.discussion-header {
+  flex-shrink: 0;
+  padding: 12px 16px 0;
+}
+
+.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;
+  height: 2px;
+  background: #36cfc9;
+}
+
+.discussion-toolbar {
+  flex-shrink: 0;
+  display: flex;
+  justify-content: flex-end;
+  padding: 8px 16px 0;
 }
 
 .message-container {
   position: relative;
   flex: 1;
   min-height: 0;
+  padding: 8px 16px 0;
 }
 
 .msg-scroll {
-  height: calc(100vh - 520px);
-  min-height: 180px;
+  height: 100%;
+}
+
+.msg-scroll ::v-deep .el-scrollbar {
+  height: 100%;
 }
 
 .msg-item + .msg-item {
@@ -2076,87 +2225,253 @@ export default {
   height: 16px;
 }
 
-.load-latest-btn {
-  position: absolute;
-  right: 12px;
-  bottom: 12px;
-  z-index: 2;
+.discussion-input {
+  flex-shrink: 0;
+  padding: 12px 16px 16px;
+  border-top: 1px solid #f0f0f0;
+  background: #fff;
+}
+
+.discussion-input ::v-deep .el-textarea__inner {
+  background: #f5f5f5;
+  border: none;
+  border-radius: 6px;
+  padding: 12px;
+  font-size: 13px;
+  color: #303133;
+}
+
+.discussion-input ::v-deep .el-textarea__inner:focus {
+  border: none;
+  box-shadow: none;
+}
+
+.discussion-input-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+  margin-top: 10px;
+}
+
+.discussion-action-btn {
+  background: #36cfc9;
+  border-color: #36cfc9;
+  color: #fff;
+}
+
+.discussion-action-btn:hover,
+.discussion-action-btn:focus {
+  background: #2eb8ab;
+  border-color: #2eb8ab;
+  color: #fff;
 }
 
-/* 营销 / 系统消息 */
+.discussion-send-btn {
+  background: #36cfc9;
+  border-color: #36cfc9;
+}
+
+.discussion-send-btn:hover,
+.discussion-send-btn:focus {
+  background: #2eb8ab;
+  border-color: #2eb8ab;
+}
+
+/* 营销 / 运营自动化 */
 .ops-panel {
-  flex-shrink: 0;
+  flex: 2;
+  min-height: 0;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  margin: 0;
+  padding: 8px 10px 10px;
+  box-sizing: border-box;
+}
+
+.ops-tabs {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+}
+
+.ops-tabs ::v-deep.el-tabs--left {
+  display: flex;
+  height: 100%;
 }
 
 .ops-tabs ::v-deep .el-tabs__header {
-  margin-bottom: 10px;
+  margin-right: 12px;
+  margin-bottom: 0;
+  flex-shrink: 0;
 }
 
 .ops-tabs ::v-deep .el-tabs__content {
-  overflow: visible;
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+  height: auto;
+}
+
+.ops-tabs ::v-deep .el-tab-pane {
+  height: 100%;
+  overflow: hidden;
+}
+
+.ops-tabs ::v-deep .el-tabs__item {
+  padding: 0 12px;
+  height: 32px;
+  line-height: 32px;
+  font-size: 13px;
 }
 
 .marketing-panel,
-.system-msg-panel {
+.automation-panel {
   display: flex;
   flex-direction: column;
+  height: 100%;
+  overflow: hidden;
 }
 
 .marketing-toolbar {
   display: flex;
   align-items: center;
-  gap: 10px;
-  margin-bottom: 10px;
+  gap: 6px;
+  margin-bottom: 6px;
   flex-wrap: wrap;
+  flex-shrink: 0;
+}
+
+.marketing-toolbar .toolbar-label {
+  font-size: 12px;
 }
 
 .marketing-tip {
   flex: 1;
-  min-width: 200px;
-  padding: 4px 8px;
+  min-width: 160px;
+  padding: 2px 6px;
 }
 
 .marketing-tip ::v-deep .el-alert__title {
-  font-size: 12px;
+  font-size: 11px;
+  line-height: 1.3;
 }
 
 .marketing-categories {
-  margin-bottom: 10px;
+  margin-bottom: 6px;
+  flex-shrink: 0;
 }
 
-.marketing-scroll {
-  height: 150px;
+.marketing-content {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.marketing-content.has-marketing-pagination {
+  padding: 0 2px 0;
+  overflow: hidden;
+}
+
+.marketing-body,
+.automation-body {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
+}
+
+.marketing-content.has-marketing-pagination .marketing-body {
+  flex: 1 1 auto;
+  min-height: 0;
+}
+
+.marketing-pagination {
+  flex: 0 0 auto;
+  width: 100%;
+  margin-top: 4px;
+  padding: 4px 0 2px;
+  border-top: 1px solid #f0f2f5;
+  background: #fff;
+  box-sizing: border-box;
+}
+
+.marketing-pagination ::v-deep .pagination-container {
+  width: 100%;
+  padding: 0 !important;
+  margin: 0 !important;
+  text-align: center;
+  background: transparent;
+}
+
+.marketing-pagination ::v-deep .el-pagination {
+  display: inline-flex;
+  flex-wrap: wrap;
+  justify-content: center;
+  align-items: center;
+  padding: 0;
+  font-weight: normal;
+  white-space: nowrap;
+}
+
+.marketing-pagination ::v-deep .el-pagination__total {
+  margin-right: 4px;
+  font-size: 12px;
+}
+
+.automation-panel .section-subtitle {
+  flex-shrink: 0;
+  margin-bottom: 4px;
+  font-size: 12px;
+}
+
+.ops-panel .timeline-item {
+  padding: 6px 0;
+}
+
+.ops-panel .timeline-time,
+.ops-panel .timeline-action {
+  font-size: 12px;
 }
 
 .marketing-cards {
-  display: grid;
-  grid-template-columns: repeat(2, 1fr);
-  gap: 8px;
-  padding-right: 4px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  padding: 2px 4px 10px 0;
 }
 
 .marketing-card {
+  width: 100px;
   border: 1px solid #ebeef5;
-  border-radius: 4px;
+  border-radius: 6px;
   overflow: hidden;
   cursor: pointer;
   background: #fff;
-  transition: border-color 0.2s;
+  transition: border-color 0.2s, box-shadow 0.2s;
+  display: flex;
+  flex-direction: column;
+  flex-shrink: 0;
 }
 
 .marketing-card:hover,
 .marketing-card.selected {
   border-color: #409eff;
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.12);
 }
 
 .card-image-wrap {
   position: relative;
-  height: 72px;
+  width: 100px;
+  height: 100px;
   overflow: hidden;
   background: #f5f7fa;
+  flex-shrink: 0;
 }
 
 .card-image {
+  display: block;
   width: 100%;
   height: 100%;
   object-fit: cover;
@@ -2164,68 +2479,96 @@ export default {
 
 .card-tags {
   position: absolute;
-  top: 6px;
-  left: 6px;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2;
   display: flex;
+  flex-wrap: wrap;
   gap: 4px;
+  padding: 6px 5px 5px;
+  background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0.15) 70%, transparent);
+  pointer-events: none;
+}
+
+.card-tags ::v-deep .el-tag {
+  height: auto;
+  min-height: 18px;
+  line-height: 1.2;
+  padding: 2px 6px;
+  font-size: 11px;
+  border: none;
+  white-space: nowrap;
 }
 
 .card-overlay {
   position: absolute;
   inset: 0;
+  z-index: 3;
   display: flex;
   flex-wrap: wrap;
   align-items: center;
   justify-content: center;
-  gap: 4px;
-  padding: 6px;
-  background: rgba(0, 0, 0, 0.5);
+  gap: 2px;
+  padding: 4px;
+  background: rgba(0, 0, 0, 0.55);
   opacity: 0;
   transition: opacity 0.2s;
 }
 
+.card-overlay .el-button {
+  padding: 4px 8px;
+  font-size: 11px;
+}
+
 .marketing-card:hover .card-overlay {
   opacity: 1;
 }
 
 .card-info {
-  padding: 6px 8px;
+  flex-shrink: 0;
+  width: 100px;
+  padding: 4px 4px 6px;
+  box-sizing: border-box;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 4px;
 }
 
 .card-title {
-  font-size: 12px;
+  flex: 1;
+  min-width: 0;
+  font-size: 11px;
   color: #303133;
+  line-height: 1.3;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
 
-.card-bottom {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-top: 4px;
-}
-
 .card-price {
-  font-size: 12px;
+  flex-shrink: 0;
+  font-size: 11px;
   font-weight: 600;
   color: #f56c6c;
+  line-height: 1.2;
 }
 
 .marketing-list {
   display: flex;
   flex-direction: column;
-  gap: 8px;
-  padding-right: 4px;
+  gap: 6px;
+  padding: 2px 0 4px;
 }
 
 .marketing-list-item {
   display: flex;
+  flex-direction: row;
   align-items: center;
   justify-content: space-between;
-  gap: 10px;
-  padding: 10px;
+  gap: 8px;
+  padding: 8px 10px;
   border: 1px solid #ebeef5;
   border-radius: 4px;
   background: #fff;
@@ -2245,20 +2588,22 @@ export default {
 }
 
 .list-title {
-  font-size: 13px;
+  font-size: 12px;
   color: #303133;
+  line-height: 1.4;
+  word-break: break-all;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
   overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
 }
 
 .list-meta {
   margin-top: 4px;
   font-size: 12px;
   color: #909399;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
+  line-height: 1.4;
+  word-break: break-all;
 }
 
 .list-price {
@@ -2282,10 +2627,9 @@ export default {
   justify-content: flex-end;
 }
 
-/* 右侧运营工具 */
-.live-player,
-.automation,
+/* 右侧氛围自动化 */
 .watermark {
+  flex-shrink: 0;
   margin: 0;
 }
 
@@ -2321,9 +2665,21 @@ export default {
   margin-top: 10px;
 }
 
-/* 滚动条 */
-::v-deep .el-scrollbar__wrap {
+/* 隐藏滚动条,保留滚动能力 */
+.console ::v-deep .el-scrollbar__bar {
+  display: none !important;
+}
+
+.console ::v-deep .el-scrollbar__wrap {
   overflow-x: hidden !important;
+  scrollbar-width: none;
+  -ms-overflow-style: none;
+}
+
+.console ::v-deep .el-scrollbar__wrap::-webkit-scrollbar {
+  width: 0;
+  height: 0;
+  display: none;
 }
 </style>
 

+ 12 - 5
src/views/live/liveConsole/LiveDashboard.vue

@@ -308,10 +308,12 @@ export default {
 <style scoped>
 /* 容器占满整个屏幕 */
 .dashboard-container {
-  width: 100vw;
-  height: 92vh;
+  width: 100%;
+  height: 100%;
+  min-height: 100%;
   overflow: hidden;
   background-color: #0f172a;
+  box-sizing: border-box;
 }
 
 /* 大屏主体:100%宽高,内边距用百分比 */
@@ -329,7 +331,8 @@ export default {
 /* 顶部数据卡片区:高度20%,横向排列 */
 .data-cards {
   width: 100%;
-  height: 20%;
+  flex: 0 0 18%;
+  min-height: 90px;
   display: flex;
   justify-content: space-between;
   gap: 1%; /* 卡片间间距 */
@@ -374,7 +377,8 @@ export default {
 /* 中间内容区:高度40%,左右分栏 */
 .middle-content {
   width: 100%;
-  height: 70%;
+  flex: 1;
+  min-height: 0;
   display: flex;
   gap: 1%;
 }
@@ -400,11 +404,14 @@ export default {
 /* 中间区域的子模块(新老用户/地域/来源/榜单):各占50%高度 */
 .new-old, .region, .source, .rank {
   width: 100%;
-  height: 60.5%; /* 100% - 1个间距(1%) = 99% → 99%/2 ≈ 49.5% */
+  flex: 1;
+  min-height: 0;
   background: #1a202c;
   border-radius: 8px;
   padding: 1%;
   box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
 }
 
 /* 标题样式:用vw单位适配 */

+ 12 - 6
src/views/live/liveConsole/LivePlayer.vue

@@ -159,15 +159,21 @@ export default {
 
 <style scoped>
 .live-player {
-  margin-bottom: 20px;
+  width: 100%;
+  height: 100%;
+  margin-bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #000;
+  border-radius: 4px;
 }
 
 .player {
   width: 100%;
-  height: auto;
-  border-radius: 8px;
-  max-height: 300px; /* 设置最大高度,避免过大 */
-  object-fit: contain; /* 保持比例,不拉伸 */
-  background-color: #000; /* 黑色背景,避免视频加载时显示白色 */
+  height: 100%;
+  border-radius: 4px;
+  object-fit: contain;
+  background-color: #000;
 }
 </style>

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

@@ -1,10 +1,12 @@
 <template>
-  <div id="app">
+  <div class="live-console-app">
     <div class="nav">
       <button @click="currentView = 'dashboard'">实时大屏</button>
       <button @click="currentView = 'console'">中控台</button>
     </div>
-    <component :is="currentView" :liveId="liveId"></component>
+    <div class="live-console-main">
+      <component :is="currentView" :liveId="liveId"></component>
+    </div>
   </div>
 </template>
 
@@ -34,10 +36,34 @@ export default {
 body {
   margin: 0;
   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 {
   display: flex;
+  flex-shrink: 0;
   background: #1e3a8a;
   color: white;
 }