Ver Fonte

直播代码

yuhongqi há 16 horas atrás
pai
commit
d5c4650199

+ 1 - 0
package.json

@@ -62,6 +62,7 @@
     "clipboard": "2.0.4",
     "compression-webpack-plugin": "^5.0.1",
     "core-js": "3.6.5",
+    "crypto-js": "^4.2.0",
     "dayjs": "^1.11.13",
     "echarts": "4.2.1",
     "element-ui": "2.15.5",

+ 61 - 0
src/api/live/live.js

@@ -0,0 +1,61 @@
+import request from '@/utils/request'
+
+// 查询直播列表
+export function listLive(query) {
+  return request({
+    url: '/live/live/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询直播详细
+export function getLive(liveId) {
+  return request({
+    url: '/live/live/' + liveId,
+    method: 'get'
+  })
+}
+
+// 新增直播
+export function addLive(data) {
+  return request({
+    url: '/live/live',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改直播
+export function updateLive(data) {
+  return request({
+    url: '/live/live',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除直播
+export function delLive(liveId) {
+  return request({
+    url: '/live/live/' + liveId,
+    method: 'delete'
+  })
+}
+
+// 导出直播
+export function exportLive(query) {
+  return request({
+    url: '/live/live/export',
+    method: 'get',
+    params: query
+  })
+}
+
+export function selectLiveToStudent(query) {
+  return request({
+    url: '/live/live/selectLiveToStudent',
+    method: 'post',
+    data: query
+  })
+}

+ 53 - 0
src/api/live/liveAnchor.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询主播列表
+export function listLiveAnchor(query) {
+  return request({
+    url: '/live/liveAnchor/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询主播详细
+export function getLiveAnchor(anchorId) {
+  return request({
+    url: '/live/liveAnchor/' + anchorId,
+    method: 'get'
+  })
+}
+
+// 新增主播
+export function addLiveAnchor(data) {
+  return request({
+    url: '/live/liveAnchor',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改主播
+export function updateLiveAnchor(data) {
+  return request({
+    url: '/live/liveAnchor',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除主播
+export function delLiveAnchor(anchorId) {
+  return request({
+    url: '/live/liveAnchor/' + anchorId,
+    method: 'delete'
+  })
+}
+
+// 导出主播
+export function exportLiveAnchor(query) {
+  return request({
+    url: '/live/liveAnchor/export',
+    method: 'get',
+    params: query
+  })
+}

+ 47 - 0
src/api/live/liveData.js

@@ -0,0 +1,47 @@
+import request from '@/utils/request'
+export function recentLive() {
+  return request({
+    url: '/liveData/liveData/recentLive',
+    method: 'get',
+  })
+}
+
+export function liveTop(query) {
+  return request({
+    url: '/liveData/liveData/getLiveTop',
+    method: 'get',
+    params: query
+  })
+}
+export function getTrendData(queryParams) {
+  return request({
+    url: '/liveData/liveData/getTrendData',
+    method: 'post',
+    data: queryParams
+  })
+}
+
+export function columns() {
+  return request({
+    url: '/liveData/liveData/columns',
+    method: 'get',
+  })
+}
+
+
+export function updateColumns(columns) {
+  return request({
+    url: '/liveData/liveData/updateColumns',
+    method: 'post',
+    data:columns,
+  })
+}
+
+export function queryStudentData(query) {
+  return request({
+    url: '/live/studentData/queryStudentData',
+    method: 'post',
+    data:query,
+  })
+}
+

+ 53 - 0
src/api/live/liveGoods.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询直播商品列表
+export function listLiveGoods(query) {
+  return request({
+    url: '/live/liveGoods/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询直播商品详细
+export function getLiveGoods(goodsId) {
+  return request({
+    url: '/live/liveGoods/' + goodsId,
+    method: 'get'
+  })
+}
+
+// 新增直播商品
+export function addLiveGoods(data) {
+  return request({
+    url: '/live/liveGoods',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改直播商品
+export function updateLiveGoods(data) {
+  return request({
+    url: '/live/liveGoods',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除直播商品
+export function delLiveGoods(goodsId) {
+  return request({
+    url: '/live/liveGoods/' + goodsId,
+    method: 'delete'
+  })
+}
+
+// 导出直播商品
+export function exportLiveGoods(query) {
+  return request({
+    url: '/live/liveGoods/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/live/liveMsg.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询直播讨论列表
+export function listLiveMsg(query) {
+  return request({
+    url: '/live/liveMsg/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询直播讨论详细
+export function getLiveMsg(msgId) {
+  return request({
+    url: '/live/liveMsg/' + msgId,
+    method: 'get'
+  })
+}
+
+// 新增直播讨论
+export function addLiveMsg(data) {
+  return request({
+    url: '/live/liveMsg',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改直播讨论
+export function updateLiveMsg(data) {
+  return request({
+    url: '/live/liveMsg',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除直播讨论
+export function delLiveMsg(msgId) {
+  return request({
+    url: '/live/liveMsg/' + msgId,
+    method: 'delete'
+  })
+}
+
+// 导出直播讨论
+export function exportLiveMsg(query) {
+  return request({
+    url: '/live/liveMsg/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/live/liveOrder.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询订单列表
+export function listLiveOrder(query) {
+  return request({
+    url: '/live/liveOrder/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询订单详细
+export function getLiveOrder(orderId) {
+  return request({
+    url: '/live/liveOrder/' + orderId,
+    method: 'get'
+  })
+}
+
+// 新增订单
+export function addLiveOrder(data) {
+  return request({
+    url: '/live/liveOrder',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改订单
+export function updateLiveOrder(data) {
+  return request({
+    url: '/live/liveOrder',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除订单
+export function delLiveOrder(orderId) {
+  return request({
+    url: '/live/liveOrder/' + orderId,
+    method: 'delete'
+  })
+}
+
+// 导出订单
+export function exportLiveOrder(query) {
+  return request({
+    url: '/live/liveOrder/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/live/liveOrderitems.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询订单商品列表
+export function listLiveOrderitems(query) {
+  return request({
+    url: '/live/liveOrderitems/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询订单商品详细
+export function getLiveOrderitems(id) {
+  return request({
+    url: '/live/liveOrderitems/' + id,
+    method: 'get'
+  })
+}
+
+// 新增订单商品
+export function addLiveOrderitems(data) {
+  return request({
+    url: '/live/liveOrderitems',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改订单商品
+export function updateLiveOrderitems(data) {
+  return request({
+    url: '/live/liveOrderitems',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除订单商品
+export function delLiveOrderitems(id) {
+  return request({
+    url: '/live/liveOrderitems/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出订单商品
+export function exportLiveOrderitems(query) {
+  return request({
+    url: '/live/liveOrderitems/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/live/liveQuestion.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询问答列表
+export function listLiveQuestion(query) {
+  return request({
+    url: '/live/liveQuestion/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询问答详细
+export function getLiveQuestion(questionId) {
+  return request({
+    url: '/live/liveQuestion/' + questionId,
+    method: 'get'
+  })
+}
+
+// 新增问答
+export function addLiveQuestion(data) {
+  return request({
+    url: '/live/liveQuestion',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改问答
+export function updateLiveQuestion(data) {
+  return request({
+    url: '/live/liveQuestion',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除问答
+export function delLiveQuestion(questionId) {
+  return request({
+    url: '/live/liveQuestion/' + questionId,
+    method: 'delete'
+  })
+}
+
+// 导出问答
+export function exportLiveQuestion(query) {
+  return request({
+    url: '/live/liveQuestion/export',
+    method: 'get',
+    params: query
+  })
+}

+ 36 - 0
src/api/live/liveQuestionBank.js

@@ -0,0 +1,36 @@
+import request from '@/utils/request'
+
+// 查询题库列表
+export function listLiveQuestionBank(query) {
+  return request({
+    url: '/live/liveQuestionBank/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 新增题库
+export function addLiveQuestionBank(data) {
+  return request({
+    url: '/live/liveQuestionBank',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改题库
+export function updateLiveQuestionBank(data) {
+  return request({
+    url: '/live/liveQuestionBank',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除题库
+export function deleteLiveQuestionBank(questionBankId) {
+  return request({
+    url: '/live/liveQuestionBank/' + questionBankId,
+    method: 'delete'
+  })
+}

+ 37 - 0
src/api/live/liveQuestionLive.js

@@ -0,0 +1,37 @@
+import request from '@/utils/request'
+
+// 查询直播间题库列表
+export function listLiveQuestionLive(query) {
+    return request({
+        url: '/live/liveQuestionLive/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询直播间可选题库列表
+export function listLiveQuestionOptionList(query) {
+    return request({
+        url: '/live/liveQuestionLive/optionList',
+        method: 'get',
+        params: query
+    })
+}
+
+// 新增直播间题库
+export function addLiveQuestionLive(data) {
+    return request({
+        url: '/live/liveQuestionLive',
+        method: 'post',
+        params: data
+    })
+}
+
+// 删除直播间题库
+export function deleteLiveQuestionLive(data) {
+    return request({
+        url: '/live/liveQuestionLive/' + data.liveId,
+        method: 'delete',
+        params: {ids: data.ids}
+    })
+}

+ 61 - 0
src/api/live/liveVideo.js

@@ -0,0 +1,61 @@
+import request from '@/utils/request'
+
+// 查询直播视频列表
+export function listLiveVideo(query) {
+  return request({
+    url: '/live/liveVideo/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询直播视频详细
+export function getLiveVideo(videoId) {
+  return request({
+    url: '/live/liveVideo/' + videoId,
+    method: 'get'
+  })
+}
+
+// 查询直播视频详细
+export function getLiveVideoByLiveId(liveId) {
+  return request({
+    url: '/live/liveVideo/liveVideoByLiveId/' + liveId,
+    method: 'get'
+  })
+}
+
+// 新增直播视频
+export function addLiveVideo(data) {
+  return request({
+    url: '/live/liveVideo',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改直播视频
+export function updateLiveVideo(data) {
+  return request({
+    url: '/live/liveVideo',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除直播视频
+export function delLiveVideo(videoId) {
+  return request({
+    url: '/live/liveVideo/' + videoId,
+    method: 'delete'
+  })
+}
+
+// 导出直播视频
+export function exportLiveVideo(query) {
+  return request({
+    url: '/live/liveVideo/export',
+    method: 'get',
+    params: query
+  })
+}

+ 19 - 0
src/api/live/liveWatchUser.js

@@ -0,0 +1,19 @@
+import request from '@/utils/request'
+
+// 查询直播间用户列表
+export function watchUserList(query) {
+  return request({
+    url: '/live/liveWatchUser/watchUserList',
+    method: 'get',
+    params: query
+  })
+}
+
+// 直播间用户禁言
+export function changeUserStatus(query) {
+  return request({
+    url: '/live/liveWatchUser/changeUserState',
+    method: 'put',
+    params: query
+  })
+}

+ 111 - 0
src/utils/liveWS.js

@@ -0,0 +1,111 @@
+import CryptoJS from 'crypto-js'
+
+export class LiveWS {
+  /**
+   * @param {string} url - WebSocket 服务器地址
+   * @param {number} liveId - 直播间ID
+   * @param {number} userId - 用户ID
+   * @param {number} checkInterval - 检查连接状态的时间间隔,单位毫秒
+   * @param {number} reconnectDelay - 连接断开后重连的延迟,单位毫秒
+   */
+  constructor(url, liveId, userId, checkInterval = 5000, reconnectDelay = 3000) {
+    let timestamp = new Date().getTime()
+    let userType = 1
+    let signature = CryptoJS.HmacSHA256(
+      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}`;
+    this.liveId = liveId;
+    this.userId = userId;
+    this.checkInterval = checkInterval;
+    this.reconnectDelay = reconnectDelay;
+    this.ws = null;
+    this.reconnectTimer = null;
+    this.heartbeatTimer = null;
+    this.isManualClose = false;
+    this.connect();
+    this.startHeartbeat();
+  }
+
+  connect() {
+    // 如果已经有一个连接处于 OPEN 或 CONNECTING 状态,则不再创建新连接
+    if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
+      return;
+    }
+
+    this.ws = new WebSocket(this.url);
+
+    // 绑定事件
+    this.ws.onopen = (event) => {
+      // 连接成功后,清除重连计时器
+      if (this.reconnectTimer) {
+        clearTimeout(this.reconnectTimer);
+        this.reconnectTimer = null;
+      }
+    };
+
+    this.ws.onmessage = (event) => {
+      this.onmessage(event);
+    };
+
+    this.ws.onerror = (error) => {
+    };
+
+    this.ws.onclose = (event) => {
+      // 如果不是主动关闭,则重连
+      if (!this.isManualClose) {
+        setTimeout(() => this.reconnect(), this.reconnectDelay);
+      }
+    };
+  }
+
+  onmessage(event) {}
+
+  reconnect() {
+    this.connect();
+  }
+
+  // 调度重连
+  scheduleReconnect() {
+    // 避免多次重连定时器同时存在
+    if (this.reconnectTimer) return;
+    this.reconnectTimer = setTimeout(() => {
+      this.connect();
+    }, this.reconnectDelay);
+  }
+
+  // 定时检查连接状态
+  startHeartbeat() {
+    this.heartbeatTimer = setInterval(() => {
+      if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
+        this.scheduleReconnect();
+      } else {
+        // 发送信息
+        this.ws.send(JSON.stringify({'cmd':'heartbeat','msg':'ping', 'liveId': this.liveId, 'userId': this.userId}));
+      }
+    }, this.checkInterval);
+  }
+
+  // 主动关闭 WebSocket 连接,并清除定时任务
+  close() {
+    this.isManualClose = true;
+    if (this.heartbeatTimer) {
+      clearInterval(this.heartbeatTimer);
+    }
+    if (this.reconnectTimer) {
+      clearTimeout(this.reconnectTimer);
+    }
+    if (this.ws) {
+      this.ws.close();
+    }
+  }
+
+  // 发送消息方法
+  send(message) {
+    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
+      this.ws.send(message);
+    } else {
+      console.error("WebSocket is not open. Message not sent.");
+    }
+  }
+}

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

@@ -0,0 +1,460 @@
+<template>
+  <div class="app-container">
+    <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>
+        <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-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['live:live:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:live:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:live:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="liveList">
+      <el-table-column label="直播封面" align="center" prop="liveImgUrl" width="180">
+        <template slot-scope="scope">
+          <el-image style="width: 180px;" :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 v-if="scope.row.status == 1">待直播</el-tag>
+          <el-tag v-if="scope.row.status == 2">直播中</el-tag>
+          <el-tag v-if="scope.row.status == 3">已结束</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="主播ID" align="center" prop="anchorId" />
+      <el-table-column label="直播类型" align="center" prop="liveType">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.liveType == 1">直播</el-tag>
+          <el-tag v-if="scope.row.liveType == 2">录播</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 v-if="scope.row.isShow == 2">下架</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
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:live:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:live:remove']"
+          >删除</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-monitor"
+            @click="handleConfig(scope.row)"
+            v-hasPermi="['live:config:list']"
+          >配置</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-monitor"
+            @click="handleManage(scope.row)"
+            v-hasPermi="['live:console:list']"
+          >管理</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改直播对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="900px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="直播名称" prop="liveName">
+          <el-input v-model="form.liveName" placeholder="请输入直播名称" />
+        </el-form-item>
+        <el-form-item label="显示类型" prop="showType">
+          <el-radio-group v-model="form.showType">
+            <el-radio :label="1">横屏</el-radio>
+            <el-radio :label="2">竖屏</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="直播类型" prop="liveType">
+          <el-radio-group disabled v-model="form.liveType">
+            <el-radio :label="1">直播</el-radio>
+            <el-radio :label="2">录播</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="直播描述" prop="liveDesc">
+          <Editor ref="myeditor" :height="300" @on-text-change="updateText"/>
+          <!--          <Editor v-model="form.liveDesc" :height="300" placeholder="直播描述" />-->
+        </el-form-item>
+        <el-form-item label="录播视屏" prop="videoUrl">
+          <file-upload v-model="form.videoUrl" :limit="1" :file-size="3" :file-type="['mp4']" />
+          <el-button @click="getVideoDuration" v-loading="timeLoading">读取视屏时长</el-button>
+          <p style="margin: 0;padding: 0;" v-loading="timeLoading">视屏时长:<span style="color: #ff4949;">{{form.durationTime}}</span></p>
+        </el-form-item>
+        <el-form-item label="开始时间" prop="startTime">
+          <el-date-picker size="small"
+                          v-model="form.startTime"
+                          @change="timeChange"
+                          type="datetime"
+                          value-format="yyyy-MM-dd HH:mm:ss"
+                          placeholder="选择开始时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="结束时间" prop="finishTime" v-loading="timeLoading">
+          <el-date-picker size="small"
+                          v-model="form.finishTime"
+                          type="datetime"
+                          disabled
+                          value-format="yyyy-MM-dd HH:mm:ss"
+                          placeholder="视屏播放结束">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="直播封面" prop="liveImgUrl">
+          <image-upload v-model="form.liveImgUrl" :limit="1" />
+        </el-form-item>
+        <el-form-item label="上下架" prop="isShow">
+          <el-radio-group v-model="form.isShow">
+            <el-radio :label="1">上架</el-radio>
+            <el-radio :label="2">下架</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLive, getLive, delLive, addLive, updateLive, exportLive } from "@/api/live/live";
+import Editor from '@/components/Editor/wang';
+
+export default {
+  name: "Live",
+  components: { Editor },
+  data() {
+    return {
+      baseUrl: process.env.VUE_APP_BASE_API,
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      timeLoading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 直播表格数据
+      liveList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        liveName: null,
+        liveDesc: null,
+        showType: null,
+        status: null,
+        anchorId: null,
+        liveType: null,
+        startTime: null,
+        finishTime: null,
+        liveImgUrl: null,
+        liveConfig: null,
+        isShow: null,
+        isDel: null,
+        qwQrCode: null,
+        rtmpUrl: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        liveName: [
+          { required: true, message: "不能为空", trigger: "burl" }
+        ],
+        showType: [
+          { required: true, message: "不能为空", trigger: "burl" }
+        ],
+        liveType: [
+          { required: true, message: "不能为空", trigger: "burl" }
+        ],
+        videoUrl: [
+          { required: true, message: "不能为空", trigger: "burl" }
+        ],
+        startTime: [
+          { required: true, message: "不能为空", trigger: "burl" }
+        ],
+        liveImgUrl: [
+          { required: true, message: "不能为空", trigger: "burl" }
+        ],
+        isShow: [
+          { required: true, message: "不能为空", trigger: "change" }
+        ],
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询直播列表 */
+    getList() {
+      this.loading = true;
+      listLive(this.queryParams).then(response => {
+        this.liveList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    urlChange(url) {
+      this.form.videoUrl = url;
+      this.getVideoDuration();
+    },
+    getVideoDuration() {
+      this.timeLoading = true;
+      const videoElement = document.createElement('video');
+      videoElement.src = this.form.videoUrl;
+      videoElement.onloadedmetadata = () => {
+        this.form.duration = Math.floor(videoElement.duration);  // 秒
+        this.form.durationTime = this.secondsToTime(this.form.duration);
+        this.timeChange();
+        this.timeLoading = false;
+      };
+    },
+    changeDuration(e){
+      this.form.duration = e.duration;
+      this.form.durationTime = e.time;
+      this.$forceUpdate();
+      this.timeChange();
+    },
+    timeChange(){
+      if(!this.form.startTime) return;
+      if(!this.form.duration) return;
+      console.info(this.form.startTime)
+      const startDateTime = new Date(this.form.startTime);
+      // 将视频时长(秒)加到开始时间
+      const endDateTime = new Date(startDateTime.getTime() + this.form.duration * 1000); // 毫秒为单位
+      // 格式化为年月日 时分秒
+      this.form.finishTime = this.formatDate(endDateTime);
+      this.$forceUpdate();
+    },
+    // 将秒数转换为时分秒
+    secondsToTime(seconds) {
+      const hours = Math.floor(seconds / 3600);
+      const minutes = Math.floor((seconds % 3600) / 60);
+      const secs = Math.floor(seconds % 60);
+      return `${this.pad(hours)}:${this.pad(minutes)}:${this.pad(secs)}`;
+    },
+    // 补零处理
+    pad(number) {
+      return number < 10 ? `0${number}` : number;
+    },
+    // 格式化日期为 年月日 时分秒
+    formatDate(date) {
+      const year = date.getFullYear();
+      const month = (date.getMonth() + 1).toString().padStart(2, '0');
+      const day = date.getDate().toString().padStart(2, '0');
+      const hours = date.getHours().toString().padStart(2, '0');
+      const minutes = date.getMinutes().toString().padStart(2, '0');
+      const seconds = date.getSeconds().toString().padStart(2, '0');
+
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        showType: 1,
+        liveType: 2,
+        isShow: 1,
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.liveId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      setTimeout(() => {
+        this.$refs.myeditor.setText("");
+      }, 100);
+      this.title = "添加直播";
+    },
+    updateText(text){
+      this.form.liveDesc=text
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const liveId = row.liveId || this.ids
+      getLive(liveId).then(response => {
+        this.form = response.data;
+        if(this.form.duration){
+          this.form.durationTime = this.secondsToTime(this.form.duration)
+        }
+        setTimeout(() => {
+          if(this.form.liveDesc==null){
+            this.$refs.myeditor.setText("");
+          }else{
+            this.$refs.myeditor.setText(this.form.liveDesc);
+          }
+        }, 1);
+        this.open = true;
+        this.title = "修改直播";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.liveId != null) {
+            updateLive(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addLive(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const liveIds = row.liveId || this.ids;
+      this.$confirm('是否确认删除直播编号为"' + liveIds + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delLive(liveIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    handleConfig(row) {
+      console.info(row)
+      this.$router.push('/live/liveConfig/' + row.liveId)
+    },
+    handleManage(row) {
+      this.$router.push('/live/liveConsole/' + row.liveId)
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有直播数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportLive(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
+    }
+  }
+};
+</script>

+ 359 - 0
src/views/live/liveAnchor/index.vue

@@ -0,0 +1,359 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="昵称" prop="nickName">
+        <el-input
+          v-model="queryParams.nickName"
+          placeholder="请输入昵称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="密码" prop="password">
+        <el-input
+          v-model="queryParams.password"
+          placeholder="请输入密码"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="手机号" prop="mobile">
+        <el-input
+          v-model="queryParams.mobile"
+          placeholder="请输入手机号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="微信OPENID" prop="openId">
+        <el-input
+          v-model="queryParams.openId"
+          placeholder="请输入微信OPENID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="身份证反面" prop="idCardBackUrl">
+        <el-input
+          v-model="queryParams.idCardBackUrl"
+          placeholder="请输入身份证反面"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="身份证正面" prop="idCardFrontUrl">
+        <el-input
+          v-model="queryParams.idCardFrontUrl"
+          placeholder="请输入身份证正面"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态  1正常 0禁用" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态  1正常 0禁用" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </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-form-item>
+    </el-form>
+
+    <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:liveAnchor:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:liveAnchor:edit']"
+        >修改</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:liveAnchor:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:liveAnchor:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="liveAnchorList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="anchorId" />
+      <el-table-column label="昵称" align="center" prop="nickName" />
+      <el-table-column label="密码" align="center" prop="password" />
+      <el-table-column label="手机号" align="center" prop="mobile" />
+      <el-table-column label="微信OPENID" align="center" prop="openId" />
+      <el-table-column label="身份证反面" align="center" prop="idCardBackUrl" />
+      <el-table-column label="身份证正面" align="center" prop="idCardFrontUrl" />
+      <el-table-column label="状态  1正常 0禁用" align="center" prop="status" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:liveAnchor:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:liveAnchor:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      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>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="昵称" prop="nickName">
+          <el-input v-model="form.nickName" placeholder="请输入昵称" />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input v-model="form.password" placeholder="请输入密码" />
+        </el-form-item>
+        <el-form-item label="手机号" prop="mobile">
+          <el-input v-model="form.mobile" placeholder="请输入手机号" />
+        </el-form-item>
+        <el-form-item label="微信OPENID" prop="openId">
+          <el-input v-model="form.openId" placeholder="请输入微信OPENID" />
+        </el-form-item>
+        <el-form-item label="身份证反面" prop="idCardBackUrl">
+          <el-input v-model="form.idCardBackUrl" placeholder="请输入身份证反面" />
+        </el-form-item>
+        <el-form-item label="身份证正面" prop="idCardFrontUrl">
+          <el-input v-model="form.idCardFrontUrl" placeholder="请输入身份证正面" />
+        </el-form-item>
+        <el-form-item label="状态  1正常 0禁用">
+          <el-radio-group v-model="form.status">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLiveAnchor, getLiveAnchor, delLiveAnchor, addLiveAnchor, updateLiveAnchor, exportLiveAnchor } from "@/api/live/liveAnchor";
+
+export default {
+  name: "LiveAnchor",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 主播表格数据
+      liveAnchorList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        nickName: null,
+        password: null,
+        mobile: null,
+        openId: null,
+        idCardBackUrl: null,
+        idCardFrontUrl: null,
+        status: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询主播列表 */
+    getList() {
+      this.loading = true;
+      listLiveAnchor(this.queryParams).then(response => {
+        this.liveAnchorList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        anchorId: null,
+        nickName: null,
+        password: null,
+        mobile: null,
+        openId: null,
+        idCardBackUrl: null,
+        idCardFrontUrl: null,
+        status: 0,
+        createTime: null,
+        createBy: null,
+        updateBy: null,
+        updateTime: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.anchorId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加主播";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const anchorId = row.anchorId || this.ids
+      getLiveAnchor(anchorId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改主播";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.anchorId != null) {
+            updateLiveAnchor(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addLiveAnchor(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const anchorIds = row.anchorId || this.ids;
+      this.$confirm('是否确认删除主播编号为"' + anchorIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delLiveAnchor(anchorIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有主播数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportLiveAnchor(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 699 - 0
src/views/live/liveConfig/index.vue

@@ -0,0 +1,699 @@
+<template>
+  <div class="live-config-container">
+    <!-- 根tabs -->
+    <el-tabs v-model="rootActiveName" @tab-click="handleClick" class="white-bg-tabs">
+      <el-tab-pane v-for="item in rootTabs" :key="item.name" :label="item.label" :name="item.name">
+        <!-- 营销内容 start -->
+        <el-tabs v-if="item.name == 'market'" v-model="marketActiveName" type="card" @tab-click="handleTabClick">
+          <el-tab-pane v-for="marketItem in marketTabs" :key="marketItem.name" :label="marketItem.label" :name="marketItem.name" class="market-tab-pane">
+            <!-- 观看奖励 start -->
+            <div v-if="marketItem.name == 'watchReward'">
+              <!-- 提示信息 -->
+              <div class="tip-message">
+                设置观看奖励,用户达到直播观看时长后可领取奖励
+              </div>
+
+              <!-- 开启观看奖励开关 -->
+              <div class="reward-switch">
+                <span class="switch-label">开启观看奖励</span>
+                <el-switch v-model="watchRewardForm.enabled"></el-switch>
+              </div>
+
+              <!-- 观看奖励设置 -->
+              <div v-if="watchRewardForm.enabled" class="section-block">
+                <div class="section-title">观看奖励设置</div>
+
+                <!-- 表单内容 -->
+                <el-form :model="watchRewardForm" ref="watchRewardForm" label-width="130px" class="reward-form">
+                  <!-- 参与条件 -->
+                  <el-form-item label="参与条件" required>
+                    <el-radio v-model="watchRewardForm.participateCondition" label="noCondition">
+                      达到指定观看时长
+                    </el-radio>
+                  </el-form-item>
+
+                  <!-- 观看时长 -->
+                  <el-form-item label="观看时长" required>
+                    <el-input v-model="watchRewardForm.watchDuration" placeholder="请输入观看时长" class="duration-input">
+                      <template slot="append">分钟</template>
+                    </el-input>
+                  </el-form-item>
+
+                  <!-- 实施动作 -->
+                  <el-form-item label="实施动作" required>
+                    <el-select v-model="watchRewardForm.action" placeholder="请选择实施动作" style="width: 300px;">
+                      <el-option
+                        v-for="item in actionOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value">
+                      </el-option>
+                    </el-select>
+                  </el-form-item>
+
+                  <!-- 领取提示语 -->
+                  <el-form-item label="领取提示语" required>
+                    <el-input v-model="watchRewardForm.receivePrompt" placeholder="请输入领取提示语"></el-input>
+                  </el-form-item>
+                </el-form>
+              </div>
+
+              <!-- 红包设置 -->
+              <div v-if="watchRewardForm.enabled" class="section-block">
+                <div class="section-title">红包设置</div>
+                <el-form :model="watchRewardForm" label-width="130px" class="reward-form">
+                  <!-- 根据实施动作类型显示不同的表单内容 -->
+                  <template v-if="watchRewardForm.action === '1'">
+                    <!-- 现金红包设置 -->
+                    <!-- 红包发放方式 -->
+                    <el-form-item label="红包发放方式" required>
+                      <el-radio-group v-model="watchRewardForm.redPacketType">
+                        <el-radio label="fixed">固定金额</el-radio>
+                        <el-radio label="random">随机金额</el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+
+                    <!-- 红包金额 -->
+                    <el-form-item label="红包金额" required>
+                      <el-input v-model="watchRewardForm.redPacketAmount" placeholder="请输入红包金额"></el-input>
+                    </el-form-item>
+
+                    <!-- 红包发放数量 -->
+                    <el-form-item label="红包发放数量">
+                      <el-input v-model="watchRewardForm.redPacketCount" placeholder="红包数量+28888人数"></el-input>
+                    </el-form-item>
+
+                    <!-- 红包领取方式 -->
+                    <el-form-item label="红包领取方式" required>
+                      <el-radio-group v-model="watchRewardForm.receiveMethod">
+                        <el-radio label="qrcode">二维码领取</el-radio>
+                        <el-radio label="wechat">微信发放</el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                  </template>
+
+                  <template v-else>
+                    <!-- 积分红包设置 -->
+                    <!-- 积分值 -->
+                    <el-form-item label="积分值" required>
+                      <el-input v-model="watchRewardForm.scoreAmount" placeholder="请输入积分值" style="width: 300px;"></el-input>
+                    </el-form-item>
+
+                    <!-- 最大领取人数 -->
+                    <el-form-item label="最大领取人数" required>
+                      <el-input v-model="watchRewardForm.maxReceivers" placeholder="请输入最大领取人数" style="width: 300px;"></el-input>
+                    </el-form-item>
+                  </template>
+                </el-form>
+              </div>
+
+              <!-- 其他设置 -->
+              <div v-if="watchRewardForm.enabled" class="section-block">
+                <div class="section-title">其他设置</div>
+                <template v-if="watchRewardForm.action === '1'">
+                  <el-form :model="watchRewardForm" label-width="130px" class="reward-form">
+                    <!-- 客服引导 -->
+                    <el-form-item label="客服引导" required>
+                      <el-radio-group v-model="watchRewardForm.showGuide">
+                        <el-radio label="show">跟进企业微信</el-radio>
+                        <el-radio label="hide">不设置</el-radio>
+                      </el-radio-group>
+                    </el-form-item>
+                    <el-form-item label="客服引导语" required>
+                      <el-input
+                        v-model="watchRewardForm.guideText"
+                        placeholder="请输入客服引导语"
+                        style="width: 300px;"
+                      ></el-input>
+                    </el-form-item>
+                  </el-form>
+                </template>
+
+                <template v-else>
+                  <el-form :model="watchRewardForm" label-width="150px" class="reward-form">
+                    <!-- 积分使用引导语 -->
+                    <el-form-item label="积分使用引导语" required>
+                      <el-input
+                        v-model="watchRewardForm.scoreGuideText"
+                        placeholder="请输入积分使用引导语"
+                        style="width: 300px;"
+                      ></el-input>
+                    </el-form-item>
+
+                    <!-- 积分使用引导链接 -->
+                    <el-form-item label="积分使用引导链接" required>
+                      <el-input
+                        v-model="watchRewardForm.scoreGuideLink"
+                        placeholder="请输入积分使用引导链接"
+                        style="width: 300px;"
+                      ></el-input>
+                    </el-form-item>
+
+                    <!-- 引导语 -->
+                    <el-form-item label="引导语" required>
+                      <el-input
+                        v-model="watchRewardForm.guideText"
+                        placeholder="请输入引导语"
+                        style="width: 300px;"
+                      ></el-input>
+                    </el-form-item>
+                  </el-form>
+                </template>
+              </div>
+
+              <!-- 保存按钮 -->
+              <div class="form-actions">
+                <el-button type="primary" @click="saveWatchReward">保存</el-button>
+              </div>
+            </div>
+            <!-- 观看奖励 end -->
+
+            <!-- 答题 start -->
+            <div v-if="marketItem.name == 'answer'">
+              <div class="tip-box">
+                选择用于本节直播课程的题库试题,试题可用于直播间内发送
+                <el-link
+                  type="primary"
+                  style="margin-left: 5px;"
+                  @click="handleToQuestionBank"
+                >配置题库试题 >></el-link>
+              </div>
+
+              <el-button type="primary" icon="el-icon-plus" style="margin: 20px 0;" @click="handleAddQuestion">添加试题</el-button>
+              <!-- 试题列表表格 -->
+              <el-table
+                :data="questionLiveList"
+                style="width: 100%"
+                v-loading="loading"
+              >
+                <!-- 题干列:显示试题的主要内容 -->
+                <el-table-column
+                  prop="title"
+                  label="题干"
+                  show-overflow-tooltip
+                ></el-table-column>
+
+                <!-- 题型列:显示是单选还是多选 -->
+                <el-table-column
+                  prop="type"
+                  label="题型"
+                >
+                  <template slot-scope="scope">
+                    {{ scope.row.type === 1 ? '单选题' : '多选题' }}
+                  </template>
+                </el-table-column>
+
+                <!-- 创建时间列:显示试题创建的时间 -->
+                <el-table-column
+                  prop="createTime"
+                  label="创建时间"
+                ></el-table-column>
+
+                <!-- 操作列:包含编辑和删除按钮 -->
+                <el-table-column
+                  label="操作"
+                  width="180"
+                  fixed="right"
+                >
+                  <template slot-scope="scope">
+                    <!-- 删除按钮:用于移除试题 -->
+                    <el-button
+                      type="text"
+                      size="small"
+                      style="color: #F56C6C;"
+                      @click="handleDelete(scope.row)"
+                    >删除</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
+
+              <!-- 分页组件:用于分页展示试题列表 -->
+              <pagination
+                v-show="questionTotal > 0"
+                :total="questionTotal"
+                :page.sync="questionParams.pageNum"
+                :limit.sync="questionParams.pageSize"
+                @pagination="getLiveQuestionLiveList"
+                style="margin-top: 20px;"
+              />
+
+              <!-- 添加试题弹窗 -->
+              <el-dialog
+                title="添加试题"
+                :visible.sync="questionDialogVisible"
+                width="800px"
+                :close-on-click-modal="false"
+                :close-on-press-escape="false"
+              >
+                <div class="dialog-content">
+                  <div style="text-align: right; margin-bottom: 20px;">
+                    <el-input
+                      v-model="searchTitle"
+                      placeholder="请输入搜索内容"
+                      style="width: 300px;"
+                      @input="handleQuestionSearch"
+                    ></el-input>
+                  </div>
+
+                  <el-table
+                    ref="questionTable"
+                    :data="questionList"
+                    style="width: 100%"
+                    v-loading="questionLoading"
+                    @selection-change="handleSelectionChange"
+                    @row-click="handleRowClick"
+                    row-key="id"
+                  >
+                    <!-- 复选框列:用于多选试题 -->
+                    <el-table-column
+                      type="selection"
+                      width="55"
+                    >
+                    </el-table-column>
+                    <!-- 题干列:显示试题的主要内容 -->
+                    <el-table-column
+                      prop="title"
+                      label="题干"
+                      class-name="clickable-column"
+                    ></el-table-column>
+                    <!-- 题型列:显示单选或多选 -->
+                    <el-table-column
+                      prop="type"
+                      label="题型"
+                      class-name="clickable-column"
+                    >
+                      <template slot-scope="scope">
+                        {{ scope.row.type === 1 ? '单选题' : '多选题' }}
+                      </template>
+                    </el-table-column>
+                    <!-- 创建人列 -->
+                    <el-table-column
+                      prop="createBy"
+                      label="创建人"
+                      class-name="clickable-column"
+                    ></el-table-column>
+                    <!-- 创建时间列 -->
+                    <el-table-column
+                      prop="createTime"
+                      label="创建时间"
+                      width="150"
+                      class-name="clickable-column"
+                    ></el-table-column>
+                  </el-table>
+
+                  <pagination
+                    v-show="total > 0"
+                    :total="total"
+                    :page.sync="queryParams.pageNum"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getQuestionList"
+                    style="margin-top: 20px;"
+                  />
+                </div>
+                <div slot="footer" class="dialog-footer">
+                  <div style="display: flex; justify-content: space-between; align-items: center;">
+                    <span class="selected-count">当前已选择 <span style="color: #00BFFF; font-style: italic;">{{ selectedQuestions.length }}</span> 题</span>
+                    <div>
+                      <el-button @click="questionDialogVisible = false">取 消</el-button>
+                      <el-button type="primary" @click="confirmAddQuestion">确 定</el-button>
+                    </div>
+                  </div>
+                </div>
+              </el-dialog>
+            </div>
+            <!-- 答题 end -->
+          </el-tab-pane>
+        </el-tabs>
+        <!-- 营销内容 end -->
+      </el-tab-pane>
+    </el-tabs>
+    <!-- 根tabs end -->
+  </div>
+</template>
+
+<script>
+import { listLiveQuestionLive, listLiveQuestionOptionList, addLiveQuestionLive, deleteLiveQuestionLive } from '@/api/live/liveQuestionLive'
+export default {
+  name: 'LiveConfig',
+  data() {
+    return {
+      liveId: null,
+      loading: true,
+      rootActiveName: 'market',
+      rootTabs: [
+        {
+          label: '营销内容',
+          name: 'market'
+        }
+      ],
+      marketActiveName: 'watchReward',
+      marketTabs: [
+        {
+          label: '观看奖励',
+          name: 'watchReward'
+        },
+        {
+          label: '答题红包',
+          name: 'answerRedPacket'
+        },
+        {
+          label: '答题',
+          name: 'answer'
+        },
+        {
+          label: '观看积分 ',
+          name: 'watchScore'
+        }
+      ],
+      questionDialogVisible: false,
+      questionLoading: false,
+      searchTitle: '',
+      questionList: [],
+      selectedQuestions: [],
+      total: 0,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        title: null
+      },
+      questionLiveList: [],
+      questionTotal: 0,
+      questionParams: {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: null
+      },
+      watchRewardForm: {
+        // 是否启用观看奖励
+        enabled: false,
+        // 参与条件
+        participateCondition: 'noCondition',
+        // 观看时长
+        watchDuration: '',
+        // 实施动作
+        action: '1',
+        // 领取提示语
+        receivePrompt: '',
+        // 红包发放方式(固定金额/随机金额)
+        redPacketType: 'fixed',
+        // 红包金额
+        redPacketAmount: '',
+        // 红包发放数量
+        redPacketCount: '',
+        // 红包领取方式
+        receiveMethod: 'qrcode',
+        // 是否显示客服引导
+        showGuide: 'show',
+        // 客服引导语
+        guideText: '',
+        // 积分值
+        scoreAmount: '',
+        // 最大领取人数
+        maxReceivers: '',
+        // 积分使用引导语
+        scoreGuideText: '',
+        // 积分使用引导链接
+        scoreGuideLink: ''
+      },
+      // 添加实施动作选项
+      actionOptions: [
+        {
+          label: '现金红包',
+          value: '1'
+        },
+        {
+          label: '积分红包',
+          value: '2'
+        }
+      ]
+    }
+  },
+  created() {
+    this.liveId = this.$route.params.liveId
+    this.queryParams.liveId = this.liveId
+    this.questionParams.liveId = this.liveId
+    this.getLiveQuestionLiveList()
+  },
+  methods: {
+    handleClick(tab, event) {
+      console.info(tab, event)
+    },
+    handleToQuestionBank() {
+      this.$router.push('/live/liveQuestionBank')
+    },
+    handleTabClick(tab) {
+      if(tab.name == 'answer') {
+        this.getLiveQuestionLiveList()
+      }
+    },
+    getLiveQuestionLiveList() {
+      this.loading = true
+      listLiveQuestionLive(this.questionParams).then(response => {
+        this.questionLiveList = response.rows
+        this.questionTotal = response.total
+        this.loading = false
+      })
+    },
+    handleAddQuestion() {
+      this.questionDialogVisible = true
+      this.getQuestionList()
+    },
+    getQuestionList() {
+      this.questionLoading = true
+      listLiveQuestionOptionList(this.queryParams).then(response => {
+        this.questionList = response.rows
+        this.total = response.total
+        this.questionLoading = false
+      })
+    },
+    handleQuestionSearch() {
+      this.queryParams.pageNum = 1
+      this.queryParams.title = this.searchTitle
+      this.getQuestionList()
+    },
+    handleSelectionChange(selection) {
+      this.selectedQuestions = selection
+    },
+    handleCurrentChange() {
+      this.getQuestionList()
+    },
+    confirmAddQuestion() {
+      if (this.selectedQuestions.length === 0) {
+        this.$message({
+          message: '请选择要添加的试题',
+          type: 'warning'
+        })
+        return
+      }
+      // 调用添加直播间试题接口
+      addLiveQuestionLive({
+        liveId: this.liveId,
+        questionIds: this.selectedQuestions.map(item => item.id).join(',')
+      }).then(response => {
+        this.questionDialogVisible = false
+        this.getLiveQuestionLiveList()
+      })
+    },
+    handleDelete(row) {
+      // 调用删除直播间试题接口
+      deleteLiveQuestionLive({
+        liveId: this.liveId,
+        ids: row.id
+      }).then(response => {
+        this.getLiveQuestionLiveList()
+      })
+    },
+    /** 处理行点击事件 */
+    handleRowClick(row, column) {
+      // 如果点击的是复选框列,不进行处理
+      if (column.type === 'selection') {
+        return
+      }
+
+      // 获取表格实例
+      const table = this.$refs.questionTable[0]
+      if (!table) {
+        return
+      }
+
+      // 判断当前行是否已经被选中
+      const isSelected = this.selectedQuestions.some(item => item.id === row.id)
+
+      // 切换选中状态
+      table.toggleRowSelection(row, !isSelected)
+    },
+    saveWatchReward() {
+      // 调用保存观看奖励接口
+      // 实现保存逻辑
+    }
+  }
+}
+</script>
+
+<style scoped>
+.live-config-container {
+  padding: 10px 20px;
+  height: calc(100vh - 84px); /* 减去头部导航的高度 */
+}
+.white-bg-tabs {
+  background-color: #fff;
+  padding: 10px 20px;
+  border-radius: 4px;
+  height: 100%;
+}
+
+.market-tab-pane {
+  height: 74vh;
+  overflow-y: auto;
+}
+.tip-box {
+  padding: 12px 16px;
+  background-color: #FFF6F2;
+  border-radius: 4px;
+  color: #666;
+  font-size: 14px;
+}
+
+/* 修改弹窗相关样式 */
+::v-deep .el-dialog {
+  height: 90%;
+  margin: 0 !important;
+  width: 900px !important;
+}
+
+::v-deep .el-dialog__body {
+  padding: 20px;
+  height: calc(100% - 110px);  /* 减去header和footer的高度 */
+  overflow: hidden;
+}
+
+.dialog-content {
+  height: 100%;
+  overflow-y: auto;
+}
+
+::v-deep .el-dialog__footer {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  background: #fff;
+  z-index: 1;
+  border-top: 1px solid #e4e7ed;
+  padding: 15px 20px;
+}
+
+::v-deep .el-dialog__header {
+  padding: 15px 20px;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.selected-count {
+  color: #999;
+  font-size: 14px;
+}
+
+/* 可点击列的样式 */
+::v-deep .clickable-column {
+  cursor: pointer;
+}
+
+/* 提示信息样式 */
+.tip-message {
+  padding: 12px 16px;
+  background-color: #FFF6F2;
+  border-radius: 4px;
+  color: #666;
+  font-size: 14px;
+  margin-bottom: 20px;
+}
+
+/* 开关容器样式 */
+.reward-switch {
+  margin-left: 200px;
+  margin-bottom: 20px;
+  padding: 20px;
+  background-color: #fff;
+  border-radius: 4px;
+  display: flex;
+  align-items: center;
+}
+
+/* 开关标签样式 */
+.reward-switch .switch-label {
+  margin-right: 10px;
+  font-size: 14px;
+  color: #333;
+  margin-left: 50px;
+}
+
+/* 表单区块样式 */
+.section-block {
+  width: 50%;
+  background-color: #fff;
+  padding: 20px;
+  border-radius: 4px;
+  margin-left: 50px;
+  margin-bottom: 20px;
+}
+
+/* 标题样式 */
+.section-block .section-title {
+  font-size: 14px;
+  color: #333;
+  margin-bottom: 20px;
+  border-left: 4px solid #409EFF;
+  padding-left: 10px;
+  line-height: 1;
+}
+
+/* 表单样式 */
+.reward-form {
+  margin-top: 20px;
+}
+
+/* 表单项样式 */
+.reward-form .el-form-item {
+  margin-bottom: 22px;
+  padding-left: 50px;
+}
+
+.reward-form .el-form-item:last-child {
+  margin-bottom: 0;
+}
+
+/* 表单标签样式 */
+.reward-form .el-form-item .el-form-item__label {
+  color: #606266;
+}
+
+/* 输入框统一宽度 */
+.reward-form .el-form-item .el-input {
+  width: 300px;
+}
+
+/* 必填项星号样式 */
+.reward-form .el-form-item.is-required .el-form-item__label:before {
+  color: #F56C6C;
+}
+
+/* 观看时长输入框样式 */
+.reward-form .el-form-item .duration-input {
+  width: 300px;
+}
+
+.reward-form .el-form-item .duration-input .el-input__inner {
+  text-align: left;
+}
+
+/* 保存按钮样式 */
+.form-actions {
+  width: 600px;
+  text-align: center;
+  margin-top: 30px;
+}
+
+.form-actions .el-button {
+  padding: 8px 20px;
+  font-size: 13px;
+}
+</style>

+ 497 - 0
src/views/live/liveConsole/index.vue

@@ -0,0 +1,497 @@
+<template>
+  <!-- 直播中控台 start -->
+  <el-row type="flex" justify="center" class="live-console" :gutter="10">
+
+    <!-- 用户列表 start -->
+    <el-col class="live-console-col" :span="5">
+      <el-tabs class="live-console-tab-left" v-model="tabLeft.activeName" @tab-click="handleClick" :stretch="true">
+        <el-tab-pane :label="onlineLabel" name="online">
+          <el-scrollbar ref="manageLeftRef_online" style="height: 800px; width: 100%;">
+            <el-row style="margin-top: 10px" type="flex" align="middle" v-for="u in onlineUserList">
+              <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-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <i class="el-icon-more" slot="reference"></i>
+                </el-popover>
+              </el-col>
+            </el-row>
+          </el-scrollbar>
+        </el-tab-pane>
+        <el-tab-pane :label="offlineLabel" name="offline">
+          <el-scrollbar ref="manageLeftRef_offline" style="height: 800px; width: 100%;">
+            <el-row style="margin-top: 10px" type="flex" align="middle" v-for="u in offlineUserList">
+              <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-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <i class="el-icon-more" slot="reference"></i>
+                </el-popover>
+              </el-col>
+            </el-row>
+          </el-scrollbar>
+        </el-tab-pane>
+        <el-tab-pane :label="silencedUserLabel" name="silenced">
+          <el-scrollbar ref="manageLeftRef_silenced" style="height: 800px; width: 100%;">
+            <el-row style="margin-top: 10px" type="flex" align="middle" v-for="u in silencedUserList">
+              <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-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <i class="el-icon-more" slot="reference"></i>
+                </el-popover>
+              </el-col>
+            </el-row>
+          </el-scrollbar>
+        </el-tab-pane>
+      </el-tabs>
+    </el-col>
+    <!-- 用户列表 end -->
+
+    <!-- 直播/视频 start -->
+    <el-col class="live-console-col" :span="11">
+      <div style="background: #000; border-radius: 5px; overflow: hidden; margin: 10px 5px;">
+        <div style="border-radius: 5px; overflow: hidden;">
+          <video 
+            controls 
+            autoplay 
+            :src="this.liveVideo.videoUrl" 
+            width="100%"
+            style="display: block; background: #000;"
+          ></video>
+        </div>
+      </div>
+      <!-- 底部导航栏 -->
+      <div style="display: flex; justify-content: space-around; padding: 15px 0; background: #fff; border-top: 1px solid #f0f0f0;">
+          <div style="display: flex; flex-direction: column; align-items: center; cursor: pointer;">
+            <i class="el-icon-microphone" style="font-size: 20px;"></i>
+            <span style="font-size: 12px; margin-top: 4px;">语音</span>
+          </div>
+          <div style="display: flex; flex-direction: column; align-items: center; cursor: pointer;">
+            <i class="el-icon-video-camera" style="font-size: 20px;"></i>
+            <span style="font-size: 12px; margin-top: 4px;">视频</span>
+          </div>
+          <div style="display: flex; flex-direction: column; align-items: center; cursor: pointer;">
+            <i class="el-icon-share" style="font-size: 20px;"></i>
+            <span style="font-size: 12px; margin-top: 4px;">分享</span>
+          </div>
+          <div style="display: flex; flex-direction: column; align-items: center; cursor: pointer;">
+            <i class="el-icon-message" style="font-size: 20px;"></i>
+            <span style="font-size: 12px; margin-top: 4px;">评论</span>
+          </div>
+          <div style="display: flex; flex-direction: column; align-items: center; cursor: pointer;">
+            <i class="el-icon-goods" style="font-size: 20px;"></i>
+            <span style="font-size: 12px; margin-top: 4px;">商品</span>
+          </div>
+          <div style="display: flex; flex-direction: column; align-items: center; cursor: pointer;">
+            <i class="el-icon-menu" style="font-size: 20px;"></i>
+            <span style="font-size: 12px; margin-top: 4px;">工具箱</span>
+          </div>
+        </div>
+    </el-col>
+    <!-- 直播/视频 end -->
+
+    <!-- 聊天 start -->
+    <el-col class="live-console-col" :span="5">
+      <el-tabs class="live-console-tab-right" v-model="tabRight.activeName" @tab-click="handleClick">
+        <el-tab-pane label="聊天" name="talk">
+          <el-scrollbar style="height: 500px; width: 100%;" ref="manageRightRef">
+            <el-row v-for="m in msgList" >
+              <el-row v-if="m.userId !== userId" style="margin-top: 5px" type="flex" align="top" >
+                <el-col :span="3" style="margin-left: 10px"><el-avatar :src="m.avatar"/></el-col>
+                <el-col :span="15">
+                  <el-row style="margin-left: 10px">
+                    <el-col><div style="font-size: 12px; color: #999; margin-bottom: 3px;">{{ m.nickName }}</div></el-col>
+                    <el-col :span="24" style="max-width: 200px;">
+                      <div style="white-space: normal; word-wrap: break-word;background-color: #f0f2f5; padding: 8px; border-radius: 5px;font-size: 14px;width: 100%;">
+                        {{ m.msg }}
+                      </div>
+                    </el-col>
+                    <el-col>
+                      <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                    </el-col>
+                  </el-row>
+                </el-col>
+              </el-row>
+              <el-row v-if="m.userId === userId" style="padding: 8px 0" type="flex" align="top" justify="end">
+                <div style="display: flex;justify-content: flex-end">
+                  <div style="display: flex;justify-content: flex-end;flex-direction: column;max-width: 200px;align-items: flex-end">
+                    <div style="font-size: 12px; color: #999; margin-bottom: 3px;">{{ m.nickName }}</div>
+                    <div style="white-space: normal; word-wrap: break-word;width: 100%; background-color: #e6f7ff; padding: 8px; border-radius: 5px;font-size: 14px;">{{ m.msg }}</div>
+                  </div>
+                  <el-avatar :src="m.avatar" style="margin-left: 10px; margin-right: 10px;"/>
+                </div>
+              </el-row>
+            </el-row>
+            <!-- 底部留白 -->
+            <div style="height: 20px;"></div>
+          </el-scrollbar>
+
+          <!-- 消息输入区域 -->
+          <div style="padding: 10px; border-top: 1px solid #ebeef5; background-color: #fff; min-height: 120px;">
+            <el-input
+              type="textarea" 
+              v-model="newMsg"
+              placeholder="请输入消息..."
+              :rows="8"
+              @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 plain @click="sendMessage">发送</el-button>
+            </div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+    </el-col>
+    <!-- 聊天 end -->
+  </el-row>
+  <!-- 直播中控台  end -->
+</template>
+
+<script>
+import { changeUserStatus, watchUserList } from '@/api/live/liveWatchUser'
+import { getLiveVideoByLiveId } from '@/api/live/liveVideo'
+import { listLiveMsg } from '@/api/live/liveMsg'
+import { LiveWS } from '@/utils/liveWS'
+
+export default {
+  name: "LiveConsole",
+  data() {
+    return {
+      tabLeft: {
+        activeName: "online",
+      },
+      tabRight: {
+        activeName: "talk",
+      },
+      liveVideo: {},
+      socket: null,
+      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: ''
+    }
+  },
+  created() {
+    this.getLiveVideo()
+    this.getList()
+    this.connectWebSocket()
+  },
+  computed: {
+    liveId() {
+      return this.$route.params.liveId;
+    },
+    userId() {
+      return this.$store.state.user.user.userId
+    },
+    companyId() {
+      return this.$store.state.user.user.companyId
+    },
+    onlineUserList() {
+      return this.userList.filter(u => u.online === 0)
+    },
+    onlineLabel() {
+      if (this.onlineUserList.length > 0) {
+        return '在线(' + this.onlineUserList.length + ')'
+      }
+      return '在线'
+    },
+    offlineUserList() {
+      return this.userList.filter(u => u.online === 1)
+    },
+    offlineLabel() {
+      if (this.offlineUserList.length > 0) {
+        return '离线(' + this.offlineUserList.length + ')'
+      }
+      return '离线'
+    },
+    silencedUserList() {
+      return this.userList.filter(u => u.msgStatus === 1)
+    },
+    silencedUserLabel() {
+      if (this.silencedUserList.length > 0) {
+        return '禁言(' + this.silencedUserList.length + ')'
+      }
+      return '禁言'
+    }
+  },
+  methods: {
+    handleClick(tab) {
+      console.log("click",tab.name)
+      console.log("liveId", this.liveId)
+    },
+    getLiveVideo() {
+      getLiveVideoByLiveId(this.liveId).then(res => {
+        this.liveVideo = res.data
+      })
+    },
+    getList() {
+      this.resetParams()
+      this.loadUserList()
+      this.loadMsgList()
+    },
+    resetParams() {
+      this.userList= []
+      this.userParams = {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: this.liveId
+      }
+      this.msgList = []
+      this.msgParams = {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: this.liveId
+      }
+    },
+    loadUserList() {
+      // 直播间用户
+      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() {
+      // 直播间消息
+      listLiveMsg({
+        liveId:this.liveId,
+        pageNum: this.msgParams.pageNum,
+        pageSize: this.msgParams.pageSize
+      }).then(response => {
+          let {code, rows,total} = response;
+          if (code === 200) {
+            let totalPage = (total % this.msgParams.pageSize == 0) ? Math.floor(total / this.msgParams.pageSize) : Math.floor(total / this.msgParams.pageSize + 1);
+            rows.forEach(row => {
+              if (!this.msgList.some(m => m.msgId === row.msgId)) {
+                
+                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)
+                })
+              }
+            })
+
+            // 没加载完继续加载
+            if (this.msgParams.pageNum < totalPage) {
+              this.msgParams.pageNum = parseInt(this.msgParams.pageNum) + 1;
+              this.loadMsgList()
+            }
+
+            // 同步更新消息列表中相同用户的状态
+            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() {
+      let max = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
+      let current = this.$refs.manageRightRef.wrap.scrollTop
+      console.log("manageRightMax", max);
+      console.log("manageRight", current)
+    },
+    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;
+            }
+          });
+
+          let msg = u.msgStatus === 0 ? "已解禁" : "已禁言"
+          this.msgSuccess(msg);
+          return
+        }
+        this.msgError("操作失败");
+      })
+    },
+    connectWebSocket() {
+      let socket = new LiveWS(this.liveWsUrl, this.liveId, this.userId);
+      socket.onmessage = (event) => this.handleWsMessage(event)
+      this.socket = socket
+    },
+    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
+          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 === 'entry' || cmd === 'out') {
+          let user = JSON.parse(data.data)
+          this.userList = this.userList.filter(u => u.userId !== user.userId)
+          this.userList.push(user)
+        }
+      }
+    },
+    sendMessage() {
+      console.log(this.newMsg);
+      // 发送前简单校验
+      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 = '';
+    }
+  },
+  destroyed() {
+    this.socket?.close()
+  }
+}
+</script>
+
+<style scoped>
+.talk-list{
+  display: flex;
+}
+  .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;
+  }
+
+</style>

+ 1483 - 0
src/views/live/liveData/index.vue

@@ -0,0 +1,1483 @@
+<template>
+  <div class="app-container">
+    <div class="title">近期直播</div>
+    <div class="live-container">
+      <div class="live-card" v-for="live in displayLives" :key="live.liveId">
+        <!-- 直播状态 -->
+        <div class="status" :class="getStatusClass(live.status)">
+          {{ getStatusText(live.status) }}
+        </div>
+        <!-- 直播信息 -->
+        <div class="content">
+          <img :src="live.liveImgUrl" alt="直播封面" class="cover-image" />
+          <div class="info">
+            <h3 class="live-title">{{ live.liveName }}</h3>
+            <p class="time">开播时间: {{ live.startTime }}</p>
+          </div>
+        </div>
+
+        <!-- 直播数据 -->
+        <div class="stats">
+          <div class="stat-item">
+            <p class="label">直播间浏览量</p>
+            <p class="value">{{ live.pageViews }}</p>
+          </div>
+          <div class="stat-item">
+            <p class="label">直播间访客数</p>
+            <p class="value">{{ live.uniqueVisitors }}</p>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 直播趋势统计 -->
+    <div class="trend-section">
+      <div class="trend-header">
+        <h3>直播趋势</h3>
+        <div class="filter-container">
+        <!-- 时间范围选择(下拉框) -->
+          <span class="filter-label">时间筛选:</span>
+          <el-select v-model="selectedTimeRange" placeholder="选择时间范围" @change="changeTimeRange" style="width: 180px">
+            <el-option label="自然天" value="day"></el-option>
+            <el-option label="自然周" value="week"></el-option>
+            <el-option label="自然月" value="month"></el-option>
+          </el-select>
+
+          <!-- 日期选择器 -->
+          <!-- 选择日期 -->
+          <el-date-picker
+            ref="datePickerRef"
+            v-model="selectedDate"
+            :type="datePickerType"
+            :format="selectedTimeRange === 'week' ? weekRange : dateFormat"
+            :value-format="valueFormat"
+            placeholder="选择日期"
+            style="width: 280px"
+            @change="changeDate"
+            @blur="setDefaultDate"
+          />
+        </div>
+      </div>
+
+      <div class="trend-cards">
+        <div class="trend-card"
+             :class="{ 'active': selectedMetric === 'page_views' }"
+             @click="changeMetric('page_views')">
+          <p class="trend-title">
+            浏览量
+            <el-tooltip class="item" effect="dark" content="观看页面的总浏览量" placement="top">
+              <i class="el-icon-info"></i>
+            </el-tooltip>
+          </p>
+          <p class="trend-value">{{ trendData.views || 0 }}</p>
+          <p class="trend-tip">{{ changeLabel }}: {{ trendData.viewsChange }}%</p>
+
+        </div>
+
+        <div class="trend-card"
+             :class="{ 'active': selectedMetric === 'unique_visitors' }"
+             @click="changeMetric('unique_visitors')">
+          <p class="trend-title">
+            访客数
+            <el-tooltip class="item" effect="dark" content="进入直播间的访客数" placement="top">
+              <i class="el-icon-info"></i>
+            </el-tooltip>
+          </p>
+          <p class="trend-value">{{ trendData.visitors|| 0 }}</p>
+          <p class="trend-tip">{{ changeLabel }}: {{ trendData.visitorsChange }}%</p>
+
+        </div>
+
+        <div class="trend-card"
+             :class="{ 'active': selectedMetric === 'streams' }"
+             @click="changeMetric('streams')">
+          <p class="trend-title">
+            创建直播数
+            <el-tooltip class="item" effect="dark" content="创建的直播间数量" placement="top">
+              <i class="el-icon-info"></i>
+            </el-tooltip>
+          </p>
+          <p class="trend-value">{{ trendData.streams|| 0 }}</p>
+          <p class="trend-tip">{{ changeLabel }}: {{ trendData.streamsChange }}%</p>
+
+        </div>
+
+        <div class="trend-card"
+             :class="{ 'active': selectedMetric === 'total_views' }"
+             @click="changeMetric('total_views')">
+          <p class="trend-title">
+            累计观看人次
+            <el-tooltip class="item" effect="dark" content="直播间被观看的总人次" placement="top">
+              <i class="el-icon-info"></i>
+            </el-tooltip>
+          </p>
+          <p class="trend-value">{{ trendData.pv|| 0 }}</p>
+          <p class="trend-tip">{{ changeLabel }}: {{ trendData.pvChange }}%</p>
+
+        </div>
+
+        <div class="trend-card"
+             :class="{ 'active': selectedMetric === 'unique_viewers' }"
+             @click="changeMetric('unique_viewers')">
+          <p class="trend-title">
+            累计观看人数
+            <el-tooltip class="item" effect="dark" content="去重后的观看人数" placement="top">
+              <i class="el-icon-info"></i>
+            </el-tooltip>
+          </p>
+          <p class="trend-value">{{ trendData.uv|| 0 }}</p>
+          <p class="trend-tip">{{ changeLabel }}: {{ trendData.uvChange }}%</p>
+
+        </div>
+      </div>
+
+
+      <!-- 直播趋势折线图 -->
+      <div id="liveChart" class="chart"></div>
+    </div>
+    <!-- 直播TOP10排行榜 -->
+    <div class="top10-section">
+      <h3>直播TOP10排行榜</h3>
+      <div class="ranking-container">
+          <!-- 红榜 -->
+          <div class="ranking-section red-rank">
+            <span class="rank-title">红榜</span>
+            <div class="rank-filters">
+              <button
+                v-for="item in redRankTypes"
+                :key="item.value"
+                :class="{ active: selectedRank === item.value }"
+                @click="selectRank(item.value)"
+              >
+                {{ item.label }}
+              </button>
+            </div>
+          </div>
+
+          <!-- 黑榜 -->
+          <div class="ranking-section black-rank">
+            <div class="rank-filters">
+              <button
+                v-for="item in blackRankTypes"
+                :key="item.value"
+                :class="{ active: selectedRank === item.value }"
+                @click="selectRank(item.value)"
+              >
+                {{ item.label }}
+              </button>
+            </div>
+            <span class="rank-title">黑榜</span>
+          </div>
+      </div>
+      <el-table :data="top10List" border style="width: 100%">
+        <el-table-column prop="rank" label="排名" width="80"></el-table-column>
+        <el-table-column prop="liveName" label="直播名称">
+          <template #default="scope">
+            <div class="live-name">
+              <img :src="scope.row.liveImgUrl" class="live-cover" alt="封面">
+              <span class="live-title">{{ scope.row.liveName }}</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="pageViews" label="直播间浏览量(PV)"></el-table-column>
+        <el-table-column prop="uniqueVisitors" label="直播间访客数(UV)"></el-table-column>
+        <el-table-column prop="totalViews" label="累计观看人次(PV)"></el-table-column>
+        <el-table-column prop="uniqueViewers" label="累计观看人数(UV)"></el-table-column>
+        <!--<el-table-column prop="avgTime" label="人均观看时长"></el-table-column>-->
+        <el-table-column prop="peakConcurrentViewers" label="最高在线人数"></el-table-column>
+      </el-table>
+
+    </div>
+    <div class="student-section">
+      <h3>直播间学员</h3>
+        <el-form :inline="true" v-show="showAdvancedSearch" label-width="100px">
+          <!-- 直播列表弹框 -->
+          <el-dialog title="选择直播" :visible.sync="dialogVisible" width="70%">
+            <el-row :gutter="20">
+              <el-col :span="8" :sm="12" :md="8">
+                <el-form-item label="直播名称:">
+                  <el-input v-model="liveFiltersParam.liveName" placeholder="请输入直播名称"></el-input>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8" :sm="12" :md="8">
+                <el-form-item label="直播状态:">
+                  <el-select v-model="liveFiltersParam.status" placeholder="请选择直播状态" clearable>
+                    <el-option label="全部" value=""></el-option>
+                    <el-option label="已结束" value="3"></el-option>
+                    <el-option label="进行中" value="2"></el-option>
+                    <el-option label="未开始" value="1"></el-option>
+                  </el-select>
+                </el-form-item>
+              </el-col>
+              <el-col :span="8" :sm="12" :md="8">
+                <el-form-item label="直播时间:">
+                  <el-date-picker
+                    v-model="liveDateRange"
+                    type="daterange"
+                    value-format="yyyy-MM-dd"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    clearable
+                    style="width: 250px"
+                    @change="handleLiveDateChange"
+                  ></el-date-picker>
+                </el-form-item>
+              </el-col>
+              <!-- 查询 & 重置 按钮 -->
+              <el-col :span="4" class="button-group">
+                <el-button type="primary" @click="getLive">查询</el-button>
+                <el-button @click="resetLiveFilters">重置</el-button>
+              </el-col>
+            </el-row>
+
+            <!-- 直播列表 -->
+            <el-table :data="liveList" border @selection-change="handleSelectionChange">
+              <el-table-column type="selection" width="50" align="center" />
+              <!-- 直播信息列 -->
+              <el-table-column label="直播名称" min-width="250">
+                <template slot-scope="{ row }">
+                  <div class="live-info">
+                    <!-- 直播封面图 -->
+                    <img :src="row.liveImgUrl" class="live-cover" />
+                    <!-- 直播名称 + 时间 -->
+                    <div class="live-text">
+                      <div type="text"class="live-name">{{ row.liveName }}</div>
+                      <div class="live-time">{{ row.startTime }}-{{row.finishTime}}</div>
+                    </div>
+                  </div>
+                </template>
+              </el-table-column>
+
+              <!-- 直播状态 -->
+              <el-table-column label="直播状态" min-width="120">
+                <template slot-scope="{ row }">
+                  <div class="status-container">
+                    <span :class="['status-dot', getStatusClass(row.status)]"></span>
+                    <span>{{ getStatusText(row.status) }}</span>
+                  </div>
+                </template>
+              </el-table-column>
+
+              <!-- 讲师 -->
+              <el-table-column label="讲师" prop="teacher" min-width="120"></el-table-column>
+            </el-table>
+            <pagination
+              v-show="liveTotal>0"
+              :total="liveTotal"
+              :page.sync="queryParams.pageNum"
+              :limit.sync="queryParams.pageSize"
+              @pagination="getLive"
+            />
+            <!-- 确认按钮 -->
+            <span slot="footer" class="dialog-footer">
+              <el-button @click="dialogVisible = false">取消</el-button>
+              <el-button type="primary" @click="confirmSelection">确认</el-button>
+            </span>
+          </el-dialog>
+          <!-- 基础筛选项:始终显示 -->
+          <el-row :gutter="20">
+            <el-col :span="8" :sm="12" :md="8">
+              <el-form-item label="直播名称:" class="el-form-item-ellipsis">
+                <el-input
+                  v-model="filters.liveName"
+                  placeholder="请选择直播"
+                  @focus="openDialog"
+                  type="text"
+                  readonly
+                >
+                  <template slot="prepend">
+                    <div v-if="filters.liveNames.length > 0" class="selected-lives">
+                      <el-tag
+                        v-for="(name, index) in filters.liveNames"
+                        :key="index"
+                        closable
+                        @close="removeLive(index)"
+                        class="live-name-tag"
+                      >
+                        {{ name }}
+                      </el-tag>
+                    </div>
+                  </template>
+                </el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8" :sm="12" :md="8">
+              <el-form-item label="用户名:" class="el-form-item-ellipsis">
+                <el-input v-model="filters.userName" placeholder="请输入用户名"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8" :sm="12" :md="8">
+              <el-form-item label="手机号:" class="el-form-item-ellipsis">
+                <el-input v-model="filters.userPhone" placeholder="请输入手机号"></el-input>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <!-- 高级筛选项 -->
+          <el-row :gutter="24" v-show="showAllFilters">
+            <el-col :span="8" :sm="12" :md="8" >
+              <el-form-item label="用户创建时间:">
+                <el-date-picker
+                  v-model="dateRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                  value-format="yyyy-MM-dd"
+                  @change="handleDateChange"
+                  class="full-width-picker"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8" :sm="12" :md="8">
+              <el-form-item label="分享次数:" class="el-form-item-ellipsis">
+                <el-input v-model="filters.shareCount" placeholder="请输入分享次数"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8" :sm="12" :md="8">
+              <el-form-item label="所属部门:" prop="deptId">
+                <treeselect style="width:220px" v-model="filters.deptId" :options="deptOptions" :show-count="true" placeholder="请选择所属部门" />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20" v-show="showAllFilters">
+            <el-col :span="8" :sm="12" :md="8"> <!-- 每列 span 总和控制在 24 内 -->
+              <el-form-item label="跟进人:" class="el-form-item-ellipsis">
+                <el-input v-model="filters.companyUserName" placeholder="请输入跟进人"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8" :sm="12" :md="8">
+              <el-form-item label="购买商品数:" class="el-form-item-ellipsis">
+                <el-input v-model="filters.goodsCount" placeholder="请输入购买商品数"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8" :sm="12" :md="8">
+              <el-form-item label="提问数:" class="flex-container">
+                <el-row>
+                  <el-col :span="8" :sm="12" :md="8">
+                    <el-input-number v-model="filters.minQuestionCount" placeholder="最小提问数" :min="0" style="width: 100%"></el-input-number>
+                  </el-col>
+                  <el-col :span="2" class="text-center">至</el-col>
+                  <el-col :span="8" :sm="12" :md="8">
+                    <el-input-number v-model="filters.maxQuestionCount" placeholder="最大提问数" :min="0" style="width: 100%"></el-input-number>
+                  </el-col>
+                </el-row>
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <!-- 按钮组:始终显示 -->
+          <el-row>
+            <el-col :span="24" class="button-group">
+              <el-button type="primary" @click="getStudentData" class="custom-button">查询</el-button>
+              <el-button @click="resetFilters" class="custom-button">重置</el-button>
+              <el-button type="text" @click="toggleAllFilters" class="custom-button toggle-button">
+                {{ showAllFilters ? '收起' : '展开' }}
+                <i :class="showAllFilters ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
+              </el-button>
+            </el-col>
+          </el-row>
+        </el-form>
+      <!--<div class="column-button-container">
+        <el-button
+          type="primary"
+          @click="showColumnSettings = true"
+          class="custom-column-button"
+        >
+          自定义列
+        </el-button>
+      </div>-->
+      <!-- 自定义列弹窗 -->
+      <el-drawer
+        title="自定义列设置"
+        :visible.sync="showColumnSettings"
+        :before-close="handleClose"
+        size="25%"
+      >
+        <!-- 抽屉内容区域 -->
+        <!--<div style="display: flex; flex-direction: column; height: 100%;">
+          &lt;!&ndash; 可拖拽的列设置区域 &ndash;&gt;
+          <draggable
+            v-model="columnOrder"
+            group="columns"
+            style="flex: 1; overflow-y: auto;"
+          >
+            <div v-for="col in columnOrder" :key="col.dataIndex" class="column-item">
+              <el-checkbox v-model="col.status" true-label="ENABLE" false-label="DISABLE">
+                {{ col.title }}
+              </el-checkbox>
+              <i class="el-icon-rank drag-handle"></i>
+            </div>
+          </draggable>
+
+          &lt;!&ndash; 底部按钮区域 &ndash;&gt;
+          <div style="padding: 12px; text-align: right; border-top: 2px solid;">
+            <el-button @click="showColumnSettings = false" style="color: black;">取消</el-button>
+            <el-button
+              type="primary"
+              @click="saveColumnsConfig"
+              style="color: black; background-color: #409EFF; border-color: #409EFF;"
+            >
+              确定
+            </el-button>
+          </div>
+        </div>-->
+      </el-drawer>
+
+
+      <div style="overflow-x: auto; white-space: nowrap;">
+        <el-table :data="liveStudentList" border style="min-width: 1200px;">
+          <!-- 固定列,内容不换行 -->
+          <el-table-column prop="userName" label="客户名" fixed :style="{whiteSpace: 'nowrap',textOverflow: 'ellipsis',overflow: 'hidden',textAlign: 'center'}"></el-table-column>
+          <el-table-column prop="liveName" label="直播间" fixed :show-overflow-tooltip="true" width=151px :min-width="'直播间'.length * 12 + 30":style="{whiteSpace: 'nowrap',textOverflow: 'ellipsis',overflow: 'hidden',textAlign: 'center'}"></el-table-column>
+          <el-table-column prop="companyUserName" label="跟进人" fixed class="no-wrap-column"></el-table-column>
+          <el-table-column prop="deptName" label="归属部门" width="150" show-overflow-tooltip/>
+          <el-table-column prop="userCreateTime" label="客户创建时间" width="150" show-overflow-tooltip/>
+          <el-table-column prop="userPhone" label="联系方式" width="150" show-overflow-tooltip/>
+          <el-table-column prop="goodsCount" label="去下单实物商品" width="150" show-overflow-tooltip/>
+          <el-table-column prop="shareCount" label="分享直播间次数" width="150" show-overflow-tooltip/>
+          <el-table-column prop="questionCount" label="答题次数" width="150" show-overflow-tooltip/>
+
+          <!-- 动态列 -->
+          <!--<el-table-column
+            v-for="col in filteredColumns"
+            :key="col.dataIndex"
+            :prop="col.dataIndex"
+            :label="col.title"
+            :width="col.width"
+            :min-width="col.title.length * 12 + 30"
+            :show-overflow-tooltip="true">
+          </el-table-column>-->
+
+        </el-table>
+        <pagination
+          v-show="liveStudentTotal>0"
+          :total="liveStudentTotal"
+          :page.sync="filters.pageNum"
+          :limit.sync="filters.pageSize"
+          @pagination="getStudentData"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+
+<script>
+  import * as echarts from "echarts";
+  import Editor from '@/components/Editor/wang';
+  import {recentLive,liveTop,getTrendData,columns,updateColumns,queryStudentData} from "@/api/live/liveData";
+  import {selectLiveToStudent} from "@/api/live/live";
+  import { treeselect } from "@/api/company/companyDept";
+  import Treeselect from "@riophae/vue-treeselect";
+  import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+  import draggable from "vuedraggable";
+  export default {
+
+    name: "LiveData",
+    components: { Editor },
+    components: { draggable,Treeselect },
+    data() {
+      return {
+        // 部门树选项
+        deptOptions: [],
+        liveDateRange:"",
+        liveFiltersParam:{
+          liveName:"",
+          status:"",
+          startTime:"",
+          finishTime:"",
+          pageNum: 1,
+          pageSize: 10,
+        },
+        liveList: [],
+        dialogVisible: false,
+        span:8,
+        showAllFilters: true, // 默认收起
+        showAdvancedSearch: true, // 控制表单的显示
+        // 直播间总条数
+        liveTotal: 0,
+        //学员数据总条数
+        liveStudentTotal:0,
+        dateRange: [],//存选择的日期范围
+        filters: {
+          minQuestionCount: 0,  // 大于提问数
+          maxQuestionCount: 0,  // 小于提问数
+          liveNames: [],  // 存放已选的直播名称
+          liveIds: [],
+          liveName: '',
+          userName: '',
+          userPhone: '',
+          userCreateTime: '',
+          startTime: '', // 开始时间
+          finishTime: '',
+          shareCount: '',
+          deptName: '',
+          deptId:null,
+          companyUserName: '',
+          goodsCount: '',
+          questionCount: '',
+          pageNum: 1,
+          pageSize: 2
+        },
+        selectedLives: [], // 临时存储选中的直播数据
+        showColumnSettings: false, // 控制自定义列弹窗
+        columnOrder: [],
+        tempOrderedColumns : [],  // 用于渲染表格的列顺序
+        selectedColumns: [],  // 用于控制哪些列被选中
+        liveStudentList:[],
+        top10List: [],
+        selectedRank: 'highFlow',
+        redRankTypes: [
+          { label: '高流量', value: 'highFlow' },
+          { label: '高观看', value: 'highView' },
+          /*{ label: '高时长', value: 'highTime' },*/
+        ],
+        blackRankTypes: [
+          { label: '低观看', value: 'lowView' },
+          { label: '低流量', value: 'lowFlow' },
+        ],
+        selectedTimeRange: 'day',  // 默认选择自然天
+        selectedDate: null,  // 用户选择的日期
+        datePickerType: 'date',  // 默认日期选择类型
+        weekRange: 'yyyy-ww',  // 周选择的日期格式
+        queryParams: {
+          type: 'day',  // 用来存储时间筛选方式(如 day, week, month)
+          date: new Date().toISOString().split("T")[0],  // 用来存储用户选择的日期默认今天
+          category:"page_views"
+        },
+        chart: null,
+        selectedTimeRange: "day", // 默认选中“自然天”
+        datePickerType: "date", // 默认 date 类型
+        trendData: {}, // 直播趋势数据
+        weekRange: "", // 存储选择周的时间范围
+        selectedDate: new Date().toISOString().split("T")[0], // 默认今天
+        selectedMetric: "page_views", // 默认选中浏览量
+        lives: [],
+      };
+    },
+    computed: {
+
+      filteredColumns() {
+        return this.columnOrder.filter(col => col.status === "ENABLE");
+      },
+      changeLabel() {
+        switch (this.selectedTimeRange) {
+          case 'day': return '比前一天';
+          case 'week': return '比上周';
+          case 'month': return '比上月';
+          default: return '变化';
+        }
+      },
+      displayLives() {
+        return this.lives.slice(0, 4); // 最多只显示前 4 条数据
+      },
+
+      dateFormat() {
+        return this.selectedTimeRange === "day"
+          ? "yyyy-MM-dd"
+          : this.selectedTimeRange === "week"
+            ? "yyyy-MM-dd 至 yyyy-MM-dd"
+            : "yyyy-MM-dd";
+      },
+      valueFormat() {
+        return this.selectedTimeRange === "day"
+          ? "yyyy-MM-dd"
+          : this.selectedTimeRange === "week"
+            ? "yyyy-MM-dd"
+            : "yyyy-MM-dd";
+      },
+    },
+    created() {
+      this.getRecentLive();
+      this.getLiveTop();
+      this.getTrendData();
+      //this.getColumns();
+      this.getTreeselect();
+
+    },
+    mounted() {
+      this.selectedColumns = this.columnOrder.map((col) => col.prop); // 默认全选
+      this.selectedMetric = this.trendData[0]; // 默认选中第一个
+      this.initChart(); // 初始化折线图
+      if (!this.selectedMetric) {
+        this.selectedMetric = "page_views";
+      };
+      this.updateChart();
+    },
+    watch: {
+      // 如果 columnOrder 更新,确保 orderedColumns 跟随更新
+      columnOrder(newOrder) {
+        this.tempOrderedColumns = [...newOrder];
+      },
+    },
+
+    methods: {
+      handleLiveDateChange(value) {
+        if (value) {
+          this.liveFiltersParam.startTime = value[0];
+          this.liveFiltersParam.finishTime = value[1];
+        } else {
+          this.liveFiltersParam.startTime = null;
+          this.liveFiltersParam.finishTime = null;
+        }
+        console.log("直播时间范围:", this.liveFiltersParam.startTime, this.liveFiltersParam.finishTime);
+      },
+      handleDateChange(value) {
+        if (value) {
+          this.filters.startTime = value[0];
+          this.filters.finishTime = value[1];
+        } else {
+          this.filters.startTime = '';
+          this.filters.finishTime = '';
+        }
+        console.log("用户创建时间范围:", this.filters.startTime, this.filters.finishTime);
+      },
+      getTreeselect() {
+        treeselect().then((response) => {
+          this.deptOptions = response.data;
+          console.log(this.deptOptions)
+        });
+      },
+      getLive(){
+        console.log(this.liveFiltersParam)
+        selectLiveToStudent(this.liveFiltersParam).then(response => {
+          console.log(this.liveFiltersParam)
+          console.log(response)
+          this.liveList = response.rows;
+          this.liveTotal = response.total;
+        });
+      },
+      resetLiveFilters(){
+        this.liveDateRange="",
+        this.liveFiltersParam = {
+          liveName:"",
+          status:"",
+          startTime:"",
+          finishTime:""
+        }
+      },
+      getStatusText(status) {
+        switch (status) {
+          case 1: return "待直播";
+          case 2: return "直播中";
+          case 3: return "已结束";
+          default: return "";
+        }
+      },
+      // 确认选择
+      confirmSelection() {
+        this.filters.liveNames = this.selectedLives.map(item => item.liveName);
+        this.filters.liveIds = this.selectedLives.map(item => item.liveId);
+        console.log(this.filters.liveIds)
+        this.dialogVisible = false; // 关闭弹框
+      },
+      // 移除已选直播
+      removeLive(index) {
+        this.filters.liveNames.splice(index, 1);
+        this.filters.liveIds.splice(index, 1);
+      },
+      // 处理表格选中
+      handleSelectionChange(selectedRows) {
+        this.selectedLives = selectedRows; // 存储选中的直播数据
+      },
+
+      openDialog() {
+        this.getLive();
+        this.dialogVisible = true;
+      },
+      toggleAllFilters() {
+        this.showAllFilters = !this.showAllFilters;
+      },
+      getStudentData() {
+        console.log("查询条件:", this.filters);
+        queryStudentData(this.filters).then(response => {
+          console.log(response)
+          this.liveStudentList = response.rows;
+          this.liveStudentTotal = response.total;
+          console.log(response.total)
+        });
+      },
+      resetFilters() {
+        this.filters = {
+          liveName: "",
+          liveNames:[],
+          liveIds:[],
+          phoneNumber: "",
+          wechatNickname: "",
+          customerId: "",
+          visitTime: [],
+          isWinner: "",
+          department: "",
+          customerTag: "",
+        };
+      },
+      handleClose(done) {
+        this.$confirm('确认关闭?')
+          .then(_ => {
+            done();
+          })
+          .catch(_ => {});
+      },
+      saveColumnsConfig() {
+        this.tempOrderedColumns = [...this.columnOrder];  // 更新 orderedColumns
+        console.log(this.tempOrderedColumns)
+        updateColumns(this.tempOrderedColumns).then(response=>{
+          //this.getColumns()
+          location.reload()
+          this.showColumnSettings = false;
+        })
+      },
+      /*getColumns(){
+        this.loading = true;
+        columns().then(response => {
+          this.columnOrder = response.data;
+          this.selectedColumns = response.data.map((col) => col.prop);
+          console.log(this.columnOrder)
+          this.loading = false;
+        });
+      },*/
+      getTrendData(){
+        getTrendData(this.queryParams).then(response=>{
+          this.trendData = response.data
+          this.updateChart()
+        })
+      },
+      // 更新折线图
+      getMetricTitle(metricKey) {
+        const titles = {
+          page_views: "浏览量",
+          unique_visitors: "访客数",
+          streams: "创建直播数",
+          total_views: "累计观看人次",
+          unique_viewers: "累计观看人数"
+        };
+        return titles [metricKey] || "未知指标";
+      },
+
+      // 点击卡片切换指标
+      changeMetric(metric) {
+        this.selectedMetric = metric;
+        // 更新 queryParams 中的 category
+        this.queryParams.category = metric;  // 默认值为 prevViews
+        this.updateChart();  // 更新图表
+        this.getTrendData();  // 重新请求数据
+      },
+      getLiveTop() {
+
+        liveTop({ rankType: this.selectedRank })
+          .then(response => {
+
+            if (response.data) {
+              // 遍历数据并添加排名字段
+              this.top10List = response.data.map((item, index) => ({
+                ...item,
+                rank: index + 1 // 排名从 1 开始
+              }));
+            } else {
+              this.top10List = [];
+            }
+          })
+          .catch(error => {
+          });
+      },
+      getStatusText(status) {
+        if (status === 1) return "待直播";
+        if (status === 2) return "直播中";
+        return "已结束";
+      },
+      getStatusClass(status) {
+        if (status === 1) return "status-upcoming";
+        if (status === 2) return "status-live";
+        return "status-ended";
+      },
+      getRecentLive(){
+        this.loading = true;
+        recentLive().then(response => {
+          this.lives = response.data;
+          this.loading = false;
+        });
+      },
+
+      getWeekRange(selectedWeek) {
+        let date = new Date(selectedWeek);
+
+        if (isNaN(date.getTime())) {
+          return "日期错误";
+        }
+
+        let dayOfWeek = date.getDay(); // 0(星期天)- 6(星期六)
+
+        // 计算周一
+        let startDate = new Date(date); // 创建一个新的 Date 实例
+        startDate.setDate(date.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1));
+
+        // 计算周日(确保 `endDate` 是新实例)
+        let endDate = new Date(startDate.getTime()); // 复制 `startDate`
+        endDate.setDate(startDate.getDate() + 6);
+
+
+        return `${this.formatDate(startDate)} 至 ${this.formatDate(endDate)}`;
+      },
+      changeDate(value) {
+        if (this.selectedTimeRange === 'week' && value) {
+          this.weekRange = this.getWeekRange(value);
+          console.log("周"+this.weekRange)
+        } else {
+          this.weekRange = '';  // 非周选择时清空周范围
+        }
+
+        // 更新 queryParams.date
+        if (value) {
+          this.queryParams.date = this.formatDate(value);
+        }
+        this.getTrendData();
+      },
+      formatDate(date) {
+        let date_ = new Date(date);
+        console.log(date_)
+        const year = date_.getFullYear();
+        const month = String(date_.getMonth() + 1).padStart(2, '0');
+        const day = String(date_.getDate()).padStart(2, '0');
+        return `${year}-${month}-${day}`;
+      },
+      // 获取周范围
+      getWeekRange(date) {
+        console.log("原始 date:", date);
+
+        // 确保 date 是 Date 类型
+        let parsedDate = new Date(date);
+        if (isNaN(parsedDate.getTime())) {
+          console.error("无效的日期:", date);
+          return "日期错误";
+        }
+
+        const startOfWeek = this.getStartOfWeek(parsedDate);
+        const endOfWeek = this.getEndOfWeek(parsedDate);
+        return `${this.formatDate(startOfWeek)} - ${this.formatDate(endOfWeek)}`;
+      },
+
+      // 获取一周的起始日期(周一)
+      getStartOfWeek(date) {
+        let parsedDate = new Date(date);
+        if (isNaN(parsedDate.getTime())) {
+          console.error("无效的日期:", date);
+          return null;
+        }
+
+        const day = parsedDate.getDay();
+        const diff = parsedDate.getDate() - day + (day === 0 ? -6 : 1); // 调整到周一
+        return new Date(parsedDate.setDate(diff));
+      },
+
+      getEndOfWeek(date) {
+        const startOfWeek = this.getStartOfWeek(date);
+        if (!startOfWeek) return null;
+
+        let endOfWeek = new Date(startOfWeek);
+        endOfWeek.setDate(startOfWeek.getDate() + 6); // 调整到周日
+        return endOfWeek;
+      },
+      computedChange(item) {
+        switch (this.selectedTimeRange) {
+          case 'day': return item.dailyChange;
+          case 'week': return item.weeklyChange;
+          case 'month': return item.monthlyChange;
+          default: return 0;
+        }
+      },
+      selectRank(type) {
+        this.selectedRank = type; // 只允许一个按钮被选中
+        this.getLiveTop();  // 重新获取排行榜数据
+      },
+      // 切换时间范围
+      changeTimeRange() {
+        // 根据选中的时间范围修改 datePicker 类型
+        if (this.selectedTimeRange === 'day') {
+          this.datePickerType = 'date';  // 自然天
+          this.weekRange = '';  // 清空周范围
+        } else if (this.selectedTimeRange === 'month') {
+          this.datePickerType = 'month';  // 自然月
+          this.weekRange = '';  // 清空周范围
+        } else if (this.selectedTimeRange === 'week') {
+          this.datePickerType = 'week';  // 自然周
+        }
+        // 立即弹出日期选择器
+        this.$nextTick(() => {
+          this.$refs.datePickerRef.focus();
+        });
+        // 重置已选日期
+        this.queryParams.type = this.selectedTimeRange;
+        this.selectedDate = null;
+        this.queryParams.date = '';  // 清空 queryParams 中的日期
+      },
+      setDefaultDate() {
+        // 如果用户没有选择日期,则使用当前日期
+        if (!this.selectedDate) {
+          this.selectedDate = this.formatDate(new Date());
+          this.queryParams.date = this.selectedDate;
+        }
+      },
+      // 切换日期时更新数据
+      updateTrendData() {
+        // 这里可以加接口请求,获取新的数据
+        this.getTrendData()
+        this.updateChart();
+      },
+      updateTrendData() {
+        this.trendData = this.trendData.map(item => ({
+          ...item,
+          value: Math.floor(Math.random() * 50),
+          change: (Math.random() * 10 - 5).toFixed(2)
+        }));
+        this.updateChart();
+      },
+      initChart() {
+        let chartDom = document.getElementById("liveChart");
+        if (!chartDom) return;
+        this.chart = echarts.init(chartDom);
+        this.updateChart();
+      },
+      // 更新折线图
+      updateChart() {
+        if (!this.chart) return;
+
+        // 获取当前选中的指标对应的数据
+        let metricKey = this.selectedMetric;
+        let metricTitle = this.getMetricTitle(metricKey); // 获取指标标题
+        let chartData = this.trendData.data || [];
+        let dates = this.trendData.dates || []; // 确保 dates 不是 null
+        let options = {
+          tooltip: {
+            trigger: "axis",
+            backgroundColor: "rgba(0, 0, 0, 0.7)",
+            textStyle: { color: "#fff" },
+            formatter: function (params) {
+              let date = params[0].name; // x 轴的日期
+              let value = params[0].value; // 对应的数值
+              return `${date}<br/>${params[0].seriesName}: ${value}`;
+            }
+          },
+          grid: {
+            left: "5%", right: "5%", bottom: "10%", top: "10%", containLabel: true
+          },
+          xAxis: {
+            type: "category",
+            data: this.trendData.dates || [],
+            axisLine: {lineStyle: {color: "#ddd"}}
+          },
+          yAxis: {
+            type: "value",
+            splitLine: {lineStyle: {type: "dashed", color: "#ddd"}}
+          },
+          series: [
+            {
+              name: metricTitle,
+              data: chartData,
+              type: "line",
+              smooth: true,
+              symbol: "circle",
+              symbolSize: 8,
+              itemStyle: {
+                color: "#007BFF",
+                borderColor: "#fff",
+                borderWidth: 2
+              },
+              lineStyle: {
+                width: 3,
+                color: "#007BFF"
+              },
+              areaStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  {offset: 0, color: "rgba(0, 123, 255, 0.5)"},
+                  {offset: 1, color: "rgba(0, 123, 255, 0)"}
+                ])
+              },
+              emphasis: {
+                focus: "series",
+                label: {
+                  show: true,
+                  formatter: "{c}",
+                  fontSize: 14,
+                  color: "#333",
+                  backgroundColor: "#fff",
+                  padding: [4, 6]
+                }
+              },
+              animationDuration: 1200,
+              animationEasing: "quadraticOut"
+            }
+          ]
+        };
+
+        this.chart.setOption(options);
+      },
+    }
+  };
+</script>
+
+<style scoped>
+  .app-container {
+    padding: 20px;
+  }
+
+  .title {
+    font-size: 18px;
+    font-weight: bold;
+    margin-bottom: 10px;
+  }
+  .live-container {
+    display: flex;
+    gap: 15px; /* 控制卡片之间的间距 */
+    overflow-x: auto; /* 允许横向滚动 */
+    padding-bottom: 10px;
+  }
+
+  /* 卡片样式 */
+  .live-card {
+    width: 380px;
+    height: 225px;
+    background: rgb(250,250,250);
+    border-radius: 10px;
+    padding: 15px;
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+
+  /* 直播状态 */
+  .status {
+    background: #d3d3d3;
+    color: #333;
+    font-size: 14px;
+    padding: 4px 8px;
+    border-radius: 5px;
+    display: inline-block;
+  }
+
+  /* 不同状态颜色 */
+  .status-upcoming {
+    background: #FFC107; /* 黄色 */
+    color: #333;
+  }
+
+  .status-live {
+    background: #28A745; /* 绿色 */
+    color: white;
+  }
+
+  .status-ended {
+    background: #D3D3D3; /* 灰色 */
+    color: #333;
+  }
+  /* 直播信息部分 */
+  .content {
+    background: rgb(250,250,250);
+    display: flex;
+    gap: 10px;
+    align-items: center;
+    width: 90%;
+    margin-top: 0px;
+  }
+
+  /* 直播封面图片 */
+  .cover-image {
+    width: 70px;
+    height: 70px;
+    border-radius: 10px;
+    object-fit: cover;
+  }
+
+  /* 直播文本信息 */
+  .info {
+    flex: 1;
+  }
+
+  .live-title {
+    font-size: 16px;
+    font-weight: bold;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: 250px; /* 防止标题过长 */
+  }
+
+  .time {
+    font-size: 14px;
+    color: #666;
+  }
+
+  /* 直播数据部分 */
+  .stats {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    flex-shrink: 0;
+    margin-top: 10px;
+  }
+
+  .stat-item {
+    text-align: center;
+    flex: 1;
+  }
+
+  .label {
+    font-size: 14px;
+    color: #888;
+  }
+
+  .value {
+    font-size: 18px;
+    font-weight: bold;
+    color: #333;
+  }
+  /* 直播趋势 */
+  .trend-section {
+    margin-top: 30px;
+  }
+  .trend-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .trend-cards {
+    display: flex;
+    gap: 15px;
+  }
+  .trend-card {
+    width: 280px;
+    hight: 100px;
+    border: 1px solid #ddd;
+    border-radius: 8px;
+    padding: 16px;
+    transition: all 0.3s;
+    position: relative;
+  }
+
+  .trend-card.active {
+    border-color: #007bff; /* 选中时边框变蓝 */
+    box-shadow: 0px 0px 10px rgba(0, 123, 255, 0.3);
+  }
+
+  .trend-card.active::after {
+    content: "✔";
+    position: absolute;
+    bottom: 8px;
+    right: 8px;
+    color: white;
+    background: #007bff;
+    border-radius: 50%;
+    width: 20px;
+    height: 20px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-size: 14px;
+    font-weight: bold;
+  }
+  .trend-value {
+    font-size: 24px;
+    font-weight: bold;
+  }
+
+  /* 折线图 */
+  .chart {
+    width: 100%;
+    height: 300px;
+    margin-top: 20px;
+  }
+  .top10-section {
+    margin-top: 20px;
+    background: #fff;
+    padding: 16px;
+    border-radius: 8px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  }
+  .live-name {
+    display: flex;
+    align-items: center;
+  }
+
+  .live-cover {
+    width: 80px;  /* 直播封面宽度 */
+    height: 45px; /* 直播封面高度 */
+    border-radius: 4px;
+    margin-right: 10px;
+    object-fit: cover;
+  }
+
+  .live-title {
+    font-size: 14px;
+    color: #333;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+  .ranking-tabs {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 10px 20px;
+  }
+
+  .ranking-container {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin: 10px 0;
+    border-radius: 8px;
+    overflow: hidden;
+  }
+
+  .ranking-section {
+    display: flex;
+    align-items: center;
+    width: 50%;
+    padding: 10px 15px;
+  }
+
+  .red-rank {
+    background: linear-gradient(90deg, #ff6b6b, #ff8e8e);
+    justify-content: flex-start;
+  }
+
+  .black-rank {
+    background: linear-gradient(90deg, #4a4a4a, #6b6b6b);
+    justify-content: flex-end;
+  }
+
+  .rank-title {
+    font-size: 18px;
+    font-weight: bold;
+    color: white;
+    margin: 0 15px;
+  }
+
+  .rank-filters {
+    display: flex;
+  }
+
+  button {
+    background: rgba(255, 255, 255, 0.2);
+    border: 1px solid rgba(255, 255, 255, 0.5);
+    color: white;
+    font-size: 14px;
+    padding: 5px 12px;
+    margin: 0 5px;
+    border-radius: 15px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+  }
+
+  button.active {
+    background: white;
+    color: #ff4d4f;
+  }
+  .filter-container {
+    display: flex;
+    align-items: center;
+    gap: 8px; /* 控制两个组件的间距 */
+  }
+
+  .filter-label {
+    font-weight: bold;
+    white-space: nowrap; /* 防止换行 */
+  }
+
+
+  .student-section {
+    margin-top: 20px;
+    background: #fff;
+    padding: 16px;
+    border-radius: 8px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  }
+   .column-item {
+     display: flex;
+     align-items: center;
+     justify-content: space-between;
+     padding: 8px;
+     border: 1px solid #ddd;
+     background: #f9f9f9;
+     margin-bottom: 5px;
+     cursor: grab;
+   }
+
+  .drag-handle {
+    cursor: grab;
+  }
+  /* 自定义列按钮的样式 */
+  .column-button-container {
+    text-align: right; /* 使按钮右对齐 */
+    margin-bottom: 10px; /* 为按钮和表格之间增加间距 */
+  }
+
+  .custom-column-button {
+    background-color: #409EFF;
+    color: black;
+    padding: 10px 20px;
+    border-radius: 5px;
+    margin-right: 10px; /* 如果有多个按钮,确保按钮之间有间距 */
+  }
+
+  /* 表格区域的样式 */
+  .student-section {
+    padding: 20px;
+  }
+
+  .el-table {
+    margin-top: 20px; /* 为表格添加顶部间距 */
+  }
+
+  .el-table-column {
+    /* 让列文本超出时显示省略号 */
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+  }
+
+  .el-table-column:hover {
+    cursor: pointer;
+  }
+  .no-wrap-column .cell {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    text-align: center; /* 使列内容居中 */
+  }
+  .advanced-search {
+    margin-bottom: 10px;
+    padding: 15px;
+  }
+  .button-group {
+    display: flex;
+    justify-content: flex-end;
+    width: 100%;
+  }
+
+  /* 让按钮默认显示 */
+  .el-button {
+    opacity: 1; /* 确保默认状态可见 */
+    color: #606266; /* 默认文字颜色 */
+    border-color: #dcdfe6; /* 默认边框颜色 */
+    background-color: #f5f7fa; /* 默认背景色 */
+    transition: all 0.3s; /* 添加动画过渡 */
+  }
+
+  /* 鼠标悬停时高亮 */
+  .el-button:hover {
+    color: #409eff !important; /* 文字变蓝 */
+    border-color: #409eff !important; /* 边框变蓝 */
+    background-color: #ecf5ff !important; /* 背景变浅蓝 */
+  }
+
+  /* “展开/收起” 按钮特殊处理 */
+  .el-button[type="text"] {
+    background: none;
+    border: none;
+    color: #606266;
+  }
+
+  /* 鼠标悬停时高亮 */
+  .el-button[type="text"]:hover {
+    color: #409eff !important;
+  }
+  .el-form-item-ellipsis .el-input__inner,
+  .el-form-item-ellipsis .el-select .el-input__inner {
+    text-align: center; /* 输入框内容居中对齐 */
+  }
+
+  /* 控制 label 长度过长时显示省略号 */
+  .el-form-item-ellipsis .el-form-item__label {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    width: 100%; /* 确保标签可以适配宽度 */
+    text-align: right;
+    vertical-align: middle;
+    float: left;
+    font-size: 14px;
+    color: #606266;
+    line-height: 40px;
+    padding: 0 10px 0 0;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    cursor: pointer;
+  }
+
+  /* 直播信息整体布局 */
+  .live-info {
+    display: flex;
+    align-items: center;
+  }
+
+  /* 直播封面图 */
+  .live-cover {
+    width: 80px;
+    height: 80px;
+    object-fit: cover;
+    border-radius: 6px;
+    margin-right: 10px;
+  }
+
+  /* 直播名称 + 时间 */
+  .live-text {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+  }
+
+
+  /* 直播时间 */
+  .live-time {
+    font-size: 12px;
+    color: #888;
+    margin-top: 4px;
+  }
+
+  /* 直播状态 */
+  .status-container {
+    display: flex;
+    align-items: center;
+  }
+
+  /* 状态圆点 */
+  .status-dot {
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    display: inline-block;
+    margin-right: 6px;
+  }
+  .live-name-input .el-input__inner {
+    white-space: normal;  /* 允许文本换行 */
+    word-wrap: break-word; /* 长文本自动换行 */
+    padding-left: 10px;
+  }
+
+  .live-name-tag {
+    margin-right: 5px;
+    display: inline-block; /* 确保标签显示为块级元素 */
+  }
+  .flex-container {
+    display: flex;
+    align-items: center;
+    gap: 10px; /* 调整 label 与输入框间距 */
+    width: 90%;
+  }
+
+  .ellipsis-label .el-form-item__label {
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: 80px; /* 你可以调整这个值 */
+    display: block;
+    padding: 0 10px 0 0;
+  }
+
+  .full-width-picker {
+    flex: 1; /* 让日期选择器填充剩余空间 */
+  }
+
+</style>
+

+ 395 - 0
src/views/live/liveGoods/index.vue

@@ -0,0 +1,395 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="直播ID" prop="liveId">
+        <el-input
+          v-model="queryParams.liveId"
+          placeholder="请输入直播ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="商品名称" prop="goodsName">
+        <el-input
+          v-model="queryParams.goodsName"
+          placeholder="请输入商品名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="描述" prop="goodsDesc">
+        <el-input
+          v-model="queryParams.goodsDesc"
+          placeholder="请输入描述"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="封图" prop="imgUrl">
+        <el-input
+          v-model="queryParams.imgUrl"
+          placeholder="请输入封图"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="单价" prop="price">
+        <el-input
+          v-model="queryParams.price"
+          placeholder="请输入单价"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="原价" prop="opPrice">
+        <el-input
+          v-model="queryParams.opPrice"
+          placeholder="请输入原价"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态 1上架 0下架" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态 1上架 0下架" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="库存表" prop="stock">
+        <el-input
+          v-model="queryParams.stock"
+          placeholder="请输入库存表"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="排序号" prop="sort">
+        <el-input
+          v-model="queryParams.sort"
+          placeholder="请输入排序号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </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-form-item>
+    </el-form>
+
+    <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:liveGoods:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:liveGoods:edit']"
+        >修改</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:liveGoods:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:liveGoods:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="liveGoodsList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="goodsId" />
+      <el-table-column label="直播ID" align="center" prop="liveId" />
+      <el-table-column label="商品名称" align="center" prop="goodsName" />
+      <el-table-column label="描述" align="center" prop="goodsDesc" />
+      <el-table-column label="封图" align="center" prop="imgUrl" />
+      <el-table-column label="组图" align="center" prop="images" />
+      <el-table-column label="单价" align="center" prop="price" />
+      <el-table-column label="原价" align="center" prop="opPrice" />
+      <el-table-column label="状态 1上架 0下架" align="center" prop="status" />
+      <el-table-column label="库存表" align="center" prop="stock" />
+      <el-table-column label="排序号" align="center" prop="sort" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:liveGoods:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:liveGoods:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      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>
+      <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" />
+        </el-form-item>
+        <el-form-item label="商品名称" prop="goodsName">
+          <el-input v-model="form.goodsName" placeholder="请输入商品名称" />
+        </el-form-item>
+        <el-form-item label="描述" prop="goodsDesc">
+          <el-input v-model="form.goodsDesc" placeholder="请输入描述" />
+        </el-form-item>
+        <el-form-item label="封图" prop="imgUrl">
+          <el-input v-model="form.imgUrl" placeholder="请输入封图" />
+        </el-form-item>
+        <el-form-item label="组图" prop="images">
+          <el-input v-model="form.images" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="单价" prop="price">
+          <el-input v-model="form.price" placeholder="请输入单价" />
+        </el-form-item>
+        <el-form-item label="原价" prop="opPrice">
+          <el-input v-model="form.opPrice" placeholder="请输入原价" />
+        </el-form-item>
+        <el-form-item label="状态 1上架 0下架">
+          <el-radio-group v-model="form.status">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="库存表" prop="stock">
+          <el-input v-model="form.stock" placeholder="请输入库存表" />
+        </el-form-item>
+        <el-form-item label="排序号" prop="sort">
+          <el-input v-model="form.sort" placeholder="请输入排序号" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLiveGoods, getLiveGoods, delLiveGoods, addLiveGoods, updateLiveGoods, exportLiveGoods } from "@/api/live/liveGoods";
+
+export default {
+  name: "LiveGoods",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 直播商品表格数据
+      liveGoodsList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: null,
+        goodsName: null,
+        goodsDesc: null,
+        imgUrl: null,
+        images: null,
+        price: null,
+        opPrice: null,
+        status: null,
+        stock: null,
+        sort: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询直播商品列表 */
+    getList() {
+      this.loading = true;
+      listLiveGoods(this.queryParams).then(response => {
+        this.liveGoodsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        goodsId: null,
+        liveId: null,
+        goodsName: null,
+        goodsDesc: null,
+        imgUrl: null,
+        images: null,
+        price: null,
+        opPrice: null,
+        status: 0,
+        stock: null,
+        sort: null,
+        createTime: null,
+        createBy: null,
+        updateBy: null,
+        updateTime: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.goodsId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加直播商品";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const goodsId = row.goodsId || this.ids
+      getLiveGoods(goodsId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改直播商品";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.goodsId != null) {
+            updateLiveGoods(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addLiveGoods(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const goodsIds = row.goodsId || this.ids;
+      this.$confirm('是否确认删除直播商品编号为"' + goodsIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delLiveGoods(goodsIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有直播商品数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportLiveGoods(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 301 - 0
src/views/live/liveMsg/index.vue

@@ -0,0 +1,301 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="直播ID" prop="liveId">
+        <el-input
+          v-model="queryParams.liveId"
+          placeholder="请输入直播ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户ID" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入用户ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="消息" prop="msg">
+        <el-input
+          v-model="queryParams.msg"
+          placeholder="请输入消息"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </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-form-item>
+    </el-form>
+
+    <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:liveMsg:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:liveMsg:edit']"
+        >修改</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:liveMsg:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:liveMsg:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="liveMsgList" @selection-change="handleSelectionChange">
+      <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" />
+      <el-table-column label="用户ID" align="center" prop="userId" />
+      <el-table-column label="消息" align="center" prop="msg" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:liveMsg:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:liveMsg:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      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>
+      <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" />
+        </el-form-item>
+        <el-form-item label="用户ID" prop="userId">
+          <el-input v-model="form.userId" placeholder="请输入用户ID" />
+        </el-form-item>
+        <el-form-item label="消息" prop="msg">
+          <el-input v-model="form.msg" placeholder="请输入消息" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLiveMsg, getLiveMsg, delLiveMsg, addLiveMsg, updateLiveMsg, exportLiveMsg } from "@/api/live/liveMsg";
+
+export default {
+  name: "LiveMsg",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 直播讨论表格数据
+      liveMsgList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: null,
+        userId: null,
+        msg: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询直播讨论列表 */
+    getList() {
+      this.loading = true;
+      listLiveMsg(this.queryParams).then(response => {
+        this.liveMsgList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        msgId: null,
+        liveId: null,
+        userId: null,
+        msg: null,
+        createTime: null,
+        createBy: null,
+        updateBy: null,
+        updateTime: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.msgId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加直播讨论";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const msgId = row.msgId || this.ids
+      getLiveMsg(msgId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改直播讨论";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.msgId != null) {
+            updateLiveMsg(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addLiveMsg(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const msgIds = row.msgId || this.ids;
+      this.$confirm('是否确认删除直播讨论编号为"' + msgIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delLiveMsg(msgIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有直播讨论数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportLiveMsg(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 465 - 0
src/views/live/liveOrder/index.vue

@@ -0,0 +1,465 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="订单号" prop="orderSn">
+        <el-input
+          v-model="queryParams.orderSn"
+          placeholder="请输入订单号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="用户ID" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入用户ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="收货人" prop="userName">
+        <el-input
+          v-model="queryParams.userName"
+          placeholder="请输入收货人"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="手机号" prop="mobile">
+        <el-input
+          v-model="queryParams.mobile"
+          placeholder="请输入手机号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="地址" prop="address">
+        <el-input
+          v-model="queryParams.address"
+          placeholder="请输入地址"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="支付金额" prop="payMoney">
+        <el-input
+          v-model="queryParams.payMoney"
+          placeholder="请输入支付金额"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="订单金额" prop="orderMoney">
+        <el-input
+          v-model="queryParams.orderMoney"
+          placeholder="请输入订单金额"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="物流编号" prop="deliveryCode">
+        <el-input
+          v-model="queryParams.deliveryCode"
+          placeholder="请输入物流编号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="物流名称" prop="deliveryName">
+        <el-input
+          v-model="queryParams.deliveryName"
+          placeholder="请输入物流名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="物流单号" prop="deliverySn">
+        <el-input
+          v-model="queryParams.deliverySn"
+          placeholder="请输入物流单号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="支付时间" prop="payTime">
+        <el-date-picker clearable size="small"
+          v-model="queryParams.payTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择支付时间">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="完成时间" prop="finishTime">
+        <el-date-picker clearable size="small"
+          v-model="queryParams.finishTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择完成时间">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="状态 1待支付 2已支付 3已发货 4已完成 -1已取消 -2已退款" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态 1待支付 2已支付 3已发货 4已完成 -1已取消 -2已退款" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </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-form-item>
+    </el-form>
+
+    <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:liveOrder:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:liveOrder:edit']"
+        >修改</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:liveOrder:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:liveOrder:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="liveOrderList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="状态 1待支付 2已支付 3已发货 4已完成 -1已取消 -2已退款" align="center" prop="orderId" />
+      <el-table-column label="订单号" align="center" prop="orderSn" />
+      <el-table-column label="用户ID" align="center" prop="userId" />
+      <el-table-column label="收货人" align="center" prop="userName" />
+      <el-table-column label="手机号" align="center" prop="mobile" />
+      <el-table-column label="地址" align="center" prop="address" />
+      <el-table-column label="支付金额" align="center" prop="payMoney" />
+      <el-table-column label="订单金额" align="center" prop="orderMoney" />
+      <el-table-column label="物流编号" align="center" prop="deliveryCode" />
+      <el-table-column label="物流名称" align="center" prop="deliveryName" />
+      <el-table-column label="物流单号" align="center" prop="deliverySn" />
+      <el-table-column label="支付时间" align="center" prop="payTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.payTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="完成时间" align="center" prop="finishTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.finishTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态 1待支付 2已支付 3已发货 4已完成 -1已取消 -2已退款" align="center" prop="status" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:liveOrder:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:liveOrder:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      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>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="订单号" prop="orderSn">
+          <el-input v-model="form.orderSn" placeholder="请输入订单号" />
+        </el-form-item>
+        <el-form-item label="用户ID" prop="userId">
+          <el-input v-model="form.userId" placeholder="请输入用户ID" />
+        </el-form-item>
+        <el-form-item label="收货人" prop="userName">
+          <el-input v-model="form.userName" placeholder="请输入收货人" />
+        </el-form-item>
+        <el-form-item label="手机号" prop="mobile">
+          <el-input v-model="form.mobile" placeholder="请输入手机号" />
+        </el-form-item>
+        <el-form-item label="地址" prop="address">
+          <el-input v-model="form.address" placeholder="请输入地址" />
+        </el-form-item>
+        <el-form-item label="支付金额" prop="payMoney">
+          <el-input v-model="form.payMoney" placeholder="请输入支付金额" />
+        </el-form-item>
+        <el-form-item label="订单金额" prop="orderMoney">
+          <el-input v-model="form.orderMoney" placeholder="请输入订单金额" />
+        </el-form-item>
+        <el-form-item label="物流编号" prop="deliveryCode">
+          <el-input v-model="form.deliveryCode" placeholder="请输入物流编号" />
+        </el-form-item>
+        <el-form-item label="物流名称" prop="deliveryName">
+          <el-input v-model="form.deliveryName" placeholder="请输入物流名称" />
+        </el-form-item>
+        <el-form-item label="物流单号" prop="deliverySn">
+          <el-input v-model="form.deliverySn" placeholder="请输入物流单号" />
+        </el-form-item>
+        <el-form-item label="支付时间" prop="payTime">
+          <el-date-picker clearable size="small"
+            v-model="form.payTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择支付时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="完成时间" prop="finishTime">
+          <el-date-picker clearable size="small"
+            v-model="form.finishTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择完成时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="状态 1待支付 2已支付 3已发货 4已完成 -1已取消 -2已退款">
+          <el-radio-group v-model="form.status">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLiveOrder, getLiveOrder, delLiveOrder, addLiveOrder, updateLiveOrder, exportLiveOrder } from "@/api/live/liveOrder";
+
+export default {
+  name: "LiveOrder",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 订单表格数据
+      liveOrderList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        orderSn: null,
+        userId: null,
+        userName: null,
+        mobile: null,
+        address: null,
+        payMoney: null,
+        orderMoney: null,
+        deliveryCode: null,
+        deliveryName: null,
+        deliverySn: null,
+        payTime: null,
+        finishTime: null,
+        status: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询订单列表 */
+    getList() {
+      this.loading = true;
+      listLiveOrder(this.queryParams).then(response => {
+        this.liveOrderList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        orderId: null,
+        orderSn: null,
+        userId: null,
+        userName: null,
+        mobile: null,
+        address: null,
+        payMoney: null,
+        orderMoney: null,
+        deliveryCode: null,
+        deliveryName: null,
+        deliverySn: null,
+        payTime: null,
+        finishTime: null,
+        status: 0,
+        createTime: null,
+        createBy: null,
+        updateBy: null,
+        updateTime: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.orderId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加订单";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const orderId = row.orderId || this.ids
+      getLiveOrder(orderId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改订单";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.orderId != null) {
+            updateLiveOrder(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addLiveOrder(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const orderIds = row.orderId || this.ids;
+      this.$confirm('是否确认删除订单编号为"' + orderIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delLiveOrder(orderIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有订单数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportLiveOrder(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 350 - 0
src/views/live/liveOrderitems/index.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="商品ID" prop="goodsId">
+        <el-input
+          v-model="queryParams.goodsId"
+          placeholder="请输入商品ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="订单ID" prop="orderId">
+        <el-input
+          v-model="queryParams.orderId"
+          placeholder="请输入订单ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="商品名" prop="goodsName">
+        <el-input
+          v-model="queryParams.goodsName"
+          placeholder="请输入商品名"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="单价" prop="price">
+        <el-input
+          v-model="queryParams.price"
+          placeholder="请输入单价"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="数量" prop="num">
+        <el-input
+          v-model="queryParams.num"
+          placeholder="请输入数量"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态 1正常 2已退款" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态 1正常 2已退款" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </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-form-item>
+    </el-form>
+
+    <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:liveOrderitems:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:liveOrderitems:edit']"
+        >修改</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:liveOrderitems:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:liveOrderitems:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="liveOrderitemsList" @selection-change="handleSelectionChange">
+      <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" />
+      <el-table-column label="订单ID" align="center" prop="orderId" />
+      <el-table-column label="商品JSON" align="center" prop="goodsJson" />
+      <el-table-column label="商品名" align="center" prop="goodsName" />
+      <el-table-column label="单价" align="center" prop="price" />
+      <el-table-column label="数量" align="center" prop="num" />
+      <el-table-column label="状态 1正常 2已退款" align="center" prop="status" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:liveOrderitems:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:liveOrderitems:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      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>
+      <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" />
+        </el-form-item>
+        <el-form-item label="订单ID" prop="orderId">
+          <el-input v-model="form.orderId" placeholder="请输入订单ID" />
+        </el-form-item>
+        <el-form-item label="商品JSON" prop="goodsJson">
+          <el-input v-model="form.goodsJson" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="商品名" prop="goodsName">
+          <el-input v-model="form.goodsName" placeholder="请输入商品名" />
+        </el-form-item>
+        <el-form-item label="单价" prop="price">
+          <el-input v-model="form.price" placeholder="请输入单价" />
+        </el-form-item>
+        <el-form-item label="数量" prop="num">
+          <el-input v-model="form.num" placeholder="请输入数量" />
+        </el-form-item>
+        <el-form-item label="状态 1正常 2已退款">
+          <el-radio-group v-model="form.status">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLiveOrderitems, getLiveOrderitems, delLiveOrderitems, addLiveOrderitems, updateLiveOrderitems, exportLiveOrderitems } from "@/api/live/liveOrderitems";
+
+export default {
+  name: "LiveOrderitems",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 订单商品表格数据
+      liveOrderitemsList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        goodsId: null,
+        orderId: null,
+        goodsJson: null,
+        goodsName: null,
+        price: null,
+        num: null,
+        status: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询订单商品列表 */
+    getList() {
+      this.loading = true;
+      listLiveOrderitems(this.queryParams).then(response => {
+        this.liveOrderitemsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        goodsId: null,
+        orderId: null,
+        goodsJson: null,
+        goodsName: null,
+        price: null,
+        num: null,
+        status: 0,
+        createTime: null,
+        createBy: null,
+        updateBy: null,
+        updateTime: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加订单商品";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getLiveOrderitems(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改订单商品";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateLiveOrderitems(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addLiveOrderitems(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除订单商品编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delLiveOrderitems(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有订单商品数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportLiveOrderitems(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 324 - 0
src/views/live/liveQuestion/index.vue

@@ -0,0 +1,324 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="用户ID" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入用户ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="问题内容" prop="question">
+        <el-input
+          v-model="queryParams.question"
+          placeholder="请输入问题内容"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="回复" prop="reply">
+        <el-input
+          v-model="queryParams.reply"
+          placeholder="请输入回复"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="回复时间" prop="replyTime">
+        <el-date-picker clearable size="small"
+          v-model="queryParams.replyTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择回复时间">
+        </el-date-picker>
+      </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-form-item>
+    </el-form>
+
+    <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:liveQuestion:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:liveQuestion:edit']"
+        >修改</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:liveQuestion:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:liveQuestion:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="liveQuestionList" @selection-change="handleSelectionChange">
+      <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" />
+      <el-table-column label="问题内容" align="center" prop="question" />
+      <el-table-column label="回复" align="center" prop="reply" />
+      <el-table-column label="回复时间" align="center" prop="replyTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.replyTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:liveQuestion:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:liveQuestion:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      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>
+      <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" />
+        </el-form-item>
+        <el-form-item label="问题内容" prop="question">
+          <el-input v-model="form.question" placeholder="请输入问题内容" />
+        </el-form-item>
+        <el-form-item label="回复" prop="reply">
+          <el-input v-model="form.reply" placeholder="请输入回复" />
+        </el-form-item>
+        <el-form-item label="回复时间" prop="replyTime">
+          <el-date-picker clearable size="small"
+            v-model="form.replyTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择回复时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLiveQuestion, getLiveQuestion, delLiveQuestion, addLiveQuestion, updateLiveQuestion, exportLiveQuestion } from "@/api/live/liveQuestion";
+
+export default {
+  name: "LiveQuestion",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 问答表格数据
+      liveQuestionList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        question: null,
+        reply: null,
+        replyTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询问答列表 */
+    getList() {
+      this.loading = true;
+      listLiveQuestion(this.queryParams).then(response => {
+        this.liveQuestionList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        questionId: null,
+        userId: null,
+        question: null,
+        reply: null,
+        replyTime: null,
+        createTime: null,
+        createBy: null,
+        updateBy: null,
+        updateTime: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.questionId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加问答";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const questionId = row.questionId || this.ids
+      getLiveQuestion(questionId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改问答";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.questionId != null) {
+            updateLiveQuestion(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addLiveQuestion(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const questionIds = row.questionId || this.ids;
+      this.$confirm('是否确认删除问答编号为"' + questionIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delLiveQuestion(questionIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有问答数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportLiveQuestion(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 552 - 0
src/views/live/liveQuestionBank/index.vue

@@ -0,0 +1,552 @@
+<template>
+  <div class="live-question-bank-container">
+    <div class="question-bank-container">
+        <div class="question-bank-header">
+            <el-button
+              type="primary"
+              icon="el-icon-plus"
+              v-hasPermi="['live:liveQuestionBank:add']"
+              @click="handleAdd"
+            >添加试题</el-button>
+            <div class="actions">
+                <el-input
+                v-model="queryParams.title"
+                @input="handleSearch"
+                placeholder="请输入搜索内容"
+                style="width: 300px;"
+                />
+            </div>
+        </div>
+
+        <el-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">
+                {{ formatType(scope.row) }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="createBy" label="创建人"></el-table-column>
+            <el-table-column prop="updateTime" label="最后更新时间"></el-table-column>
+            <el-table-column label="操作" width="150" fixed="right">
+                <template slot-scope="scope">
+                <el-button
+                  type="text"
+                  size="small"
+                  style="color: #00CC66;"
+                  @click="handleEdit(scope.row)"
+                >编辑</el-button>
+                <el-button
+                  type="text"
+                  size="small"
+                  style="color: #F56C6C;"
+                  @click="handleDelete(scope.row)"
+                >删除</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+
+        <pagination
+          v-show="total>0"
+          :total="total"
+          :page.sync="queryParams.pageNum"
+          :limit.sync="queryParams.pageSize"
+          @pagination="getQuestionBankList"
+        />
+
+        <!-- 添加试题弹窗 -->
+        <el-dialog
+          :title="dialogTitle"
+          :visible.sync="dialogVisible"
+          :close-on-click-modal="false"
+          :close-on-press-escape="false"
+        >
+          <el-form :model="form" ref="form" :rules="dynamicRules" label-width="80px">
+            <el-form-item label="题型" prop="type">
+              <el-select v-model="form.type" placeholder="请选择题型" style="width: 100%">
+                <el-option
+                  v-for="item in questionTypes"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="题干" prop="title">
+              <el-input
+                type="textarea"
+                v-model="form.title"
+                placeholder="请输入题干"
+                :autosize="{ minRows: 4 }"
+              ></el-input>
+            </el-form-item>
+            <!-- 动态渲染选项 -->
+            <el-form-item
+              v-for="(option, index) in form.options"
+              :key="index"
+              :label="`选项${option.label}`"
+              :prop="`options.${index}.content`"
+            >
+              <el-input
+                type="textarea"
+                v-model="option.content"
+                placeholder="请输入选项内容"
+                :autosize="{ minRows: 4 }"
+              ></el-input>
+              <div class="option-helper">
+                <el-checkbox
+                  v-model="option.isCorrect"
+                  @change="() => handleCorrectChange(option)"
+                >设为正确答案</el-checkbox>
+                <el-link type="primary" style="margin-left: auto;" @click="removeOption(index)">删除</el-link>
+              </div>
+            </el-form-item>
+            <!-- 添加选项按钮 -->
+            <el-form-item>
+              <el-button type="text" @click="addOption" icon="el-icon-plus">添加选项</el-button>
+            </el-form-item>
+            <el-form-item label="答案" prop="analysis">
+              <el-input
+                type="text"
+                v-model="form.analysis"
+                style="width: 150px"
+                disabled
+              ></el-input>
+            </el-form-item>
+          </el-form>
+          <div slot="footer" class="dialog-footer">
+            <el-button @click="dialogVisible = false">取 消</el-button>
+            <el-button type="primary" @click="submitForm">保 存</el-button>
+            <el-button
+              v-if="!isEdit"
+              type="success"
+              @click="submitAndContinue"
+            >保存并继续添加</el-button>
+          </div>
+        </el-dialog>
+    </div>
+  </div>
+</template>
+
+<script>
+import { listLiveQuestionBank, addLiveQuestionBank, updateLiveQuestionBank, deleteLiveQuestionBank } from '@/api/live/liveQuestionBank'
+export default {
+  name: 'LiveQuestionBank',
+  data() {
+    return {
+      total: 0,
+      loading: true,
+      queryParams: {
+        title: null,
+        pageNum: 1,
+        pageSize: 10
+      },
+      questionBankList: [],
+      dialogVisible: false,
+      dialogTitle: '添加试题',
+      isEdit: false,
+      form: {
+        id: null,
+        title: '',
+        type: 1,
+        options: [
+          { label: 'A', content: '', isCorrect: false },
+          { label: 'B', content: '', isCorrect: false },
+          { label: 'C', content: '', isCorrect: false },
+          { label: 'D', content: '', isCorrect: false }
+        ],
+        analysis: ''
+      },
+      questionTypes: [
+        { label: '单选题', value: 1 },
+        { label: '多选题', value: 2 }
+      ]
+    }
+  },
+  computed: {
+    dynamicRules() {
+      const rules = {
+        title: [{ required: true, message: '请输入题干', trigger: 'blur' }],
+        type: [{ required: true, message: '请选择题型', trigger: 'change' }],
+        analysis: [{ required: true, message: '请设置正确答案', trigger: 'change' }]
+      }
+
+      // 动态添加选项验证规则
+      this.form.options.forEach((_, index) => {
+        rules[`options.${index}.content`] = [{ required: true, message: '请输入选项内容', trigger: 'blur' }]
+      })
+
+      return rules
+    }
+  },
+  created() {
+    this.getQuestionBankList()
+  },
+  watch: {
+    'form.options': {
+      handler(options) {
+        const correctLabels = options
+          .filter(option => option.isCorrect)
+          .map(option => option.label)
+          .join(',')
+        this.form.analysis = correctLabels
+      },
+      deep: true
+    },
+    'form.type'() {
+      this.handleTypeChange()
+    }
+  },
+  methods: {
+    getQuestionBankList() {
+      this.loading = true;
+      listLiveQuestionBank(this.queryParams).then(response => {
+        this.questionBankList = response.rows
+        this.total = response.total;
+        this.loading = false;
+      })
+    },
+    handleSearch() {
+      this.getQuestionBankList()
+    },
+    handleAdd() {
+      this.dialogTitle = '添加试题'
+      this.isEdit = false
+      this.dialogVisible = true
+      this.resetForm()
+    },
+    handleEdit(row) {
+      this.dialogTitle = '编辑试题'
+      this.isEdit = true
+      this.dialogVisible = true
+
+      // 先设置基础数据
+      this.form.id = row.id
+      this.form.title = row.title
+
+      // 先回显选项数据
+      let options = JSON.parse(row.content)
+      const answerArray = row.answer.split(',')
+      this.form.options = options.map(option => ({
+        label: option.label,
+        content: option.content,
+        isCorrect: answerArray.includes(option.label)
+      }))
+
+      // 最后设置题型和答案,因为设置题型会触发handleTypeChange
+      this.form.type = row.type
+      this.form.analysis = row.answer
+    },
+    submitForm() {
+      this.$refs.form.validate(valid => {
+        if (!valid) {
+          this.$message({
+            message: '请检查表单填写是否正确',
+            type: 'warning'
+          })
+          return
+        }
+
+        // 验证是否有正确答案
+        if (!this.form.analysis) {
+          this.$message({
+            message: '请设置正确答案',
+            type: 'warning'
+          })
+          return
+        }
+
+        // 验证答案数量
+        const correctCount = this.form.options.filter(option => option.isCorrect).length
+        if (this.form.type === 1 && correctCount !== 1) {
+          this.$message({
+            message: '单选题必须设置一个正确答案',
+            type: 'warning'
+          })
+          return
+        }
+        if (this.form.type === 2 && correctCount < 2) {
+          this.$message({
+            message: '多选题至少需要设置两个正确答案',
+            type: 'warning'
+          })
+          return
+        }
+
+        // 构造提交的参数
+        let params = {
+          id: this.isEdit ? this.form.id : null,
+          title: this.form.title,
+          type: this.form.type,
+          content: JSON.stringify(this.form.options),
+          answer: this.form.analysis
+        }
+
+        // TODO: 根据isEdit判断调用添加还是更新接口
+        if (this.isEdit) {
+          // 调用更新接口
+          updateLiveQuestionBank(params).then(response => {
+            this.msgSuccess("更新成功");
+            this.getQuestionBankList();
+          })
+        } else {
+          // 调用添加接口
+          addLiveQuestionBank(params).then(response => {
+            this.msgSuccess("新增成功");
+            this.getQuestionBankList();
+          })
+        }
+
+        this.dialogVisible = false
+        this.resetForm()
+      })
+    },
+    submitAndContinue() {
+      this.$refs.form.validate(valid => {
+        if (!valid) {
+          this.$message({
+            message: '请检查表单填写是否正确',
+            type: 'warning'
+          })
+          return
+        }
+
+        // 验证是否有正确答案
+        if (!this.form.analysis) {
+          this.$message({
+            message: '请设置正确答案',
+            type: 'warning'
+          })
+          return
+        }
+
+        // 验证答案数量
+        const correctCount = this.form.options.filter(option => option.isCorrect).length
+        if (this.form.type === 1 && correctCount !== 1) {
+          this.$message({
+            message: '单选题必须设置一个正确答案',
+            type: 'warning'
+          })
+          return
+        }
+        if (this.form.type === 2 && correctCount < 2) {
+          this.$message({
+            message: '多选题至少需要设置两个正确答案',
+            type: 'warning'
+          })
+          return
+        }
+
+        // 构造提交的参数
+        let params = {
+          title: this.form.title,
+          type: this.form.type,
+          content: JSON.stringify(this.form.options),
+          answer: this.form.analysis
+        }
+        // 保存逻辑
+        addLiveQuestionBank(params).then(response => {
+            this.msgSuccess("新增成功");
+            this.getQuestionBankList();
+          })
+        this.resetForm()
+      })
+    },
+    addOption() {
+      if (this.form.options.length >= 6) {
+        this.$message({
+          message: '最多只能添加6个选项',
+          type: 'warning'
+        })
+        return
+      }
+      const nextLabel = String.fromCharCode('A'.charCodeAt(0) + this.form.options.length)
+      this.form.options.push({
+        label: nextLabel,
+        content: '',
+        isCorrect: false
+      })
+    },
+    removeOption(index) {
+      if (this.form.options.length <= 2) {
+        this.$message({
+          message: '至少需要保持两个选项',
+          type: 'warning'
+        })
+        return
+      }
+      this.form.options.splice(index, 1)
+      // 重新排列选项标签
+      this.form.options.forEach((option, idx) => {
+        option.label = String.fromCharCode('A'.charCodeAt(0) + idx)
+      })
+    },
+    resetForm() {
+      if (this.$refs.form) {
+        this.$refs.form.resetFields()
+      }
+      // 重置表单数据
+      this.form = {
+        id: null,
+        title: '',
+        type: 1,
+        options: [
+          { label: 'A', content: '', isCorrect: false },
+          { label: 'B', content: '', isCorrect: false },
+          { label: 'C', content: '', isCorrect: false },
+          { label: 'D', content: '', isCorrect: false }
+        ],
+        analysis: ''
+      }
+      this.isEdit = false
+    },
+    handleCorrectChange(currentOption) {
+      const correctCount = this.form.options.filter(option => option.isCorrect).length
+
+      if (this.form.type === 1) { // 单选题
+        // 如果当前选项被选中,则取消其他选项的选中状态
+        if (currentOption.isCorrect) {
+          this.form.options.forEach(option => {
+            if (option !== currentOption) {
+              option.isCorrect = false
+            }
+          })
+        }
+      } else { // 多选题
+        // 如果取消选中会导致正确答案少于2个,阻止操作
+        if (!currentOption.isCorrect && correctCount < 2) {
+          currentOption.isCorrect = true
+          this.$message({
+            message: '多选题至少需要2个正确答案',
+            type: 'warning'
+          })
+        }
+      }
+    },
+    handleTypeChange() {
+      // 如果是编辑状态,不重置答案
+      if (!this.isEdit) {
+        // 重置所有选项的正确答案状态
+        this.form.options.forEach(option => {
+          option.isCorrect = false
+        })
+        this.form.analysis = ''
+      }
+    },
+    handleDelete(row) {
+      this.$confirm('是否确认删除该试题?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        deleteLiveQuestionBank(row.id).then(response => {
+          this.msgSuccess("删除成功");
+          this.getQuestionBankList();
+        })
+      }).catch(() => {
+        // 取消删除,不做任何操作
+      })
+    },
+    // 添加获取题型值的方法
+    getQuestionTypeName(type) {
+      const found = this.questionTypes.find(item => item.value === type)
+      return found ? found.label : ''
+    },
+
+    // 修改表格列的显示
+    formatType(row) {
+      return this.getQuestionTypeName(row.type)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.live-question-bank-container {
+  padding: 10px 20px;
+  height: calc(100vh - 84px); /* 减去头部导航的高度 */
+}
+.question-bank-container {
+    background-color: #fff;
+    padding: 10px 20px;
+    border-radius: 4px;
+    height: 100%;
+}
+
+.question-bank-header {
+  margin-top: 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.title {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.actions {
+  display: flex;
+  align-items: center;
+}
+
+/* 修改表格行高 */
+::v-deep .el-table td {
+  padding: 12px 0;
+}
+
+/* 修改表格hover颜色 */
+::v-deep .el-table tbody tr:hover > td {
+  background-color: #F5F7FA;
+}
+
+.option-helper {
+  display: flex;
+  align-items: center;
+  margin-top: 8px;
+}
+
+/* 修改弹窗样式 */
+::v-deep .el-dialog {
+  height: 100%;
+  margin: 0 !important;
+  display: flex;
+  flex-direction: column;
+}
+
+::v-deep .el-dialog__body {
+  flex: 1;
+  padding: 30px 40px;
+  overflow-y: auto;
+  margin-bottom: 80px;  /* 给底部按钮预留空间 */
+}
+
+::v-deep .el-dialog__header {
+  padding: 20px 40px;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+::v-deep .el-dialog__footer {
+  padding: 20px 40px;
+  border-top: 1px solid #e4e7ed;
+  position: fixed;
+  bottom: 0;
+  width: 100%;
+  background: #fff;
+  box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.12);
+  z-index: 1;
+}
+
+::v-deep .el-form-item__label {
+  font-weight: normal;
+}
+
+::v-deep .el-textarea__inner {
+  background-color: #fff;
+  resize: none;
+}
+
+.dialog-footer {
+  text-align: center;
+  padding-top: 10px;
+}
+</style>

+ 314 - 0
src/views/live/liveVideo/index.vue

@@ -0,0 +1,314 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="直播ID" prop="liveId">
+        <el-input
+          v-model="queryParams.liveId"
+          placeholder="请输入直播ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="视频地址" prop="videoUrl">
+        <el-input
+          v-model="queryParams.videoUrl"
+          placeholder="请输入视频地址"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="类型 1录播 2回放" prop="videoType">
+        <el-select v-model="queryParams.videoType" placeholder="请选择类型 1录播 2回放" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="排序号" prop="sort">
+        <el-input
+          v-model="queryParams.sort"
+          placeholder="请输入排序号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </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-form-item>
+    </el-form>
+
+    <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="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['live:liveVideo:edit']"
+        >修改</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>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['live:liveVideo:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="liveVideoList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="videoId" />
+      <el-table-column label="直播ID" align="center" prop="liveId" />
+      <el-table-column label="视频地址" align="center" prop="videoUrl" />
+      <el-table-column label="类型 1录播 2回放" align="center" prop="videoType" />
+      <el-table-column label="排序号" align="center" prop="sort" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['live:liveVideo:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['live:liveVideo:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      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>
+      <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" />
+        </el-form-item>
+        <el-form-item label="视频地址" prop="videoUrl">
+          <el-input v-model="form.videoUrl" placeholder="请输入视频地址" />
+        </el-form-item>
+        <el-form-item label="类型 1录播 2回放" prop="videoType">
+          <el-select v-model="form.videoType" placeholder="请选择类型 1录播 2回放">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="排序号" prop="sort">
+          <el-input v-model="form.sort" placeholder="请输入排序号" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLiveVideo, getLiveVideo, delLiveVideo, addLiveVideo, updateLiveVideo, exportLiveVideo } from "@/api/live/liveVideo";
+
+export default {
+  name: "LiveVideo",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 直播视频表格数据
+      liveVideoList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: null,
+        videoUrl: null,
+        videoType: null,
+        sort: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询直播视频列表 */
+    getList() {
+      this.loading = true;
+      listLiveVideo(this.queryParams).then(response => {
+        this.liveVideoList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        videoId: null,
+        liveId: null,
+        videoUrl: null,
+        videoType: null,
+        sort: null,
+        createTime: null,
+        createBy: null,
+        updateBy: null,
+        updateTime: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.videoId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加直播视频";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const videoId = row.videoId || this.ids
+      getLiveVideo(videoId).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改直播视频";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.videoId != null) {
+            updateLiveVideo(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addLiveVideo(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const videoIds = row.videoId || this.ids;
+      this.$confirm('是否确认删除直播视频编号为"' + videoIds + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delLiveVideo(videoIds);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有直播视频数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportLiveVideo(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 51 - 0
src/views/live/liveWatchUser/index.vue

@@ -0,0 +1,51 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="直播ID" prop="liveId">
+        <el-input
+          v-model="queryParams.liveId"
+          placeholder="请输入直播ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </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-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'LiveWatchUser',
+  data() {
+    return {
+      showSearch: true,
+      queryForm: null,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10
+      }
+    }
+  },
+  created() {
+
+  },
+  methods: {
+    handleQuery() {
+      console.log(this.queryParams);
+    },
+    resetQuery() {
+      console.log(this.queryParams);
+    }
+  }
+}
+</script>
+
+<style scoped>
+
+</style>