yuhongqi преди 6 дни
родител
ревизия
12d3c23e4c

+ 2 - 2
.env.prod-bjzm

@@ -21,9 +21,9 @@ VUE_APP_OBS_BUCKET = bjzm
 # 存储桶配置
 VUE_APP_COS_BUCKET = bjzmky-1323137866
 # 存储桶配置
-VUE_APP_COS_REGION = ap-guangzhou
+VUE_APP_COS_REGION = ap-chongqing
 # 线路一地址
-VUE_APP_VIDEO_LINE_1 = https://bjzmky-1323137866.cos.ap-guangzhou.myqcloud.com
+VUE_APP_VIDEO_LINE_1 = https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com
 # 线路二地址
 VUE_APP_VIDEO_LINE_2 = https://bjzmobs.ylrztop.com
 

+ 14 - 14
src/components/LiveVideoUpload/single.vue

@@ -2,20 +2,20 @@
   <div>
     <el-form-item label="">
       <div class="upload_video" id="upload_video">
-        <el-upload
-          class="upload-demo"
-          ref="upload"
-          action="#"
-          :http-request="uploadVideoToTxPcdn"
-          accept=".mp4"
-          :limit="1"
-          :on-remove="handleRemove"
-          :on-change="handleChange"
-          :auto-upload="false"
-          :key="uploadKey"
-        >
-          <el-button slot="trigger" size="small" type="primary" >选取视频</el-button>
-          <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">点击上传</el-button>
+<!--        <el-upload-->
+<!--          class="upload-demo"-->
+<!--          ref="upload"-->
+<!--          action="#"-->
+<!--          :http-request="uploadVideoToTxPcdn"-->
+<!--          accept=".mp4"-->
+<!--          :limit="1"-->
+<!--          :on-remove="handleRemove"-->
+<!--          :on-change="handleChange"-->
+<!--          :auto-upload="false"-->
+<!--          :key="uploadKey"-->
+<!--        >-->
+<!--          <el-button slot="trigger" size="small" type="primary" >选取视频</el-button>-->
+<!--          <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">点击上传</el-button>-->
           <!-- 仅当showControl为true时显示视频库选取按钮 -->
           <el-button v-if="showControl" style="margin-left: 10px;" size="small" type="success" @click="openVideoLibrary">视频库选取</el-button>
           <!-- 线路一 -->

+ 1 - 1
src/views/his/coupon/index.vue

@@ -120,7 +120,7 @@
                type="text"
                @click="handledetails(scope.row)"
            >详情
-           
+
           </el-button>
           <el-button
             size="mini"

+ 1 - 1
src/views/live/live/index.vue

@@ -432,7 +432,7 @@ import {
 } from "@/api/live/live";
 import Editor from '@/components/Editor/wang';
 import user from '@/store/modules/user';
-import VideoUpload from "@/components/VideoUpload/index.vue";
+import VideoUpload from "@/components/LiveVideoUpload/single.vue";
 
 export default {
   name: "Live",

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

@@ -133,7 +133,9 @@ export default {
       activeTab: 'watchReward',
       liveId: null,
       liveInfo: {},
+      socket:null,
       currentComponent: WatchReward,
+      liveWsUrl: process.env.VUE_APP_LIVE_WS_URL + '/app/webSocket',
       menuList:[
         { name: '观看奖励', label: '观看奖励', index: 'watchReward'},
         { name: '直播预告', label: '直播预告', index: 'preview'},
@@ -154,8 +156,23 @@ export default {
   created() {
     this.liveId = this.$route.params.liveId
     this.getLiving()
+    this.connectWebSocket()
+  },
+  computed: {
+    userId() {
+      return this.$store.state.user.user.userId
+    },
   },
   methods: {
+    connectWebSocket() {
+      this.$store.dispatch('initLiveWs', {
+        liveWsUrl: this.liveWsUrl,
+        liveId: this.liveId,
+        userId: this.userId
+      })
+      this.socket = this.$store.state.liveWs[this.liveId]
+      this.socket.onmessage = (event) => this.handleWsMessage(event)
+    },
     handleSelect(index){
       this.currentComponent = index; // 切换当前显示的组件
     },

+ 44 - 0
src/views/live/liveConfig/task.vue

@@ -209,6 +209,26 @@
             </div>
           </el-select>
         </el-form-item>
+        <el-form-item label="商品选择" prop="goodsId" v-if="form.taskType == 6">
+          <el-select v-model="form.goodsId" placeholder="请选择商品" ref="selectGoods" >
+            <el-option v-for="i in productOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
+            <!-- 加载载中状态 -->
+            <div v-if="isLoading" class="loading-indicator">
+              <i class="el-icon-loading"></i>
+              <span>加载中...</span>
+            </div>
+
+            <!-- 没有更多数据 -->
+            <div v-if="!hasMore && !isLoading" class="no-more">
+              没有更多数据了
+            </div>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="上下架状态" prop="goodsStatus" v-if="form.taskType == 6">
+          <el-select v-model="form.goodsStatus" placeholder="请选择上下架状态">
+            <el-option v-for="i in goodsStatusOptions" :key="i.value" :label="i.label" :value="i.value"></el-option>
+          </el-select>
+        </el-form-item>
         <el-form-item label="触发时间" prop="content">
           <el-time-picker
             default-value="2025-01-01 00:00:00"
@@ -273,6 +293,10 @@ export default {
         {
           value: 5,
           label: "定时优惠券"
+        },
+        {
+          value: 6,
+          label: "定时商品上下架"
         }
       ],
       statusOptions:[{
@@ -284,6 +308,15 @@ export default {
           label: "启用"
         }
       ],
+      goodsStatusOptions:[{
+        value: 0,
+        label: "下架"
+      },
+        {
+          value: 1,
+          label: "上架"
+        }
+      ],
       haveData:{
         goods:false,
         red:false,
@@ -420,6 +453,8 @@ export default {
           return "定时抽奖";
         case 5:
           return "定时优惠券";
+        case 6:
+          return "定时商品上下架";
         default:
           return "--";
       }
@@ -492,6 +527,8 @@ export default {
           await this.addLotteryList();
         }else if(this.form.taskType == 5){
           await this.addCouponList();
+        }else if(this.form.taskType == 6){
+          await this.addGoodsList();
         }
       }catch ( err){
         console.error('加载数据失败:', err);
@@ -629,6 +666,8 @@ export default {
         triggerType: null,
         triggerValue: null,
         content: null,
+        goodsId: null,
+        goodsStatus: null,
         status: 1,
         createdTime: null,
         updatedTime: null
@@ -674,6 +713,8 @@ export default {
         await this.addLotteryList();
       }else if(row.taskType == 5) {
         await this.addCouponList();
+      }else if(row.taskType == 6) {
+        await this.addGoodsList();
       }
       const id = row.id || this.ids
       getTask(id).then(response => {
@@ -687,6 +728,9 @@ export default {
           this.form.content = content.desc;
         }else if(this.form.taskType == 5){
           this.form.content = content.couponId;
+        }else if(this.form.taskType == 6){
+          this.form.goodsId = content.goodsId;
+          this.form.goodsStatus = content.goodsStatus;
         }
         this.open = true;
         this.title = "修改直播间自动化任务配置";

+ 68 - 0
src/views/live/liveConsole/LiveConsole.vue

@@ -169,6 +169,7 @@
         <div class="message-actions">
           <button @click="sendMessage">发送消息</button>
           <button @click="sendPopMessage">弹窗消息</button>
+          <button @click="showTopMsgDialog">顶部消息</button>
         </div>
       </div>
     </div>
@@ -214,6 +215,33 @@
       </div>
     </div>
 
+    <!-- 顶部消息对话框 -->
+    <el-dialog title="发送顶部消息" :visible.sync="topMsgDialogVisible" width="500px">
+      <el-form :model="topMsgForm" label-width="100px">
+        <el-form-item label="消息内容">
+          <el-input
+            type="textarea"
+            v-model="topMsgForm.msg"
+            placeholder="请输入消息内容"
+            :rows="3"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="显示时长">
+          <el-input-number
+            v-model="topMsgForm.duration"
+            :min="1"
+            :max="60"
+            placeholder="请输入显示时长(分钟)"
+          ></el-input-number>
+          <span style="margin-left: 10px;">分钟</span>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="topMsgDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="sendTopMessage">确 定</el-button>
+      </div>
+    </el-dialog>
+
   </div>
 </template>
 
@@ -357,6 +385,11 @@ export default {
       newMsg:'',
       autoMsgTimer: null,
       checkInterval: 2000, // 检查间隔(1分钟,可根据需求调整)
+      topMsgDialogVisible: false,
+      topMsgForm: {
+        msg: '',
+        duration: 5
+      }
     };
   },
   computed: {
@@ -494,6 +527,41 @@ export default {
 
       this.newMsg = '';
     },
+    // 显示顶部消息对话框
+    showTopMsgDialog() {
+      this.topMsgForm.msg = '';
+      this.topMsgForm.duration = 5;
+      this.topMsgDialogVisible = true;
+    },
+    // 发送顶部消息
+    sendTopMessage() {
+      // 发送前简单校验
+      if (this.topMsgForm.msg.trim() === '') {
+        this.$message.warning('请输入消息内容');
+        return;
+      }
+
+      if (!this.topMsgForm.duration || this.topMsgForm.duration < 1) {
+        this.$message.warning('请输入有效的显示时长');
+        return;
+      }
+
+      let msg = {
+        msg: this.topMsgForm.msg,
+        duration: this.topMsgForm.duration,
+        liveId: this.liveId,
+        userId: this.userId,
+        userType: 1,
+        cmd: 'sendTopMsg',
+        avatar: this.$store.state.user.user.avatar,
+        nickName: this.$store.state.user.user.nickName,
+      }
+
+      this.socket.send(JSON.stringify(msg))
+
+      this.topMsgDialogVisible = false;
+      this.$message.success('顶部消息发送成功');
+    },
     sendMessage() {
       // 发送前简单校验
       if (this.newMsg.trim() === '') {

+ 108 - 19
src/views/live/liveConsole/LiveDashboard.vue

@@ -7,6 +7,10 @@
         <div class="card" v-for="item in dataCards" :key="item.title">
           <h3>{{ item.title }}</h3>
           <p class="value">{{ item.value }}</p>
+          <div class="sub-values" v-if="item.subValues">
+            <span class="sub-item">直播: {{ item.subValues.live }}</span>
+            <span class="sub-item">回放: {{ item.subValues.replay }}</span>
+          </div>
         </div>
       </div>
 
@@ -15,12 +19,12 @@
         <!-- 左侧用户分析区(占50%宽度) -->
         <div class="user-analysis">
           <div class="new-old">
-            <h3>新老用户占比</h3>
-            <EChartsComponent chartId="newOldChart" :option="newOldOption" />
+            <h3>直播新老用户占比</h3>
+            <EChartsComponent chartId="liveNewOldChart" :option="liveNewOldOption" />
           </div>
           <div class="region">
-            <h3>地域访客</h3>
-            <EChartsComponent chartId="regionChart" :option="regionOption" />
+            <h3>回放新老用户占比</h3>
+            <EChartsComponent chartId="replayNewOldChart" :option="replayNewOldOption" />
           </div>
         </div>
 
@@ -69,16 +73,36 @@ export default {
     return {
       // 数据保持不变(与原代码一致)
       dataCards: [
-        { title: '在线人数', value: '2.5k' },
-        { title: '观看人次', value: '15.2k' },
-        { title: '点赞数', value: '12.8k' },
-        { title: '评论数', value: '3.2k' }
+        { title: '在线人数', value: 0 },
+        { 
+          title: '观看人次', 
+          value: 0,
+          subValues: { live: 0, replay: 0 }
+        },
+        { 
+          title: '点赞数', 
+          value: 0,
+          subValues: { live: 0, replay: 0 }
+        },
+        { 
+          title: '评论数', 
+          value: 0,
+          subValues: { live: 0, replay: 0 }
+        }
       ],
-      newOldOption: {
+      liveNewOldOption: {
         tooltip: { trigger: 'item' },
         series: [{
           type: 'pie', radius: '70%',
-          data: [{ value: 65, name: '新用户' }, { value: 35, name: '老用户' }],
+          data: [{ value: 0, name: '新用户' }, { value: 0, name: '老用户' }],
+          itemStyle: { colors: ['#36cfc9', '#722ed1'] }
+        }]
+      },
+      replayNewOldOption: {
+        tooltip: { trigger: 'item' },
+        series: [{
+          type: 'pie', radius: '70%',
+          data: [{ value: 0, name: '新用户' }, { value: 0, name: '老用户' }],
           itemStyle: { colors: ['#36cfc9', '#722ed1'] }
         }]
       },
@@ -202,29 +226,80 @@ export default {
       });
     },
     dealData(data){
+      // 计算总计
+      const totalViewNum = (data.liveViewNum || 0) + (data.replayViewNum || 0);
+      const totalLikeNum = (data.liveLikeNum || 0) + (data.replayLikeNum || 0);
+      const totalCommentNum = (data.liveCommentNum || 0) + (data.replayCommentNum || 0);
+      
       this.dataCards = [
-        { title: '在线人数', value: data.onlineNum },
-        { title: '观看人次', value: data.viewNum },
-        { title: '点赞数', value: data.likeNum },
-        { title: '评论数', value: data.commentNum }
+        { title: '在线人数', value: data.onlineNum || 0 },
+        { 
+          title: '观看人次', 
+          value: totalViewNum,
+          subValues: { 
+            live: data.liveViewNum || 0, 
+            replay: data.replayViewNum || 0 
+          }
+        },
+        { 
+          title: '点赞数', 
+          value: totalLikeNum,
+          subValues: { 
+            live: data.liveLikeNum || 0, 
+            replay: data.replayLikeNum || 0 
+          }
+        },
+        { 
+          title: '评论数', 
+          value: totalCommentNum,
+          subValues: { 
+            live: data.liveCommentNum || 0, 
+            replay: data.replayCommentNum || 0 
+          }
+        }
       ]
-      this.newOldOption= {
+      
+      // 直播新老用户占比
+      this.liveNewOldOption = {
+        tooltip: { trigger: 'item' },
+        series: [{
+          type: 'pie', radius: '70%',
+          data: [
+            { value: data.liveNewUserNum || 0, name: '新用户' }, 
+            { value: data.liveOldUserNum || 0, name: '老用户' }
+          ],
+          itemStyle: { colors: ['#36cfc9', '#722ed1'] }
+        }]
+      }
+      
+      // 回放新老用户占比
+      this.replayNewOldOption = {
         tooltip: { trigger: 'item' },
         series: [{
           type: 'pie', radius: '70%',
-          data: [{ value: data.newUserNum, name: '新用户' }, { value: data.oldUserNum, name: '老用户' }],
+          data: [
+            { value: data.replayNewUserNum || 0, name: '新用户' }, 
+            { value: data.replayOldUserNum || 0, name: '老用户' }
+          ],
           itemStyle: { colors: ['#36cfc9', '#722ed1'] }
         }]
       }
+      
+      // 观众来源
       this.sourceOption = {
         tooltip: { trigger: 'item' },
         series: [{
           type: 'pie', radius: '70%',
-          data: [{ value: data.shareUrlNum, name: '分享链接' }, { value: data.directAccessNum, name: '直接访问' }, { value: 0, name: '其他' }],
-          itemStyle: { colors: ['#3b82f6', '#10b981', '#f59e0b'] }
+          data: [
+            { value: data.shareUrlNum || 0, name: '分享链接' }, 
+            { value: data.directAccessNum || 0, name: '直接访问' }
+          ],
+          itemStyle: { colors: ['#3b82f6', '#10b981'] }
         }]
       }
-      this.rankList = data.inviteUserList;
+      
+      // 邀请用户列表
+      this.rankList = data.inviteUserList || [];
     },
   }
 };
@@ -282,6 +357,20 @@ export default {
   margin: 0;
 }
 
+.card .sub-values {
+  display: flex;
+  justify-content: space-around;
+  margin-top: 10px;
+  font-size: 0.8vw;
+  color: #94a3b8;
+}
+
+.card .sub-item {
+  padding: 3px 8px;
+  background: rgba(54, 207, 201, 0.1);
+  border-radius: 4px;
+}
+
 /* 中间内容区:高度40%,左右分栏 */
 .middle-content {
   width: 100%;

+ 15 - 1
src/views/live/liveCoupon/index.vue

@@ -91,6 +91,12 @@
       <el-table-column label="优惠券面值" align="center" prop="couponPrice" />
       <el-table-column label="最低消费" align="center" prop="useMinPrice" />
       <el-table-column label="优惠券有效期限(天)" align="center" prop="couponTime" />
+      <el-table-column label="是否无门槛" align="center" prop="isNoThreshold">
+        <template slot-scope="scope">
+          <span>{{ scope.row.isNoThreshold == 1 ? '是' : '否' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="限制领取次数" align="center" prop="limitReceiveCount" />
       <el-table-column label="排序" align="center" prop="sort" />
       <el-table-column label="类型" align="center" prop="type" >
           <template slot-scope="scope">
@@ -171,6 +177,9 @@
             </el-option>
           </el-select>
         </el-form-item>
+        <el-form-item label="限制领取次数" prop="limitReceiveCount" v-if=" form.type==3">
+          <el-input-number v-model="form.limitReceiveCount"  :min="1"  label="请输入限制领取次数"></el-input-number>
+        </el-form-item>
         <el-form-item label="状态">
             <el-radio-group v-model="form.status">
               <el-radio :label="item.dictValue" v-for="item in statusOptions" >{{item.dictLabel}}</el-radio>
@@ -381,7 +390,9 @@ export default {
         packageCateIds:[],
         createTime: null,
         updateTime: null,
-        isDel: null
+        isDel: null,
+        isNoThreshold: 0,
+        limitReceiveCount: 1,
       };
       this.resetForm("form");
     },
@@ -429,6 +440,9 @@ export default {
           if(this.form.packageCateIds!=null){
             this.form.packageCateIds=this.form.packageCateIds.toString();
           }
+          if(this.form.type==3){
+            this.form.isNoThreshold = 1
+          }
           if (this.form.couponId != null) {
             updateStoreCoupon(this.form).then(response => {
               if (response.code === 200) {

+ 1 - 1
src/views/live/liveVideo/index.vue

@@ -250,7 +250,7 @@ export default {
             this.msgError("请上传视频");
             return;
           }
-          this.form.videoUrl.replace(/\.mp4$/, '.m3u8');
+          this.form.videoUrl = this.form.videoUrl.replace('.mp4', '.m3u8');
           addLiveVideo(this.form).then(response => {
             this.msgSuccess("新增成功");
             this.open = false;