<template>
  <!-- 直播中控台 start -->
  <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 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>
                  <div class="msg-box">{{ m.msg }}</div>
                </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">
            <el-input
              type="textarea"
              v-model="newMsg"
              placeholder="请输入消息..."
              :rows="3"
              @keyup.enter.native="sendMessage"
              clearable
              resize="none"
            >
            </el-input>
            <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>
      </el-tabs>
    </el-col>
    <!-- 聊天 end -->


    <!-- 直播/视频 start -->
    <el-col class="live-console-col" :span="12">
      <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 class="player-panel__inner" v-else-if="status != 2 && status != 4">
          <img :src="require('@/assets/image/videoNotStart.png')" class="player-panel__poster">
        </div>
        <div class="player-panel__inner" v-else-if="liveType == 1">
          <video
            id="live-tcplayer-container"
            ref="livingPlayer"
            preload="auto"
            playsinline
            webkit-playsinline
            class="custom-video tcplayer-live"
            style="display: block; background: #000; height: 45vh; width: 100%;"
          ></video>
          <!-- 时间显示（可选） -->
          <div ref="liveElapsedTime" class="time-display">
            已播放：<span id="liveElapsedTime">00:00:00</span>
          </div>
        </div>
        <div class="player-panel__inner" v-else-if="liveType == 2">
          <video
            controls
            ref="videoPlayer"
            loop
            autoplay
            width="100%"
            muted
            playsinline
            @click.prevent
            @contextmenu.prevent
            class="custom-video"
            style="display: block; background: #000; height: 40vh;"
          >
            <source :src="videoUrl" type="application/x-mpegURL">
          </video>
          <!-- 自定义进度条容器 -->
          <div ref="progressBar" class="progress-container">
            <div id="progressBar" class="progress-bar"></div>
          </div>

          <!-- 时间显示（可选） -->
          <div ref="elapsedTime" class="time-display">
            已播放：<span id="elapsedTime">00:00:00</span>
          </div>
        </div>
        <div class="player-panel__inner" v-else-if="liveType == 3">
          <video
            controls
            ref="liveReplay"
            loop
            autoplay
            width="100%"
            playsinline
            style="display: block; background: #000; height: 40vh;"
          >
            <source :src="videoUrl" type="application/x-mpegURL">
          </video>
        </div>
        <div class="player-panel__inner" v-else>
          <img :src="require('@/assets/image/videoNotStart.png')" class="player-panel__poster">
        </div>
      </div>
      <!-- 底部导航栏 -->
      <div class="tool-bar">
        <div class="top-box" @click="handleClickRed">
          <img src="@/assets/image/hbpz.png"/>
          <span>红包配置</span>
        </div>
        <div class="top-box" @click="handleClickLottery">
          <img src="@/assets/image/cjpz.png"/>
          <span>抽奖配置</span>
        </div>
        <div class="top-box" @click="handleClickGoods">
          <img src="@/assets/image/sp.png"/>
          <span>商品</span>
        </div>
        <div class="top-box" @click="handleClickOrder">
          <img src="@/assets/image/zbdd.png"/>
          <span>直播订单</span>
        </div>
        <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 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"
              :key="item.value"
              :label="item.label"
              :value="item.value">
            </el-option>
          </el-select>
          <el-select v-model="searchQuery.timeGranularity" placeholder="请选择" style="width: 150px"  @change="timeGranularityChange">
            <el-option
              v-for="item in timeGranularity"
              :key="item.value"
              :label="item.label"
              :value="item.value">
            </el-option>
          </el-select>
          <!--          <el-button type="primary" @click="applyFilter">搜索</el-button>-->
        </div>
      </div>
    </el-col>
    <!-- 直播/视频 end -->

    <!-- 用户列表 start -->
    <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" 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">
                <a class="user-row__pop-op" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
                <a class="user-row__pop-op" @click="blockUser(u)">拉黑</a>
                <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" 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">
                <a class="user-row__pop-op" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
                <a class="user-row__pop-op" @click="blockUser(u)">拉黑</a>
                <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" 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">
                <a class="user-row__pop-op" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
                <a class="user-row__pop-op" @click="blockUser(u)">拉黑</a>
                <i class="el-icon-more" slot="reference"></i>
              </el-popover>
            </div>
          </el-scrollbar>
        </el-tab-pane>
      </el-tabs>
    </el-col>
    <!-- 顶部消息对话框 -->
    <el-dialog title="发送顶部消息" :visible.sync="topMsgDialogVisible" width="500px">
      <el-form :model="topMsgForm" label-width="100px">
        <el-form-item label="消息内容">
          <el-input
            type="textarea"
            v-model="topMsgForm.msg"
            placeholder="请输入消息内容"
            :rows="3"
          ></el-input>
        </el-form-item>
        <el-form-item label="显示时长">
          <el-input-number
            v-model="topMsgForm.duration"
            :min="1"
            :max="60"
            placeholder="请输入显示时长（分钟）"
          ></el-input-number>
          <span style="margin-left: 10px;">分钟</span>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="topMsgDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="sendTopMessage">确 定</el-button>
      </div>
    </el-dialog>
    <!-- 用户列表 end -->
  </el-row>
  <!-- 直播中控台  end -->
</template>

<script>
import { blockUser,changeUserStatus,getLiveUserTotals, watchUserList } from '@/api/live/liveWatchUser'
import { getLiveVideoByLiveId } from '@/api/live/liveVideo'
import {getLive, getLivingUrl} from '@/api/live/live'
import { getLiveOrderTimeGranularity } from '@/api/live/liveOrder'
import { listLiveMsg,delLiveMsg } from '@/api/live/liveMsg'
import Hls from 'hls.js';
import LiveLotteryConf from '@/views/live/liveConfig/liveLotteryConf.vue'
import LiveRedConf from '@/views/live/liveConfig/liveRedConf.vue'
import LiveGoods from '@/views/live/liveConfig/goods.vue'
import LiveLiveCoupon from '@/views/live/liveConfig/liveCoupon.vue'
import echarts from 'echarts'

export default {
  name: "LiveConsole",
  components: { LiveLotteryConf,LiveRedConf },
  data() {
    return {
      loading:true,
      tabLeft: {
        activeName: "online",
      },
      tabRight: {
        activeName: "talk",
      },
      livingUrl:"",
      videoUrl: "",
      tcPlayer: null,
      tcPlayerSdkReady: false,
      status: 0,
      loadMsgMaxPage: 2,
      liveVideo: {},
      liveWsUrl: process.env.VUE_APP_LIVE_WS_URL + '/app/webSocket',
      userParams:{
        pageNum: 1,
        pageSize: 10,
        liveId: null
      },
      msgParams: {
        pageNum: 1,
        pageSize: 10,
        liveId: null
      },
      userList: [],
      msgList: [],
      newMsg: '',
      isAudit: false,
      myChart: null, // 用于存储 ECharts 实例
      liveType: 1,
      tableRadio: '订单数',
      searchQuery: {timeOptions:'2',timeGranularity:'10',liveId: null},
      timeOptions: [
        {value:'2',label:'最近2小时',key:'2'},
        {value:'4',label:'最近4小时',key:'4'},
        {value:'all',label:'全场',key:'all'},
      ],
      timeGranularity: [
        {value:'10',label:'10分钟',key:'10'},
        {value:'30',label:'30分钟',key:'30'},
        {value:'60',label:'1小时',key:'60'},
      ],
      videoDuration: 0,
      startTime: null,
      processInterval: null,
      // ... 其他数据
      chatScrollTop: 0, // 保存聊天滚动位置
      socket: null,
      userTotal: {
        online: 0,       // 在线总人数
        offline: 0,      // 离线总人数
        silenced: 0      // 禁言总人数
      },
      // 各Tab的显示列表（仅存储当前需要展示的数据）
      onlineDisplayList: [],    // 在线用户显示列表
      offlineDisplayList: [],   // 离线用户显示列表
      silencedDisplayList: [],  // 禁言用户显示列表
      // 各Tab的分页参数
      pageParams: {
        online: {
          currentPage: 1,       // 当前页（下一页加载用）
          pageSize: 20,       // 当前页（下一页加载用）
          prevPage: 0,          // 上一页页码（上一页加载用）
          totalLoaded: 0,       // 已加载总条数
          total: 0,             // 总数据量
          hasMore: true,        // 是否有下一页
          hasPrev: false        // 是否有上一页
        },
        offline: {
          currentPage: 1,
          pageSize: 20,
          prevPage: 0,
          totalLoaded: 0,
          total: 0,
          hasMore: true,
          hasPrev: false
        },
        silenced: {
          currentPage: 1,
          pageSize: 20,
          prevPage: 0,
          totalLoaded: 0,
          total: 0,
          hasMore: true,
          hasPrev: false
        }
      },
      scrLoading: {
        online: { next: false, prev: false },
        offline: { next: false, prev: false },
        silenced: { next: false, prev: false }
      },
      topMsgDialogVisible: false,
      topMsgForm: {
        msg: '',
        duration: 5
      }
    }
  },
  created() {
    // this.getLiveVideo()
    this.getList()
    this.connectWebSocket()
    this.getLive()
    this.searchQuery.liveId = this.liveId
  },
  computed: {
    liveId() {
      return this.$route.params.liveId;
    },
    userId() {
      return this.$store.state.user.user.userId
    },
    companyId() {
      return this.$store.state.user.user.companyId
    },
    onlineLabel() {
      return `在线(${this.userTotal.online})`;
    },
    offlineLabel() {
      return `离线(${this.userTotal.offline})`;
    },
    silencedUserLabel() {
      return `禁言(${this.userTotal.silenced})`;
    }
  },
  mounted() {

    this.$nextTick(() => {
      this.restoreChatScrollPosition();
    });
    this.getEchartsTables();
    // 添加滚动事件监听器
    this.$nextTick(() => {
      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
        this.$refs.manageRightRef.wrap.addEventListener('scroll', this.saveChatScrollPosition);
      }
    });
    this.initScrollListeners();
  },
  beforeDestroy() {
    this.saveTabScrollPositions()
    // 移除滚动监听（避免内存泄漏）
    const scrollRefs = {
      online: this.$refs.manageLeftRef_online,
      offline: this.$refs.manageLeftRef_offline,
      silenced: this.$refs.manageLeftRef_silenced
    };
    Object.keys(scrollRefs).forEach(tabName => {
      const scrollEl = scrollRefs[tabName]?.wrap;
      if (scrollEl) {
        scrollEl.removeEventListener('scroll', () =>
          this.handleTabScroll(tabName, scrollEl)
        );
      }
    })
    this.releaseWebSocket()
  },
  // 使用 deactivated 和 activated 钩子替代 beforeDestroy 和 destroyed
  deactivated() {
    this.saveChatScrollPosition();
    this.destroyTcPlayer();
  },

  activated() {
    this.$nextTick(() => {
      this.restoreChatScrollPosition();
    });
    this.$nextTick(() => {
      if (this.liveType === 1 && this.livingUrl) {
        this.initPlayer();
      } else {
        const video = this.$refs.videoPlayer;
        if (video != null) {
          this.initVideoPlayer(this.liveInfo.startTime);
        }
      }
    });
  },
  methods: {
    deleteMsg(m){
      // 1. 弹出确认对话框
      this.$confirm('此操作将永久删除该消息, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        delLiveMsg(m.msgId).then(res => {
          if (res.code === 200) {
            const index = this.msgList.findIndex(item => item.msgId === m.msgId);
            if (index !== -1) {
              this.msgList.splice(index, 1);
              console.log(`消息 ${m.msgId} 已在本地删除。`);
            }
            let msg = {
              liveId: this.liveId,
              userId: m.userId,
              msg: m.msgId, // 关键：将消息ID发送给后台
              cmd: 'deleteMsg',
            };
            this.socket.send(JSON.stringify(msg));
            // 可以在这里给用户一个删除成功的提示
            this.$message({
              type: 'success',
              message: '消息删除成功!'
            });
          }
        })
      }).catch(() => {
        // 3. 用户点击“取消”或关闭对话框后的回调
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    },
    // 显示顶部消息对话框
    showTopMsgDialog() {
      this.topMsgForm.msg = '';
      this.topMsgForm.duration = 5;
      this.topMsgDialogVisible = true;
    },
    // 发送顶部消息
    sendTopMessage() {
      // 发送前简单校验
      if (this.topMsgForm.msg.trim() === '') {
        this.$message.warning('请输入消息内容');
        return;
      }

      if (!this.topMsgForm.duration || this.topMsgForm.duration < 1) {
        this.$message.warning('请输入有效的显示时长');
        return;
      }

      let msg = {
        msg: this.topMsgForm.msg,
        duration: this.topMsgForm.duration,
        liveId: this.liveId,
        userId: this.userId,
        userType: 1,
        cmd: 'sendTopMsg',
        avatar: this.$store.state.user.user.avatar,
        nickName: this.$store.state.user.user.nickName,
      }

      this.socket.send(JSON.stringify(msg))

      this.topMsgDialogVisible = false;
      this.$message.success('顶部消息发送成功');
    },
    updateVideoPosition(video){
      const currentTime = new Date().getTime(); // 当前时间戳（毫秒）
      const elapsedTime = currentTime - this.startTime; // 已流逝时间（毫秒）

      if (elapsedTime < 0) {
        // 未开播：视频停在初始位置
        video.currentTime = 0;
        return;
      }

      // 已开播：计算视频循环后的位置（流逝时间 % 视频时长）
      const elapsedSeconds = elapsedTime / 1000; // 转换为秒
      const videoPosition = elapsedSeconds % this.videoDuration; // 视频内的播放位置（秒）

      // 设置视频播放位置
      video.currentTime = videoPosition;
    },
    updateProgress() {
      const progressBar = this.$refs.progressBar;
      const elapsedTimeEl = this.$refs.elapsedTime;
      if (!this.videoDuration) return; // 视频时长未加载时不更新

      const currentTime = new Date().getTime();
      const elapsedTime = currentTime - this.startTime; // 总流逝时间（毫秒）

      if (elapsedTime < 0) {
        // 未开播状态
        progressBar.style.width = '0%';
        elapsedTimeEl.textContent = '00:00:00';
        return;
      }

      // 计算进度百分比（基于视频循环）
      const elapsedSeconds = elapsedTime / 1000;
      const videoPosition = elapsedSeconds % this.videoDuration; // 当前在视频中的位置
      const progressPercent = (videoPosition / this.videoDuration) * 100; // 进度百分比

      // 更新进度条宽度
      progressBar.style.width = `${progressPercent}%`;

      // 格式化总流逝时间为“时:分:秒”并显示
      elapsedTimeEl.textContent = this.formatTime(elapsedTime);
    },
    formatTime(ms) {
      const totalSeconds = Math.floor(ms / 1000);
      const hours = Math.floor(totalSeconds / 3600);
      const minutes = Math.floor((totalSeconds % 3600) / 60);
      const seconds = totalSeconds % 60;

      // 补零处理（确保两位数）
      return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    },
    initVideoPlayer: function (startTime) {
      const video = this.$refs.videoPlayer;
      this.hls = new Hls();
      this.hls.attachMedia(video);
      this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
        this.hls.loadSource(this.videoUrl);
        this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
          video.play();
        });
      });
      this.hls.on(Hls.Events.ERROR, (event, data) => {
        console.error('HLS 错误:', data);
      });

      // 1. 初始化开播时间
      startTime = new Date(startTime).getTime();
      this.startTime = startTime;
      // 2. 监听视频元数据加载完成（获取视频时长）
      video.addEventListener('loadedmetadata', () => {
        this.videoDuration = video.duration; // 获取视频时长（秒）

        // 初始化视频播放位置
        this.updateVideoPosition(video);

        // 启动实时进度更新（每秒刷新一次）
        this.processInterval = setInterval(this.updateProgress, 1000);
      });

    },
    // 保存聊天滚动位置
    saveChatScrollPosition() {
      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
        this.chatScrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight;
      }
    },

    // 恢复聊天滚动位置
    restoreChatScrollPosition() {
      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
        this.$refs.manageRightRef.wrap.scrollTop = this.chatScrollTop;
      }
    },
    // ... 其他方法 ...
    timeChange(val) {
      this.searchQuery.timeOptions = val
      this.getEchartsTables(this.searchQuery)
      this.initChart()
    },
    timeGranularityChange(val) {
      this.searchQuery.timeGranularity =  val
      this.getEchartsTables()
      this.initChart()
    },
    getEchartsTables() {
      getLiveOrderTimeGranularity(this.searchQuery).then(res => {
        if (res.code == 200) {
          this.echartsXLine = res.hourlySlots
          this.echartsXValue = res.hourlySlotsValue
          this.initChart()
        }
      })
    },
    initChart() {
      const chartDom = this.$refs.chartContainer;
      this.myChart = echarts.init(chartDom);
      const option = {
        tooltip: {trigger: 'axis'},
        legend: {data: ['订单数']},
        xAxis: {type: 'category', boundaryGap: false, data: this.echartsXLine},
        yAxis: {type: 'value'},
        series: [
          {name: '订单数', type: 'line', data: this.echartsXValue}
        ],
      };
      this.myChart.setOption(option);
    },
    handleClickGoods(){
      this.$router.push({
        name: 'LiveGoods',
        query: {
          liveId: this.liveId
        }
      })
    },
    updateLiveProgress() {
      const elapsedTimeEl = this.$refs.liveElapsedTime;

      const currentTime = new Date().getTime();
      const elapsedTime = currentTime - this.startTime; // 总流逝时间（毫秒）

      if (elapsedTime < 0) {
        elapsedTimeEl.textContent = '00:00:00';
        return;
      }


      // 格式化总流逝时间为“时:分:秒”并显示
      elapsedTimeEl.textContent = this.formatTime(elapsedTime);
    },
    handleClickOrder(){
      this.$router.push({
        name: 'LiveOrder',
        query: {
          liveId: this.liveId
        }
      })
    },
    handleClickCoupon(){
      this.$router.push({
        name: 'LiveLiveCoupon',
        query: {
          liveId: this.liveId
        }
      })
    },
    getLive(){
      getLive(this.liveId).then(res => {
        if (res.code == 200) {
          if (res.data.isAudit != 1) {
            this.$message.error("当前直播间未经审核");
            this.loading = false
            return
          }
          this.isAudit = true
          this.status = res.data.status
          if(res.data.status == 4){
            this.liveType = 3
            this.videoUrl = res.data.videoUrl;
          }else {
            if (res.data.status != 2) {
              this.$message.error("当前直播间未直播");
              this.loading = false
              return
            }
            if (res.data.liveType == 1) {
              this.liveType = 1
              this.livingUrl = res.data.flvHlsUrl
              // this.livingUrl = this.livingUrl.replace("flv","m3u8")
              this.$nextTick(() => {
                this.initPlayer()
              })
              this.startTime = new Date(res.data.startTime).getTime()
              this.processInterval = setInterval(this.updateLiveProgress, 1000);
            } else {
              this.liveType = 2
              this.videoUrl = res.data.videoUrl;
              this.$nextTick(() => {
                this.initVideoPlayer(res.data.startTime)
              })
            }
          }
          this.loading = false
        } else {
          this.$message.error(res.msg)
          this.loading = false
        }
        this.liveInfo = res.data
      })
    },
    handleClickLottery(){
      this.$router.push({
        name: 'LiveLotteryConf',
        query: {
          liveId: this.liveId
        }
      })
    },
    handleClickRed(){
      this.$router.push({
        name: 'LiveRedConf',
        query: {
          liveId: this.liveId
        }
      })
    },
    getLiveUrl(){
      getLivingUrl(this.liveId).then(res=>{
        if(res.code === 200){
          this.livingUrl = res.livingUrl
          this.initPlayer()
        }
      })
    },
    loadTcPlayerSdk() {
      if (this.tcPlayerSdkReady && window.TCPlayer) {
        return Promise.resolve();
      }
      if (this._tcPlayerSdkPromise) {
        return this._tcPlayerSdkPromise;
      }
      const loadScript = (src) => new Promise((resolve, reject) => {
        if (document.querySelector(`script[src="${src}"]`)) {
          resolve();
          return;
        }
        const script = document.createElement('script');
        script.src = src;
        script.onload = () => resolve();
        script.onerror = () => reject(new Error('Failed to load ' + src));
        document.head.appendChild(script);
      });
      const loadCss = (href) => {
        if (document.querySelector(`link[href="${href}"]`)) {
          return;
        }
        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = href;
        document.head.appendChild(link);
      };
      this._tcPlayerSdkPromise = Promise.resolve()
        .then(() => {
          loadCss('https://web.sdk.qcloud.com/player/tcplayer/release/v4.9.1/tcplayer.min.css');
          return loadScript('https://video.sdk.qcloudecdn.com/web/TXLivePlayer-1.3.2.min.js');
        })
        .then(() => loadScript('https://web.sdk.qcloud.com/player/tcplayer/release/v4.9.1/tcplayer.v4.9.1.min.js'))
        .then(() => {
          this.tcPlayerSdkReady = true;
        });
      return this._tcPlayerSdkPromise;
    },
    destroyTcPlayer() {
      if (this.tcPlayer) {
        try {
          this.tcPlayer.dispose();
        } catch (e) {
          console.error('销毁 TCPlayer 失败:', e);
        }
        this.tcPlayer = null;
      }
    },
    initPlayer(){
      const isUrl = this.livingUrl === null || this.livingUrl.trim() === '';
      if (isUrl) {
        console.error('直播地址为空，无法初始化播放器');
        return;
      }
      this.loadTcPlayerSdk().then(() => {
        this.destroyTcPlayer();
        this.$nextTick(() => {
          if (!this.$refs.livingPlayer || !window.TCPlayer) {
            console.error('找不到 video 元素或 TCPlayer 未加载');
            return;
          }
          this.tcPlayer = window.TCPlayer('live-tcplayer-container', {
            sources: [this.livingUrl],
            autoplay: true,
            width: '100%',
            height: '45vh',
            live: true,
            controls: true,
            playsinline: true,
            'webkit-playsinline': true,
            x5_player: true,
            x5_type: 'h5-page',
            x5_fullscreen: false
          });
        });
      }).catch(err => {
        console.error('TCPlayer SDK 加载失败:', err);
        this.$message.error('直播播放器加载失败，请刷新页面重试');
      });
    },
    handleClick(tab) {
      const tabName = tab.name;
      const params = this.pageParams[tabName];
      const displayList = this[`${tabName}DisplayList`];
      // 首次切换到该Tab或列表为空时初始化
      if (displayList.length < 20) {
        // 重置分页参数
        params.currentPage = 1;
        params.pageSize = 20;
        params.prevPage = 0;
        params.totalLoaded = 0;
        params.hasMore = true;
        params.hasPrev = false;
        // 加载第一页
        this.loadNextPage(tabName);
      } else {
        // 非首次切换，恢复滚动位置
        this.$nextTick(() => {
          const scrollEl = this.getScrollElement(tabName);
          if (scrollEl) {
            scrollEl.scrollTop = this.tabScrollPositions[tabName] || 0;
          }
        });
      }
    },
    saveTabScrollPositions() {
      this.tabScrollPositions = {
        online: this.getScrollElement('online')?.scrollTop || 0,
        offline: this.getScrollElement('offline')?.scrollTop || 0,
        silenced: this.getScrollElement('silenced')?.scrollTop || 0
      };
    },
    // 加载指定Tab的用户列表（核心加载逻辑）
    loadNextPage(tabName) {
      const params = this.pageParams[tabName];
      const displayList = this[`${tabName}DisplayList`];
      console.log(`加载 ${tabName} 用户列表`)
      console.log(!params.hasMore || this.scrLoading[tabName].next)
      console.log(params.currentPage)
      // 若没有更多数据或正在加载，直接返回
      if (!params.hasMore || this.scrLoading[tabName].next) {
        return;
      }

      this.scrLoading[tabName].next = true;
      // 与 liveUserTotals 口径一致：在线/离线仅非禁言；禁言不限在线状态
      const queryParams = {
        liveId: this.liveId,
        pageNum: params.currentPage,
        pageSize: 20,
        ...this.buildWatchUserQuery(tabName)
      };
      // 调用接口加载对应状态的分页数据（需后端支持按状态筛选）
      watchUserList(queryParams).then(response => {
        this.scrLoading[tabName].next = false;
        if (response.code !== 200) return;

        const { rows, total } = response;
        params.total = total; // 记录总数据量
        // Tab 角标与列表接口 total 保持一致
        if (tabName === 'online') {
          this.userTotal.online = total || 0;
        } else if (tabName === 'offline') {
          this.userTotal.offline = total || 0;
        } else if (tabName === 'silenced') {
          this.userTotal.silenced = total || 0;
        }
        // 过滤重复数据（基于userId）
        const newRows = rows.filter(row =>
          !displayList.some(u => u.userId === row.userId)
        );
        displayList.push(...newRows)
        // 添加新数据并限制最大长度（避免内存占用过大）
        if (displayList.length >= 40) { // 最大保留100条
          this[`${tabName}DisplayList`] = displayList.slice(-40);
          // 记录滚动位置（用于加载后校准）
          const scrollEl = this.getScrollElement(tabName);
          // 校准滚动位置（保持视觉连续性）
          this.$nextTick(() => {
            if (scrollEl) {
              scrollEl.scrollTop = scrollEl.scrollHeight * 0.5;
            }
          });
        }
        // 更新分页状态
        params.hasMore = params.currentPage * params.pageSize < total;
        params.currentPage += 1;
        params.hasPrev = params.currentPage > 2; // 当前页>2时一定有上一页
        params.prevPage = params.currentPage - 2;
      }).catch(() => {
        this.scrLoading[tabName].next = false;
      });
    },
    // 新增：加载上一页（向上滚动时）
    loadPrevPage(tabName) {
      const params = this.pageParams[tabName];
      const displayList = this[`${tabName}DisplayList`];
      // 边界校验：无上一页/正在加载/当前页<=1
      console.log(`加载 ${tabName} 上一页`);
      console.log(!params.hasPrev || this.scrLoading[tabName].prev || params.currentPage <= 1)
      if (!params.hasPrev || this.scrLoading[tabName].prev || params.currentPage <= 1) {
        return;
      }
      this.scrLoading[tabName].prev = true;
      const targetPage = params.prevPage > 0 ? params.prevPage : params.currentPage - 2;
      const queryParams = {
        liveId: this.liveId,
        pageNum: targetPage,
        pageSize: 20,
        ...this.buildWatchUserQuery(tabName)
      };
      watchUserList(queryParams).then(response => {
        this.scrLoading[tabName].prev = false;
        if (response.code !== 200) return;

        const { rows } = response;
        if (rows.length === 0) {
          params.hasPrev = false;
          return;
        }

        // 记录滚动位置（用于加载后校准）
        const scrollEl = this.getScrollElement(tabName);
        const scrollTop = scrollEl?.scrollTop || 0;
        const itemHeight = 80; // 预估行高（根据实际样式调整）
        const newItemsHeight = rows.length * itemHeight;

        // 过滤重复数据并添加到列表头部
        const newRows = rows.filter(row => !displayList.some(u => u.userId === row.userId));
        this[`${tabName}DisplayList`] = [...newRows, ...displayList];
        params.totalLoaded += newRows.length;

        // 限制最大长度
        if (this[`${tabName}DisplayList`].length > 40) {
          this[`${tabName}DisplayList`] = this[`${tabName}DisplayList`].slice(0, 40);
        }

        // 更新分页状态
        params.prevPage = targetPage - 1;
        params.hasPrev = targetPage > 1; // 上一页页码>1时还有更多上一页
        params.currentPage = params.currentPage - 1;
        if(params.currentPage * 20 < params.total) params.hasMore = true;
        // 校准滚动位置（保持视觉连续性）
        this.$nextTick(() => {
          if (scrollEl) {
            scrollEl.scrollTop = scrollEl.scrollHeight * 0.5;
          }
        });
      }).catch(() => {
        this.scrLoading[tabName].prev = false;
      });
    },
    // 与 liveUserTotals 统计口径一致的筛选条件
    buildWatchUserQuery(tabName) {
      if (tabName === 'silenced') {
        return { msgStatus: 1 };
      }
      if (tabName === 'online') {
        return { online: 0, msgStatus: 0 };
      }
      // offline
      return { online: 1, msgStatus: 0 };
    },
    // 辅助：获取Tab对应的滚动容器
    getScrollElement(tabName) {
      const scrollRefs = {
        online: this.$refs.manageLeftRef_online,
        offline: this.$refs.manageLeftRef_offline,
        silenced: this.$refs.manageLeftRef_silenced
      };
      return scrollRefs[tabName]?.wrap;
    },
    getLiveVideo() {
      getLiveVideoByLiveId(this.liveId).then(res => {
        this.liveVideo = res.data
      })
    },
    getList() {
      this.resetParams()
      // this.loadUserList()
      this.loadUserTotals(); // 先加载总人数
      // this.handleClick('online')
      this.handleClick({name:'online'})
      this.loadMsgList()
    },
    loadUserTotals() {
      if (!this.liveId) return;
      // 假设后端提供一个接口返回总人数（如果没有，可通过首次加载全量数据后统计）
      getLiveUserTotals({ liveId: this.liveId }).then(res => {
        if (res.code === 200) {
          this.userTotal = res.data; // { online, offline, silenced }
        }
      });
    },
    resetParams() {
      // 重置各Tab的显示列表和分页参数
      this.onlineDisplayList = [];
      this.offlineDisplayList = [];
      this.silencedDisplayList = [];
      this.pageParams = {
        online: {
          currentPage: 1,       // 当前页（下一页加载用）
          pageSize: 20,       // 当前页（下一页加载用）
          prevPage: 0,          // 上一页页码（上一页加载用）
          totalLoaded: 0,       // 已加载总条数
          total: 0,             // 总数据量
          hasMore: true,        // 是否有下一页
          hasPrev: false        // 是否有上一页
        },
        offline: {
          currentPage: 1,
          pageSize: 20,
          prevPage: 0,
          totalLoaded: 0,
          total: 0,
          hasMore: true,
          hasPrev: false
        },
        silenced: {
          currentPage: 1,
          pageSize: 20,
          prevPage: 0,
          totalLoaded: 0,
          total: 0,
          hasMore: true,
          hasPrev: false
        }
      };
      // 消息参数保留
      this.msgList = [];
      this.msgParams = {
        pageNum: 1,
        pageSize: 10,
        liveId: this.liveId
      };
    },
    loadUserList() {
      if(this.liveId == null)  return
      // 直播间用户
      watchUserList({
        liveId: this.liveId,
        pageNum: this.userParams.pageNum,
        pageSize: this.userParams.pageSize
      }).then(response => {
        let {code,rows,total} = response
        if (code === 200) {
          let totalPage = (total % this.userParams.pageSize == 0) ? Math.floor(total / this.userParams.pageSize) : Math.floor(total / this.userParams.pageSize + 1);
          rows.forEach(row => {
            if (!this.userList.some(u => u.userId === row.userId)) {
              this.userList.push(row)
            }
          })

          // 没加载完继续加载
          if (this.userParams.pageNum < totalPage) {
            this.userParams.pageNum = parseInt(this.userParams.pageNum) + 1;
            this.loadUserList()
          }
        }
      })
    },
    loadMsgList() {
      // 直播间消息 - 只加载最近的30条
      listLiveMsg({
        liveId:this.liveId,
        pageNum: 1,
        pageSize: 30
      }).then(response => {
          let {code, rows} = response;
          if (code === 200) {
            // 清空现有消息列表，只保留最新的30条
            this.msgList = [];

            rows.forEach(row => {
              let user = this.userList.find(u => u.userId === row.userId)
              if (user) {
                row.msgStatus = user.msgStatus
              } else {
                row.msgStatus = 0
              }

              this.msgList.push(row)
            })

            // 移动到底部
            this.$nextTick(() => {
              setTimeout(() => {
                this.$refs.manageRightRef.wrap.scrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
              }, 200)
            })

            // 同步更新消息列表中相同用户的状态
            this.userList.forEach(u => {
              this.msgList.filter(m => m.userId === u.userId).forEach(m => m.msgStatus = u.msgStatus)
            })
          }
        })

      // 添加滚动监听
      this.$nextTick(() => {
        this.$refs.manageRightRef.wrap.addEventListener("scroll", this.manageRightScroll)
      })
    },
    manageRightScroll() {
      this.saveChatScrollPosition();
    },
    blockUser(u){
      this.$confirm('是否确认封禁用户账号为:"' + u.nickName + '-' + u.userId + '"?', "警告", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      }).then(() => {
        let msg = {
          msg: "",
          liveId: this.liveId,
          userId: u.userId,
          userType: 0,
          cmd: 'blockUser',
          avatar: this.$store.state.user.user.avatar,
          nickName: this.$store.state.user.user.nickName
        }
        this.socket.send(JSON.stringify(msg))
        return blockUser(u.userId);
      }).then(() => {
        this.msgSuccess("封禁成功");
      }).catch(() => {});
    },
    changeUserState(u) {
      // 修改状态
      changeUserStatus({liveId: u.liveId, userId: u.userId}).then(response => {
        let { code } = response;
        if (200 === code) {
          u.msgStatus = u.msgStatus === 0 ? 1 : 0
          // 同步更新消息列表中相同用户的状态
          this.msgList.forEach(msg => {
            if (msg.userId === u.userId) {
              msg.msgStatus = u.msgStatus;
            }
          });

          this.userList.forEach(user => {
            if (user.userId === u.userId) {
              user.msgStatus = u.msgStatus;
            }
          });
          // 4. 关键：重新筛选所有Tab的显示列表，确保状态同步
          this.refreshUserDisplayLists(u);

          // 5. 通过 WebSocket 通知后端更新 Redis 禁言列表，发消息时会据此判断
          const wsMsg = {
            liveId: this.liveId,
            userId: u.userId,
            cmd: 'mutedUser',
            status: u.msgStatus,
            userType: 1
          };
          this.socket.send(JSON.stringify(wsMsg));

          let msg = u.msgStatus === 0 ? "已解禁" : "已禁言"
          this.msgSuccess(msg);
          return
        }
        this.msgError("操作失败");
      })
    },
    // 新增：重新筛选所有Tab的显示列表
    refreshUserDisplayLists(user) {
      const { userId, msgStatus: newStatus, online } = user;
      const oldStatus = newStatus === 1 ? 0 : 1; // 操作前的状态（反向）

      // 1. 禁言操作（newStatus=1）：从原在线/离线列表移除，加入禁言列表
      if (newStatus === 1) {
        // 从在线/离线列表中移除该用户（根据当前在线状态）
        if (online === 0) {
          this.onlineDisplayList = this.onlineDisplayList.filter(u => u.userId !== userId);
          this.userTotal.online = Math.max(0, this.userTotal.online - 1);
        } else {
          this.offlineDisplayList = this.offlineDisplayList.filter(u => u.userId !== userId);
          this.userTotal.offline = Math.max(0, this.userTotal.offline - 1);
        }
        this.userTotal.silenced = this.userTotal.silenced + 1;
        // 添加到禁言列表（去重+限制长度）
        const silencedList = this.silencedDisplayList.filter(u => u.userId !== userId);
        silencedList.push(user);
        this.silencedDisplayList = silencedList.slice(-40);
      }

      // 2. 解禁操作（newStatus=0）：从禁言列表移除，回到原在线/离线列表
      else {
        // 从禁言列表移除
        this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== userId);
        this.userTotal.silenced = Math.max(0, this.userTotal.silenced - 1);
        // 回到对应的在线/离线列表（根据当前在线状态）
        if (online === 0) {
          // 加入在线列表（去重+限制长度）
          const onlineList = this.onlineDisplayList.filter(u => u.userId !== userId);
          onlineList.push(user);
          this.onlineDisplayList = onlineList.slice(-40);
          this.userTotal.online = this.userTotal.online + 1;
        } else {
          // 加入离线列表（去重+限制长度）
          const offlineList = this.offlineDisplayList.filter(u => u.userId !== userId);
          offlineList.push(user);
          this.offlineDisplayList = offlineList.slice(-40);
          this.userTotal.offline = this.userTotal.offline + 1;
        }
      }
    },
    connectWebSocket() {
      this._wsMessageHandler = (event) => this.handleWsMessage(event)
      this.$store.dispatch('acquireLiveWs', {
        liveWsUrl: this.liveWsUrl,
        liveId: this.liveId,
        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 = null
    },
    handleWsMessage(event) {
      let { code, data } = JSON.parse(event.data)
      if (code === 200) {
        let { cmd } = data
        if (cmd === 'sendMsg') {
          let message = JSON.parse(data.data)

          let user = this.userList.find(u => u.userId === message.userId)
          if (user) {
            message.msgStatus = user.msgStatus
          } else {
            message.msgStatus = 0
          }
          delete message.params
          if(this.msgList.length > 50){
            this.msgList.shift()
          }
          this.msgList.push(message)
          // 移动到底部
          this.$nextTick(() => {
            setTimeout(() => {
              this.$refs.manageRightRef.wrap.scrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
            }, 200)
          })
        } else if (cmd === 'deleteMsg') {
          const msgId = data.msgId != null ? data.msgId : (data.msg != null ? Number(data.msg) : null)
          if (msgId != null) {
            const index = this.msgList.findIndex(item => item.msgId == msgId)
            if (index !== -1) {
              this.msgList.splice(index, 1)
            }
          }
        }
        else if (cmd === 'entry' || cmd === 'out') {
          const user = data;
          const online = cmd === 'entry' ? 0 : 1; // 0=在线，1=离线
          const info = {
            online:online,
            msgStatus: user.msgStatus || 0,
            nickName: user.nickName || '',
            userType: user.userType || 0,
            userId: user.userId || '',
          };

          // 1. 更新总人数（在线/离线互转）
          if (cmd === 'entry') {
            this.userTotal.online += 1;
            this.userTotal.offline = Math.max(0, this.userTotal.offline - 1); // 确保不小于0
          } else {
            this.userTotal.offline += 1;
            this.userTotal.online = Math.max(0, this.userTotal.online - 1); // 确保不小于0
          }
          // 2. 强制更新相关列表（无论当前激活哪个Tab）
          if (cmd === 'entry') {
            // 用户进入：从离线列表删除，添加到在线列表
            this.offlineDisplayList = this.offlineDisplayList.filter(u => u.userId !== user.userId);
            const newOnlineList = this.onlineDisplayList.filter(u => u.userId !== user.userId);
            newOnlineList.push(info);
            this.onlineDisplayList = newOnlineList.slice(-40); // 限制最大50条
          } else {
            // 用户离开：从在线列表删除，添加到离线列表
            this.onlineDisplayList = this.onlineDisplayList.filter(u => u.userId !== user.userId);
            const newOfflineList = this.offlineDisplayList.filter(u => u.userId !== user.userId);
            newOfflineList.push(info);
            this.offlineDisplayList = newOfflineList.slice(-40); // 限制最大50条
          }
          // 3. 处理禁言列表（如果用户是禁言状态，无论进出都要同步）
          if (info.msgStatus === 1) {
            // 禁言用户：从禁言列表删除旧数据，添加新数据
            const newSilencedList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
            newSilencedList.push(info);
            this.silencedDisplayList = newSilencedList.slice(-40);
          } else {
            // 非禁言用户：从禁言列表删除（如果存在）
            this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
          }
        } else if (cmd === 'live_start') {

        } else if (cmd === 'live_end') {

        }
      }
    },
    sendMessage() {
      // 发送前简单校验
      if (this.newMsg.trim() === '') {
        return;
      }

      let msg = {
        msg: this.newMsg,
        liveId: this.liveId,
        userId: this.userId,
        userType: 1,
        cmd: 'sendMsg',
        avatar: this.$store.state.user.user.avatar,
        nickName: this.$store.state.user.user.nickName
      }

      this.socket.send(JSON.stringify(msg))

      this.newMsg = '';
    },
    // 初始化滚动监听（在mounted中调用）
    initScrollListeners() {
      // 为每个Tab的滚动容器添加监听
      this.$nextTick(() => {
        const scrollRefs = {
          online: this.$refs.manageLeftRef_online,
          offline: this.$refs.manageLeftRef_offline,
          silenced: this.$refs.manageLeftRef_silenced
        };

        Object.keys(scrollRefs).forEach(tabName => {
          const scrollEl = scrollRefs[tabName]?.wrap;
          if (scrollEl) {
            scrollEl.addEventListener('scroll', () =>
              this.handleTabScroll(tabName, scrollEl)
            );
          }
        });
      });
    },
    handleTabScroll(tabName, scrollEl) {
      const { scrollTop, scrollHeight, clientHeight } = scrollEl;
      const bottomThreshold = 50; // 距离底部100px触发下一页
      const topThreshold = 50;    // 距离顶部100px触发上一页

      // 加载下一页（滚动到底部附近）
      if (scrollHeight - scrollTop - clientHeight < bottomThreshold) {
        this.loadNextPage(tabName);
      }

      // 加载上一页（滚动到顶部附近）
      if (scrollTop < topThreshold) {
        this.loadPrevPage(tabName);
      }
    },
  },
  destroyed() {
    this.destroyTcPlayer();
    this.hls?.destroy();
    clearInterval(this.processInterval);
  }
}
</script>

<style scoped>
.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-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;
}
</style>
