Selaa lähdekoodia

直播间优化版本

yuhongqi 5 päivää sitten
vanhempi
commit
e40a98f3c5
41 muutettua tiedostoa jossa 2095 lisäystä ja 1073 poistoa
  1. 1 0
      src/assets/styles/index.scss
  2. 432 0
      src/assets/styles/live.scss
  3. 76 20
      src/store/index.js
  4. 55 11
      src/utils/liveWS.js
  5. 5 5
      src/views/live/components/productAfterSalesOrder.vue
  6. 11 11
      src/views/live/components/productOrder.vue
  7. 11 3
      src/views/live/gift/index.vue
  8. 360 272
      src/views/live/live/index.vue
  9. 12 4
      src/views/live/liveAfteraSales/index.vue
  10. 12 4
      src/views/live/liveAfteraSales/myIndex.vue
  11. 11 3
      src/views/live/liveAnchor/index.vue
  12. 12 2
      src/views/live/liveConfig/answer.vue
  13. 12 5
      src/views/live/liveConfig/barrage.vue
  14. 16 12
      src/views/live/liveConfig/goods.vue
  15. 10 3
      src/views/live/liveConfig/idCard.vue
  16. 99 89
      src/views/live/liveConfig/index.vue
  17. 16 8
      src/views/live/liveConfig/liveCoupon.vue
  18. 18 9
      src/views/live/liveConfig/liveLotteryConf.vue
  19. 17 9
      src/views/live/liveConfig/liveRedConf.vue
  20. 9 2
      src/views/live/liveConfig/liveReplay.vue
  21. 69 71
      src/views/live/liveConfig/preview.vue
  22. 17 7
      src/views/live/liveConfig/task.vue
  23. 10 1
      src/views/live/liveConfig/watchReward.vue
  24. 388 250
      src/views/live/liveConsole/index.vue
  25. 46 75
      src/views/live/liveData/index.vue
  26. 14 6
      src/views/live/liveGoods/index.vue
  27. 14 5
      src/views/live/liveLotteryConf/index.vue
  28. 14 5
      src/views/live/liveLotteryProductConf/index.vue
  29. 14 5
      src/views/live/liveMsg/index.vue
  30. 11 3
      src/views/live/liveOrder/index.vue
  31. 120 76
      src/views/live/liveOrder/liveOrderDetails.vue
  32. 11 3
      src/views/live/liveOrder/myIndex.vue
  33. 14 5
      src/views/live/liveOrderitems/index.vue
  34. 17 7
      src/views/live/liveProfit/index.vue
  35. 14 5
      src/views/live/liveQuestion/index.vue
  36. 12 4
      src/views/live/liveQuestionBank/index.vue
  37. 14 5
      src/views/live/liveRedConf/index.vue
  38. 10 4
      src/views/live/liveRewardRecord/index.vue
  39. 72 59
      src/views/live/liveVideo/index.vue
  40. 8 2
      src/views/live/liveWatchUser/index.vue
  41. 11 3
      src/views/live/words/index.vue

+ 1 - 0
src/assets/styles/index.scss

@@ -4,6 +4,7 @@
 @import './element-ui.scss';
 @import './sidebar.scss';
 @import './btn.scss';
+@import './live.scss';
 
 body {
   height: 100%;

+ 432 - 0
src/assets/styles/live.scss

@@ -0,0 +1,432 @@
+/* 直播模块统一视觉 —— 简洁、轻量、少彩色标签 */
+
+$live-border: #e8eaed;
+$live-text: #1f2329;
+$live-sub: #8a919f;
+$live-accent: #1a6d5b;
+$live-soft: #f4f6f8;
+$live-radius: 10px;
+
+.live-page {
+  --live-border: #{$live-border};
+  --live-text: #{$live-text};
+  --live-sub: #{$live-sub};
+  --live-accent: #{$live-accent};
+  --live-soft: #{$live-soft};
+}
+
+.live-page__header {
+  display: flex;
+  align-items: flex-end;
+  justify-content: space-between;
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.live-page__title {
+  margin: 0;
+  font-size: 22px;
+  font-weight: 600;
+  color: $live-text;
+  letter-spacing: 0.02em;
+}
+
+.live-page__desc {
+  margin: 4px 0 0;
+  font-size: 13px;
+  color: $live-sub;
+}
+
+.live-page__actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.live-panel {
+  background: #fff;
+  border: 1px solid $live-border;
+  border-radius: $live-radius;
+  padding: 14px 16px 4px;
+  margin-bottom: 14px;
+}
+
+.live-panel--table {
+  padding: 12px 12px 4px;
+}
+
+.live-search-form .el-form-item {
+  margin-bottom: 12px;
+}
+
+/* 未手工包 panel 的列表页:直接美化第一层表单/工具条/表格 */
+.live-page > .el-form,
+.live-page > form.el-form {
+  background: #fff;
+  border: 1px solid $live-border;
+  border-radius: $live-radius;
+  padding: 14px 16px 4px;
+  margin-bottom: 14px;
+}
+
+.live-page > .el-row.mb8,
+.live-page > .mb8,
+.live-page > .selection-toolbar {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 8px;
+  background: #fff;
+  border: 1px solid $live-border;
+  border-radius: $live-radius;
+  padding: 10px 12px;
+  margin-bottom: 12px !important;
+}
+
+.live-page > .el-row.mb8 .el-col {
+  width: auto !important;
+  float: none !important;
+}
+
+.live-page .el-table {
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+.live-page .el-table th,
+.live-table th {
+  background: $live-soft !important;
+  color: #5c6470;
+  font-weight: 500;
+}
+
+.live-page .el-table td,
+.live-table td {
+  padding: 10px 0;
+  color: $live-text;
+}
+
+.live-page .el-table--border,
+.live-page .el-table--border th,
+.live-page .el-table--border td {
+  border-color: #eef0f3;
+}
+
+.live-page .el-tag {
+  border-radius: 4px;
+  border: none;
+  font-weight: 500;
+}
+
+.live-cell {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  min-width: 0;
+}
+
+.live-cell__cover {
+  width: 56px;
+  height: 74px;
+  border-radius: 8px;
+  flex-shrink: 0;
+  background: $live-soft;
+  overflow: hidden;
+  object-fit: cover;
+}
+
+.live-cell__cover--square {
+  width: 48px;
+  height: 48px;
+}
+
+.live-cell__meta {
+  min-width: 0;
+  text-align: left;
+}
+
+.live-cell__name {
+  font-size: 14px;
+  font-weight: 600;
+  color: $live-text;
+  line-height: 1.35;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.live-cell__sub {
+  margin-top: 4px;
+  font-size: 12px;
+  color: $live-sub;
+}
+
+.live-status {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  color: $live-text;
+}
+
+.live-status__dot {
+  width: 7px;
+  height: 7px;
+  border-radius: 50%;
+  background: #c0c4cc;
+}
+
+.live-status.is-1 .live-status__dot { background: #e6a23c; }
+.live-status.is-2 .live-status__dot { background: #f56c6c; box-shadow: 0 0 0 3px rgba(245, 108, 108, 0.18); }
+.live-status.is-3 .live-status__dot { background: #909399; }
+.live-status.is-4 .live-status__dot { background: #409eff; }
+
+.live-muted { color: $live-sub; font-size: 13px; }
+.live-ok { color: $live-accent; font-size: 13px; }
+.live-warn { color: #c45656; font-size: 13px; }
+
+/* 配置壳 */
+.live-config-container {
+  padding: 12px 16px 20px;
+}
+
+.live-config-container .live-config-header {
+  border: 1px solid $live-border !important;
+  border-radius: $live-radius !important;
+  margin-bottom: 12px;
+  box-shadow: none !important;
+}
+
+.live-config-container .live-config-header .el-card__body {
+  padding: 0;
+}
+
+.live-config-container .detail-header {
+  padding: 16px 18px !important;
+  border-bottom: none !important;
+  align-items: center;
+}
+
+.live-config-container .header-left-area {
+  flex: 0 0 64px !important;
+}
+
+.live-config-container .header-left-area img {
+  width: 64px !important;
+  height: 84px !important;
+  border-radius: 8px !important;
+  object-fit: cover;
+}
+
+.live-config-container .live-info-title .title-text {
+  font-size: 18px;
+  font-weight: 600;
+  color: $live-text;
+}
+
+.live-config-container .live-info-desc,
+.live-config-container .info-state-text {
+  color: $live-sub !important;
+  font-size: 13px;
+}
+
+.live-config-container .live-config-left {
+  border: 1px solid $live-border !important;
+  border-radius: $live-radius !important;
+  box-shadow: none !important;
+}
+
+.live-config-container .live-config-left .el-card__body {
+  padding: 0;
+}
+
+.live-config-container .left-menu {
+  width: 180px !important;
+  background: #fafbfc;
+  border-right: 1px solid $live-border !important;
+  padding: 10px 8px;
+  min-height: 560px;
+}
+
+.live-config-container .left-menu .el-menu {
+  border-right: none !important;
+  background: transparent;
+}
+
+.live-config-container .el-menu-item {
+  height: 40px;
+  line-height: 40px;
+  margin: 2px 0 !important;
+  border-radius: 8px !important;
+  color: #5c6470;
+}
+
+.live-config-container .el-menu-item.is-active,
+.live-config-container .el-menu-item:hover {
+  background-color: rgba(26, 109, 91, 0.1) !important;
+  color: $live-accent !important;
+}
+
+.live-config-container .right-info {
+  padding: 16px 18px !important;
+  background: #fff;
+}
+
+/* 中控台 */
+.live-console {
+  padding: 10px 12px 16px !important;
+  background: $live-soft;
+  min-height: calc(100vh - 84px);
+  align-items: stretch !important;
+}
+
+.live-console .live-console-col {
+  background: #fff;
+  border: 1px solid $live-border;
+  border-radius: $live-radius;
+  overflow: hidden;
+}
+
+.live-console .msg-box,
+.live-console .msg-box-right {
+  border-radius: 10px !important;
+  line-height: 1.5;
+}
+
+.live-console .shuru {
+  padding: 12px 14px !important;
+  min-height: auto !important;
+  border-top: 1px solid $live-border !important;
+}
+
+.live-console .shuru .el-textarea__inner {
+  min-height: 72px !important;
+  border-radius: 8px;
+}
+
+.live-console .top-box img {
+  width: 36px !important;
+  height: 36px !important;
+}
+
+.live-console .top-box span {
+  margin-top: 6px !important;
+  color: #5c6470;
+}
+
+.live-console .row-box {
+  padding: 8px 4px;
+  border-radius: 8px;
+}
+
+.live-console .row-box:hover {
+  background: $live-soft;
+}
+
+/* 数据看板指标卡 */
+.live-page .stat-card,
+.live-data-page .el-card {
+  border: 1px solid $live-border !important;
+  border-radius: $live-radius !important;
+  box-shadow: none !important;
+}
+
+.live-stats-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+  gap: 10px;
+  margin-bottom: 14px;
+}
+
+.live-stats-grid .statistics-item {
+  text-align: center;
+  padding: 10px 8px;
+  background: $live-soft;
+  border-radius: 8px;
+  border: 1px solid $live-border;
+}
+
+.live-stats-grid .statistics-title {
+  font-size: 12px;
+  color: $live-sub;
+  margin-bottom: 6px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 2px;
+  line-height: 1.3;
+}
+
+.live-stats-grid .statistics-value {
+  font-size: 18px;
+  font-weight: 600;
+  color: $live-text;
+}
+
+.live-video-thumb {
+  position: relative;
+  width: 48px;
+  height: 48px;
+  margin: 0 auto;
+  cursor: pointer;
+}
+
+.live-video-thumb__empty {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: $live-sub;
+  background: $live-soft;
+}
+
+.live-video-thumb__play {
+  position: absolute;
+  right: 2px;
+  bottom: 2px;
+  font-size: 14px;
+  color: #fff;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
+}
+
+.live-video-dialog-player {
+  width: 100%;
+  max-height: 70vh;
+  background: #000;
+}
+
+/* 商品/媒体列表内过大图片收敛 */
+.live-page img[style*="100px"],
+.live-page .el-image[style*="100px"] {
+  max-width: 56px !important;
+  max-height: 56px !important;
+  border-radius: 8px;
+  object-fit: cover;
+}
+
+/* 订单详情通用 */
+.live-order-detail {
+  background: $live-soft;
+  min-height: 100%;
+  padding: 16px;
+}
+
+.live-order-detail .operate-container {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.live-order-detail .desct {
+  font-size: 15px;
+  font-weight: 600;
+  color: $live-text;
+  margin: 8px 0 12px;
+}
+
+.live-order-detail .el-card {
+  border: 1px solid $live-border !important;
+  border-radius: $live-radius !important;
+  box-shadow: none !important;
+}

+ 76 - 20
src/store/index.js

@@ -10,6 +10,15 @@ import {LiveWS} from "@/utils/liveWS";
 
 Vue.use(Vuex)
 
+function normalizeLiveId(liveId) {
+  return liveId == null ? '' : String(liveId)
+}
+
+function getLiveWs(state, liveId) {
+  const key = normalizeLiveId(liveId)
+  return state.liveWs[key] || state.liveWs[liveId] || null
+}
+
 const store = new Vuex.Store({
   modules: {
     app,
@@ -20,40 +29,87 @@ const store = new Vuex.Store({
   },
   getters,
   state: {
-    liveWs: {}
+    liveWs: {},
+    // { [liveId]: { console: true, config: true } }
+    liveWsOwners: {}
   },
   mutations: {
     // 更新为支持多个 WebSocket 连接
     setLiveWs(state, { ws, liveId }) {
-      // 使用 liveId 作为键来存储不同的 WebSocket 连接
-      Vue.set(state.liveWs, liveId, ws);
+      const key = normalizeLiveId(liveId)
+      Vue.set(state.liveWs, key, ws);
     },
     // 添加移除 WebSocket 连接的 mutation
     removeLiveWs(state, liveId) {
-      Vue.delete(state.liveWs, liveId);
+      const key = normalizeLiveId(liveId)
+      Vue.delete(state.liveWs, key);
+    },
+    addLiveWsOwner(state, { liveId, owner }) {
+      const key = normalizeLiveId(liveId)
+      if (!state.liveWsOwners[key]) {
+        Vue.set(state.liveWsOwners, key, {})
+      }
+      Vue.set(state.liveWsOwners[key], owner, true)
+    },
+    removeLiveWsOwner(state, { liveId, owner }) {
+      const key = normalizeLiveId(liveId)
+      if (!state.liveWsOwners[key]) return
+      Vue.delete(state.liveWsOwners[key], owner)
+      if (Object.keys(state.liveWsOwners[key]).length === 0) {
+        Vue.delete(state.liveWsOwners, key)
+      }
     }
   },
   actions: {
-    // 修改 action 以正确传递参数
-    initLiveWs({ commit, state }, { liveWsUrl, liveId, userId }) {
-      // 如果已经存在对应 liveId 的连接,先关闭它
-      if (state.liveWs[liveId]) {
-        state.liveWs[liveId].close();
+    /**
+     * 获取/复用直播间 WebSocket。同一 liveId 多页面共享一条连接。
+     * owner: 'console' | 'config' | 其它业务标识
+     */
+    acquireLiveWs({ commit, state }, { liveWsUrl, liveId, userId, owner }) {
+      const key = normalizeLiveId(liveId)
+      if (!key) {
+        console.error('[liveWs] acquireLiveWs: liveId is empty')
+        return null
+      }
+      let ws = getLiveWs(state, key)
+      const needCreate = !ws || ws.isManualClose
+      if (needCreate) {
+        ws = new LiveWS(liveWsUrl, key, userId)
+        commit('setLiveWs', { ws, liveId: key })
+      }
+      if (owner) {
+        commit('addLiveWsOwner', { liveId: key, owner })
+      }
+      return ws
+    },
+    /**
+     * 释放占用。当该 liveId 已无任何 owner 时真正关闭连接。
+     */
+    releaseLiveWs({ commit, state, dispatch }, { liveId, owner }) {
+      const key = normalizeLiveId(liveId)
+      if (!key) return
+      if (owner) {
+        commit('removeLiveWsOwner', { liveId: key, owner })
+      }
+      const owners = state.liveWsOwners[key]
+      if (!owners || Object.keys(owners).length === 0) {
+        dispatch('closeLiveWs', key)
       }
-
-      // 创建新的 WebSocket 连接
-      const ws = new LiveWS(liveWsUrl, liveId, userId);
-
-      // 提交到 mutation
-      commit('setLiveWs', { ws, liveId });
-
-      return ws;
+    },
+    // 兼容旧调用:不再强行重连;已有连接则复用
+    initLiveWs({ dispatch }, { liveWsUrl, liveId, userId, owner = 'legacy' }) {
+      return dispatch('acquireLiveWs', { liveWsUrl, liveId, userId, owner })
     },
     // 添加关闭特定 WebSocket 连接的 action
     closeLiveWs({ commit, state }, liveId) {
-      if (state.liveWs[liveId]) {
-        state.liveWs[liveId].close();
-        commit('removeLiveWs', liveId);
+      const key = normalizeLiveId(liveId)
+      const ws = getLiveWs(state, key)
+      if (ws) {
+        ws.close();
+        commit('removeLiveWs', key);
+      }
+      if (state.liveWsOwners[key]) {
+        Vue.delete(state.liveWsOwners, key)
       }
     }
   }

+ 55 - 11
src/utils/liveWS.js

@@ -1,10 +1,17 @@
 import CryptoJS from 'crypto-js'
 
+/** 从 Vuex 取直播间共享 WebSocket */
+export function getLiveSocket(store, liveId) {
+  if (!store || liveId == null) return null
+  const key = String(liveId)
+  return store.state.liveWs[key] || store.state.liveWs[liveId] || null
+}
+
 export class LiveWS {
   /**
    * @param {string} url - WebSocket 服务器地址
-   * @param {number} liveId - 直播间ID
-   * @param {number} userId - 用户ID
+   * @param {number|string} liveId - 直播间ID
+   * @param {number|string} userId - 用户ID
    * @param {number} checkInterval - 检查连接状态的时间间隔,单位毫秒
    * @param {number} reconnectDelay - 连接断开后重连的延迟,单位毫秒
    */
@@ -15,7 +22,6 @@ export class LiveWS {
       CryptoJS.enc.Utf8.parse('' + liveId + userId + userType + timestamp),
       CryptoJS.enc.Utf8.parse(timestamp)).toString(CryptoJS.enc.Hex)
     this.url = url + `?liveId=${liveId}&userId=${userId}&userType=${userType}&timestamp=${timestamp}&signature=${signature}`;
-    console.log(this.url)
     this.liveId = liveId;
     this.userId = userId;
     this.checkInterval = checkInterval;
@@ -24,6 +30,7 @@ export class LiveWS {
     this.reconnectTimer = null;
     this.heartbeatTimer = null;
     this.isManualClose = false;
+    this._messageHandlers = new Set();
     this.connect();
     this.startHeartbeat();
   }
@@ -37,8 +44,7 @@ export class LiveWS {
     this.ws = new WebSocket(this.url);
 
     // 绑定事件
-    this.ws.onopen = (event) => {
-      // 连接成功后,清除重连计时器
+    this.ws.onopen = () => {
       if (this.reconnectTimer) {
         clearTimeout(this.reconnectTimer);
         this.reconnectTimer = null;
@@ -46,13 +52,12 @@ export class LiveWS {
     };
 
     this.ws.onmessage = (event) => {
-      this.onmessage(event);
+      this._dispatchMessage(event);
     };
 
-    this.ws.onerror = (error) => {
-    };
+    this.ws.onerror = () => {};
 
-    this.ws.onclose = (event) => {
+    this.ws.onclose = () => {
       // 如果不是主动关闭,则重连
       if (!this.isManualClose) {
         setTimeout(() => this.reconnect(), this.reconnectDelay);
@@ -60,8 +65,38 @@ export class LiveWS {
     };
   }
 
+  /** @deprecated 兼容旧写法,请优先使用 addMessageListener */
   onmessage(event) {}
 
+  _dispatchMessage(event) {
+    if (this._messageHandlers.size > 0) {
+      this._messageHandlers.forEach((handler) => {
+        try {
+          handler(event);
+        } catch (e) {
+          console.error('[LiveWS] message handler error', e);
+        }
+      });
+      return;
+    }
+    // 兼容:外部直接赋值 socket.onmessage = fn
+    if (typeof this.onmessage === 'function') {
+      this.onmessage(event);
+    }
+  }
+
+  addMessageListener(handler) {
+    if (typeof handler === 'function') {
+      this._messageHandlers.add(handler);
+    }
+  }
+
+  removeMessageListener(handler) {
+    if (handler) {
+      this._messageHandlers.delete(handler);
+    }
+  }
+
   reconnect() {
     this.connect();
   }
@@ -90,23 +125,32 @@ export class LiveWS {
   // 主动关闭 WebSocket 连接,并清除定时任务
   close() {
     this.isManualClose = true;
+    this._messageHandlers.clear();
     if (this.heartbeatTimer) {
       clearInterval(this.heartbeatTimer);
+      this.heartbeatTimer = null;
     }
     if (this.reconnectTimer) {
       clearTimeout(this.reconnectTimer);
+      this.reconnectTimer = null;
     }
     if (this.ws) {
       this.ws.close();
+      this.ws = null;
     }
   }
 
+  get readyState() {
+    return this.ws ? this.ws.readyState : WebSocket.CLOSED;
+  }
+
   // 发送消息方法
   send(message) {
     if (this.ws && this.ws.readyState === WebSocket.OPEN) {
       this.ws.send(message);
-    } else {
-      console.error("WebSocket is not open. Message not sent.");
+      return true;
     }
+    console.error("WebSocket is not open. Message not sent.");
+    return false;
   }
 }

+ 5 - 5
src/views/live/components/productAfterSalesOrder.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="order-content">
+  <div class="order-content live-page">
       <div class="order-status" v-if="afterSales!=null" >
           <el-steps :active="afterSales.status==4?afterSales.status+1:afterSales.status" align-center>
             <el-step title="待审核(24小时自动审核)"></el-step>
@@ -13,7 +13,7 @@
       <el-card shadow="never"  style="margin-top: 15px">
         <div class="operate-container">
           <span style="margin-left: 20px" class="color-danger">售后状态:
-            <el-tag prop="status" v-for="(item, index) in salesStatusOptions"    v-if="afterSales.salesStatus==item.dictValue">{{item.dictLabel}}</el-tag>
+            <span class="live-muted" prop="status" v-for="(item, index) in salesStatusOptions"    v-if="afterSales.salesStatus==item.dictValue">{{item.dictLabel}}</span>
           </span>
           <div class="operate-button-container"  >
             <el-button size="mini" v-hasPermi="['store:storeAfterSales:edit']" v-show="afterSales.salesStatus==0&&afterSales.status===1"  @click="addDelivery">编辑物流</el-button>
@@ -41,7 +41,7 @@
             </el-descriptions-item>
             <el-descriptions-item label="申请类型"  >
                 <span v-if="afterSales!=null">
-                  <el-tag prop="serviceType" v-for="(item, index) in serviceTypeOptions"    v-if="afterSales.serviceType==item.dictValue">{{item.dictLabel}}</el-tag>
+                  <span class="live-muted" prop="serviceType" v-for="(item, index) in serviceTypeOptions"    v-if="afterSales.serviceType==item.dictValue">{{item.dictLabel}}</span>
                 </span>
             </el-descriptions-item>
             <el-descriptions-item label="申请原因"  >
@@ -56,12 +56,12 @@
             </el-descriptions-item>
             <el-descriptions-item label="状态"  >
                 <span v-if="afterSales!=null">
-                  <el-tag   v-for="(item, index) in statusOptions"    v-if="afterSales.status==item.dictValue" >{{item.dictLabel}}</el-tag>
+                  <span class="live-muted"   v-for="(item, index) in statusOptions"    v-if="afterSales.status==item.dictValue" >{{item.dictLabel}}</span>
                 </span>
             </el-descriptions-item>
             <el-descriptions-item label="售后状态"  >
                 <span v-if="afterSales!=null">
-                  <el-tag  v-for="(item, index) in salesStatusOptions"    v-if="afterSales.salesStatus==item.dictValue" >{{item.dictLabel}}</el-tag>
+                  <span class="live-muted"  v-for="(item, index) in salesStatusOptions"    v-if="afterSales.salesStatus==item.dictValue" >{{item.dictLabel}}</span>
                 </span>
             </el-descriptions-item>
         </el-descriptions>

+ 11 - 11
src/views/live/components/productOrder.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="order-content">
+  <div class="order-content live-page">
       <div class="order-status" v-if="order!=null" >
           <el-steps  :active="order.status==3?order.status+1:order.status" align-center>
             <el-step title="待支付"></el-step>
@@ -13,7 +13,7 @@
       <el-card shadow="never" style="margin-top: 15px">
       <div class="operate-container"  v-if="order!=null">
         <span  style="margin-left: 20px" class="color-danger">订单状态:
-           <el-tag prop="status" v-for="(item, index) in statusOptions"    v-if="order.status==item.dictValue">{{item.dictLabel}}</el-tag>
+           <span class="live-muted" prop="status" v-for="(item, index) in statusOptions"    v-if="order.status==item.dictValue">{{item.dictLabel}}</span>
         </span>
 
         <div class="operate-button-container" >
@@ -38,8 +38,8 @@
                 <span v-if="order!=null">
                   {{order.orderCode}}
                 </span>
-                <el-tag  v-for="(item, index) in createTypeOptions"    v-if="order!=null&&order.orderCreateType==item.dictValue">{{item.dictLabel}}
-                </el-tag>
+                <span class="live-muted"  v-for="(item, index) in createTypeOptions"    v-if="order!=null&&order.orderCreateType==item.dictValue">{{item.dictLabel}}
+                </span>
             </el-descriptions-item>
             <el-descriptions-item label="会员"  >
                 <span v-if="user!=null">
@@ -84,12 +84,12 @@
             </el-descriptions-item>
             <el-descriptions-item label="支付方式"  >
                 <span v-if="order!=null ">
-                  <el-tag prop="orderType" v-for="(item, index) in payTypeOptions"    v-if="order.payType==item.dictValue">{{item.dictLabel}}</el-tag>
+                  <span class="live-muted" prop="orderType" v-for="(item, index) in payTypeOptions"    v-if="order.payType==item.dictValue">{{item.dictLabel}}</span>
                 </span>
             </el-descriptions-item>
             <el-descriptions-item label="订单类型"  >
                 <span v-if="order!=null ">
-                  <el-tag prop="orderType" v-for="(item, index) in orderTypeOptions"    v-if="order.orderType==item.dictValue">{{item.dictLabel}}</el-tag>
+                  <span class="live-muted" prop="orderType" v-for="(item, index) in orderTypeOptions"    v-if="order.orderType==item.dictValue">{{item.dictLabel}}</span>
                 </span>
             </el-descriptions-item>
 <!--            <el-descriptions-item label="物流公司编号"  >-->
@@ -108,8 +108,8 @@
 <!--                </span>-->
 <!--            </el-descriptions-item>-->
             <el-descriptions-item label="档期归属"  >
-              <el-tag prop="scheduleId" v-for="(item, index) in scheduleOptions"    v-if="order!=null&&order.scheduleId==item.id">{{item.name}}
-              </el-tag>
+              <span class="live-muted" prop="scheduleId" v-for="(item, index) in scheduleOptions"    v-if="order!=null&&order.scheduleId==item.id">{{item.name}}
+              </span>
             </el-descriptions-item>
             <el-descriptions-item label="用户备注"  >
                 <span v-if="order!=null ">
@@ -123,7 +123,7 @@
             </el-descriptions-item>
             <el-descriptions-item label="跟随阶段"  v-if="company.companyId == 174">
               <span v-if="order!=null ">
-                <el-tag prop="orderVisit" v-for="(item, index) in customerUserStatusOptions"    v-if="order.orderVisit==item.dictValue">{{item.dictLabel}}</el-tag>
+                <span class="live-muted" prop="orderVisit" v-for="(item, index) in customerUserStatusOptions"    v-if="order.orderVisit==item.dictValue">{{item.dictLabel}}</span>
               </span>
             </el-descriptions-item>
             <el-descriptions-item v-if="customerInfo!=null " label="客户编码"  >
@@ -166,9 +166,9 @@
           <el-table-column label="物流状态" width="300" align="center">
             <template slot-scope="scope">
               <span>
-              <el-tag v-for="(item, index) in deliveryStatusOptions"    v-if="scope.row!=null&&scope.row.status==item.dictValue">
+              <span class="live-muted" v-for="(item, index) in deliveryStatusOptions"    v-if="scope.row!=null&&scope.row.status==item.dictValue">
               {{item.dictLabel}}
-              </el-tag>
+              </span>
               </span>
             </template>
           </el-table-column>

+ 11 - 3
src/views/live/gift/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播礼物</h2>
+    </div>
+
+    <div class="live-panel" v-show="showSearch">
+      <el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="礼物名称" prop="giftName">
         <el-input
           v-model="queryParams.giftName"
@@ -25,8 +30,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -108,6 +115,7 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
+    </div>
 
     <!-- 添加或修改直播间礼物配置对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>

+ 360 - 272
src/views/live/live/index.vue

@@ -1,267 +1,217 @@
 <template>
-  <div class="app-container">
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <div class="live-page__title-wrap">
+        <h2 class="live-page__title">直播间</h2>
+        <p class="live-page__desc">管理本司直播、录播与回放场次</p>
+      </div>
+      <div class="live-page__actions">
         <el-button
           type="primary"
-          plain
           icon="el-icon-plus"
-          size="mini"
+          size="small"
           @click="handleAdd"
           v-hasPermi="['live:live:add']"
-        >新增</el-button>
-      </el-col>
-
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="直播名称" prop="liveName">
-        <el-input
-          v-model="queryParams.liveName"
-          placeholder="请输入直播名称"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="直播状态" prop="liveStatus">
-        <el-select
-          v-model="queryParams.status"
-          placeholder="请选择直播状态"
-          clearable
-          size="small"
-        >
-          <el-option label="待直播" value="1"></el-option>
-          <el-option label="直播中" value="2"></el-option>
-          <el-option label="已结束" value="3"></el-option>
-          <el-option label="直播回放中" value="4"></el-option>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="公司名称" prop="companyName">
-        <el-input
-          v-model="queryParams.companyName"
-          placeholder="请输入公司名称"
-          clearable
-          size="small"
-        />
-      </el-form-item>
-      <el-form-item label="直播类型" prop="liveType">
-        <el-select
-          v-model="queryParams.liveType"
-          placeholder="请选择直播类型"
-          clearable
-          size="small"
-        >
-          <el-option label="直播" value="1"></el-option>
-          <el-option label="录播" value="2"></el-option>
-          <el-option label="直播回放" value="3"></el-option>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="开始时间" prop="startTimeS">
-        <el-date-picker
-          v-model="queryParams.startTimeS"
-          type="datetime"
-          placeholder="选择起始时间"
-          value-format="yyyy-MM-dd HH:mm:ss"
-          size="small"
-        ></el-date-picker>
-      </el-form-item>
-      <!-- 开始时间-范围结束 -->
-      <el-form-item label="结束时间" prop="endTimeE">
-        <el-date-picker
-          v-model="queryParams.endTimeE"
-          type="datetime"
-          placeholder="选择结束时间"
-          value-format="yyyy-MM-dd HH:mm:ss"
-          size="small"
-          :disabled="!queryParams.startTimeS"
-        ></el-date-picker>
-      </el-form-item>
-      <el-form-item label="上下架" prop="isShow">
-        <el-select
-          v-model="queryParams.isShow"
-          placeholder="请选择上下架状态"
-          clearable
-          size="small"
-        >
-          <el-option label="上架" value="1"></el-option>
-          <el-option label="下架" value="2"></el-option>
-        </el-select>
-      </el-form-item>
-      <!-- 审核状态 -->
-      <el-form-item label="审核状态" prop="isAudit">
-        <el-select
-          v-model="queryParams.isAudit"
-          placeholder="请选择审核状态"
-          clearable
-          size="small"
-        >
-          <el-option label="审核未通过" value="0"></el-option>
-          <el-option label="审核通过" value="1"></el-option>
-        </el-select>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-        <el-button icon="el-icon-download" size="mini" v-hasPermi="['live:live:export']" @click="handleExport">导出</el-button>
-      </el-form-item>
-    </el-form>
-    <div class="selection-toolbar">
-      <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
-        {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
-      </el-checkbox>
-      <el-button  plain size="mini" @click="handleShelf" v-hasPermi="['live:live:edit']">上架</el-button>
-      <el-button  plain size="mini" @click="handleUnshelf" v-hasPermi="['live:live:edit']">下架</el-button>
-      <el-button  plain size="mini" @click="handleDeleteSelected" v-hasPermi="['live:live:remove']">删除</el-button>
-<!--      <el-dropdown>-->
-<!--        <el-button plain size="mini">-->
-<!--          更多操作<i class="el-icon-arrow-down el-icon&#45;&#45;right"></i>-->
-<!--        </el-button>-->
-<!--        <el-dropdown-menu slot="dropdown">-->
-<!--          <el-dropdown-item>操作一</el-dropdown-item>-->
-<!--          <el-dropdown-item>操作二</el-dropdown-item>-->
-<!--        </el-dropdown-menu>-->
-<!--      </el-dropdown>-->
+        >新建直播间</el-button>
+        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+      </div>
     </div>
-    <el-table ref="liveTable" v-loading="loading" :data="liveList"  @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55"></el-table-column>
-      <el-table-column label="直播ID" align="center" prop="liveId" />
-      <el-table-column label="直播封面" align="center" prop="liveImgUrl" width="180">
-        <template slot-scope="scope">
-          <el-image style="width: 90px;height: 90px;" :src="scope.row.liveImgUrl" mode="aspectFill" :preview-src-list="[scope.row.liveImgUrl]" />
-        </template>
-      </el-table-column>
-      <el-table-column label="直播名称" align="center" prop="liveName" />
-      <el-table-column label="显示类型" align="center" prop="showType">
-        <template slot-scope="scope">
-          <el-tag v-if="scope.row.showType == 1">横屏</el-tag>
-          <el-tag v-if="scope.row.showType == 2">竖屏</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="直播状态" align="center" prop="status">
-        <template slot-scope="scope">
-          <el-tag type="warning" v-if="scope.row.status == 1">待直播</el-tag>
-          <el-tag style="color:#3491FA;background-color:#E8F7FF;border-color: #E8F7FF;" v-if="scope.row.status == 2">直播中</el-tag>
-          <el-tag type="info" v-if="scope.row.status == 3">已结束</el-tag>
-          <el-tag style="color:#9B6DDA;background-color:#F5E8FF;border-color: #F5E8FF;" v-if="scope.row.status == 4">直播回放中</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="公司名称" align="center" prop="companyName" >
-        <template slot-scope="scope">
-          <el-tag v-if="scope.row.companyName">{{ scope.row.companyName }}</el-tag>
-          <el-tag v-else>总台</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="直播类型" align="center" prop="liveType">
-        <template slot-scope="scope">
-          <el-tag type="danger" v-if="scope.row.liveType == 1">直播</el-tag>
-          <el-tag type="success" v-if="scope.row.liveType == 2">录播</el-tag>
-          <el-tag v-if="scope.row.liveType == 3">直播回放</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="开始时间" align="center" prop="startTime" width="180" />
-      <el-table-column label="结束时间" align="center" prop="finishTime" width="180" />
-      <el-table-column label="上下架" align="center" prop="isShow">
-        <template slot-scope="scope">
-         <el-tag v-if="scope.row.isShow == 1">上架</el-tag>
-         <el-tag type="info" v-if="scope.row.isShow == 2">下架</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="审核状态" align="center" prop="isAudit">
-        <template slot-scope="scope">
-          <el-tag type="danger" v-if="scope.row.isAudit == 0">审核未通过</el-tag>
-          <el-tag v-if="scope.row.isAudit == 1">审核通过</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-button
-            v-if="scope.row.companyName != '总台' && scope.row.status != 2"
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['live:live:edit']"
-          >修改</el-button>
-          <el-button
-            v-if="scope.row.companyName == '总台'"
-            size="mini"
-            type="text"
-            style="color: #00CC66"
-            icon="el-icon-edit"
-            @click="handleView(scope.row)"
-            v-hasPermi="['live:live:edit']"
-          >查看</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-setting"
-            @click="handleConfig(scope.row)"
-            v-hasPermi="['live:config:list']"
-          >配置</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-service"
-            @click="handleManage(scope.row)"
-            v-hasPermi="['live:console:list']"
-          >进入直播间</el-button>
-          <el-dropdown trigger="hover">
-            <el-button size="mini" type="text" icon="el-icon-more">
-              更多
-            </el-button>
-            <el-dropdown-menu slot="dropdown">
-              <!-- 结束按钮 -->
-              <el-dropdown-item
-                v-if="scope.row.status == 2 && scope.row.liveType == 1"
-                @click.native="showLivingUrl(scope.row)"
-                v-hasPermi="['live:live:query']"
-              >
-                <i class="el-icon-switch-button"></i> 推流码
-              </el-dropdown-item>
-
-              <!-- 去直播按钮 -->
-              <el-dropdown-item
-                v-if="scope.row.status != 2"
-                @click.native="handleStart(scope.row)"
-                v-hasPermi="['live:live:edit']"
-              >
-                <i class="el-icon-monitor"></i> 去直播
-              </el-dropdown-item>
-
-              <!-- 进入直播间按钮 -->
-              <el-dropdown-item
-                v-if="scope.row.status == 2"
-                @click.native="handleEnded(scope.row)"
-                v-hasPermi="['live:live:edit']"
-              >
-                <i class="el-icon-service"></i> 结束
-              </el-dropdown-item>
-              <el-dropdown-item
-                @click.native="handleCopy(scope.row)"
-                v-hasPermi="['live:live:edit']"
-              >
-                <i class="el-icon-service"></i> 复制直播间
-              </el-dropdown-item>
-              <el-dropdown-item
-                v-if="scope.row.liveCodeUrl == null"
-                @click.native="handleGenerateCode(scope.row)"
-                v-hasPermi="['live:live:edit']"
-              >
-                <i class="el-icon-service"></i> 生成二维码
-              </el-dropdown-item>
-              <el-dropdown-item
-                v-if="scope.row.liveCodeUrl != null"
-                @click.native="handleCheckCode(scope.row)"
-              >
-                <i class="el-icon-service"></i> 查看二维码
-              </el-dropdown-item>
-            </el-dropdown-menu>
-          </el-dropdown>
 
-        </template>
-      </el-table-column>
-    </el-table>
+    <div class="live-panel" v-show="showSearch">
+      <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="72px" class="live-search-form">
+        <el-form-item label="直播名称" prop="liveName">
+          <el-input
+            v-model="queryParams.liveName"
+            placeholder="搜索名称"
+            clearable
+            size="small"
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="状态" prop="liveStatus">
+          <el-select v-model="queryParams.status" placeholder="全部状态" clearable size="small">
+            <el-option label="待直播" value="1"></el-option>
+            <el-option label="直播中" value="2"></el-option>
+            <el-option label="已结束" value="3"></el-option>
+            <el-option label="直播回放中" value="4"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="公司" prop="companyName">
+          <el-input v-model="queryParams.companyName" placeholder="公司名称" clearable size="small" />
+        </el-form-item>
+        <el-form-item label="类型" prop="liveType">
+          <el-select v-model="queryParams.liveType" placeholder="全部类型" clearable size="small">
+            <el-option label="直播" value="1"></el-option>
+            <el-option label="录播" value="2"></el-option>
+            <el-option label="直播回放" value="3"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="开始时间" prop="startTimeS">
+          <el-date-picker
+            v-model="queryParams.startTimeS"
+            type="datetime"
+            placeholder="起始时间"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            size="small"
+          />
+        </el-form-item>
+        <el-form-item label="结束时间" prop="endTimeE">
+          <el-date-picker
+            v-model="queryParams.endTimeE"
+            type="datetime"
+            placeholder="结束时间"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            size="small"
+            :disabled="!queryParams.startTimeS"
+          />
+        </el-form-item>
+        <el-form-item label="上下架" prop="isShow">
+          <el-select v-model="queryParams.isShow" placeholder="全部" clearable size="small">
+            <el-option label="上架" value="1"></el-option>
+            <el-option label="下架" value="2"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="审核" prop="isAudit">
+          <el-select v-model="queryParams.isAudit" placeholder="全部" clearable size="small">
+            <el-option label="审核未通过" value="0"></el-option>
+            <el-option label="审核通过" value="1"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item class="live-search-form__ops">
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+          <el-button icon="el-icon-download" size="mini" v-hasPermi="['live:live:export']" @click="handleExport">导出</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <div class="live-panel live-panel--table">
+      <div class="selection-toolbar">
+        <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
+          {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
+        </el-checkbox>
+        <div class="selection-toolbar__btns">
+          <el-button plain size="mini" @click="handleShelf" v-hasPermi="['live:live:edit']">上架</el-button>
+          <el-button plain size="mini" @click="handleUnshelf" v-hasPermi="['live:live:edit']">下架</el-button>
+          <el-button plain size="mini" @click="handleDeleteSelected" v-hasPermi="['live:live:remove']">删除</el-button>
+        </div>
+      </div>
+      <el-table
+        ref="liveTable"
+        v-loading="loading"
+        :data="liveList"
+        class="live-table"
+        @selection-change="handleSelectionChange"
+      >
+        <el-table-column type="selection" width="48" align="center" />
+        <el-table-column label="直播间" min-width="260">
+          <template slot-scope="scope">
+            <div class="live-cell">
+              <el-image
+                class="live-cell__cover"
+                :src="scope.row.liveImgUrl"
+                fit="cover"
+                :preview-src-list="scope.row.liveImgUrl ? [scope.row.liveImgUrl] : []"
+              >
+                <div slot="error" class="live-cell__cover-empty">无封面</div>
+              </el-image>
+              <div class="live-cell__meta">
+                <div class="live-cell__name" :title="scope.row.liveName">{{ scope.row.liveName }}</div>
+                <div class="live-cell__sub">ID {{ scope.row.liveId }} · {{ scope.row.companyName || '总台' }}</div>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" align="center" width="110">
+          <template slot-scope="scope">
+            <span class="live-status" :class="'is-' + scope.row.status">
+              <i class="live-status__dot"></i>
+              <template v-if="scope.row.status == 1">待直播</template>
+              <template v-else-if="scope.row.status == 2">直播中</template>
+              <template v-else-if="scope.row.status == 3">已结束</template>
+              <template v-else-if="scope.row.status == 4">回放中</template>
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="类型" align="center" width="90">
+          <template slot-scope="scope">
+            <span class="live-muted">
+              <template v-if="scope.row.liveType == 1">直播</template>
+              <template v-else-if="scope.row.liveType == 2">录播</template>
+              <template v-else-if="scope.row.liveType == 3">回放</template>
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="画面" align="center" width="80">
+          <template slot-scope="scope">
+            <span class="live-muted">{{ scope.row.showType == 2 ? '竖屏' : '横屏' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="开始时间" align="center" prop="startTime" width="168" />
+        <el-table-column label="结束时间" align="center" prop="finishTime" width="168" />
+        <el-table-column label="上架" align="center" width="80">
+          <template slot-scope="scope">
+            <span :class="scope.row.isShow == 1 ? 'live-ok' : 'live-muted'">{{ scope.row.isShow == 1 ? '上架' : '下架' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核" align="center" width="96">
+          <template slot-scope="scope">
+            <span :class="scope.row.isAudit == 1 ? 'live-ok' : 'live-warn'">{{ scope.row.isAudit == 1 ? '已通过' : '未通过' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="left" width="230" fixed="right" class-name="small-padding fixed-width">
+          <template slot-scope="scope">
+            <el-button
+              v-if="scope.row.companyName != '总台' && scope.row.status != 2"
+              size="mini"
+              type="text"
+              @click="handleUpdate(scope.row)"
+              v-hasPermi="['live:live:edit']"
+            >修改</el-button>
+            <el-button
+              v-if="scope.row.companyName == '总台'"
+              size="mini"
+              type="text"
+              class="live-view-btn"
+              @click="handleView(scope.row)"
+              v-hasPermi="['live:live:edit']"
+            >查看</el-button>
+            <el-button size="mini" type="text" @click="handleConfig(scope.row)" v-hasPermi="['live:config:list']">配置</el-button>
+            <el-button size="mini" type="text" @click="handleManage(scope.row)" v-hasPermi="['live:console:list']">进入</el-button>
+            <el-dropdown trigger="hover">
+              <el-button size="mini" type="text">更多<i class="el-icon-arrow-down el-icon--right"></i></el-button>
+              <el-dropdown-menu slot="dropdown">
+                <el-dropdown-item
+                  v-if="scope.row.status == 2 && scope.row.liveType == 1"
+                  @click.native="showLivingUrl(scope.row)"
+                  v-hasPermi="['live:live:query']"
+                >推流码</el-dropdown-item>
+                <el-dropdown-item
+                  v-if="scope.row.status != 2"
+                  @click.native="handleStart(scope.row)"
+                  v-hasPermi="['live:live:edit']"
+                >去直播</el-dropdown-item>
+                <el-dropdown-item
+                  v-if="scope.row.status == 2"
+                  @click.native="handleEnded(scope.row)"
+                  v-hasPermi="['live:live:edit']"
+                >结束</el-dropdown-item>
+                <el-dropdown-item @click.native="handleCopy(scope.row)" v-hasPermi="['live:live:edit']">复制直播间</el-dropdown-item>
+                <el-dropdown-item
+                  v-if="scope.row.liveCodeUrl == null"
+                  @click.native="handleGenerateCode(scope.row)"
+                  v-hasPermi="['live:live:edit']"
+                >生成二维码</el-dropdown-item>
+                <el-dropdown-item
+                  v-if="scope.row.liveCodeUrl != null"
+                  @click.native="handleCheckCode(scope.row)"
+                >查看二维码</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
 
     <el-dialog
       title="直播二维码"
@@ -951,44 +901,182 @@ export default {
 </script>
 
 <style scoped>
+.live-page {
+  --live-border: #e8eaed;
+  --live-text: #1f2329;
+  --live-sub: #8a919f;
+  --live-accent: #1a6d5b;
+  --live-soft: #f4f6f8;
+}
+
+.live-page__header {
+  display: flex;
+  align-items: flex-end;
+  justify-content: space-between;
+  gap: 16px;
+  margin-bottom: 16px;
+}
+
+.live-page__title {
+  margin: 0;
+  font-size: 22px;
+  font-weight: 600;
+  color: var(--live-text);
+  letter-spacing: 0.02em;
+}
+
+.live-page__desc {
+  margin: 4px 0 0;
+  font-size: 13px;
+  color: var(--live-sub);
+}
+
+.live-page__actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.live-panel {
+  background: #fff;
+  border: 1px solid var(--live-border);
+  border-radius: 10px;
+  padding: 14px 16px 4px;
+  margin-bottom: 14px;
+}
+
+.live-panel--table {
+  padding: 12px 12px 4px;
+}
+
+.live-search-form .el-form-item {
+  margin-bottom: 12px;
+}
+
+.live-search-form__ops {
+  margin-left: 4px;
+}
+
 .selection-toolbar {
   display: flex;
   align-items: center;
+  justify-content: space-between;
+  gap: 12px;
   margin-bottom: 10px;
-  padding-left: 10px;
+  padding: 2px 4px 8px;
+  border-bottom: 1px solid var(--live-soft);
+}
+
+.selection-toolbar__btns {
+  display: flex;
+  gap: 8px;
+}
+
+.live-table ::v-deep .el-table__header th {
+  background: var(--live-soft);
+  color: #5c6470;
+  font-weight: 500;
+}
+
+.live-table ::v-deep .el-table__row td {
+  padding: 12px 0;
+}
+
+.live-cell {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  min-width: 0;
 }
 
-.selection-toolbar .el-checkbox {
-  margin-right: 10px;
+.live-cell__cover {
+  width: 56px;
+  height: 74px;
+  border-radius: 8px;
+  flex-shrink: 0;
+  background: var(--live-soft);
+  overflow: hidden;
 }
 
-/* 调整 checkbox 内部输入框的对齐 */
-.selection-toolbar .el-checkbox .el-checkbox__inner {
-  top: 8px; /* 根据实际需求调整 */
+.live-cell__cover-empty {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 12px;
+  color: var(--live-sub);
+  background: var(--live-soft);
 }
-/* 图片容器:居中 + 内边距 */
+
+.live-cell__meta {
+  min-width: 0;
+  text-align: left;
+}
+
+.live-cell__name {
+  font-size: 14px;
+  font-weight: 600;
+  color: var(--live-text);
+  line-height: 1.35;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.live-cell__sub {
+  margin-top: 4px;
+  font-size: 12px;
+  color: var(--live-sub);
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.live-status {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  color: var(--live-text);
+}
+
+.live-status__dot {
+  width: 7px;
+  height: 7px;
+  border-radius: 50%;
+  background: #c0c4cc;
+}
+
+.live-status.is-1 .live-status__dot { background: #e6a23c; }
+.live-status.is-2 .live-status__dot { background: #f56c6c; box-shadow: 0 0 0 3px rgba(245, 108, 108, 0.18); }
+.live-status.is-3 .live-status__dot { background: #909399; }
+.live-status.is-4 .live-status__dot { background: #409eff; }
+
+.live-muted { color: var(--live-sub); font-size: 13px; }
+.live-ok { color: var(--live-accent); font-size: 13px; }
+.live-warn { color: #c45656; font-size: 13px; }
+.live-view-btn { color: #1a6d5b !important; }
+
 .qrcode-img-container {
   text-align: center;
   padding: 20px 0;
 }
 
-/* 图片样式:自适应弹窗宽度,避免溢出 */
 .qrcode-img {
-  max-width: 100%;    /* 最大宽度不超过容器 */
-  max-height: 400px;  /* 最大高度限制,避免过高 */
-  cursor: pointer;    /* 鼠标悬浮变指针,提示可点击 */
-  transition: all 0.3s ease;  /* 过渡动画,hover 更流畅 */
+  max-width: 100%;
+  max-height: 400px;
+  cursor: pointer;
+  transition: all 0.3s ease;
 }
 
-/* 图片 hover 效果:轻微放大 */
 .qrcode-img:hover {
   transform: scale(1.02);
-  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);  /* 加阴影增强层次感 */
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
 }
 
-/* 底部提示文字样式 */
 .qrcode-footer {
-  text-align: center !important;  /* 覆盖 Element 默认样式,居中显示 */
+  text-align: center !important;
 }
 
 .text-gray {

+ 12 - 4
src/views/live/liveAfteraSales/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播售后</h2>
+    </div>
+
+    <div class="live-panel" v-show="showSearch">
+      <el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
 
       <el-form-item label="所属部门" prop="deptId">
         <treeselect style="width:220px" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择所属部门" />
@@ -154,8 +159,10 @@
       </el-form-item>
       </div>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="warning"
@@ -201,7 +208,7 @@
       </el-table-column>
       <el-table-column label="物流状态" align="center" prop="deliveryStatus" >
         <template slot-scope="scope">
-          <el-tag prop="status" v-for="(item, index) in deliveryStatusOptions"    v-if="scope.row.deliveryStatus==item.dictValue">{{item.dictLabel}}</el-tag>
+          <span class="live-muted" v-for="(item, index) in deliveryStatusOptions" v-if="scope.row.deliveryStatus==item.dictValue" :key="index">{{item.dictLabel}}</span>
         </template>
       </el-table-column>
 
@@ -232,6 +239,7 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
+    </div>
     <el-drawer
       size="75%"
       :title="title" :visible.sync="open"

+ 12 - 4
src/views/live/liveAfteraSales/myIndex.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">我的直播售后</h2>
+    </div>
+
+    <div class="live-panel" v-show="showSearch">
+      <el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
 
       <el-form-item label="所属部门" prop="deptId">
         <treeselect style="width:220px" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择所属部门" />
@@ -153,8 +158,10 @@
       </el-form-item>
       </div>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="warning"
@@ -200,7 +207,7 @@
       </el-table-column>
       <el-table-column label="物流状态" align="center" prop="deliveryStatus" >
         <template slot-scope="scope">
-          <el-tag prop="status" v-for="(item, index) in deliveryStatusOptions"    v-if="scope.row.deliveryStatus==item.dictValue">{{item.dictLabel}}</el-tag>
+          <span class="live-muted" v-for="(item, index) in deliveryStatusOptions" v-if="scope.row.deliveryStatus==item.dictValue" :key="index">{{item.dictLabel}}</span>
         </template>
       </el-table-column>
 
@@ -231,6 +238,7 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
+    </div>
     <el-drawer
       size="75%"
       :title="title" :visible.sync="open"

+ 11 - 3
src/views/live/liveAnchor/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播主播</h2>
+    </div>
+
+    <div class="live-panel" v-show="showSearch">
+      <el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="昵称" prop="nickName">
         <el-input
           v-model="queryParams.nickName"
@@ -65,8 +70,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -151,6 +158,7 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
+    </div>
 
     <!-- 添加或修改主播对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>

+ 12 - 2
src/views/live/liveConfig/answer.vue

@@ -1,5 +1,10 @@
 <template>
-  <div class="container-md">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">答题配置</h2>
+    </div>
+
+<div class="live-panel">
     <div class="tip-box">
       选择用于本节直播课程的题库试题,试题可用于直播间内发送
       <el-link
@@ -9,9 +14,12 @@
       >配置题库试题 >></el-link>
     </div>
 
+    </div>
+
+    <div class="live-panel live-panel--table">
     <el-button type="primary" icon="el-icon-plus" style="margin: 20px 0;" @click="handleAddQuestion">添加试题</el-button>
     <!-- 试题列表表格 -->
-    <el-table
+    <el-table class="live-table"
       :data="questionLiveList"
       style="width: 100%"
       v-loading="loading"
@@ -66,6 +74,8 @@
       @pagination="getLiveQuestionLiveList"
       style="margin-top: 20px;"
     />
+    </div>
+
 
     <!-- 添加试题弹窗 -->
     <el-dialog

+ 12 - 5
src/views/live/liveConfig/barrage.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-row :gutter="10" class="mb8">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">弹幕任务</h2>
+    </div>
+
+<div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary" plain icon="el-icon-refresh" size="mini" @click="handleReload" v-hasPermi="['live:task:add']"
@@ -32,7 +37,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="taskList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="编号" align="center" prop="id" />
       <el-table-column label="人物名称" align="center" prop="taskName" />
@@ -58,7 +63,9 @@
       v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"
     />
     <!-- 添加或修改直播间自动化任务配置对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="直播间ID" prop="liveId" v-show="false">
           <el-input :disabled="liveAbled" v-model="form.liveId" placeholder="请输入直播间ID" />
@@ -240,7 +247,7 @@ export default {
     }
   },
   created() {
-    this.socket = this.$store.state.liveWs[this.liveId]
+    this.socket = (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId])
   },
   methods: {
     triggerTypeFormatter(row, column, value){

+ 16 - 12
src/views/live/liveConfig/goods.vue

@@ -1,8 +1,11 @@
 <template>
-  <div class="app-container">
-  <div class="el-container-md">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播商品配置</h2>
+    </div>
 
-    <div class="selection-toolbar">
+    <div class="live-panel live-panel--table">
+<div class="selection-toolbar">
       <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
         {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
       </el-checkbox>
@@ -32,7 +35,7 @@
         <template slot-scope="scope">
           <img
             :src="scope.row.imgUrl"
-            style="display: block; max-width: 100%; width: 100px; height: 100px"
+            class="live-cell__cover live-cell__cover--square" style="object-fit: cover;"
           />
         </template>
 
@@ -85,8 +88,8 @@
         label="上下架"
       >
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.status == 1">上架</el-tag>
-          <el-tag type="info" v-if="scope.row.status == 0">下架</el-tag>
+          <span v-if="scope.row.status == 1" class="live-ok">上架</span>
+          <span v-if="scope.row.status == 0" class="live-muted">下架</span>
         </template>
       </el-table-column>
 
@@ -95,8 +98,8 @@
         label="店铺上下架"
       >
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.fsStatus == 1">上架</el-tag>
-          <el-tag type="info" v-if="scope.row.fsStatus == 0">下架</el-tag>
+          <span v-if="scope.row.fsStatus == 1" class="live-ok">上架</span>
+          <span v-if="scope.row.fsStatus == 0" class="live-muted">下架</span>
         </template>
       </el-table-column>
 
@@ -148,6 +151,7 @@
       @pagination="getLiveGoodsList"
       style="margin-top: 20px;"
     />
+    </div>
 
     <!-- 添加商品弹窗 -->
     <el-dialog
@@ -290,7 +294,6 @@
       </div>
     </el-dialog>
   </div>
-  </div>
 </template>
 
 <script>
@@ -391,7 +394,7 @@ export default {
           return;
         }
         this.getLiveGoodsList();
-        this.socket = this.$store.state.liveWs[this.liveId]
+        this.socket = (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId])
       },
       // 初始化时立即执行一次
       immediate: true
@@ -405,7 +408,7 @@ export default {
     // }
     // this.goodsParams.liveId = this.liveId
     // this.getLiveGoodsList();
-    // this.socket = this.$store.state.liveWs[this.liveId]
+    // this.socket = (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId])
   },
   methods: {
     handleRouteChange(to, from) {
@@ -417,8 +420,9 @@ export default {
       const targetStatus = row.isShow;
       const originalIsShow = !row.isShow
       const goodsList = [row.goodsId];
+      this.socket = this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]
       if (this.socket == null) {
-        this.$message.error("请从直播间开启展示状态!");
+        this.$message.error("直播连接未就绪,请稍后重试");
 
         this.$nextTick(() => {
           row.isShow = originalIsShow;

+ 10 - 3
src/views/live/liveConfig/idCard.vue

@@ -1,14 +1,21 @@
 <template>
-  <el-form ref="form"  label-width="80px">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">身份认证</h2>
+    </div>
+
+<div class="live-panel">
+<el-form ref="form"  label-width="80px">
     <el-form-item>
       <el-form-item label="上传图片" prop="materialUrl">
-        <img :src="idCardUrl" alt="身份证" style="height: 150px; width: 150px" v-if="idCardUrl != null">
+        <img :src="idCardUrl" alt="身份证" class="live-cell__cover" style="width: 96px; height: 96px; object-fit: cover;" v-if="idCardUrl != null">
         <ImageUpload @input="handleUrl" type="image" :num="10" :width="150" :height="150" />
       </el-form-item>
       <el-button type="primary" size="mini" @click="submit" v-show="!isViewOnly">保存</el-button>
     </el-form-item>
   </el-form>
-</template>
+    </div>
+</div></template>
 
 <script>
 import ImageUpload from '@/components/ImageUpload/index';

+ 99 - 89
src/views/live/liveConfig/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="live-config-container">
+  <div class="live-config-container live-page">
     <el-card class="box-card live-config-header">
       <div class="detail-header">
         <div class="header-left-area">
@@ -21,32 +21,21 @@
             <div class="live-type">{{ liveInfo.liveType == 1 ? '视频直播' : '录播' }}</div>
             <div class="line"></div>
             <div class="live-mode">{{ liveInfo.showType == 1 ? '横屏' : '竖屏' }}</div>
-            <div class="big-screen-info">
-              <div class="dataLarge">
-                <a class="dataLarge__screen ss-popover__reference" aria-describedby="ss-popover-7614" tabindex="0" data-auth-id="818">
-                  <span class="dataLarge__screen__icon"></span>
-                  <span class="dataLarge__screen__txt">实时大屏</span>
-                </a>
-              </div>
-            </div>
           </div>
 
           <div class="live-info-state">
             <div class="info-state-text live-time">直播时间:{{ liveInfo.startTime }} 至 {{ liveInfo.finishTime }}</div>
           </div>
           <div class="live-info-state">
-            <!-- <div class="info-state-text live-state">
-            {{ liveInfo.status == 3 ? '已结束' : liveInfo.status == 2 ? '直播中' : '未开始'}}
-            </div> -->
-            <el-tag type="warning" v-if="liveInfo.status == 1">待直播</el-tag>
-            <el-tag style="color:#3491FA;background-color:#E8F7FF;border-color: #E8F7FF;" v-if="liveInfo.status == 2">直播中</el-tag>
-            <el-tag type="info" v-if="liveInfo.status == 3">已结束</el-tag>
-            <el-tag style="color:#9B6DDA;background-color:#F5E8FF;border-color: #F5E8FF;" v-if="liveInfo.status == 4">直播回放中</el-tag>
-            <div class="info-state-text live-active-state" style="margin-left: 5px;">
-            <el-tag v-if="liveInfo.isShow == 1">上架</el-tag>
-            <el-tag type="info" v-if="liveInfo.isShow == 2">下架</el-tag>
-            </div>
-
+            <span class="live-status" :class="'is-' + liveInfo.status">
+              <i class="live-status__dot"></i>
+              <template v-if="liveInfo.status == 1">待直播</template>
+              <template v-else-if="liveInfo.status == 2">直播中</template>
+              <template v-else-if="liveInfo.status == 3">已结束</template>
+              <template v-else-if="liveInfo.status == 4">直播回放中</template>
+            </span>
+            <span class="shelf-text" v-if="liveInfo.isShow == 1">上架</span>
+            <span class="shelf-text is-off" v-if="liveInfo.isShow == 2">下架</span>
           </div>
         </div>
       </div>
@@ -109,6 +98,7 @@ export default {
       liveId: null,
       liveInfo: {},
       currentComponent: WatchReward,
+      liveWsUrl: process.env.VUE_APP_LIVE_WS_URL + '/app/webSocket',
       menuList:[
         // { name: '观看奖励', label: '观看奖励', index: 'watchReward'},
         // { name: '直播预告', label: '直播预告', index: 'preview'},
@@ -137,11 +127,40 @@ export default {
 
     }
   },
+  computed: {
+    userId() {
+      return this.$store.state.user.user && this.$store.state.user.user.userId
+    }
+  },
+  watch: {
+    '$route.params.liveId'(val, oldVal) {
+      if (!val || String(val) === String(oldVal)) return
+      if (oldVal) {
+        this.$store.dispatch('releaseLiveWs', { liveId: oldVal, owner: 'config' })
+      }
+      this.liveId = val
+      this.connectLiveWs()
+      this.getLiving()
+    }
+  },
   created() {
     this.liveId = this.$route.params.liveId
+    this.connectLiveWs()
     this.getLiving()
   },
+  beforeDestroy() {
+    this.$store.dispatch('releaseLiveWs', { liveId: this.liveId, owner: 'config' })
+  },
   methods: {
+    connectLiveWs() {
+      if (!this.liveId || !this.userId) return
+      this.$store.dispatch('acquireLiveWs', {
+        liveWsUrl: this.liveWsUrl,
+        liveId: this.liveId,
+        userId: this.userId,
+        owner: 'config'
+      })
+    },
     handleSelect(index){
       this.currentComponent = index; // 切换当前显示的组件
     },
@@ -163,107 +182,95 @@ export default {
 .el-tabs__header .is-top {
   display: none !important;
 }
-.live-config-header{
+.live-config-header {
   padding: 5px;
-  border: 1px solid #ebeef5;
+  border: 1px solid #e8eaed;
+  border-radius: 10px;
 }
 .detail-header {
   display: flex;
-  padding: 20px;
-  border-bottom: 1px solid #ebeef5;
+  align-items: center;
+  padding: 16px 18px;
 }
 
 .header-left-area {
-  flex: 0 0 120px;
+  flex: 0 0 64px;
   position: relative;
 }
 
 .header-left-area img {
-  width: 100px;
-  height: 100px;
-  border-radius: 4px;
+  width: 64px;
+  height: 84px;
+  border-radius: 8px;
+  object-fit: cover;
 }
 
 .header-middle-area {
   flex: 1;
-  padding: 0 20px;
-}
-
-.header-right-area {
-  flex: 0 0 200px;
+  padding: 0 18px;
+  min-width: 0;
 }
 
 .live-info-title .title-text {
-  font-size: 20px;
-  font-weight: 500;
-  color: #303133;
+  font-size: 18px;
+  font-weight: 600;
+  color: #1f2329;
+  line-height: 1.35;
 }
 
 .live-info-desc {
   display: flex;
   align-items: center;
-  margin-top: 10px;
-  color: #999999;
+  margin-top: 8px;
+  color: #8a919f;
+  font-size: 13px;
 }
 
 .live-info-desc .line {
   width: 1px;
   height: 12px;
-  background: #c0c4cc;
+  background: #e8eaed;
   margin: 0 10px;
 }
 
 .live-info-state {
   display: flex;
   align-items: center;
-  margin-top: 10px;
-}
-
-.live-info-state .line {
-  width: 1px;
-  height: 12px;
-  background: #c0c4cc;
-  margin: 0 10px;
-}
-
-.live-state-dot {
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-}
-
-.live-state-dot-liveEnd {
-  background: #909399;
+  gap: 12px;
+  margin-top: 8px;
 }
 
 .info-state-text {
-  font-size: 14px;
-  color: #999999;
+  font-size: 13px;
+  color: #8a919f;
 }
 
-.operation-wrapper {
-  display: flex;
+.live-status {
+  display: inline-flex;
   align-items: center;
+  gap: 6px;
+  font-size: 13px;
+  color: #1f2329;
 }
 
-.operation-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  cursor: pointer;
+.live-status__dot {
+  width: 7px;
+  height: 7px;
+  border-radius: 50%;
+  background: #c0c4cc;
 }
 
-.operation-item-text {
+.live-status.is-1 .live-status__dot { background: #e6a23c; }
+.live-status.is-2 .live-status__dot { background: #f56c6c; box-shadow: 0 0 0 3px rgba(245, 108, 108, 0.18); }
+.live-status.is-3 .live-status__dot { background: #909399; }
+.live-status.is-4 .live-status__dot { background: #409eff; }
+
+.shelf-text {
   font-size: 12px;
-  color: #606266;
-  margin-top: 4px;
+  color: #1a6d5b;
 }
-
-.operation-wrapper .line {
-  width: 1px;
-  height: 20px;
-  background: #c0c4cc;
-  margin: 0 10px;
+.shelf-text.is-off {
+  color: #8a919f;
 }
 
 .container {
@@ -271,24 +278,27 @@ export default {
   height: 100%;
 }
 .left-menu {
-  width: 200px;
-  border-right: 1px solid #e4e7ed;
-  flex-shrink: 0; /* 不收缩 */
+  width: 180px;
+  border-right: 1px solid #e8eaed;
+  flex-shrink: 0;
+  background: #fafbfc;
+  padding: 10px 8px;
 }
-.el-menu-item{
-  margin-right: 20px;
+.el-menu-item {
+  margin: 2px 0;
+  height: 40px;
+  line-height: 40px;
+  border-radius: 8px;
+  color: #5c6470;
 }
-.el-menu-item.is-active,.el-menu-item:hover{
-  background-color: rgba(19,194,194,0.1);
-  border-radius: 8px 8px 8px 8px;
+.el-menu-item.is-active,
+.el-menu-item:hover {
+  background-color: rgba(26, 109, 91, 0.1);
+  color: #1a6d5b;
 }
 .right-info {
   flex: 1;
-  padding: 20px;
-}
-
-.right-tabs {
-  height: 100%;
+  padding: 16px 18px;
 }
 
 </style>

+ 16 - 8
src/views/live/liveConfig/liveCoupon.vue

@@ -1,8 +1,13 @@
 <template>
-  <div class="app-container">
-  <div class="el-container-md">
+  <div class="app-container live-page">
+      <div class="live-page__header">
+      <h2 class="live-page__title">直播优惠券</h2>
+    </div>
+
+<div class="el-container-md">
 
-    <div class="selection-toolbar">
+    <div class="live-panel live-panel--table">
+<div class="selection-toolbar">
       <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
         {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
       </el-checkbox>
@@ -17,7 +22,7 @@
       style="width: 100%"
       v-loading="loading"
       @selection-change="handleSelectionChange"
-    >
+     class="live-table">
       <el-table-column type="selection" width="55"></el-table-column>
       <!-- 题干列:显示试题的主要内容 -->
       <el-table-column
@@ -103,7 +108,9 @@
     />
 
     <!-- 添加优惠券弹窗 -->
-    <el-dialog
+    </div>
+
+<el-dialog
       title="添加优惠券"
       :visible.sync="couponDialogVisible"
       width="800px"
@@ -316,7 +323,7 @@ export default {
           return;
         }
         this.getLiveCouponList();
-        this.socket = this.$store.state.liveWs[this.liveId]
+        this.socket = (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId])
       },
       // 初始化时立即执行一次
       immediate: true
@@ -330,7 +337,7 @@ export default {
     // }
     // this.goodsParams.liveId = this.liveId
     // this.getLiveGoodsList();
-    // this.socket = this.$store.state.liveWs[this.liveId]
+    // this.socket = (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId])
   },
   methods: {
     handleGoodsChange(row){
@@ -351,8 +358,9 @@ export default {
       // 1. 获取「即将切换到的目标状态」(当前状态取反)
       const targetStatus = !row.isShow
       const couponList = row.id;
+      this.socket = this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]
       if (this.socket == null) {
-        this.$message.error("请从直播间开启展示状态!");
+        this.$message.error("直播连接未就绪,请稍后重试");
         return;
       }
       handleIsShowChange({"couponId":couponList,"isShow":targetStatus,"liveId":this.liveId}).then(res=>{

+ 18 - 9
src/views/live/liveConfig/liveLotteryConf.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">抽奖配置</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="直播间ID" prop="liveId">
         <el-input
           v-model="queryParams.liveId"
@@ -43,8 +48,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           :disabled="isViewOnly"
@@ -93,7 +100,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="liveLotteryConfList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="liveLotteryConfList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="抽奖ID" align="center" prop="lotteryId" />
       <el-table-column label="操作商品" align="center" class-name="small-padding fixed-width">
@@ -182,7 +189,9 @@
     />
 
     <!-- 添加或修改直播抽奖配置对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="直播间ID" prop="liveId">
           <el-input v-model="form.liveId" placeholder="请输入直播间ID"
@@ -744,8 +753,8 @@ export default {
     },
     /** 抽奖状态修改 */
     handleStatusChange(row, status) {
-      if (this.$store.state.liveWs[this.liveId] == null) {
-        this.msgError("请从直播间进行操作!");
+      if ((this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]) == null) {
+        this.msgError("直播连接未就绪,请稍后重试");
         return;
       }
       //结束的抽奖不能进行操作
@@ -791,7 +800,7 @@ export default {
             cmd: 'lottery',
             data: doLotteryParam
           };
-          this.$store.state.liveWs[this.liveId].send(JSON.stringify(msg));
+          (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]).send(JSON.stringify(msg));
 
           this.msgSuccess("修改成功");
         }else{
@@ -860,7 +869,7 @@ export default {
               duration: row.duration
             }
           };
-          this.$store.state.liveWs[this.liveId].send(JSON.stringify(msg));
+          (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]).send(JSON.stringify(msg));
         }).catch(() => {});
     },
 

+ 17 - 9
src/views/live/liveConfig/liveRedConf.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">红包配置</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="红包状态" prop="redStatus">
         <el-select v-model="queryParams.redStatus" placeholder="请选择红包状态" clearable size="small">
           <el-option v-for="(item,index) in redStatusOptions" :key="item.dictValue+index" :label="item.dictLabel" :value="item.dictValue" />
@@ -51,8 +56,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           :disabled="isViewOnly"
@@ -90,7 +97,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="liveRedConfList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="liveRedConfList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="红包ID" align="center" prop="redId" />
       <el-table-column label="红包状态" align="center" prop="redStatus" :formatter="redStatusFormatter"/>
@@ -176,8 +183,9 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
+    </div>
 
-    <el-dialog :title="claimDialog.title" :visible.sync="claimDialog.open" width="800px" append-to-body>
+<el-dialog :title="claimDialog.title" :visible.sync="claimDialog.open" width="800px" append-to-body>
       <el-table v-loading="claimDialog.loading" :data="claimDialog.list" border>
         <el-table-column label="用户ID" align="center" prop="userId" width="100" />
         <el-table-column label="昵称" align="center" prop="nickname" min-width="120" />
@@ -357,8 +365,8 @@ export default {
   },
   methods: {
     handleStatusChange(row, status) {
-      if (this.$store.state.liveWs[this.liveId] == null) {
-        this.msgError("请从直播间进行操作!");
+      if ((this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]) == null) {
+        this.msgError("直播连接未就绪,请稍后重试");
         return;
       }
       //结束的抽奖不能进行操作
@@ -405,7 +413,7 @@ export default {
             cmd: 'red',
             data: doRedParam
           };
-          this.$store.state.liveWs[this.liveId].send(JSON.stringify(msg));
+          (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]).send(JSON.stringify(msg));
           this.msgSuccess("修改成功");
           console.log(1)
         }else{
@@ -567,7 +575,7 @@ export default {
               duration: row.duration
             }
           };
-          this.$store.state.liveWs[this.liveId].send(JSON.stringify(msg));
+          (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]).send(JSON.stringify(msg));
         }).catch(() => {});
     },
     /** 导出按钮操作 */

+ 9 - 2
src/views/live/liveConfig/liveReplay.vue

@@ -1,5 +1,11 @@
 <template>
-  <div class="app-container" v-loading.fullscreen.lock="loading">
+<div class="app-container live-page" v-loading.fullscreen.lock="loading">
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播回放</h2>
+    </div>
+
+    <div class="live-panel">
+
     <!-- 直播回放开关区域 -->
 
     <div class="switch-area">
@@ -158,7 +164,8 @@
 <!--        </el-table-column>-->
       </el-table>
     </div>
-  </div>
+    </div>
+</div>
 </template>
 
 <script>

+ 69 - 71
src/views/live/liveConfig/preview.vue

@@ -1,83 +1,81 @@
 <template>
-  <div class="app-container" v-loading.fullscreen.lock="loading">
-    <!-- 直播回放开关区域 -->
+  <div class="app-container live-page" v-loading.fullscreen.lock="loading">
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播预告</h2>
+    </div>
 
-    <!-- 回放内容区域 -->
-    <div class="playback-content">
-      <div>
-        <span>预告内容:</span>
-        <el-button class="upload-btn" type="normal" @click="handleUploadVideo" v-show="!isViewOnly">上传视频</el-button>
-        <!--        <el-button-->
-        <!--          class="upload-btn"-->
-        <!--          type="text"-->
-        <!--          icon="el-icon-plus"-->
-        <!--          @click="handleUploadVideo"-->
-        <!--        >上传视频</el-button>-->
-        <!--        <span class="upload-tip">上传视频大小不可超过5GB</span>-->
-      </div>
-      <el-dialog title="添加直播预告" :visible.sync="open" width="900px" append-to-body>
-        <el-form ref="form" :model="form"  label-width="80px">
-          <video-upload
-            :type = "1"
-            :isPrivate = "isPrivate"
-            :fileKey.sync = "form.fileKey"
-            :fileSize.sync = "form.fileSize"
-            :videoUrl.sync="videoUrl"
-            :fileName.sync="form.fileName"
-            :line_2.sync="form.lineTwo"
-            :line_1.sync="form.lineOne"
-            :thumbnail.sync="form.thumbnail"
-            :uploadType.sync="form.uploadType"
-            :isTranscode.sync="form.isTranscode"
-            :transcodeFileKey.sync="form.transcodeFileKey"
-            @video-duration="handleVideoDuration"
-            @change="handleVideoChange"
-            ref="videoUpload"
-            append-to-body
-          />
-        </el-form>
-        <div slot="footer" class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="open = false">取 消</el-button>
+    <div class="live-panel live-panel--table">
+      <div class="playback-content">
+        <div>
+          <span>预告内容:</span>
+          <el-button class="upload-btn" type="normal" @click="handleUploadVideo" v-show="!isViewOnly">上传视频</el-button>
         </div>
-      </el-dialog>
 
-      <!-- 视频列表 -->
-      <el-table
-        :data="videoList"
-        border
-        style="width: 100%; margin-top: 10px"
-      >
-        <el-table-column prop="duration" label="时长" />
-        <el-table-column prop="updateTime" label="更新时间" />
-        <el-table-column label="视频地址(可点击查看)" prop="videoUrl" >
-          <template slot-scope="scope">
-            <el-tooltip
-              :content="scope.row.videoUrl"
-              placement="top"
-              effect="dark"
-            >
-              <a
-                :href="scope.row.videoUrl"
-                target="_blank"
-                class="video-url-container"
-                rel="noopener noreferrer"
+        <el-table
+          :data="videoList"
+          border
+          class="live-table"
+          style="width: 100%; margin-top: 10px"
+        >
+          <el-table-column prop="duration" label="时长" />
+          <el-table-column prop="updateTime" label="更新时间" />
+          <el-table-column label="视频地址(可点击查看)" prop="videoUrl">
+            <template slot-scope="scope">
+              <el-tooltip
+                :content="scope.row.videoUrl"
+                placement="top"
+                effect="dark"
               >
-                {{
-                  scope.row.videoUrl.length > 32
-                    ? scope.row.videoUrl.substring(0, 32) + '...'
-                    : scope.row.videoUrl
-                }}
-                <i class="el-icon-external-link video-url-icon"></i>
-              </a>
-            </el-tooltip>
-          </template>
-        </el-table-column>
-      </el-table>
+                <a
+                  :href="scope.row.videoUrl"
+                  target="_blank"
+                  class="video-url-container"
+                  rel="noopener noreferrer"
+                >
+                  {{
+                    scope.row.videoUrl.length > 32
+                      ? scope.row.videoUrl.substring(0, 32) + '...'
+                      : scope.row.videoUrl
+                  }}
+                  <i class="el-icon-external-link video-url-icon"></i>
+                </a>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
     </div>
+
+    <el-dialog title="添加直播预告" :visible.sync="open" width="900px" append-to-body>
+      <el-form ref="form" :model="form" label-width="80px">
+        <video-upload
+          :type = "1"
+          :isPrivate = "isPrivate"
+          :fileKey.sync = "form.fileKey"
+          :fileSize.sync = "form.fileSize"
+          :videoUrl.sync="videoUrl"
+          :fileName.sync="form.fileName"
+          :line_2.sync="form.lineTwo"
+          :line_1.sync="form.lineOne"
+          :thumbnail.sync="form.thumbnail"
+          :uploadType.sync="form.uploadType"
+          :isTranscode.sync="form.isTranscode"
+          :transcodeFileKey.sync="form.transcodeFileKey"
+          @video-duration="handleVideoDuration"
+          @change="handleVideoChange"
+          ref="videoUpload"
+          append-to-body
+        />
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="open = false">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
+
 <script>
 import { getLive,} from '@/api/live/live'
 import {getLiveVideoByLiveIdAndType,addLiveVideo} from '@/api/live/liveVideo'

+ 17 - 7
src/views/live/liveConfig/task.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">自动化任务</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="直播间ID" prop="liveId">
         <el-input
           v-model="queryParams.liveId"
@@ -40,8 +45,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           :disabled="isViewOnly"
@@ -91,7 +98,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="taskList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="taskList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="编号" align="center" prop="id" />
       <el-table-column label="任务名称" align="center" prop="taskName" />
@@ -137,7 +144,9 @@
     />
 
     <!-- 添加或修改直播间自动化任务配置对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="直播间ID" prop="liveId">
           <el-input :disabled="liveAbled" v-model="form.liveId" placeholder="请输入直播间ID" />
@@ -431,7 +440,7 @@ export default {
     }
   },
   created() {
-    this.socket = this.$store.state.liveWs[this.liveId]
+    this.socket = (this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId])
   },
   methods: {
     statusFormatter(row, column, value){
@@ -794,8 +803,9 @@ export default {
     },
     /** 删除按钮操作 */
     handleDelete(row) {
+      this.socket = this.$store.state.liveWs[String(this.liveId)] || this.$store.state.liveWs[this.liveId]
       if(this.socket == null) {
-        this.$message.error("请进入直播间后,在进行操作!")
+        this.$message.error("直播连接未就绪,请稍后重试")
         return;
       }
       const ids = row.id || this.ids;

+ 10 - 1
src/views/live/liveConfig/watchReward.vue

@@ -1,5 +1,12 @@
 <template >
-  <div v-loading.fullscreen.lock="loading">
+<div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">观看奖励</h2>
+    </div>
+
+    <div class="live-panel">
+<div v-loading.fullscreen.lock="loading">
+
     <!-- 提示信息 -->
     <div class="tip-message" >
       设置观看奖励,用户达到直播观看时长后可领取奖励
@@ -235,6 +242,8 @@
       </el-form>
     </div>
   </div>
+    </div>
+</div>
 </template>
 
 <script>

+ 388 - 250
src/views/live/liveConsole/index.vue

@@ -1,67 +1,54 @@
 <template>
   <!-- 直播中控台 start -->
-  <el-row type="flex" justify="center" class="live-console" :gutter="10" v-loading="loading">>
+  <el-row type="flex" justify="center" class="live-console live-page" :gutter="10" v-loading="loading">
     <!-- 聊天 start -->
     <el-col class="live-console-col" :span="6">
       <el-tabs class="live-console-tab-left" v-model="tabRight.activeName" @tab-click="handleClick" :stretch="true">
       <el-tab-pane label="讨论" name="talk">
-        <el-scrollbar style="height: 500px; width: 100%;" ref="manageRightRef">
-          <el-row v-for="m in msgList" style="margin-bottom: 20px;">
-            <el-row v-if="m.userId !== userId" type="flex" align="top" >
-              <el-col :span="24" style="display: flex; align-items: center;">
-                <el-avatar style="width: 36px;height: 36px;" :src="m.avatar!==null?m.avatar: require('@/assets/image/default-a.png')"/>
-                 <div style="font-size: 14px; color: #999;margin: 0 12px;">{{ m.nickName }}</div>
-                 <div style="display: flex; align-items: center; gap: 8px;">
-                   <a style="cursor: pointer;color: #13C2C2;font-size: 14px;" @click="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                   <span style="color:#13C2C2;">|</span>
-                   <a style="cursor: pointer;color: #13C2C2;font-size: 14px;" @click="blockUser(m)">拉黑</a>
-                   <span style="color:#13C2C2;">|</span>
-                   <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="deleteMsg(m)">删除</a>
-                 </div>
-                </el-col>
-            </el-row>
-            <el-row v-if="m.userId !== userId" type="flex" align="top">
-              <el-col :span="19" :offset="3">
-                <div class="msg-box">
-                  {{ m.msg }}
-                </div>
-              </el-col>
-            </el-row>
-            <el-row v-if="m.userId === userId" style="padding: 8px 0;margin-right: 10px;" 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="display: flex;align-items: center;">
-                    <div style="font-size: 14px; color: #999;margin-right: 12px;">{{ m.nickName }}</div>
-                     <el-avatar :src="m.avatar?m.avatar: require('@/assets/image/default-a.png')" style="width: 36px;height: 36px;"/>
+        <el-scrollbar class="chat-scroll" ref="manageRightRef">
+          <div class="chat-msg" v-for="m in msgList" :key="m.msgId || m.userId + '-' + m.msg">
+            <div v-if="m.userId !== userId" class="chat-msg__row chat-msg__row--left">
+              <el-avatar class="chat-msg__avatar" :src="m.avatar!==null?m.avatar: require('@/assets/image/default-a.png')"/>
+              <div class="chat-msg__body">
+                <div class="chat-msg__meta">
+                  <span class="chat-msg__name">{{ m.nickName }}</span>
+                  <div class="chat-msg__ops">
+                    <a class="chat-msg__op" @click="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                    <a class="chat-msg__op" @click="blockUser(m)">拉黑</a>
+                    <a class="chat-msg__op chat-msg__op--danger" @click="deleteMsg(m)">删除</a>
                   </div>
-
-                 <div class="msg-box-right">
-                 {{ m.msg }}</div>
                 </div>
-
+                <div class="msg-box">{{ m.msg }}</div>
               </div>
-            </el-row>
-          </el-row>
-          <!-- 底部留白 -->
-          <div style="height: 20px;"></div>
+            </div>
+            <div v-else class="chat-msg__row chat-msg__row--right">
+              <div class="chat-msg__body chat-msg__body--right">
+                <div class="chat-msg__meta chat-msg__meta--right">
+                  <span class="chat-msg__name">{{ m.nickName }}</span>
+                  <el-avatar class="chat-msg__avatar" :src="m.avatar?m.avatar: require('@/assets/image/default-a.png')"/>
+                </div>
+                <div class="msg-box-right">{{ m.msg }}</div>
+              </div>
+            </div>
+          </div>
+          <div class="chat-scroll__spacer"></div>
         </el-scrollbar>
 
         <!-- 消息输入区域 -->
-        <div class="shuru" style="padding: 20px; border-top: 1px solid #ebeef5; background-color: #fff; min-height: 120px;">
+        <div class="shuru">
           <el-input
             type="textarea"
             v-model="newMsg"
             placeholder="请输入消息..."
-            :rows="8"
+            :rows="3"
             @keyup.enter.native="sendMessage"
             clearable
             resize="none"
-            style="flex: 1; margin-right: 10px;"
           >
           </el-input>
-          <div style="display: flex; justify-content: flex-end; margin-top: 10px;">
-            <el-button style="background: #13C2C2;color: #fff;" @click="showTopMsgDialog">顶部消息</el-button>
-            <el-button style="background: #13C2C2;color: #fff;" @click="sendMessage">发送</el-button>
+          <div class="shuru__actions">
+            <el-button class="btn-accent" @click="showTopMsgDialog">顶部消息</el-button>
+            <el-button class="btn-accent" @click="sendMessage">发送</el-button>
           </div>
         </div>
       </el-tab-pane>
@@ -72,14 +59,14 @@
 
     <!-- 直播/视频 start -->
     <el-col class="live-console-col" :span="12">
-      <div style="background: #000; border-radius: 5px; overflow: hidden; margin: 10px 5px;">
-        <div style="border-radius: 5px; overflow: hidden;" v-if="!isAudit">
-          <img :src="require('@/assets/image/videoIsAudit.png')" style="width: 100%; height: 45vh;">
+      <div class="player-panel">
+        <div class="player-panel__inner" v-if="!isAudit">
+          <img :src="require('@/assets/image/videoIsAudit.png')" class="player-panel__poster">
         </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else-if="status != 2 && status != 4">
-          <img :src="require('@/assets/image/videoNotStart.png')" style="width: 100%; height: 45vh;">
+        <div class="player-panel__inner" v-else-if="status != 2 && status != 4">
+          <img :src="require('@/assets/image/videoNotStart.png')" class="player-panel__poster">
         </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 1">
+        <div class="player-panel__inner" v-else-if="liveType == 1">
           <video
             id="live-tcplayer-container"
             ref="livingPlayer"
@@ -95,7 +82,7 @@
             已播放:<span id="liveElapsedTime">00:00:00</span>
           </div>
         </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 2">
+        <div class="player-panel__inner" v-else-if="liveType == 2">
           <video
             controls
             ref="videoPlayer"
@@ -121,7 +108,7 @@
             已播放:<span id="elapsedTime">00:00:00</span>
           </div>
         </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 3">
+        <div class="player-panel__inner" v-else-if="liveType == 3">
           <video
             controls
             ref="liveReplay"
@@ -134,44 +121,39 @@
             <source :src="videoUrl" type="application/x-mpegURL">
           </video>
         </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else>
-          <img :src="require('@/assets/image/videoNotStart.png')" style="width: 100%; height: 45vh;">
+        <div class="player-panel__inner" v-else>
+          <img :src="require('@/assets/image/videoNotStart.png')" class="player-panel__poster">
         </div>
       </div>
       <!-- 底部导航栏 -->
-    <div style="display: flex; justify-content: space-around; padding: 15px 0; background: #fff; border-top: 1px solid #f0f0f0;">
-      <div class="top-box" style="display: flex; flex-direction: column; align-items: center; cursor: pointer;" @click="handleClickRed">
-        <!-- <i class="el-icon-money" style="font-size: 20px;"></i> -->
-        <img src="@/assets/image/hbpz.png" width="48"/>
-        <span style="font-size: 12px; margin-top: 10px;">红包配置</span>
+    <div class="tool-bar">
+      <div class="top-box" @click="handleClickRed">
+        <img src="@/assets/image/hbpz.png"/>
+        <span>红包配置</span>
       </div>
-      <div class="top-box" style="display: flex; flex-direction: column; align-items: center; cursor: pointer;" @click="handleClickLottery">
-        <!-- <i class="el-icon-present" style="font-size: 20px;"></i> -->
-        <img src="@/assets/image/cjpz.png" width="48"/>
-        <span style="font-size: 12px; margin-top: 10px;">抽奖配置</span>
+      <div class="top-box" @click="handleClickLottery">
+        <img src="@/assets/image/cjpz.png"/>
+        <span>抽奖配置</span>
       </div>
-      <div class="top-box" style="display: flex; flex-direction: column; align-items: center; cursor: pointer;" @click="handleClickGoods">
-        <!-- <i class="el-icon-goods" style="font-size: 20px;"></i> -->
-        <img src="@/assets/image/sp.png" width="48"/>
-        <span style="font-size: 12px; margin-top: 10px;">商品</span>
+      <div class="top-box" @click="handleClickGoods">
+        <img src="@/assets/image/sp.png"/>
+        <span>商品</span>
       </div>
-      <div class="top-box" style="display: flex; flex-direction: column; align-items: center; cursor: pointer;" @click="handleClickOrder">
-        <!-- <i class="el-icon-goods" style="font-size: 20px;"></i> -->
-        <img src="@/assets/image/zbdd.png" width="48"/>
-        <span style="font-size: 12px;margin-top: 10px;">直播订单</span>
+      <div class="top-box" @click="handleClickOrder">
+        <img src="@/assets/image/zbdd.png"/>
+        <span>直播订单</span>
       </div>
-      <div class="top-box" style="display: flex; flex-direction: column; align-items: center; cursor: pointer;" @click="handleClickCoupon">
-        <!-- <i class="el-icon-goods" style="font-size: 20px;"></i> -->
-        <img src="@/assets/image/zbyhq.png" width="48"/>
-        <span style="font-size: 12px; margin-top: 10px;">直播优惠券</span>
+      <div class="top-box" @click="handleClickCoupon">
+        <img src="@/assets/image/zbyhq.png"/>
+        <span>直播优惠券</span>
       </div>
     </div>
      <!-- <el-radio-group v-model="tableRadio" >
         <el-radio-button label="订单数">订单数</el-radio-button>
       </el-radio-group> -->
-      <div  style="position: relative;width: 100%; height: 270px;margin-top: 20px;">
-        <div ref="chartContainer" style="width: 100%; height: 100%;"></div>
-        <div style="position: absolute; top: 10px; right: 10px; background: #fff; padding: 5px; z-index: 1;">
+      <div class="chart-panel">
+        <div ref="chartContainer" class="chart-panel__canvas"></div>
+        <div class="chart-panel__filters">
           <el-select v-model="searchQuery.timeOptions" placeholder="请选择" style="width: 150px"  @change="timeChange">
             <el-option
               v-for="item in timeOptions"
@@ -198,76 +180,57 @@
     <el-col class="live-console-col" :span="6">
       <el-tabs class="live-console-tab-right" v-model="tabLeft.activeName" @tab-click="handleClick" :stretch="true">
         <el-tab-pane :label="onlineLabel" name="online" style="height: 100%;">
-          <el-scrollbar ref="manageLeftRef_online" style="height:100%; width: 100%;padding: 10px;">
-            <el-row  class="row-box" style="margin-bottom:10px" type="flex" align="middle" 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"
-                  style="text-align: center;"
-                  trigger="click">
-                 <div style="text-align: center;">
-                   <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>
-                   </div>
-                  <i class="el-icon-more" slot="reference"></i>
-                </el-popover>
-              </el-col>
-            </el-row>
+          <el-scrollbar ref="manageLeftRef_online" class="user-scroll">
+            <div class="row-box" v-for="u in onlineDisplayList" :key="u.userId">
+              <el-avatar class="user-row__avatar" :src="u.avatar"></el-avatar>
+              <div class="user-row__info">
+                <div class="user-row__name">{{ u.nickName }}</div>
+                <div class="user-row__id">{{ u.userId }}</div>
+              </div>
+              <el-popover width="100" trigger="click">
+                <div class="user-row__pop">
+                  <a class="user-row__pop-op" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a class="user-row__pop-op" @click="blockUser(u)">拉黑</a>
+                </div>
+                <i class="el-icon-more" slot="reference"></i>
+              </el-popover>
+            </div>
           </el-scrollbar>
         </el-tab-pane>
         <el-tab-pane :label="offlineLabel" name="offline" style="height: 100%;">
-          <el-scrollbar ref="manageLeftRef_offline" style="height:100%; width: 100%;padding: 10px;">
-            <el-row  class="row-box" style="margin-bottom:10px" type="flex" align="middle" 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">
-                  <div style="text-align: center;">
-                    <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>
-                    </div>
-                  <i class="el-icon-more" slot="reference"></i>
-                </el-popover>
-              </el-col>
-            </el-row>
+          <el-scrollbar ref="manageLeftRef_offline" class="user-scroll">
+            <div class="row-box" v-for="u in offlineDisplayList" :key="u.userId">
+              <el-avatar class="user-row__avatar" :src="u.avatar"></el-avatar>
+              <div class="user-row__info">
+                <div class="user-row__name">{{ u.nickName }}</div>
+                <div class="user-row__id">{{ u.userId }}</div>
+              </div>
+              <el-popover width="100" trigger="click">
+                <div class="user-row__pop">
+                  <a class="user-row__pop-op" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a class="user-row__pop-op" @click="blockUser(u)">拉黑</a>
+                </div>
+                <i class="el-icon-more" slot="reference"></i>
+              </el-popover>
+            </div>
           </el-scrollbar>
         </el-tab-pane>
         <el-tab-pane :label="silencedUserLabel" name="silenced" style="height: 100%;">
-          <el-scrollbar ref="manageLeftRef_silenced" style="height: 800px; width: 100%;padding: 10px;">
-            <el-row class="row-box" style="margin-bottom:10px" type="flex" align="middle" 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">
-                 <div style="text-align: center;">
-                   <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>
-                   </div>
-                  <i class="el-icon-more" slot="reference"></i>
-                </el-popover>
-              </el-col>
-            </el-row>
+          <el-scrollbar ref="manageLeftRef_silenced" class="user-scroll user-scroll--tall">
+            <div class="row-box" v-for="u in silencedDisplayList" :key="u.userId">
+              <el-avatar class="user-row__avatar" :src="u.avatar"></el-avatar>
+              <div class="user-row__info">
+                <div class="user-row__name">{{ u.nickName }}</div>
+                <div class="user-row__id">{{ u.userId }}</div>
+              </div>
+              <el-popover width="100" trigger="click">
+                <div class="user-row__pop">
+                  <a class="user-row__pop-op" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a class="user-row__pop-op" @click="blockUser(u)">拉黑</a>
+                </div>
+                <i class="el-icon-more" slot="reference"></i>
+              </el-popover>
+            </div>
           </el-scrollbar>
         </el-tab-pane>
       </el-tabs>
@@ -486,6 +449,7 @@ export default {
         );
       }
     })
+    this.releaseWebSocket()
   },
   // 使用 deactivated 和 activated 钩子替代 beforeDestroy 和 destroyed
   deactivated() {
@@ -1276,13 +1240,29 @@ export default {
       }
     },
     connectWebSocket() {
-      this.$store.dispatch('initLiveWs', {
+      this._wsMessageHandler = (event) => this.handleWsMessage(event)
+      this.$store.dispatch('acquireLiveWs', {
         liveWsUrl: this.liveWsUrl,
         liveId: this.liveId,
-        userId: this.userId
+        userId: this.userId,
+        owner: 'console'
+      }).then((ws) => {
+        this.socket = ws || this.$store.state.liveWs[String(this.liveId)]
+        if (this.socket) {
+          this.socket.addMessageListener(this._wsMessageHandler)
+        }
+      })
+    },
+    releaseWebSocket() {
+      if (this.socket && this._wsMessageHandler) {
+        this.socket.removeMessageListener(this._wsMessageHandler)
+      }
+      this._wsMessageHandler = null
+      this.$store.dispatch('releaseLiveWs', {
+        liveId: this.liveId,
+        owner: 'console'
       })
-      this.socket = this.$store.state.liveWs[this.liveId]
-      this.socket.onmessage = (event) => this.handleWsMessage(event)
+      this.socket = null
     },
     handleWsMessage(event) {
       let { code, data } = JSON.parse(event.data)
@@ -1433,113 +1413,271 @@ export default {
 }
 </script>
 
-<style lang="stylus" scoped>
-  .top-box{
-    justify-content: center;
-    width: 140px;
-    height: 110px;
-    background: #FFFFFF;
-    border-radius: 16px 16px 16px 16px;
-    border: 1px solid #EBF0F9;
-  }
-  .row-box{
-    padding: 10px 16px;
-   background: #FAFBFD;
-   border-radius: 8px 8px 8px 8px;
-  }
-  .msg-box{
-   word-break: break-all;
-       word-wrap: break-word;
-       white-space: pre-wrap;
-     background-color: #F7F8FA;
-     padding: 10px;
-     border-radius: 0px 12px 12px 12px;
-     font-size: 14px;
-    display: inline-block;
-  }
-  .msg-box-right{
-    margin-right: 50px;
-   word-break: break-all;
-       word-wrap: break-word;
-       white-space: pre-wrap;
-     background-color:#13C2C2;
-     padding: 10px;
-     color: #fff;
-     border-radius: 12px 0px 12px 12px;
-     font-size: 14px;
-    display: inline-block;
-  }
-  .shuru ::v-deep.el-textarea__inner{
-    background-color: #F7F8FA !important;
-    border-radius: 12px;
-  }
-  .el-icon-more{
-    color:#D9D9D9
-  }
-  .live-console-tab-right{
-    height: 100%;
-  }
- .live-console-tab-right .el-scrollbar__view{
-    padding: 10px;
-  }
-  .live-console-tab-right .el-col-offset-1{
-    color:#666
-  }
- .live-console-tab-right ::v-deep.el-tabs__content{
-    height: 90%;
-  }
-</style>
 <style scoped>
-.talk-list{
+.live-console {
+  width: 90vw;
+  padding: 10px 0;
+}
+.live-console .live-console-col {
+  height: 88vh;
+  margin-left: 5px;
+  padding: 0 10px;
+  background-color: #fff;
+  border: 1px solid #e8eaed;
+  border-radius: 10px;
+}
+.chat-scroll {
+  height: 500px;
+  width: 100%;
+}
+.chat-scroll__spacer {
+  height: 12px;
+}
+.chat-msg {
+  margin-bottom: 14px;
+}
+.chat-msg__row {
   display: flex;
+  align-items: flex-start;
+  gap: 10px;
+}
+.chat-msg__row--right {
+  justify-content: flex-end;
+}
+.chat-msg__avatar {
+  width: 32px !important;
+  height: 32px !important;
+  flex-shrink: 0;
+}
+.chat-msg__body {
+  min-width: 0;
+  flex: 1;
+}
+.chat-msg__body--right {
+  flex: 0 1 auto;
+  max-width: 220px;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+}
+.chat-msg__meta {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 6px 10px;
+  margin-bottom: 6px;
+}
+.chat-msg__meta--right {
+  justify-content: flex-end;
+}
+.chat-msg__name {
+  font-size: 13px;
+  color: #8a919f;
+}
+.chat-msg__ops {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.chat-msg__op {
+  cursor: pointer;
+  font-size: 12px;
+  color: #8a919f;
+  line-height: 1;
+}
+.chat-msg__op:hover {
+  color: #1a6d5b;
+}
+.chat-msg__op--danger:hover {
+  color: #c45656;
+}
+.msg-box {
+  word-break: break-all;
+  word-wrap: break-word;
+  white-space: pre-wrap;
+  background-color: #f4f6f8;
+  padding: 8px 10px;
+  border-radius: 0 10px 10px 10px;
+  font-size: 13px;
+  color: #1f2329;
+  display: inline-block;
+  line-height: 1.5;
+}
+.msg-box-right {
+  word-break: break-all;
+  word-wrap: break-word;
+  white-space: pre-wrap;
+  background-color: #1a6d5b;
+  padding: 8px 10px;
+  color: #fff;
+  border-radius: 10px 0 10px 10px;
+  font-size: 13px;
+  display: inline-block;
+  line-height: 1.5;
+}
+.shuru {
+  padding: 12px 14px;
+  border-top: 1px solid #e8eaed;
+  background-color: #fff;
+}
+.shuru__actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+  margin-top: 8px;
+}
+.btn-accent {
+  background: #1a6d5b !important;
+  border-color: #1a6d5b !important;
+  color: #fff !important;
+}
+.shuru ::v-deep .el-textarea__inner {
+  background-color: #f4f6f8 !important;
+  border-radius: 8px;
+  border: none !important;
+  box-shadow: none !important;
+  resize: none !important;
+  min-height: 72px !important;
+}
+.player-panel {
+  background: #000;
+  border-radius: 10px;
+  overflow: hidden;
+  margin: 10px 5px;
+}
+.player-panel__inner {
+  overflow: hidden;
+}
+.player-panel__poster {
+  width: 100%;
+  height: 45vh;
+  display: block;
+  object-fit: cover;
+}
+.tool-bar {
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  padding: 10px 8px;
+  background: #fff;
+  border-top: 1px solid #e8eaed;
+}
+.top-box {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  min-width: 64px;
+  padding: 4px 6px;
+  border-radius: 8px;
+}
+.top-box:hover {
+  background: #f4f6f8;
+}
+.top-box img {
+  width: 36px;
+  height: 36px;
+  display: block;
+}
+.top-box span {
+  font-size: 12px;
+  margin-top: 6px;
+  color: #5c6470;
+}
+.chart-panel {
+  position: relative;
+  width: 100%;
+  height: 270px;
+  margin-top: 20px;
+}
+.chart-panel__canvas {
+  width: 100%;
+  height: 100%;
+}
+.chart-panel__filters {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  background: #fff;
+  padding: 5px;
+  z-index: 1;
+  display: flex;
+  gap: 8px;
+}
+.user-scroll {
+  height: 100%;
+  width: 100%;
+  padding: 8px;
+}
+.user-scroll--tall {
+  height: 800px;
+}
+.row-box {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 10px 12px;
+  margin-bottom: 8px;
+  background: #fafbfc;
+  border-radius: 8px;
+  border: 1px solid transparent;
+}
+.row-box:hover {
+  border-color: #e8eaed;
+}
+.user-row__avatar {
+  width: 36px !important;
+  height: 36px !important;
+  flex-shrink: 0;
+}
+.user-row__info {
+  flex: 1;
+  min-width: 0;
+}
+.user-row__name {
+  font-size: 13px;
+  color: #1f2329;
+  line-height: 1.35;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.user-row__id {
+  margin-top: 2px;
+  font-size: 12px;
+  color: #8a919f;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.user-row__pop {
+  text-align: center;
+}
+.user-row__pop-op {
+  cursor: pointer;
+  color: #c45656;
+  font-size: 12px;
+  margin-right: 8px;
+}
+.el-icon-more {
+  color: #c0c4cc;
+  cursor: pointer;
+}
+.live-console-tab-right {
+  height: 100%;
+}
+.live-console-tab-right ::v-deep .el-tabs__content {
+  height: 90%;
+}
+::v-deep .el-scrollbar__wrap {
+  overflow-x: hidden;
+}
+.tcplayer-live {
+  width: 100%;
+}
+::v-deep .tcplayer {
+  margin: 0 auto;
 }
-  .live-console {
-    width: 90vw;
-    padding: 10px 0;
-  }
-  .live-console .live-console-col {
-    height: 88vh;
-    margin-left: 5px;
-    padding: 0 10px;
-    background-color: white;
-    border-radius: 4px;
-  }
-  /*隐藏水平滚动条*/
-  ::v-deep .el-scrollbar__wrap {
-    overflow-x: hidden;
-  }
-  /* 消息输入区域 */
-  .chat-input {
-    display: flex;
-    padding: 10px;
-    border-top: 1px solid #ebeef5;
-    background-color: #fff;
-    min-height: 120px;
-  }
-
-  .chat-input .el-input {
-    flex: 1;
-    margin-right: 10px;
-  }
-
-  .chat-input .el-textarea__inner {
-    resize: none;
-    min-height: 100px;
-  }
-  ::v-deep .el-textarea__inner {
-    border: none !important;
-    box-shadow: none !important;
-    resize: none !important;
-  }
-  ::v-deep .el-textarea__inner:focus {
-    border: none !important;
-    box-shadow: none !important;
-  }
-  .tcplayer-live {
-    width: 100%;
-  }
-  ::v-deep .tcplayer {
-    margin: 0 auto;
-  }
 </style>
 

+ 46 - 75
src/views/live/liveData/index.vue

@@ -1,16 +1,14 @@
 <template>
-  <div class="el-container-md">
-    <!-- 数据统计指标展示区域 -->
-    <el-card class="statistics-card" shadow="never">
-      <el-row :gutter="20">
-        <el-col :span="4">
-          <div class="statistics-item">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">直播数据</h2>
+    </div>
+    <div class="live-stats-grid">
+      <div class="statistics-item">
             <div class="statistics-title">累计观看人数</div>
             <div class="statistics-value">{{ statisticsData.totalViewers || 0 }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">
               累计完课人数
               <el-popover placement="top" width="200" trigger="click">
@@ -23,15 +21,11 @@
             </div>
             <div class="statistics-value">{{ statisticsData.totalCompletedCourses || 0 }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">直播观看人数</div>
             <div class="statistics-value">{{ statisticsData.liveViewers || 0 }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">
               直播完课人数
               <el-popover placement="top" width="200" trigger="click">
@@ -44,15 +38,11 @@
             </div>
             <div class="statistics-value">{{ statisticsData.liveCompletedCourses || 0 }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">回放观看人数</div>
             <div class="statistics-value">{{ statisticsData.playbackViewers || 0 }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">
               回放完课人数
               <el-popover placement="top" width="200" trigger="click">
@@ -65,58 +55,39 @@
             </div>
             <div class="statistics-value">{{ statisticsData.playbackCompletedCourses || 0 }}</div>
           </div>
-        </el-col>
-      </el-row>
-      <el-row :gutter="20" style="margin-top: 20px;">
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">直播峰值</div>
             <div class="statistics-value">{{ statisticsData.livePeak || 0 }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">直播平均时长</div>
             <div class="statistics-value">{{ formatDuration(statisticsData.liveAvgDuration) }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">回放平均时长</div>
             <div class="statistics-value">{{ formatDuration(statisticsData.playbackAvgDuration) }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">GMV</div>
             <div class="statistics-value">¥{{ statisticsData.gmv || 0 }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">付费人数</div>
             <div class="statistics-value">{{ statisticsData.paidUsers || 0 }}</div>
           </div>
-        </el-col>
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">付费单数</div>
             <div class="statistics-value">{{ statisticsData.paidOrders || 0 }}</div>
           </div>
-        </el-col>
-      </el-row>
-      <el-row :gutter="20" style="margin-top: 20px;">
-        <el-col :span="4">
-          <div class="statistics-item">
+      <div class="statistics-item">
             <div class="statistics-title">销量统计</div>
             <div class="statistics-value">{{ statisticsData.salesCount || 0 }}</div>
           </div>
-        </el-col>
-      </el-row>
-    </el-card>
+    </div>
 
     <!-- 筛选条件区域 -->
-    <el-form :model="queryParams" class="live-data-css" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+    <div class="live-panel" v-show="showSearch">
+      <el-form :model="queryParams" class="live-data-css live-search-form" ref="queryForm" :inline="true" label-width="100px">
       <el-form-item label="直播名称" prop="liveName">
         <el-input
           v-model="queryParams.liveName"
@@ -169,9 +140,11 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
     <!-- 操作工具栏 -->
-    <div class="selection-toolbar">
+    <div class="live-panel live-panel--table">
+<div class="selection-toolbar">
       <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
         {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
       </el-checkbox>
@@ -193,17 +166,20 @@
       <el-table-column prop="liveName" label="直播间名称" min-width="160" show-overflow-tooltip></el-table-column>
       <el-table-column prop="liveType" label="直播类型" width="100" align="center">
         <template slot-scope="scope">
-          <el-tag type="danger" v-if="scope.row.liveType == 1">直播</el-tag>
-          <el-tag type="success" v-else-if="scope.row.liveType == 2">录播</el-tag>
-          <el-tag v-else-if="scope.row.liveType == 3">直播回放</el-tag>
+          <span v-if="scope.row.liveType == 1" class="live-muted">直播</span>
+          <span v-else-if="scope.row.liveType == 2" class="live-muted">录播</span>
+          <span v-else-if="scope.row.liveType == 3" class="live-muted">直播回放</span>
           <span v-else>-</span>
         </template>
       </el-table-column>
       <el-table-column prop="status" label="状态" width="100" align="center">
         <template slot-scope="scope">
-          <el-tag type="info" v-if="scope.row.status == 1">未开始</el-tag>
-          <el-tag type="warning" v-else-if="scope.row.status == 2">进行中</el-tag>
-          <el-tag type="success" v-else-if="scope.row.status == 3">已结束</el-tag>
+          <span class="live-status" :class="'is-' + scope.row.status" v-if="scope.row.status == 1 || scope.row.status == 2 || scope.row.status == 3">
+            <i class="live-status__dot"></i>
+            <template v-if="scope.row.status == 1">未开始</template>
+            <template v-else-if="scope.row.status == 2">进行中</template>
+            <template v-else-if="scope.row.status == 3">已结束</template>
+          </span>
           <span v-else>-</span>
         </template>
       </el-table-column>
@@ -243,6 +219,7 @@
       @pagination="getList"
       style="margin-top: 20px;"
     />
+    </div>
 
     <!-- 自动打标签弹窗 -->
     <el-dialog
@@ -979,30 +956,25 @@ export default {
 </script>
 
 <style scoped>
-.statistics-card {
-  margin-bottom: 20px;
-}
-
-.statistics-item {
+.live-stats-grid .statistics-item {
   text-align: center;
-  padding: 15px;
-  background: #f5f7fa;
-  border-radius: 4px;
+  padding: 10px 8px;
 }
 
-.statistics-title {
-  font-size: 14px;
-  color: #606266;
-  margin-bottom: 10px;
+.live-stats-grid .statistics-title {
+  font-size: 12px;
+  color: #8a919f;
+  margin-bottom: 6px;
   display: flex;
   align-items: center;
   justify-content: center;
+  gap: 2px;
 }
 
-.statistics-value {
-  font-size: 24px;
-  font-weight: bold;
-  color: #303133;
+.live-stats-grid .statistics-value {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1f2329;
 }
 
 .selection-toolbar {
@@ -1017,8 +989,7 @@ export default {
 }
 
 .live-data-css {
-  padding-left: 10px;
-  padding-top: 30px;
+  padding: 0;
 }
 
 .detail-content {

+ 14 - 6
src/views/live/liveGoods/index.vue

@@ -1,7 +1,12 @@
 <template>
-  <div class="el-container-md">
+  <div class="app-container live-page">
 
-    <el-form :model="queryParams" class="live-goods-css" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播商品</h2>
+    </div>
+
+    <div class="live-panel" v-show="showSearch">
+      <el-form :model="queryParams" class="live-goods-css live-search-form" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="商品名称" prop="keywords" >
         <el-input
           v-model="queryParams.keywords"
@@ -25,8 +30,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <div class="selection-toolbar">
+    <div class="live-panel live-panel--table">
+<div class="selection-toolbar">
       <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
         {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
       </el-checkbox>
@@ -56,7 +63,7 @@
         <template slot-scope="scope">
           <img
             :src="scope.row.imgUrl"
-            style="display: block; max-width: 100%; width: 100px; height: 100px"
+            class="live-cell__cover live-cell__cover--square" style="object-fit: cover;"
           />
         </template>
 
@@ -87,8 +94,8 @@
         label="上下架"
       >
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.status == 1">上架</el-tag>
-          <el-tag v-if="scope.row.status == 0">下架</el-tag>
+          <span v-if="scope.row.status == 1" class="live-ok">上架</span>
+          <span v-if="scope.row.status == 0" class="live-muted">下架</span>
         </template>
       </el-table-column>
 
@@ -118,6 +125,7 @@
       @pagination="getLiveGoodsList"
       style="margin-top: 20px;"
     />
+    </div>
 
     <!-- 添加商品弹窗 -->
     <el-dialog

+ 14 - 5
src/views/live/liveLotteryConf/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">抽奖配置</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="直播间ID" prop="liveId">
         <el-input
           v-model="queryParams.liveId"
@@ -43,8 +48,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -91,7 +98,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="liveLotteryConfList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="liveLotteryConfList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="抽奖ID" align="center" prop="lotteryId" />
       <el-table-column label="操作商品" align="center" class-name="small-padding fixed-width">
@@ -166,7 +173,9 @@
     />
 
     <!-- 添加或修改直播抽奖配置对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="直播间ID" prop="liveId">
           <el-input v-model="form.liveId" placeholder="请输入直播间ID"

+ 14 - 5
src/views/live/liveLotteryProductConf/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">抽奖商品配置</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="抽奖ID" prop="lotteryId">
         <el-input
           v-model="queryParams.lotteryId"
@@ -77,8 +82,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -125,7 +132,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="liveLotteryProductConfList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="liveLotteryProductConfList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="ID" align="center" prop="id" />
       <el-table-column label="抽奖ID" align="center" prop="lotteryId" />
@@ -164,7 +171,9 @@
     />
 
     <!-- 添加或修改直播抽奖记录对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="抽奖ID" prop="lotteryId">
           <el-input v-model="form.lotteryId" placeholder="请输入抽奖ID" />

+ 14 - 5
src/views/live/liveMsg/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">直播消息</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="直播ID" prop="liveId">
         <el-input
           v-model="queryParams.liveId"
@@ -33,8 +38,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -81,7 +88,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="liveMsgList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="liveMsgList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="id" align="center" prop="msgId" />
       <el-table-column label="直播ID" align="center" prop="liveId" />
@@ -117,7 +124,9 @@
     />
 
     <!-- 添加或修改直播讨论对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="直播ID" prop="liveId">
           <el-input v-model="form.liveId" placeholder="请输入直播ID" />

+ 11 - 3
src/views/live/liveOrder/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播订单</h2>
+    </div>
+
+    <div class="live-panel" v-show="showSearch">
+      <el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="所属部门" prop="deptId">
         <treeselect style="width:220px" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择所属部门" />
       </el-form-item>
@@ -173,8 +178,10 @@
         </el-form-item>
       </div>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="warning"
@@ -244,6 +251,7 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
+    </div>
     <el-drawer
       :with-header="false"
       size="75%"

+ 120 - 76
src/views/live/liveOrder/liveOrderDetails.vue

@@ -1,9 +1,9 @@
 <template>
-  <div style="background-color: #f0f2f5; padding-bottom: 20px; min-height: 100%; " >
-    <div style="padding: 20px; background-color: #fff;">
-      订单详情
+  <div class="live-page live-order-detail">
+    <div class="live-page__header">
+      <h2 class="live-page__title">订单详情</h2>
     </div>
-    <div class="contentx" v-if="item!=null">
+    <div class="contentx live-panel" v-if="item!=null">
       <div class="desct"></div>
       <div class="order-status" v-if="item!=null" >
         <el-steps  :active="item.status==4?item.status:item.status" align-center finish-status="success">
@@ -13,9 +13,9 @@
           <el-step title="已完成"></el-step>
         </el-steps>
       </div>
-      <el-card shadow="never" style="margin-top: 15px">
+      <el-card shadow="never" class="detail-card">
         <div class="operate-container"  v-if="item!=null">
-               <span style="margin-left: 20px" class="color-danger">订单状态:
+               <span class="operate-status color-danger">订单状态:
                   <el-tag prop="status" v-for="(ite, index) in orderOptions"    v-if="item.status==ite.dictValue">{{ite.dictLabel}}</el-tag>
                </span>
 <!--          <div class="operate-button-container" v-if="item.status==3">-->
@@ -81,10 +81,10 @@
           <el-descriptions-item label="店铺名称"  ><span v-if="item!=null">{{item.storeName}}</span></el-descriptions-item>
           <el-descriptions-item label="收货人" >  <span v-if="item!=null">{{item.userName}}</span>  </el-descriptions-item>
           <el-descriptions-item label="收货人电话" > <span v-if="item!=null">{{item.userPhone}}</span>
-            <el-button icon="el-icon-search" size="mini" @click="handlePhone()" style="margin-left: 20px;" circle v-hasPermi="['live:liveOrder:queryPhone']"></el-button>
+            <el-button icon="el-icon-search" size="mini" @click="handlePhone()" class="inline-search-btn" circle v-hasPermi="['live:liveOrder:queryPhone']"></el-button>
           </el-descriptions-item>
           <el-descriptions-item label="详细地址" > <span v-if="item!=null">{{item.userAddress}}</span>
-            <el-button icon="el-icon-search" size="mini" @click="handleAddress()" style="margin-left: 20px;" circle v-hasPermi="['live:liveOrder:queryAddress']"></el-button>
+            <el-button icon="el-icon-search" size="mini" @click="handleAddress()" class="inline-search-btn" circle v-hasPermi="['live:liveOrder:queryAddress']"></el-button>
           </el-descriptions-item>
           <el-descriptions-item label="所属公司"><span v-if="item!=null">{{item.companyName}}</span></el-descriptions-item>
           <el-descriptions-item label="员工"><span v-if="item!=null">{{item.companyUserName}}</span></el-descriptions-item>
@@ -112,17 +112,17 @@
       </el-card>
     </div>
 
-    <div class="contentx" v-if="item!=null" style="padding-bottom: 70px;">
+    <div class="contentx live-panel contentx--pad" v-if="item!=null">
       <div class="desct">
         商品信息
       </div>
-      <el-tooltip class="item" effect="dark" :content="showList ? '显示全部' : '隐藏'" placement="top" style="float: right;">
+      <el-tooltip class="item toggle-tip" effect="dark" :content="showList ? '显示全部' : '隐藏'" placement="top">
         <el-button size="mini" circle icon="el-icon-search" @click="showListD()" />
       </el-tooltip>
-      <el-table border v-if="showProd!=null" :data="showProd" size="small" style="width: 100%;margin-top: 20px" >
+      <el-table border v-if="showProd!=null" :data="showProd" size="small" class="detail-table" >
         <el-table-column label="商品图片" width="150" align="center">
           <template slot-scope="scope">
-            <img :src="scope.row.imgUrl" style="height: 80px">
+            <img :src="scope.row.imgUrl" class="goods-thumb">
           </template>
         </el-table-column>
         <el-table-column label="商品编码" width="300" align="center">
@@ -161,15 +161,15 @@
 <!--          </template>-->
 <!--        </el-table-column>-->
       </el-table>
-      <div style="float: right;margin: 20px" v-if="item.totalPrice!=null">
+      <div class="section-total" v-if="item.totalPrice!=null">
         合计:<span class="color-danger">¥{{item.totalPrice.toFixed(2)}}</span>
       </div>
 
-      <div class="contentx" v-if="item!=null" style="padding-bottom: 70px;">
+      <div class="contentx live-panel contentx--pad fee-panel" v-if="item!=null">
         <div class="desct">
           费用信息
         </div>
-        <el-tooltip class="item" effect="dark" :content="showList ? '显示全部' : '隐藏'" placement="top" style="float: right;">
+        <el-tooltip class="item toggle-tip" effect="dark" :content="showList ? '显示全部' : '隐藏'" placement="top">
           <el-button size="mini" circle icon="el-icon-search" @click="showListD()" />
         </el-tooltip>
         <el-descriptions :column="3" border  >
@@ -190,14 +190,14 @@
 <!--          <span v-else-if="item!=null">¥0.00</span>-->
 <!--        </el-descriptions-item>-->
         </el-descriptions>
-        <div style="float: right;margin: 20px" v-if="item.payMoney!=null">
+        <div class="section-total" v-if="item.payMoney!=null">
           合计:<span class="color-danger">¥{{item.payMoney.toFixed(2)}}</span>
         </div>
       </div>
 
     </div>
 
-    <div class="contentx" v-if="payments!=null" style="padding-bottom: 70px;">
+    <div class="contentx live-panel contentx--pad" v-if="payments!=null">
       <div class="desct">
         支付信息
       </div>
@@ -205,7 +205,7 @@
       border
       :data="payments"
       size="small"
-      style="width: 100%;margin-top: 20px" >
+      class="detail-table" >
       <el-table-column label="支付单号" align="center" prop="payCode" width="120px" />
       <el-table-column label="支付金额" align="center" prop="payMoney" />
       <el-table-column label="类型" align="center" prop="payTypeCode" />
@@ -265,11 +265,11 @@
 
     <el-dialog :title="editDy.title" :visible.sync="editDy.open" width="600px" append-to-body>
       <el-form ref="editDyForm" :model="editDyForm" :rules="editDyRules" label-width="100px">
-        <div  v-hasPermi="['live:liveOrder:updateErpOrder']"  style="margin-bottom: 20px;" >
+        <div  v-hasPermi="['live:liveOrder:updateErpOrder']"  class="dialog-action-bar" >
           <el-button size="mini" @click="updateErpOrder" >同步物流单号信息</el-button>
         </div>
         <el-form-item label="物流公司" prop="deliveryCode" >
-          <el-select style="width:220px" v-model="editDyForm.deliveryCode" placeholder="请选择" clearable size="small">
+          <el-select class="delivery-select" v-model="editDyForm.deliveryCode" placeholder="请选择" clearable size="small">
             <el-option key="SF"  label="顺丰" value="SF" />
             <el-option key="EMS"  label="邮政" value="EMS" />
             <el-option key="ZTO"  label="中通" value="ZTO" />
@@ -292,7 +292,7 @@
       <div  v-hasPermi="['his:storeOrder:syncExpress']"  >
         <el-button size="mini" @click="syncExpress()" >同步快递鸟物流状态</el-button>
       </div>
-      <el-table style="margin-top: 20px;width: 100%"
+      <el-table class="detail-table"
                 ref="orderHistoryTable"
                 :data="traces" border>
         <el-table-column label="操作时间"  width="160" align="center">
@@ -970,99 +970,143 @@ export default {
 
 
 <style scoped>
-.content{
-  height: 100%;
-  background-color: #fff;
-  padding: 0px 20px;
-
+.live-order-detail {
+  min-height: 100%;
+  padding-bottom: 20px;
+  background-color: #f4f6f8;
 }
-.contentx{
-  height: 100%;
-  background-color: #fff;
-  padding: 0px 20px 20px;
 
-
-  margin: 20px;
+.live-order-detail .live-page__header {
+  margin: 0 0 16px;
+  padding: 16px 20px;
+  background: #fff;
+  border: 1px solid #e8eaed;
+  border-radius: 10px;
 }
-.el-descriptions-item__label.is-bordered-label{
-  font-weight: normal;
+
+.contentx {
+  margin: 0 0 14px;
+  padding: 14px 16px 16px;
 }
-.el-descriptions-item__content {
-  max-width: 150px;
-  min-width: 100px;
+
+.contentx--pad {
+  padding-bottom: 24px;
 }
-.desct{
-  padding-top: 20px;
-  padding-bottom: 20px;
-  color: #524b4a;
-  font-weight: bold;
+
+.fee-panel {
+  margin-top: 16px;
+  clear: both;
 }
 
-.order-content{
-  margin: 10px;
+.detail-card {
+  margin-top: 15px;
+  border: 1px solid #e8eaed;
+  border-radius: 10px;
+}
 
+.desct {
+  padding: 12px 0;
+  color: #1f2329;
+  font-size: 15px;
+  font-weight: 600;
 }
 
 .operate-container {
-  background: #F2F6FC;
-  height: 60px;
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 8px 10px;
+  background: #f4f6f8;
   margin: -20px -20px 0;
-  line-height: 60px;
+  padding: 12px 16px;
+  border-bottom: 1px solid #e8eaed;
+  min-height: auto;
+  line-height: 1.4;
+}
+
+.operate-status {
+  margin-right: auto;
 }
 
 .operate-button-container {
+  float: none;
+  margin: 0;
+}
+
+.inline-search-btn {
+  margin-left: 12px;
+}
+
+.toggle-tip {
   float: right;
-  margin-right: 20px
 }
-</style>
 
-<style scoped>
-.chat-records {
+.detail-table {
+  width: 100%;
+  margin-top: 16px;
+}
+
+.goods-thumb {
+  height: 80px;
+  border-radius: 8px;
+  object-fit: cover;
+}
+
+.section-total {
+  float: right;
+  margin: 20px 0;
+  color: #1f2329;
+}
+
+.dialog-action-bar {
+  margin-bottom: 20px;
+}
+
+.delivery-select {
+  width: 220px;
+}
 
+.el-descriptions-item__label.is-bordered-label {
+  font-weight: normal;
+  color: #8a919f;
+}
+
+.el-descriptions-item__content {
+  max-width: 150px;
+  min-width: 100px;
+  color: #1f2329;
+}
+
+.chat-records {
   overflow-y: auto;
 }
+
 .timestamp {
   font-size: 12px;
-  color: #A9A9A9;
+  color: #8a919f;
 }
+
 .chat-record {
   margin: 10px;
   flex-direction: column;
   align-items: flex-start;
 }
+
 .sent {
   background-color: #fbfdff;
-  color: #000839;
+  color: #1f2329;
 }
+
 .sent .timestamp {
   float: right;
 }
-.right{
+
+.right {
   float: right;
 }
+
 .received {
   background-color: #fbfdff;
-  color: #000000;
-}
-
-.el-descriptions-item__content {
-  max-width: 150px;
-  min-width: 100px;
-}
-.order-content{
-  margin: 10px;
-
-}
-
-.operate-container {
-  background: #F2F6FC;
-  height: 60px;
-  margin: -20px -20px 0;
-  line-height: 60px;
-}
-
-.operate-button-container {
-  float: right;
-  margin-right: 20px
+  color: #1f2329;
 }
 </style>

+ 11 - 3
src/views/live/liveOrder/myIndex.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">我的直播订单</h2>
+    </div>
+
+    <div class="live-panel" v-show="showSearch">
+      <el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="所属部门" prop="deptId">
         <treeselect style="width:220px" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择所属部门" />
       </el-form-item>
@@ -142,8 +147,10 @@
         </el-form-item>
       </div>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
 
       <el-col :span="1.5">
         <el-button
@@ -214,6 +221,7 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
+    </div>
     <el-drawer
       :with-header="false"
       size="75%"

+ 14 - 5
src/views/live/liveOrderitems/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">订单明细</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="商品ID" prop="goodsId">
         <el-input
           v-model="queryParams.goodsId"
@@ -56,8 +61,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -104,7 +111,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="liveOrderitemsList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="liveOrderitemsList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="ID" align="center" prop="id" />
       <el-table-column label="商品ID" align="center" prop="goodsId" />
@@ -144,7 +151,9 @@
     />
 
     <!-- 添加或修改订单商品对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="商品ID" prop="goodsId">
           <el-input v-model="form.goodsId" placeholder="请输入商品ID" />

+ 17 - 7
src/views/live/liveProfit/index.vue

@@ -1,8 +1,13 @@
 <template>
-  <div class="app-container">
-  <div class="el-container-md">
+  <div class="app-container live-page">
+      <div class="live-page__header">
+      <h2 class="live-page__title">直播收益</h2>
+    </div>
 
-    <el-form :model="queryParams" class="live-goods-css" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+<div class="el-container-md">
+
+    <div class="live-panel" v-show="showSearch">
+<el-form :model="queryParams" class="live-goods-css live-search-form" ref="queryForm" :inline="true" label-width="100px">
       <el-form-item label="直播名称" prop="liveName">
         <el-input
           v-model="queryParams.liveName"
@@ -44,8 +49,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-<!--    <div class="selection-toolbar">-->
+<!--    <div class="live-panel live-panel--table">
+<div class="selection-toolbar">-->
 <!--      <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">-->
 <!--        {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}-->
 <!--      </el-checkbox>-->
@@ -53,7 +60,10 @@
 <!--      <el-button  plain size="mini" @click="handleUnshelf">下架</el-button>-->
 <!--      <el-button  plain size="mini" @click="handleDeleteSelected">删除</el-button>-->
 <!--      <el-button  plain type="mini" icon="el-icon-plus" @click="handleAddLiveGoods">添加商品</el-button>-->
-<!--    </div>-->
+<!--
+    </div>
+
+</div>-->
     <el-table
       ref="dataTable"
       :data="dataList"
@@ -120,8 +130,8 @@
 <!--        label="是否支付"-->
 <!--      >-->
 <!--        <template slot-scope="scope">-->
-<!--          <el-tag type="success" v-if="scope.row.isPay == 1">已支付</el-tag>-->
-<!--          <el-tag type="info" v-if="scope.row.isPay == 0">未支付</el-tag>-->
+<!--          <el-tag v-if="scope.row.isPay == 1">已支付</el-tag>-->
+<!--          <el-tag v-if="scope.row.isPay == 0">未支付</el-tag>-->
 <!--        </template>-->
 <!--      </el-table-column>-->
 <!--      <el-table-column-->

+ 14 - 5
src/views/live/liveQuestion/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">直播答题</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="用户ID" prop="userId">
         <el-input
           v-model="queryParams.userId"
@@ -41,8 +46,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -89,7 +96,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="liveQuestionList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="liveQuestionList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="ID" align="center" prop="questionId" />
       <el-table-column label="用户ID" align="center" prop="userId" />
@@ -130,7 +137,9 @@
     />
 
     <!-- 添加或修改问答对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="用户ID" prop="userId">
           <el-input v-model="form.userId" placeholder="请输入用户ID" />

+ 12 - 4
src/views/live/liveQuestionBank/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="live-question-bank-container">
-    <div class="question-bank-container">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">题库管理</h2>
+    </div>
+
+<div class="live-panel live-panel--table">
+<div class="question-bank-container">
         <div class="question-bank-header">
             <el-button
               type="primary"
@@ -18,7 +23,7 @@
             </div>
         </div>
 
-        <el-table :data="questionBankList" style="width: 100%; margin-top: 20px;" v-loading="loading">
+        <el-table class="live-table" :data="questionBankList" style="width: 100%; margin-top: 20px;" v-loading="loading">
             <el-table-column prop="title" label="题干内容" width="500"></el-table-column>
             <el-table-column label="题型" width="100">
               <template slot-scope="scope">
@@ -52,6 +57,10 @@
           :limit.sync="queryParams.pageSize"
           @pagination="getQuestionBankList"
         />
+    </div>
+
+
+    </div>
 
         <!-- 添加试题弹窗 -->
         <el-dialog
@@ -123,7 +132,6 @@
             >保存并继续添加</el-button>
           </div>
         </el-dialog>
-    </div>
   </div>
 </template>
 

+ 14 - 5
src/views/live/liveRedConf/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">红包配置</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="红包状态" prop="redStatus">
         <el-select v-model="queryParams.redStatus" placeholder="请选择红包状态" clearable size="small">
           <el-option v-for="(item,index) in redStatusOptions" :key="item.dictValue+index" :label="item.dictLabel" :value="item.dictValue" />
@@ -51,8 +56,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -88,7 +95,7 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table border v-loading="loading" :data="liveRedConfList" @selection-change="handleSelectionChange">
+    <el-table border v-loading="loading" :data="liveRedConfList" @selection-change="handleSelectionChange" class="live-table">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="红包ID" align="center" prop="redId" />
       <el-table-column label="红包状态" align="center" prop="redStatus" :formatter="redStatusFormatter"/>
@@ -164,7 +171,9 @@
     />
 
     <!-- 添加或修改直播红包记录配置对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    </div>
+
+<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="持续时间" prop="duration">
           <el-input v-model="form.duration" placeholder="请输入红包有效时间 单位:分" />

+ 10 - 4
src/views/live/liveRewardRecord/index.vue

@@ -1,6 +1,10 @@
 <template>
-  <div class="app-container">
-    <div class="reward-container">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">打赏记录</h2>
+    </div>
+
+<div class="reward-container">
       <!-- 时间步长选择 -->
       <span class="demonstration">月份选择(可多选)</span>
       <el-date-picker
@@ -28,13 +32,14 @@
     <!-- 直播间数据表格 + 总消耗 -->
     <div style="margin-top: 24px;">
       <h3 style="font-size: 16px; font-weight: 600; margin-bottom: 12px;">直播间积分消耗明细</h3>
-      <el-table
+      <div class="live-panel live-panel--table">
+<el-table
         :data="tableData"
         border
         show-summary
         stripe
         style="width: 100%; margin-bottom: 12px;"
-      >
+       class="live-table">
         <el-table-column label="直播名称" prop="liveNameAndId" width="100" align="center" />
         <el-table-column
           v-for="(date, index) in timeSeries"
@@ -48,6 +53,7 @@
         </el-table-column>
         <el-table-column label="单直播间总计" prop="total" width="120" align="center" />
       </el-table>
+      </div>
     </div>
   </div>
 </template>

+ 72 - 59
src/views/live/liveVideo/index.vue

@@ -1,66 +1,52 @@
 <template>
-  <div class="app-container">
+  <div class="app-container live-page">
 
-<!--    <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:liveVideo:add']"-->
-<!--        >新增</el-button>-->
-<!--      </el-col>-->
-<!--      <el-col :span="1.5">-->
-<!--        <el-button-->
-<!--          type="danger"-->
-<!--          plain-->
-<!--          icon="el-icon-delete"-->
-<!--          size="mini"-->
-<!--          :disabled="multiple"-->
-<!--          @click="handleDelete"-->
-<!--          v-hasPermi="['live:liveVideo:remove']"-->
-<!--        >删除</el-button>-->
-<!--      </el-col>-->
-<!--      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>-->
-<!--    </el-row>-->
+    <div class="live-page__header">
+      <h2 class="live-page__title">直播视频</h2>
+    </div>
 
-    <el-table border v-loading="loading" :data="liveVideoList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="编号" align="center" prop="videoId" />
-      <el-table-column label="视频名称" align="center" prop="videoName" />
-      <el-table-column label="分类" align="center" prop="category" />
-      <el-table-column label="排序" align="center" prop="sort" />
-      <el-table-column label="备注" align="center" prop="remark" />
-      <el-table-column label="视频地址" align="center" prop="videoUrl">
-        <template slot-scope="scope">
-          <video
-            :src="scope.row.videoUrl"
-            controls
-            controlsList="nodownload"
-            class="video-player"
-            @contextmenu.prevent
-          ></video>
-        </template>
-      </el-table-column>
-      <el-table-column label="时长" align="center" prop="duration" :formatter="formatDuration"/>
-      <el-table-column label="转码状态" align="center" prop="finishStatus">
-        <template slot-scope="scope">
-          <el-tag v-if="scope.row.finishStatus == 1">转码成功</el-tag>
-          <el-tag v-else-if="scope.row.finishStatus == 0">转码中</el-tag>
-          <el-tag v-else>---</el-tag>
-        </template>
-      </el-table-column>
-    </el-table>
+    <div class="live-panel live-panel--table">
+      <el-table border v-loading="loading" :data="liveVideoList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="编号" align="center" prop="videoId" />
+        <el-table-column label="视频名称" align="center" prop="videoName" />
+        <el-table-column label="分类" align="center" prop="category" />
+        <el-table-column label="排序" align="center" prop="sort" />
+        <el-table-column label="备注" align="center" prop="remark" />
+        <el-table-column label="视频" align="center" prop="videoUrl" width="96">
+          <template slot-scope="scope">
+            <div class="live-video-thumb" @click="openVideoPlay(scope.row)">
+              <img
+                v-if="scope.row.thumbnail"
+                :src="scope.row.thumbnail"
+                class="live-cell__cover live-cell__cover--square"
+                alt=""
+              />
+              <div v-else class="live-cell__cover live-cell__cover--square live-video-thumb__empty">
+                <i class="el-icon-video-camera"></i>
+              </div>
+              <i class="el-icon-video-play live-video-thumb__play"></i>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="时长" align="center" prop="duration" :formatter="formatDuration"/>
+        <el-table-column label="转码状态" align="center" prop="finishStatus">
+          <template slot-scope="scope">
+            <span v-if="scope.row.finishStatus == 1" class="live-ok">转码成功</span>
+            <span v-else-if="scope.row.finishStatus == 0" class="live-warn">转码中</span>
+            <span v-else class="live-muted">---</span>
+          </template>
+        </el-table-column>
+      </el-table>
 
-    <pagination
-      v-show="total>0"
-      :total="total"
-      :page.sync="queryParams.pageNum"
-      :limit.sync="queryParams.pageSize"
-      @pagination="getList"
-    />
+      <pagination
+        v-show="total>0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
 
     <!-- 添加或修改直播视频对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
@@ -100,6 +86,23 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+    <el-dialog
+      title="视频播放"
+      :visible.sync="videoPlayVisible"
+      width="720px"
+      append-to-body
+      @closed="currentPlayUrl = ''"
+    >
+      <video
+        v-if="currentPlayUrl"
+        :src="currentPlayUrl"
+        controls
+        controlsList="nodownload"
+        class="live-video-dialog-player"
+        @contextmenu.prevent
+      ></video>
+    </el-dialog>
+
   </div>
 </template>
 
@@ -112,6 +115,8 @@ export default {
   components: { LiveVideoUpload },
   data() {
     return {
+      videoPlayVisible: false,
+      currentPlayUrl: '',
       //字典
       videoTypeOptions: [],
       // 遮罩层
@@ -159,6 +164,14 @@ export default {
     this.getList();
   },
   methods: {
+    openVideoPlay(row) {
+      if (!row || !row.videoUrl) {
+        this.$message.warning('暂无视频地址');
+        return;
+      }
+      this.currentPlayUrl = row.videoUrl;
+      this.videoPlayVisible = true;
+    },
     handleVideoDuration(duration) {
       this.form.duration = duration;
     },

+ 8 - 2
src/views/live/liveWatchUser/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+  <div class="app-container live-page">
+        <div class="live-page__header">
+      <h2 class="live-page__title">观看用户</h2>
+    </div>
+
+<div class="live-panel" v-show="showSearch">
+<el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="68px">
       <el-form-item label="直播ID" prop="liveId">
         <el-input
           v-model="queryParams.liveId"
@@ -15,6 +20,7 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
   </div>
 </template>
 

+ 11 - 3
src/views/live/words/index.vue

@@ -1,6 +1,11 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="80px">
+  <div class="app-container live-page">
+    <div class="live-page__header">
+      <h2 class="live-page__title">敏感词</h2>
+    </div>
+
+    <div class="live-panel" v-show="showSearch">
+      <el-form class="live-search-form" :model="queryParams" ref="queryForm" :inline="true" label-width="80px">
       <el-form-item label="敏感词" prop="word">
         <el-input
           v-model="queryParams.word"
@@ -23,8 +28,10 @@
         <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
+    </div>
 
-    <el-row :gutter="10" class="mb8">
+    <div class="live-panel live-panel--table">
+<el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
         <el-button
           type="primary"
@@ -107,6 +114,7 @@
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
     />
+    </div>
 
     <!-- 添加或修改直播间敏感词过滤对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>