Browse Source

直播代码迁移

wangxy 1 week ago
parent
commit
2ab91c2bd5
37 changed files with 11374 additions and 2442 deletions
  1. 2 0
      .env.prod-hdt
  2. 24 0
      src/api/live/live.js
  3. 10 0
      src/api/live/liveAfterSales.js
  4. 51 0
      src/api/live/liveData.js
  5. 8 0
      src/api/live/liveMsg.js
  6. 43 0
      src/api/live/liveOrder.js
  7. 9 0
      src/api/live/liveWatchUser.js
  8. 64 0
      src/api/live/mergedOrder.js
  9. 9 0
      src/api/live/task.js
  10. 75 29
      src/components/LiveVideoUpload/single.vue
  11. 12 1
      src/router/index.js
  12. 6 1
      src/utils/obs.js
  13. 42 1
      src/views/live/components/productAfterSalesOrder.vue
  14. 534 205
      src/views/live/live/index.vue
  15. 44 7
      src/views/live/liveAfteraSales/index.vue
  16. 17 0
      src/views/live/liveConfig/index.vue
  17. 25 0
      src/views/live/liveConfig/liveCoupon.vue
  18. 57 2
      src/views/live/liveConfig/task.vue
  19. 130 15
      src/views/live/liveConfig/watchReward.vue
  20. 61 0
      src/views/live/liveConsole/EchartsComponent.vue
  21. 1789 0
      src/views/live/liveConsole/LiveConsole.vue
  22. 462 0
      src/views/live/liveConsole/LiveDashboard.vue
  23. 257 0
      src/views/live/liveConsole/LivePlayer.vue
  24. 55 0
      src/views/live/liveConsole/ScreenScale.vue
  25. 1289 0
      src/views/live/liveConsole/index-backup.vue
  26. 37 1269
      src/views/live/liveConsole/index.vue
  27. 15 1
      src/views/live/liveCoupon/index.vue
  28. 1 1
      src/views/live/liveCouponIssue/index.vue
  29. 659 0
      src/views/live/liveData/index-old.vue
  30. 976 727
      src/views/live/liveData/index.vue
  31. 384 129
      src/views/live/liveOrder/index.vue
  32. 56 53
      src/views/live/liveOrder/liveOrderDetails.vue
  33. 1 1
      src/views/live/liveVideo/index.vue
  34. 1292 0
      src/views/live/order/index.vue
  35. 1118 0
      src/views/live/order/liveDetail.vue
  36. 880 0
      src/views/live/order/storeDetail.vue
  37. 880 0
      src/views/live/order/userDetail.vue

+ 2 - 0
.env.prod-hdt

@@ -40,5 +40,7 @@ VUE_APP_BASE_API = '/prod-api'
 #默认 1、会员 2、企微
 VUE_APP_COURSE_DEFAULT = 1
 
+VUE_APP_LIVE_WS_URL = ws://live.hbhdt.top/ws
+
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true

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

@@ -134,3 +134,27 @@ export function copyLive(data) {
     params: data
   })
 }
+
+// 查询公司list
+export function getCompanyDropList() {
+  return request({
+    url: '/live/live/getCompanyDropList',
+    method: 'get'
+  })
+}
+
+// 根据公司查询主体list
+export function getQwCorpList(companyId) {
+  return request({
+    url: '/live/live/getQwCorpList/' + companyId,
+    method: 'get'
+  })
+}
+// 根据主体条件查询标签信息
+export function getTagsListByCorpId(qwTagGroup) {
+  return request({
+    url: '/live/live/getTagsListByCorpId',
+    method: 'get',
+    params:qwTagGroup
+  })
+}

+ 10 - 0
src/api/live/liveAfterSales.js

@@ -90,3 +90,13 @@ export function audit2(data) {
     data: data
   })
 }
+
+export function handleImmediatelyRefund(data) {
+  return request({
+    url: '/live/liveAfterSales/handleImmediatelyRefund',
+    method: 'post',
+    data: data
+  })
+}
+
+

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

@@ -5,3 +5,54 @@ export function recentLive() {
     method: 'get',
   })
 }
+
+export function dashboardData(liveId) {
+  return request({
+    url: '/liveData/liveData/dashboardData' + '?liveId=' +liveId,
+    method: 'get'
+  })
+}
+
+export function listLiveData(data) {
+  return request({
+    url: '/liveData/liveData/listLiveData',
+    method: 'post',
+    data:data,
+  })
+}
+
+export function getLiveDataDetailBySql(liveId) {
+  return request({
+    url: '/liveData/liveData/getLiveDataDetailBySql?liveId=' + liveId,
+    method: 'get'
+  })
+}
+
+export function getLiveUserDetailListBySql(liveId) {
+  return request({
+    url: '/liveData/liveData/getLiveUserDetailListBySql?liveId=' + liveId,
+    method: 'get'
+  })
+}
+
+export function getLiveDataDetailByServer(liveId) {
+  return request({
+    url: '/liveData/liveData/getLiveDataDetailByServer?liveId=' + liveId,
+    method: 'get'
+  })
+}
+
+export function getLiveUserDetailListByServer(liveId) {
+  return request({
+    url: '/liveData/liveData/getLiveUserDetailListByServer?liveId=' + liveId,
+    method: 'get'
+  })
+}
+
+// 导出直播间用户详情数据
+export function exportLiveUserDetail(liveId) {
+  return request({
+    url: '/liveData/liveData/exportLiveUserDetail?liveId=' + liveId,
+    method: 'get'
+  })
+}

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

@@ -8,6 +8,14 @@ export function listLiveMsg(query) {
     params: query
   })
 }
+// 查询直播讨论列表
+export function listLiveSingleMsg(query) {
+  return request({
+    url: '/live/liveMsg/singleList',
+    method: 'get',
+    params: query
+  })
+}
 
 // 查询直播讨论详细
 export function getLiveMsg(msgId) {

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

@@ -9,6 +9,15 @@ export function listLiveOrder(query) {
   })
 }
 
+// 查询订单列表
+export function listLiveOrderZm(query) {
+  return request({
+    url: '/live/liveOrder/listZm',
+    method: 'get',
+    params: query
+  })
+}
+
 // 查询订单详细
 export function getLiveOrder(orderId) {
   return request({
@@ -68,6 +77,23 @@ export function exportLiveOrder(query) {
   })
 }
 
+// 导出订单
+export function exportLiveOrderZm(query) {
+  return request({
+    url: '/live/liveOrder/exportZm',
+    method: 'get',
+    params: query
+  })
+}
+export function exportZmNew(query) {
+  return request({
+    url: '/live/liveOrder/exportZmNew',
+    method: 'get',
+    params: query
+  })
+}
+
+
 export function getExpress(data) {
   return request({
     url: '/live/liveOrder/getExpressByDeliverId',
@@ -275,3 +301,20 @@ export function updateErp(data) {
     data: data
   })
 }
+
+
+// 导出发货单
+export function healthExportShippingOrder(query) {
+  return request({
+    url: '/live/liveOrder/healthExportShippingOrder',
+    method: 'get',
+    params: query
+  })
+}
+
+export function importDeliveryNoteExpressTemplate() {
+  return request({
+    url: '/live/liveOrder/importDeliveryNoteExpressTemplate',
+    method: 'get'
+  })
+}

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

@@ -33,3 +33,12 @@ export function blockUser(query) {
     method: 'get',
   })
 }
+
+// 查询直播间用户列表
+export function dashBoardWatchUserList(query) {
+  return request({
+    url: '/live/liveWatchUser/dashBoardWatchUserList',
+    method: 'get',
+    params: query
+  })
+}

+ 64 - 0
src/api/live/mergedOrder.js

@@ -0,0 +1,64 @@
+import request from '@/utils/request'
+
+// 查询合并订单列表
+export function listMergedOrder(query) {
+  return request({
+    url: '/order/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出合并订单
+export function exportMergedOrder(query) {
+  return request({
+    url: '/order/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出合并订单明细
+export function exportMergedOrderItems(query) {
+  return request({
+    url: '/order/exportItems',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出合并订单(明文)
+export function exportMergedOrderDetails(query) {
+  return request({
+    url: '/order/exportDetails',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出合并订单明细(明文)
+export function exportMergedOrderItemsDetails(query) {
+  return request({
+    url: '/order/exportItemsDetails',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出发货单
+export function exportMergedShipping(query) {
+  return request({
+    url: '/order/exportShipping',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导入物流单号模板下载
+export function importDeliveryNoteExpressTemplate() {
+  return request({
+    url: '/order/importDeliveryNoteTemplate',
+    method: 'get'
+  })
+}
+

+ 9 - 0
src/api/live/task.js

@@ -76,3 +76,12 @@ export function importTemplate() {
     method: 'get'
   })
 }
+
+// 新直播控制台任务列表
+export function consoleList(query) {
+  return request({
+    url: '/live/task/consoleList',
+    method: 'get',
+    params: query
+  })
+}

+ 75 - 29
src/components/LiveVideoUpload/single.vue

@@ -2,34 +2,34 @@
   <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>
-          <!-- 线路一 -->
-          <div class="progress-container">
-            <span class="progress-label">线路一</span>
-            <el-progress
-              style="margin-top: 5px;"
-              class="progress"
-              :text-inside="true"
-              :stroke-width="18"
-              :percentage="txProgress"
-              status="success">
-            </el-progress>
-          </div>
+<!--          &lt;!&ndash; 线路一 &ndash;&gt;-->
+<!--          <div class="progress-container">-->
+<!--            <span class="progress-label">线路一</span>-->
+<!--            <el-progress-->
+<!--              style="margin-top: 5px;"-->
+<!--              class="progress"-->
+<!--              :text-inside="true"-->
+<!--              :stroke-width="18"-->
+<!--              :percentage="txProgress"-->
+<!--              status="success">-->
+<!--            </el-progress>-->
+<!--          </div>-->
 
           <!-- 线路二 -->
 <!--          <div class="progress-container">-->
@@ -44,7 +44,7 @@
 <!--            </el-progress>-->
 <!--          </div>-->
           <div slot="tip" class="el-upload__tip">只能上传mp4文件,且不超过500M</div>
-        </el-upload>
+<!--        </el-upload>-->
       </div>
     </el-form-item>
     <el-form-item label="视频播放">
@@ -125,6 +125,7 @@ import { uploadObject } from "@/utils/cos.js";
 import Pagination from "@/components/Pagination";
 // import { listVideoResource } from '@/api/course/videoResource';
 import { listLiveVideo, getLiveVideo, delLiveVideo, addLiveVideo, updateLiveVideo, exportLiveVideo } from "@/api/live/liveVideo";
+import Hls from 'hls.js';
 
 export default {
   components: {
@@ -182,6 +183,9 @@ export default {
       default: true,
     }
   },
+  destroyed() {
+    this.hls?.destroy();
+  },
   data() {
     return {
       videoName:'',
@@ -206,6 +210,7 @@ export default {
       libraryTotal: 0,
       libraryList: [],
       selectedVideo: null,
+      hls:null,
       libraryQueryParams: {
         pageNum: 1,
         pageSize: 10,
@@ -230,13 +235,50 @@ export default {
   },
   mounted() {
     this.reset();
+    if (this.videoUrl.endsWith(".m3u8")) {
+      this.$nextTick(() => {
+        this.initPlayer()
+      })
+    }
   },
   watch: {
+    videoUrl(newVal,oldVal){
+      console.log("触发数据改变")
+      if (this.videoUrl.endsWith(".m3u8")) {
+        this.$nextTick(() => {
+          this.initPlayer()
+        })
+      }
+    },
     uploadType(newType) {
       this.localUploadType = newType;
     },
+
   },
   methods: {
+    initPlayer() {
+      this.hls?.destroy();
+      if (Hls.isSupported()) {
+        const videoElement = this.$refs.myM3u8Video
+        if (!videoElement) {
+          console.error('找不到 video 元素')
+          return
+        }
+        this.hls = new Hls();
+        this.hls.attachMedia(videoElement);
+        this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
+          this.hls.loadSource(this.videoUrl);
+          this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
+            videoElement.play();
+          });
+        });
+        this.hls.on(Hls.Events.ERROR, (event, data) => {
+          console.error('HLS 错误:', data);
+        });
+      } else {
+        console.error('浏览器不支持 HLS')
+      }
+    },
     /** 查询直播视频列表 */
     getList() {
       this.libraryLoading = true;
@@ -348,7 +390,7 @@ export default {
       // 重置内部状态
       this.txProgress = 0;
       this.fileList = [];
-      this.$refs.upload.clearFiles();
+      // this.$refs.upload.clearFiles();
       this.uploadKey++;
     },
     /** 查询视频库列表 */
@@ -412,7 +454,11 @@ export default {
       this.$emit("update:videoUrl", this.selectedVideo.videoUrl);
       this.$emit("change", this.selectedVideo.videoUrl,this.selectedVideo.lineOne);
 
-
+      if (this.videoUrl.endsWith(".m3u8")) {
+        this.$nextTick(() => {
+          this.initPlayer()
+        })
+      }
       this.libraryOpen = false;
     },
     /** 取消视频选择 */

+ 12 - 1
src/router/index.js

@@ -5,6 +5,8 @@ Vue.use(Router)
 
 /* Layout */
 import Layout from '@/layout'
+import LiveConsole from "@/views/live/liveConsole/index.vue";
+
 
 /**
  * Note: 路由配置项
@@ -227,7 +229,16 @@ export const constantRoutes = [
         meta: { title: '查看营期', activeMenu: '/qw/sop' }
       }
     ]
-  }
+  },
+  // 独立页路由:不嵌套根布局,直接渲染目标组件
+  {
+    path: '/live/liveConsole/:liveId',
+    name: 'LiveConsole',
+    component: LiveConsole, // 直接渲染目标组件,无侧边栏
+    meta: {
+      isIndependentPage: true // 标记为“独立页”
+    }
+  },
 ]
 
 export default new Router({

+ 6 - 1
src/utils/obs.js

@@ -39,7 +39,11 @@ export const uploadToOBS = async (file, progressCallback, type, cancelCallback)
           },
         })
       }
-
+      //四福堂专属配置
+   /*   resolve({
+        "RequestId": "",
+        "urlPath": ""
+      })*/
       obsClient.putObject(
         {
           Bucket: process.env.VUE_APP_OBS_BUCKET,
@@ -65,6 +69,7 @@ export const uploadToOBS = async (file, progressCallback, type, cancelCallback)
           }
         },
       )
+      //注释到这里【四福堂】
     })
   } catch (error) {
     console.error("Error during upload:", error)

+ 42 - 1
src/views/live/components/productAfterSalesOrder.vue

@@ -43,6 +43,11 @@
                   {{afterSales.refundAmount}}
                 </span>
           </el-descriptions-item>
+          <el-descriptions-item label="运费"  >
+                <span v-if="order!=null">
+                  {{order.payDelivery}}
+                </span>
+          </el-descriptions-item>
           <el-descriptions-item label="申请类型"  >
                 <span v-if="afterSales!=null">
                   <el-tag prop="serviceType" v-for="(item, index) in serviceTypeOptions"    v-if="afterSales.serviceType==item.dictValue">{{item.dictLabel}}</el-tag>
@@ -144,6 +149,10 @@
             </template>
           </el-table-column>
         </el-table>
+        <div style="margin-top: 12px; text-align: right;" v-if="order">
+          <div>订单金额:¥{{ goodsTotal.toFixed(2) }}</div>
+          <div>运费金额:¥{{ (order.payDelivery || 0).toFixed(2) }}</div>
+        </div>
         <div style="margin-top: 20px">
           <span class="font-small">操作信息</span>
         </div>
@@ -208,7 +217,7 @@
 </template>
 
 <script>
-import {getLiveAfterSales,cancel,refund,audit,updateLiveAfterSales,audit1,audit2 } from "@/api/live/liveAfterSales";
+import {getLiveAfterSales,cancel,refund,audit,updateLiveAfterSales,audit1,audit2,handleImmediatelyRefund } from "@/api/live/liveAfterSales";
 
 import productOrder from "./productOrder";
 export default {
@@ -289,6 +298,17 @@ export default {
       this.serviceTypeOptions = response.data;
     });
   },
+  computed: {
+    goodsTotal() {
+      if (!this.items || this.items.length === 0) return 0;
+      return this.items.reduce((sum, it) => {
+        const info = JSON.parse(it.jsonInfo || "{}");
+        const num = Number(info.num) || 0;
+        const price = Number(info.price) || 0;
+        return sum + num * price;
+      }, 0);
+    }
+  },
   methods: {
     submitForm() {
       var id=this.afterSales.id;
@@ -327,6 +347,27 @@ export default {
       this.form.salesId=this.afterSales.id;
       this.form.refundAmount=this.afterSales.refundAmount;
     },
+    handleImmediatelyRefund(){
+      let _this = this;
+       this.$confirm('确认立即退款吗?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+
+        let param ={orderId:_this.order.orderId}
+        console.log(param)
+         debugger;
+        handleImmediatelyRefund(param).then(res=>{
+          _this.msgSuccess("操作成功");
+          _this.getLiveAfterSales(_this.afterSales.id);
+        }).catch(res=>{
+          console.log(res);
+        });
+      }).then(() => {
+        // this.getLiveAfterSales(id);
+      }).catch(function() {});
+    },
     handleAudit1(){
       var id=this.afterSales.id;
       this.$confirm('确定审请通过?', "警告", {

File diff suppressed because it is too large
+ 534 - 205
src/views/live/live/index.vue


+ 44 - 7
src/views/live/liveAfteraSales/index.vue

@@ -15,10 +15,10 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="员工姓名" prop="companyUserNickName">
+      <el-form-item label="销售名称" prop="companyUserNickName">
         <el-input
           v-model="queryParams.companyUserNickName"
-          placeholder="请输入员工姓名"
+          placeholder="请输入销售名称"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
@@ -33,6 +33,15 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
+      <el-form-item label="产品名称" prop="productName">
+        <el-input
+          v-model="queryParams.productName"
+          placeholder="请输入产品名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
 
 
 
@@ -98,7 +107,23 @@
           size="small"
           @keyup.enter.native="handleQuery"/>
       </el-form-item>
-
+      <el-form-item label="退货物流单号" prop="returnDeliverySn">
+        <el-input
+          v-model="queryParams.returnDeliverySn"
+          placeholder="请输入退货物流单号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="汇付商户订单" prop="hfOrderCode" label-width="100">
+          <el-input
+            v-model="queryParams.hfOrderCode"
+            placeholder="请输入汇付商户订单"
+            clearable
+            size="small"
+            @keyup.enter.native="handleQuery"/>
+       </el-form-item>
 
       <el-form-item label="提交时间" prop="createTime">
         <el-date-picker
@@ -125,7 +150,7 @@
             size="mini"
             :disabled="exportLoading"
             @click="handleExport"
-            v-hasPermi="['live:liveAfteraSales:export']"
+            v-hasPermi="['live:liveAfterSales:export']"
           >导出</el-button>
         </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
@@ -138,7 +163,9 @@
       <el-table-column label="所属员工" align="center" prop="companyUserNickName" />
       <el-table-column label="购买用户" align="center" prop="userName" />
       <el-table-column label="订单单号" align="center" prop="orderCode" />
+      <el-table-column label="支付单号" align="center" prop="payCode" />
       <el-table-column label="会员手机号" align="center" prop="userPhone" />
+      <el-table-column label="产品名称" align="center" prop="productName" :show-overflow-tooltip="true" />
       <el-table-column label="退款金额" align="center" prop="refundAmount" />
       <el-table-column label="退款类型" align="center" prop="refundType" >
         <template slot-scope="scope">
@@ -183,7 +210,7 @@
             size="mini"
             type="text"
             @click="handleShow(scope.row)"
-            v-hasPermi="['store:storeAfterSales:query']"
+            v-hasPermi="['live:liveAfterSales:query']"
           >查看</el-button>
 
         </template>
@@ -252,6 +279,7 @@ export default {
         deliveryCode: null,
         deliverySn: null,
         deliveryName: null,
+        hfOrderCode:null,
         status: null,
         Status: null,
         isDel: null,
@@ -266,7 +294,9 @@ export default {
         deliveryStatus: null,         // 物流状态,确认是否接口支持
         createTimeBegin: null,        // 创建时间起
         createTimeEnd: null,          // 创
-        userPhone: null
+        userPhone: null,
+        productName: null,
+        returnDeliverySn: null        // 退货物流单号(售后物流单号)
       },
       // 表单参数
       form: {},
@@ -318,7 +348,12 @@ export default {
     /** 查询售后记录列表 */
     getList() {
       this.loading = true;
-      listLiveAfterSales(this.queryParams).then(response => {
+      const params = { ...this.queryParams, deliverySn: this.queryParams.returnDeliverySn || this.queryParams.deliverySn };
+      if(!!params.hfOrderCode && params.hfOrderCode.indexOf("-") > -1){
+        let ss = params.hfOrderCode.split("-");
+        params.hfOrderCode = ss[1];
+      }
+      listLiveAfterSales(params).then(response => {
         this.liveAfterSalesList = response.rows;
         this.total = response.total;
         this.loading = false;
@@ -339,11 +374,13 @@ export default {
         refundAmount: null,
         refundType: null,
         reasons: null,
+        hfOrderCode:null,
         explains: null,
         explainImg: null,
         deliveryCode: null,
         deliverySn: null,
         deliveryName: null,
+        companyUserNickName:null,
         status: 0,
         salesStatus: 0,
         orderStatus: 0,

+ 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; // 切换当前显示的组件
     },

+ 25 - 0
src/views/live/liveConfig/liveCoupon.vue

@@ -45,6 +45,16 @@
         prop="couponTime"
         label="优惠券有效时间"
       ></el-table-column>
+
+      <el-table-column
+        prop="type"
+        label="优惠券类型"
+      >
+        <template slot-scope="scope">
+          <dict-tag :options="couponTypeOptions" :value="scope.row.type"/>
+        </template>
+      </el-table-column>
+
       <el-table-column
         prop="productName"
         label="绑定直播商品"
@@ -150,6 +160,15 @@
             label="优惠券有效时间"
             class-name="clickable-column"
           ></el-table-column>
+          <el-table-column
+            prop="type"
+            label="优惠券类型"
+            class-name="clickable-column"
+          >
+            <template slot-scope="scope">
+              <dict-tag :options="couponTypeOptions" :value="scope.row.type"/>
+            </template>
+          </el-table-column>
         </el-table>
 
         <pagination
@@ -236,6 +255,7 @@ export default {
       },
       couponLiveList: [],
       couponLiveTotal: 0,
+      couponTypeOptions: [],
       couponParams: {
         pageNum: 1,
         pageSize: 10,
@@ -305,6 +325,11 @@ export default {
     // this.goodsParams.liveId = this.liveId
     // this.getLiveGoodsList();
     // this.socket = this.$store.state.liveWs[this.liveId]
+
+    // 获取优惠券类型字典
+    this.getDicts("store_coupon_type").then((response) => {
+      this.couponTypeOptions = response.data;
+    });
   },
   methods: {
     handleGoodsChange(row){

+ 57 - 2
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 => {
@@ -682,11 +723,14 @@ export default {
         if (this.form.taskType == 1) {
           this.form.content = content.goodsId;
         }else if (this.form.taskType == 2) {
-          this.form.content = content.desc;
+          this.form.content = content.redId;
         }else if (this.form.taskType == 4) {
-          this.form.content = content.desc;
+          this.form.content = content.lotteryId;
         }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.status;
         }
         this.open = true;
         this.title = "修改直播间自动化任务配置";
@@ -699,6 +743,17 @@ export default {
         this.msgError("请选择直播间");
         return;
       }
+      if(this.form.taskType == 6){
+        if(this.form.goodsId == null) {
+          this.msgError("请选择商品");
+          return;
+        }
+        this.form.content = JSON.stringify({
+          goodsId: this.form.goodsId,
+          status: this.form.goodsStatus
+        })
+
+      }
       this.$refs["form"].validate(valid => {
         if (valid) {
           if (this.form.id != null) {

+ 130 - 15
src/views/live/liveConfig/watchReward.vue

@@ -24,20 +24,51 @@
       >
         <!-- 参与条件 -->
         <el-form-item label="参与条件" prop="participateCondition">
-          <el-radio v-model="watchRewardForm.participateCondition" label="1">
-            达到指定观看时长
-          </el-radio>
+          <el-radio-group v-model="watchRewardForm.participateCondition">
+            <el-radio label="1">达到指定观看时长</el-radio>
+            <el-radio label="2">启用完课积分</el-radio>
+          </el-radio-group>
         </el-form-item>
 
         <!-- 观看时长 -->
-        <el-form-item label="观看时长" prop="watchDuration">
+        <el-form-item label="观看时长" prop="watchDuration" v-if="watchRewardForm.participateCondition === '1'">
           <el-input v-model="watchRewardForm.watchDuration" placeholder="请输入观看时长" class="duration-input">
             <template #append>分钟</template>
           </el-input>
         </el-form-item>
 
-        <!-- 实施动作 -->
-        <el-form-item label="实施动作" prop="action">
+        <!-- 完课率要求 -->
+        <el-form-item label="完课率要求" prop="completionRate" v-if="watchRewardForm.participateCondition === '2'">
+          <el-input-number 
+            v-model="watchRewardForm.completionRate" 
+            :min="1" 
+            :max="100" 
+            :precision="0"
+            placeholder="请输入完课率"
+            style="width: 200px;"
+          ></el-input-number>
+          <span style="margin-left: 10px; color: #909399;">%(观看时长占直播总时长的比例)</span>
+        </el-form-item>
+
+        <!-- 连续完课积分配置 -->
+        <el-form-item label="连续完课积分" prop="pointsConfig" v-if="watchRewardForm.participateCondition === '2'">
+          <div style="display: flex; flex-direction: column; gap: 10px;">
+            <div v-for="(point, index) in watchRewardForm.pointsConfig" :key="index" style="display: flex; align-items: center;">
+              <span style="width: 80px;">第{{ index + 1 }}天:</span>
+              <el-input-number 
+                v-model="watchRewardForm.pointsConfig[index]" 
+                :min="0" 
+                :precision="0"
+                placeholder="请输入积分值"
+                style="width: 200px;"
+              ></el-input-number>
+              <span style="margin-left: 10px; color: #909399;">积分</span>
+            </div>
+          </div>
+        </el-form-item>
+
+        <!-- 实施动作(仅在达到指定观看时长时显示) -->
+        <el-form-item label="实施动作" prop="action" v-if="watchRewardForm.participateCondition === '1'">
           <el-select v-model="watchRewardForm.action" placeholder="请选择实施动作" style="width: 300px;">
             <el-option
               v-for="item in actionOptions"
@@ -53,8 +84,8 @@
 <!--          <el-input v-model="watchRewardForm.receivePrompt" placeholder="请输入领取提示语"></el-input>-->
 <!--        </el-form-item>-->
 
-        <!-- 红包设置 -->
-        <div>
+        <!-- 红包设置(仅在达到指定观看时长时显示) -->
+        <div v-if="watchRewardForm.participateCondition === '1'">
           <div class="section-title">红包设置</div>
 
           <!-- 根据实施动作类型显示不同的表单内容 -->
@@ -107,8 +138,8 @@
           </template>
         </div>
 
-        <!-- 其他设置 -->
-        <div >
+        <!-- 其他设置(仅在达到指定观看时长时显示) -->
+        <div v-if="watchRewardForm.participateCondition === '1'">
           <div class="section-title">其他设置</div>
 
           <template v-if="watchRewardForm.action === '1'">
@@ -178,7 +209,7 @@ export default {
         liveId: null,
         // 是否启用观看奖励
         enabled: false,
-        // 参与条件
+        // 参与条件 1:达到指定观看时长 2:启用完课积分
         participateCondition: '1',
         // 观看时长
         watchDuration: '',
@@ -205,17 +236,66 @@ export default {
         // 积分使用引导语
         scoreGuideText: '',
         // 积分使用引导链接
-        scoreGuideLink: ''
+        scoreGuideLink: '',
+        // 完课积分相关配置(participateCondition为2时使用)
+        // 完课率要求(1-100)
+        completionRate: 90,
+        // 连续完课积分配置(第1-10天)
+        pointsConfig: [100, 110, 120, 130, 140, 150, 160, 170, 180, 200]
       },
       rules:{
         participateCondition:[
-          { required: true, message: '请选择参与条件', trigger: 'blur'}
+          { required: true, message: '请选择参与条件', trigger: 'change'}
         ],
         watchDuration:[
-          { required: true, message: '请输入观看时长', trigger: 'blur'}
+          { 
+            validator: (rule, value, callback) => {
+              if (this.watchRewardForm.participateCondition === '1') {
+                if (!value) {
+                  callback(new Error('请输入观看时长'));
+                } else {
+                  callback();
+                }
+              } else {
+                callback();
+              }
+            }, 
+            trigger: 'blur'
+          }
+        ],
+        completionRate:[
+          { 
+            validator: (rule, value, callback) => {
+              if (this.watchRewardForm.participateCondition === '2') {
+                if (!value && value !== 0) {
+                  callback(new Error('请输入完课率要求'));
+                } else if (value < 1 || value > 100) {
+                  callback(new Error('完课率要求范围为1-100'));
+                } else {
+                  callback();
+                }
+              } else {
+                callback();
+              }
+            }, 
+            trigger: 'blur'
+          }
         ],
         action:[
-          { required: true, message: '请选择实施动作', trigger: 'blur'}
+          { 
+            validator: (rule, value, callback) => {
+              if (this.watchRewardForm.participateCondition === '1') {
+                if (!value) {
+                  callback(new Error('请选择实施动作'));
+                } else {
+                  callback();
+                }
+              } else {
+                callback();
+              }
+            }, 
+            trigger: 'change'
+          }
         ],
         // receivePrompt:[
         //   { required: true, message: '请输入领取提示语', trigger: 'blur'}
@@ -263,6 +343,15 @@ export default {
       },
       // 初始化时立即执行一次
       immediate: true
+    },
+    // 监听开启观看奖励开关变化
+    'watchRewardForm.enabled': {
+      handler(newValue, oldValue) {
+        // 只有当关闭开关时才自动保存(从 true 变为 false)
+        if (oldValue === true && newValue === false && this.liveId) {
+          this.autoSaveEnabled();
+        }
+      }
     }
   },
   created() {
@@ -276,6 +365,32 @@ export default {
         this.loading = false
       })
     },
+    // 自动保存开关状态
+    autoSaveEnabled() {
+      if (this.watchRewardForm.id == null) {
+        addConfig(this.watchRewardForm, this.liveId).then(res => {
+          if (res.code == 200) {
+            this.msgSuccess("已关闭观看奖励");
+            // 更新 id,下次修改时使用 update
+            if (res.data && res.data.id) {
+              this.watchRewardForm.id = res.data.id;
+            }
+          }
+        }).catch(() => {
+          // 如果保存失败,恢复开关状态
+          this.watchRewardForm.enabled = true;
+        });
+      } else {
+        updateConfig(this.watchRewardForm, this.liveId).then(response => {
+          if (response.code == 200) {
+            this.msgSuccess("已关闭观看奖励");
+          }
+        }).catch(() => {
+          // 如果保存失败,恢复开关状态
+          this.watchRewardForm.enabled = true;
+        });
+      }
+    },
     saveWatchReward() {
       this.$refs["watchRewardForm"].validate(valid => {
         console.log(valid)

+ 61 - 0
src/views/live/liveConsole/EchartsComponent.vue

@@ -0,0 +1,61 @@
+<template>
+  <div :id="chartId" class="chart-container"></div>
+</template>
+
+<script>
+import * as echarts from 'echarts';
+import 'echarts/map/js/china'; // 确保地图模块引入
+
+export default {
+  name: 'EChartsComponent',
+  props: {
+    chartId: {type: String, required: true},
+    option: {type: Object, required: true}
+  },
+  mounted() {
+    this.$nextTick(() => { // 确保DOM渲染完成后初始化
+      this.initChart();
+      window.addEventListener('resize', this.resizeChart);
+    });
+  },
+  // 监听 option 变化,自动更新图表
+  watch: {
+    option: {
+      deep: true, // 深度监听对象内部变化
+      handler(newVal) {
+        if (this.chart) {
+          this.chart.setOption(newVal); // 关键:用新数据更新图表
+        }
+      }
+    }
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.resizeChart);
+    if (this.chart) this.chart.dispose();
+  },
+  methods: {
+    initChart() {
+      const container = document.getElementById(this.chartId);
+      if (!container) return; // 防止容器不存在
+      this.chart = echarts.init(container);
+      this.chart.setOption(this.option);
+    },
+    resizeChart() {
+      if (this.chart) this.chart.resize();
+    },
+    // 暴露手动更新方法(可选,用于特殊场景)
+    updateOption(newOption) {
+      if (this.chart) {
+        this.chart.setOption(newOption);
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.chart-container {
+  width: 100%;
+  height: 100%;
+}
+</style>

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

@@ -0,0 +1,1789 @@
+<template>
+  <div class="console">
+
+    <div class="left-panel">
+      <h2>学员列表</h2>
+      <div class="search">
+        <input type="text" placeholder="搜索用户昵称" v-model="searchKeyword">
+        <button @click="searchUsers()">搜索</button>
+      </div>
+      <el-tabs class="live-console-tab-right" v-model="tabLeft.activeName" @tab-click="handleUserClick" :stretch="true">
+        <el-tab-pane :label="alLabel" name="al">
+          <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_al" style="height: 800px; width: 100%;">
+            <el-row class='scrollbar-demo-item' v-for="u in alDisplayList" :key="u.userId">
+              <el-col :span="20">
+                <el-row type="flex" align="middle">
+                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
+                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
+                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
+                  <i class="el-icon-more" slot="reference"></i>
+                </el-popover>
+              </el-col>
+            </el-row>
+          </el-scrollbar>
+        </el-tab-pane>
+        <el-tab-pane :label="onlineLabel" name="online">
+          <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_online" style="height: 800px; width: 100%;">
+            <el-row class='scrollbar-demo-item' v-for="u in onlineDisplayList" :key="u.userId">
+              <el-col :span="20">
+                <el-row type="flex" align="middle">
+                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
+                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
+                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
+                  <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 class="custom-scrollbar" ref="manageLeftRef_offline" style="height: 800px; width: 100%;">
+            <el-row class='scrollbar-demo-item' v-for="u in offlineDisplayList" :key="u.userId">
+              <el-col :span="20">
+                <el-row type="flex" align="middle">
+                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
+                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
+                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <i class="el-icon-more" slot="reference"></i>
+                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
+                </el-popover>
+              </el-col>
+            </el-row>
+          </el-scrollbar>
+        </el-tab-pane>
+        <el-tab-pane :label="silencedUserLabel" name="silenced">
+          <el-scrollbar class="custom-scrollbar" ref="manageLeftRef_silenced" style="height: 800px; width: 100%;">
+            <el-row class='scrollbar-demo-item' v-for="u in silencedDisplayList" :key="u.userId">
+              <el-col :span="20">
+                <el-row type="flex" align="middle">
+                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
+                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
+                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
+                  <i class="el-icon-more" slot="reference"></i>
+                </el-popover>
+              </el-col>
+            </el-row>
+          </el-scrollbar>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+
+    <div class="middle-panel">
+      <h2>消息管理</h2>
+
+
+      <div class="discussion-messages">
+        <h3>讨论区消息</h3>
+        <div class="message-settings">
+          <label>
+            <input type="checkbox" v-model="globalVisible" @change="globalVisibleChange">
+            全局用户自见
+          </label>
+        </div>
+        <div class="message-container" @click="handleMessageBoxClick">
+          <el-scrollbar class="custom-scrollbar" style="height: 500px; width: 100%;" ref="manageRightRef">
+            <el-row v-for="m in msgList" :key="m.msgId">
+            <el-row v-if="m.userId === userId && m.msgId == null" 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 v-else 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.stop="changeUserState(m)">{{ m.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                    <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="blockUser(m)">拉黑</a>
+                    <a v-if="m.singleVisible === 1" style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">解除用户自见</a>
+                    <a v-else style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="singleVisible(m)">用户自见</a>
+                    <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click.stop="deleteMsg(m)">删除</a>
+                  </el-col>
+                </el-row>
+              </el-col>
+            </el-row>
+
+          </el-row>
+          <!-- 底部留白 -->
+          <div style="height: 20px;"></div>
+          </el-scrollbar>
+          <!-- 加载最新消息按钮 -->
+          <el-button
+            v-if="showLoadLatestBtn"
+            class="load-latest-btn"
+            type="primary"
+            size="small"
+            @click.stop="loadLatestMessages"
+            :disabled="isLoadingLatest"
+            :loading="isLoadingLatest"
+            icon="el-icon-refresh">
+            加载最新消息
+          </el-button>
+        </div>
+        <!--        <div class="message-list">-->
+        <!--          <div class="message-item" v-for="msg in msgList" :key="msg.id">-->
+        <!--            <div class="message-avatar">-->
+        <!--              <img :src="msg.avatar" alt="用户头像">-->
+        <!--            </div>-->
+        <!--            <div class="message-content">-->
+        <!--              <div class="message-user">{{ msg.user }}</div>-->
+        <!--              <div class="message-text">{{ msg.text }}</div>-->
+        <!--            </div>-->
+        <!--            <div class="message-actions">-->
+        <!--&lt;!&ndash;              <button @click="toggleVisible(msg)">&ndash;&gt;-->
+        <!--&lt;!&ndash;                {{ msg.isVisible ? '仅用户自见' : '全局可见' }}&ndash;&gt;-->
+        <!--&lt;!&ndash;              </button>&ndash;&gt;-->
+        <!--              <button @click="deleteMessage(msg)">删除</button>-->
+        <!--            </div>-->
+        <!--          </div>-->
+        <!--        </div>-->
+      </div>
+      <div class="system-messages">
+        <h3>系统消息</h3>
+        <textarea placeholder="输入系统消息" v-model="newMsg"></textarea>
+        <div class="message-actions">
+          <button @click="sendMessage">发送消息</button>
+          <button @click="sendPopMessage">弹窗消息</button>
+          <button @click="showTopMsgDialog">顶部消息</button>
+        </div>
+      </div>
+    </div>
+
+    <div class="right-panel">
+      <h2>运营工具</h2>
+      <div class="live-player">
+        <h3>直播观看</h3>
+        <LivePlayer ref="livePlayer" :videoParam="videoParam" />
+      </div>
+
+      <div class="automation">
+        <h3>运营自动化</h3>
+        <div class="timeline">
+          <h4>时间轴设置</h4>
+          <div class="timeline-items">
+            <div class="timeline-item" v-for="item in timelineItems.slice(0,2)" :key="item.time">
+              <div class="time">{{ formatDate(item.absValue) }}</div>
+              <div class="action">{{ item.taskName }}</div>
+              <button class="delete" @click="removeTimelineItem(item)">删除</button>
+            </div>
+          </div>
+          <!--          <button class="add" @click="addTimelineItem">添加时间节点</button>-->
+        </div>
+      </div>
+
+      <div class="watermark">
+        <h3>直播氛围自动化</h3>
+        <div class="watermark-settings">
+          <textarea :disabled="autoWatermark" v-model="watermarkTemplate" placeholder="水军弹幕内容模板,每行一条"></textarea>
+          <div class="watermark-options">
+            <label>
+              发送间隔:
+              <input type="number" :disabled="autoWatermark" v-model.number="interval" min="1">
+              秒
+            </label>
+            <label>
+              <input type="checkbox" v-model="autoWatermark" @change="changeAutoWatermark">
+              启用水军自动化
+            </label>
+          </div>
+        </div>
+      </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>
+
+<script>
+import LivePlayer from './LivePlayer.vue';
+import {blockUser, changeUserStatus, getLiveUserTotals, dashBoardWatchUserList} from '@/api/live/liveWatchUser'
+import { listLiveSingleMsg,delLiveMsg } from '@/api/live/liveMsg'
+import { getLive } from '@/api/live/live'
+import { consoleList } from '@/api/live/task'
+import ScreenScale from './ScreenScale.vue'; // 路径根据实际位置调整
+
+
+export default {
+  components: {
+    LivePlayer,ScreenScale
+  },
+  props: {
+    liveId: {
+      type: String,
+      default: null
+    }
+  },
+  data() {
+    return {
+      watermarkIndex: 0,
+      watermarkList:[],
+      watermarkTemplate: '',
+      liveInfo: {},
+      videoParam:{
+        startTime:'',
+        livingUrl: '',
+        liveType: 1,
+        videoUrl: '',
+      },
+      msgList:[],
+      currentTab: 'al',
+      searchKeyword: '',
+      globalVisible: false,
+      interval: 10,
+      autoWatermark: false,
+      streamUrl: 'rtmp://your-live-stream-url',
+      users: [
+        { id: 1, name: '用户1', avatar: 'https://via.placeholder.com/40', status: 'online', statusText: '在线', silenced: false, msgStatus: false },
+        { id: 2, name: '用户2', avatar: 'https://via.placeholder.com/40', status: 'online', statusText: '在线', silenced: false, msgStatus: true },
+        { id: 3, name: '用户3', avatar: 'https://via.placeholder.com/40', status: 'offline', statusText: '离线', silenced: true, msgStatus: false },
+        { id: 4, name: '用户4', avatar: 'https://via.placeholder.com/40', status: 'online', statusText: '在线', silenced: false, msgStatus: false },
+        { id: 5, name: '用户5', avatar: 'https://via.placeholder.com/40', status: 'offline', statusText: '离线', silenced: false, msgStatus: false }
+      ],
+      messages: [
+        { id: 1, user: '用户1', avatar: 'https://via.placeholder.com/30', text: '这个产品怎么样?', isVisible: true },
+        { id: 2, user: '用户2', avatar: 'https://via.placeholder.com/30', text: '看起来不错', isVisible: true },
+        { id: 3, user: '用户3', avatar: 'https://via.placeholder.com/30', text: '有优惠吗?', isVisible: true }
+      ],
+      timelineItems: [
+        { "searchValue": null,
+          "createBy": null,
+          "createTime": "2025-09-23 10:36:17",
+          "updateBy": null,
+          "updateTime": "2025-10-17 09:18:00",
+          "remark": null,
+          "params": {},
+          "id": 6573,
+          "liveId": 128,
+          "taskName": "2",
+          "taskType": 1,
+          "triggerType": 2,
+          "triggerValue": "2025-01-01T00:02:00.000+0800",
+          "absValue": "2025-11-23T10:43:00.000+0800",
+          "status": 1,
+          "finishStatus": 0 },
+      ],
+      userTotal: {
+        online: 0,       // 在线总人数
+        offline: 0,      // 离线总人数
+        silenced: 0,      // 禁言总人数
+        al: 0
+      },
+      tabLeft: {
+        activeName: "al",
+      },
+      taskParams:{
+        currentPage: 1,
+        pageSize: 20,
+        prevPage: 0,
+        totalLoaded: 0,
+        total: 0,
+        hasMore: true,
+        hasPrev: false
+      },
+      loadMsgMaxPage: 2,
+      liveWsUrl: process.env.VUE_APP_LIVE_WS_URL + '/app/webSocket',
+      alDisplayList: [],
+      onlineDisplayList: [],    // 在线用户显示列表
+      offlineDisplayList: [],   // 离线用户显示列表
+      silencedDisplayList: [],  // 禁言用户显示列表
+      // 各Tab的分页参数
+      pageParams: {
+        al: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        },
+        online: {
+          currentPage: 1,       // 当前页(下一页加载用)
+          pageSize: 20,       // 当前页(下一页加载用)
+          prevPage: 0,          // 上一页页码(上一页加载用)
+          totalLoaded: 0,       // 已加载总条数
+          total: 0,             // 总数据量
+          hasMore: true,        // 是否有下一页
+          hasPrev: false        // 是否有上一页
+        },
+        offline: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        },
+        silenced: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        }
+      },
+      scrLoading: {
+        al: { next: false, prev: false },
+        online: { next: false, prev: false },
+        offline: { next: false, prev: false },
+        silenced: { next: false, prev: false }
+      },
+      newMsg:'',
+      autoMsgTimer: null,
+      checkInterval: 2000, // 检查间隔(1分钟,可根据需求调整)
+      topMsgDialogVisible: false,
+      topMsgForm: {
+        msg: '',
+        duration: 5
+      },
+      // 消息滚动控制
+      isAutoScrollEnabled: true, // 是否启用自动滚动
+      autoScrollTimer: null, // 自动滚动定时器
+      showLoadLatestBtn: false, // 是否显示加载最新消息按钮
+      isLoadingLatest: false, // 是否正在加载最新消息
+      scrollDebounceTimer: null, // 滚动防抖定时器
+      msgParams: {
+        pageNum: 1,
+        pageSize: 30,
+        liveId: null
+      }
+    };
+  },
+  computed: {
+    userId() {
+      return this.$store.state.user.user.userId
+    },
+    companyId() {
+      return this.$store.state.user.user.companyId
+    },
+    onlineLabel() {
+      return `在线(${this.userTotal.online})`;
+    },
+    alLabel() {
+      return `全部(${this.userTotal.al})`;
+    },
+    offlineLabel() {
+      return `离线(${this.userTotal.offline})`;
+    },
+    silencedUserLabel() {
+      return `禁言(${this.userTotal.silenced})`;
+    }
+  },
+  created() {
+    if(!this.liveId) return
+    this.getList()
+    this.handleUserClick({name:'al'})
+    this.connectWebSocket()
+    this.getLive()
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.restoreChatScrollPosition();
+
+      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+        this.$refs.manageRightRef.wrap.addEventListener('scroll', this.saveChatScrollPosition);
+      }
+    });
+    this.initScrollListeners();
+  },
+  methods: {
+    // 点击消息框
+    handleMessageBoxClick() {
+      // 点击消息框时,停止自动滚动
+      this.isAutoScrollEnabled = false;
+      // 停止自动滚动定时器
+      if (this.autoScrollTimer) {
+        clearTimeout(this.autoScrollTimer);
+        this.autoScrollTimer = null;
+      }
+      // 检查是否在底部,如果不在底部则显示加载最新消息按钮
+      this.$nextTick(() => {
+        if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+          const wrap = this.$refs.manageRightRef.wrap;
+          const scrollHeight = wrap.scrollHeight;
+          const clientHeight = wrap.clientHeight;
+          const currentScrollTop = wrap.scrollTop;
+          const maxScrollTop = scrollHeight - clientHeight;
+
+          if (currentScrollTop < maxScrollTop - 50) {
+            this.showLoadLatestBtn = true;
+          }
+        }
+      });
+    },
+    // 滚动到底部
+    scrollToBottom(forceScroll = false) {
+      // 如果自动滚动被禁用且不是强制滚动,则不执行
+      if (!this.isAutoScrollEnabled && !forceScroll) {
+        return;
+      }
+
+      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+        this.$nextTick(() => {
+          const wrap = this.$refs.manageRightRef.wrap;
+          if (!wrap) return;
+
+          const scrollHeight = wrap.scrollHeight;
+          const clientHeight = wrap.clientHeight;
+          const currentScrollTop = wrap.scrollTop;
+          const maxScrollTop = scrollHeight - clientHeight;
+
+          // 强制滚动或启用自动滚动时,直接滚动到底部并隐藏按钮
+          if (forceScroll || this.isAutoScrollEnabled) {
+            this.showLoadLatestBtn = false;
+            wrap.scrollTop = maxScrollTop;
+          }
+        });
+      }
+    },
+    // 加载最新消息
+    loadLatestMessages() {
+      // 如果正在加载,直接返回
+      if (this.isLoadingLatest) {
+        return;
+      }
+      this.isLoadingLatest = true;
+      this.showLoadLatestBtn = false;
+      // 恢复自动滚动
+      this.isAutoScrollEnabled = true;
+      // 重新请求最新消息
+      this.resetMsgParams();
+      // loadMsgList 中会自动滚动到底部,因为 isAutoScrollEnabled 已经是 true
+      this.loadMsgList();
+    },
+    singleVisible(m){
+      // 过滤当前所有消息 找到userId的相同的消息 更改他们的自可见状态
+      m.singleVisible= m.singleVisible === 1 ? 0 : 1
+      this.msgList.forEach(m1 => {m1.singleVisible = m1.userId === m.userId ? m.singleVisible : !m.singleVisible})
+      // 消息自可见
+      let msg = {
+        liveId: this.liveId,
+        userId: m.userId,
+        userType: 0,
+        cmd: 'singleVisible',
+        status:m.singleVisible
+      }
+      this.socket.send(JSON.stringify(msg))
+    },
+    deleteMsg(m){
+      // 1. 弹出确认对话框
+      this.$confirm('此操作将永久删除该消息, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        delLiveMsg(m.msgId).then(res => {
+          if (res.code === 200) {
+            const index = this.msgList.findIndex(item => item.msgId === m.msgId);
+            if (index !== -1) {
+              this.msgList.splice(index, 1);
+              console.log(`消息 ${m.msgId} 已在本地删除。`);
+            }
+            let msg = {
+              liveId: this.liveId,
+              userId: m.userId,
+              msg: m.msgId, // 关键:将消息ID发送给后台
+              cmd: 'deleteMsg',
+            };
+            this.socket.send(JSON.stringify(msg));
+            // 可以在这里给用户一个删除成功的提示
+            this.$message({
+              type: 'success',
+              message: '消息删除成功!'
+            });
+          }
+        })
+      }).catch(() => {
+        // 3. 用户点击“取消”或关闭对话框后的回调
+        this.$message({
+          type: 'info',
+          message: '已取消删除'
+        });
+      });
+    },
+    globalVisibleChange( val){
+      // 消息自可见
+      let msg = {
+        liveId: this.liveId,
+        userId: '9999',
+        userType: 0,
+        cmd: 'globalVisible',
+        status:this.globalVisible ? 1 :0
+      }
+      this.socket.send(JSON.stringify(msg))
+    },
+    changeAutoWatermark( val){
+      this.watermarkList = this.watermarkTemplate
+        .split('\n')
+        .map(line => line.trim())
+        .filter(line => line !== '');
+      if(this.watermarkTemplate.length < 1 || this.watermarkList.length === 0) {
+        this.$message.warning('请先填写水印模板')
+        this.$nextTick(() => {
+          this.autoWatermark = false
+        });
+        return
+      }
+      this.disabled = this.autoWatermark
+      if(this.autoWatermark){
+        this.autoMsgTimer = setInterval(() => {
+          this.sendNormalMessage()
+        }, 1000 * this.interval)
+      } else {
+        clearInterval(this.autoMsgTimer)
+        this.watermarkIndex = 0;
+        this.watermarkList = [];
+      }
+    },
+    sendNormalMessage() {
+      if(this.watermarkIndex >= this.watermarkList.length){
+        clearInterval(this.autoMsgTimer)
+        this.watermarkIndex = 0;
+        this.watermarkList = [];
+        this.$message.success('自动发送消息已结束');
+        this.$nextTick(() => {
+          this.autoWatermark = false
+        });
+        return;
+      }
+      const curMsg = this.watermarkList[this.watermarkIndex]
+      // 发送前简单校验
+      if (curMsg.trim() === '') {
+        return;
+      }
+      let msg = {
+        msg: curMsg,
+        liveId: this.liveId,
+        userId: '9999',
+        userType: 0,
+        cmd: 'sendNormalMsg',
+        avatar: this.$store.state.user.user.avatar,
+        nickName: this.$store.state.user.user.nickName,
+      }
+      this.socket.send(JSON.stringify(msg))
+      this.watermarkIndex += 1
+    },
+    // 弹窗消息
+    sendPopMessage() {
+      // 发送前简单校验
+      if (this.newMsg.trim() === '') {
+        return;
+      }
+
+      let msg = {
+        msg: this.newMsg,
+        liveId: this.liveId,
+        userId: this.userId,
+        userType: 1,
+        cmd: 'sendPopMsg',
+        avatar: this.$store.state.user.user.avatar,
+        nickName: this.$store.state.user.user.nickName,
+      }
+
+      this.socket.send(JSON.stringify(msg))
+
+      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() === '') {
+        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 = '';
+    },
+    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.alDisplayList.find(u => u.userId === message.userId)
+          if (user) {
+            message.msgStatus = user.msgStatus
+          } else {
+            message.msgStatus = 0
+          }
+          delete message.params
+          if(this.msgList.length > 50){
+            this.msgList.shift()
+          }
+          this.msgList.push(message)
+          // 如果启用自动滚动,自动滚动到底部
+          if (this.isAutoScrollEnabled) {
+            this.$nextTick(() => {
+              this.autoScrollTimer = setTimeout(() => {
+                this.scrollToBottom();
+              }, 200);
+            });
+          } else {
+            // 自动滚动被禁用时,显示加载最新消息按钮
+            this.showLoadLatestBtn = true;
+          }
+        } else if (cmd === 'entry' || cmd === 'out') {
+          const user = data;
+          const online = cmd === 'entry' ? 0 : 1; // 0=在线,1=离线
+          const info = {
+            online:online,
+            msgStatus: user.msgStatus || 0,
+            nickName: user.nickName || '',
+            userType: user.userType || 0,
+            userId: user.userId || '',
+          };
+
+          // 1. 更新总人数(在线/离线互转)
+          if (cmd === 'entry') {
+            this.userTotal.online += 1;
+            this.userTotal.offline = Math.max(0, this.userTotal.offline - 1); // 确保不小于0
+          } else {
+            this.userTotal.offline += 1;
+            this.userTotal.online = Math.max(0, this.userTotal.online - 1); // 确保不小于0
+          }
+          // 2. 强制更新相关列表(无论当前激活哪个Tab)
+          if (cmd === 'entry') {
+            // 用户进入:从离线列表删除,添加到在线列表
+            this.offlineDisplayList = this.offlineDisplayList.filter(u => u.userId !== user.userId);
+            const newOnlineList = this.onlineDisplayList.filter(u => u.userId !== user.userId);
+            newOnlineList.push(info);
+            this.onlineDisplayList = newOnlineList.slice(-40); // 限制最大50条
+          } else {
+            // 用户离开:从在线列表删除,添加到离线列表
+            this.onlineDisplayList = this.onlineDisplayList.filter(u => u.userId !== user.userId);
+            const newOfflineList = this.offlineDisplayList.filter(u => u.userId !== user.userId);
+            newOfflineList.push(info);
+            this.offlineDisplayList = newOfflineList.slice(-40); // 限制最大50条
+          }
+          // 3. 处理禁言列表(如果用户是禁言状态,无论进出都要同步)
+          if (info.msgStatus === 1) {
+            // 禁言用户:从禁言列表删除旧数据,添加新数据
+            const newSilencedList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
+            newSilencedList.push(info);
+            this.silencedDisplayList = newSilencedList.slice(-40);
+          } else {
+            // 非禁言用户:从禁言列表删除(如果存在)
+            this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
+          }
+
+        } else if (cmd === 'live_start') {
+
+        } else if (cmd === 'live_end') {
+
+        }
+      }
+    },
+    getLive(){
+      getLive(this.liveId).then(res => {
+        if (res.code == 200) {
+          if (res.data.isAudit != 1) {
+            this.$message.error("当前直播间未经审核");
+            this.loading = false
+            return
+          }
+          this.isAudit = true
+          this.status = res.data.status
+          this.videoParam.startTime = new Date(res.data.startTime).getTime()
+          if(res.data.status == 4){
+            this.videoParam.liveType = 3
+            this.videoParam.videoUrl = res.data.videoUrl;
+          }else {
+            if (res.data.status != 2) {
+              this.$message.error("当前直播间未直播");
+              this.loading = false
+              return
+            }
+            if (res.data.liveType == 1) {
+              this.videoParam.livingUrl = res.data.flvHlsUrl
+              this.videoParam.livingUrl = this.videoParam.livingUrl.replace("flv","m3u8")
+              // this.$nextTick(() => {
+              //   this.initPlayer()
+              // })
+              // this.processInterval = setInterval(this.updateLiveProgress, 1000);
+            } else {
+              this.videoParam.liveType = 2
+              this.videoParam.videoUrl = res.data.videoUrl;
+              // this.$nextTick(() => {
+              //   this.initVideoPlayer(res.data.startTime)
+              // })
+            }
+          }
+          this.$nextTick(() => {
+            this.globalVisible = res.data.globalVisible
+            this.$refs.livePlayer.initPlayer()
+          })
+          this.loading = false
+        } else {
+          this.$message.error(res.msg)
+          this.loading = false
+        }
+        this.liveInfo = res.data
+      })
+    },
+    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)
+    },
+    changeUserState(u) {
+      const displayList = this[`${this.currentTab}DisplayList`];
+      // 修改状态
+      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;
+            }
+          });
+
+          displayList.forEach(user => {
+            if (user.userId === u.userId) {
+              user.msgStatus = u.msgStatus;
+            }
+          });
+          // 4. 关键:重新筛选所有Tab的显示列表,确保状态同步
+          this.refreshUserDisplayLists(u);
+
+          let msg = u.msgStatus === 0 ? "已解禁" : "已禁言"
+          this.msgSuccess(msg);
+          return
+        }
+        this.msgError("操作失败");
+      })
+    },
+    refreshUserDisplayLists(user) {
+      const { userId, msgStatus: newStatus, online } = user;
+      const oldStatus = newStatus === 1 ? 0 : 1; // 操作前的状态(反向)
+
+      // 1. 禁言操作(newStatus=1):从原在线/离线列表移除,加入禁言列表
+      if (newStatus === 1) {
+        // 从在线/离线列表中移除该用户(根据当前在线状态)
+        if (online === 0) {
+          this.onlineDisplayList = this.onlineDisplayList.filter(u => u.userId !== userId);
+          this.userTotal.online = Math.max(0, this.userTotal.online - 1);
+        } else {
+          this.offlineDisplayList = this.offlineDisplayList.filter(u => u.userId !== userId);
+          this.userTotal.offline = Math.max(0, this.userTotal.offline - 1);
+        }
+        this.userTotal.silenced = this.userTotal.silenced + 1;
+        // 添加到禁言列表(去重+限制长度)
+        const silencedList = this.silencedDisplayList.filter(u => u.userId !== userId);
+        silencedList.push(user);
+        this.silencedDisplayList = silencedList.slice(-40);
+      }
+
+      // 2. 解禁操作(newStatus=0):从禁言列表移除,回到原在线/离线列表
+      else {
+        // 从禁言列表移除
+        this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== userId);
+        this.userTotal.silenced = Math.max(0, this.userTotal.silenced - 1);
+        // 回到对应的在线/离线列表(根据当前在线状态)
+        if (online === 0) {
+          // 加入在线列表(去重+限制长度)
+          const onlineList = this.onlineDisplayList.filter(u => u.userId !== userId);
+          onlineList.push(user);
+          this.onlineDisplayList = onlineList.slice(-40);
+          this.userTotal.online = this.userTotal.online + 1;
+        } else {
+          // 加入离线列表(去重+限制长度)
+          const offlineList = this.offlineDisplayList.filter(u => u.userId !== userId);
+          offlineList.push(user);
+          this.offlineDisplayList = offlineList.slice(-40);
+          this.userTotal.offline = this.userTotal.offline + 1;
+        }
+      }
+    },
+    blockUser(u){
+      this.$confirm('是否确认封禁用户账号为:"' + u.nickName + '-' + u.userId + '"?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return blockUser(u.userId);
+      }).then(() => {
+        if(u.msgStatus === 1){
+          this.userTotal.silenced -= 1
+          this.silencedDisplayList = this.silencedDisplayList.filter(user => user.userId !== u.userId)
+        } else if( u.online === 0){
+          this.userTotal.online -= 1
+          this.onlineDisplayList = this.onlineDisplayList.filter(user => user.userId !== u.userId)
+        } else {
+          this.userTotal.offline -= 1
+          this.offlineDisplayList = this.offlineDisplayList.filter(user => user.userId !== u.userId)
+        }
+        this.userTotal.al -= 1
+        this.alDisplayList = this.alDisplayList.filter(user => user.userId !== u.userId)
+        let msg = {
+          msg: "",
+          liveId: this.liveId,
+          userId: u.userId,
+          userType: 0,
+          cmd: 'blockUser',
+          avatar: this.$store.state.user.user.avatar,
+          nickName: this.$store.state.user.user.nickName
+        }
+        this.socket.send(JSON.stringify(msg))
+        this.msgSuccess("封禁成功");
+
+
+      }).catch(() => {});
+    },
+    searchUsers(){
+      this.resetUserParams()
+      this.loadNextPage(this.currentTab)
+    },
+    handleUserClick(tab){
+      const tabName = tab.name;
+      this.currentTab = tabName;
+      const params = this.pageParams[tabName];
+      const displayList = this[`${tabName}DisplayList`];
+      // 首次切换到该Tab或列表为空时初始化
+      if (displayList.length < 20) {
+        // 重置分页参数
+        params.currentPage = 1;
+        params.pageSize = 20;
+        params.prevPage = 0;
+        params.totalLoaded = 0;
+        params.hasMore = true;
+        params.hasPrev = false;
+        // 加载第一页
+        this.loadNextPage(tabName);
+      } else {
+        // 非首次切换,恢复滚动位置
+        this.$nextTick(() => {
+          const scrollEl = this.getScrollElement(tabName);
+          if (scrollEl) {
+            scrollEl.scrollTop = this.tabScrollPositions[tabName] || 0;
+          }
+        });
+      }
+    },
+    loadNextPage(tabName) {
+      const params = this.pageParams[tabName];
+      const displayList = this[`${tabName}DisplayList`];
+
+      // 若没有更多数据或正在加载,直接返回
+      if (!params.hasMore || this.scrLoading[tabName].next) {
+        return;
+      }
+
+      this.scrLoading[tabName].next = true;
+      const queryParams = {
+        liveId: this.liveId,
+        pageNum: params.currentPage,
+        pageSize: 20,
+        online: tabName === 'online' ? 0 : 1,
+        msgStatus: tabName === 'silenced' ? 1 : 0,
+        all: tabName === 'al' ? 1 : 0,
+        userName: this.searchKeyword
+      };
+      // 调用接口加载对应状态的分页数据(需后端支持按状态筛选)
+      dashBoardWatchUserList(queryParams).then(response => {
+        this.scrLoading[tabName].next = false;
+        if (response.code !== 200) return;
+
+        const { rows, total } = response;
+        params.total = total; // 记录总数据量
+        // 过滤重复数据(基于userId)
+        const newRows = rows.filter(row =>
+          !displayList.some(u => u.userId === row.userId)
+        );
+        displayList.push(...newRows)
+        // 添加新数据并限制最大长度(避免内存占用过大)
+        if (displayList.length >= 40) { // 最大保留100条
+          this[`${tabName}DisplayList`] = displayList.slice(-40);
+          // 记录滚动位置(用于加载后校准)
+          const scrollEl = this.getScrollElement(tabName);
+          // 校准滚动位置(保持视觉连续性)
+          this.$nextTick(() => {
+            if (scrollEl) {
+              scrollEl.scrollTop = scrollEl.scrollHeight * 0.5;
+            }
+          });
+        }
+        // 更新分页状态
+        params.hasMore = params.currentPage * params.pageSize < total;
+        params.currentPage += 1;
+        params.hasPrev = params.currentPage > 2; // 当前页>2时一定有上一页
+        params.prevPage = params.currentPage - 2;
+      }).catch(() => {
+        this.scrLoading[tabName].next = false;
+      });
+    },
+    // 新增:加载上一页(向上滚动时)
+    loadPrevPage(tabName) {
+      const params = this.pageParams[tabName];
+      const displayList = this[`${tabName}DisplayList`];
+      // 边界校验:无上一页/正在加载/当前页<=1
+      console.log(`加载 ${tabName} 上一页`);
+      console.log(!params.hasPrev || this.scrLoading[tabName].prev || params.currentPage <= 1)
+      if (!params.hasPrev || this.scrLoading[tabName].prev || params.currentPage <= 1) {
+        return;
+      }
+      this.scrLoading[tabName].prev = true;
+      const targetPage = params.prevPage > 0 ? params.prevPage : params.currentPage - 2;
+      const queryParams = {
+        liveId: this.liveId,
+        pageNum: targetPage,
+        pageSize: 20,
+        online: tabName === 'online' ? 0 : 1,
+        msgStatus: tabName === 'silenced' ? 1 : 0,
+        all: tabName === 'al' ? 1 : 0,
+        userName: this.searchKeyword
+      };
+      dashBoardWatchUserList(queryParams).then(response => {
+        this.scrLoading[tabName].prev = false;
+        if (response.code !== 200) return;
+
+        const { rows } = response;
+        if (rows.length === 0) {
+          params.hasPrev = false;
+          return;
+        }
+
+        // 记录滚动位置(用于加载后校准)
+        const scrollEl = this.getScrollElement(tabName);
+        const scrollTop = scrollEl?.scrollTop || 0;
+        const itemHeight = 80; // 预估行高(根据实际样式调整)
+        const newItemsHeight = rows.length * itemHeight;
+
+        // 过滤重复数据并添加到列表头部
+        const newRows = rows.filter(row => !displayList.some(u => u.userId === row.userId));
+        this[`${tabName}DisplayList`] = [...newRows, ...displayList];
+        params.totalLoaded += newRows.length;
+
+        // 限制最大长度
+        if (this[`${tabName}DisplayList`].length > 40) {
+          this[`${tabName}DisplayList`] = this[`${tabName}DisplayList`].slice(0, 40);
+        }
+
+        // 更新分页状态
+        params.prevPage = targetPage - 1;
+        params.hasPrev = targetPage > 1; // 上一页页码>1时还有更多上一页
+        params.currentPage = params.currentPage - 1;
+        if(params.currentPage * 20 < params.total) params.hasMore = true;
+        // 校准滚动位置(保持视觉连续性)
+        this.$nextTick(() => {
+          if (scrollEl) {
+            scrollEl.scrollTop = scrollEl.scrollHeight * 0.5;
+          }
+        });
+      }).catch(() => {
+        this.scrLoading[tabName].prev = false;
+      });
+    },
+    getList() {
+      this.resetUserParams()
+      this.resetMsgParams()
+      // this.loadUserList()
+      this.loadUserTotals(); // 先加载总人数
+      // this.handleClick('online')
+      // this.handleClick({name:'online'})
+      this.loadMsgList()
+      this.loadLiveTask()
+    },
+    formatDate(value) {
+      // 检查值是否存在且为日期类型(或可转换为日期)
+      if (!value) return '';
+
+      let date;
+      // 处理字符串类型的日期
+      if (typeof value === 'string') {
+        date = new Date(value);
+      }
+      // 处理Date对象
+      else if (value instanceof Date) {
+        date = value;
+      }
+      // 无效类型直接返回原值
+      else {
+        return value;
+      }
+
+      // 检查是否为有效日期
+      if (isNaN(date.getTime())) {
+        return value;
+      }
+
+      // 格式化年月日时分秒(补零处理)
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hours = String(date.getHours()).padStart(2, '0');
+      const minutes = String(date.getMinutes()).padStart(2, '0');
+      const seconds = String(date.getSeconds()).padStart(2, '0');
+
+      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+    },
+    loadLiveTask(){
+      if(!this.taskParams.hasMore) return
+      const queryParams = {
+        liveId: this.liveId,
+        pageSize: 10,
+        pageNum: 1
+      }
+      consoleList(queryParams).then(res => {
+        let {code, rows,total} = res;
+        if (code === 200) {
+          this.taskParams.total = total
+          this.timelineItems = rows
+          if(rows.length  < this.taskParams.pageSize){
+            this.taskParams.hasMore = false
+          }
+          this.startTaskTimer()
+        } else {
+          this.stopTaskTimer()
+        }
+      })
+    },
+    loadMsgList(){
+      // 直播间消息
+      listLiveSingleMsg({
+        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.alDisplayList.find(u => u.userId === row.userId)
+              if (user) {
+                row.msgStatus = user.msgStatus
+              } else {
+                row.msgStatus = 0
+              }
+              this.msgList.push(row)
+            }
+          })
+
+          this.msgList.reverse()
+          // 同步更新消息列表中相同用户的状态
+          this.alDisplayList.forEach(u => {
+            this.msgList.filter(m => m.userId === u.userId).forEach(m => m.msgStatus = u.msgStatus)
+          })
+          // 所有消息加载完成后,根据自动滚动状态决定是否滚动
+          this.$nextTick(() => {
+            setTimeout(() => {
+              // 重置加载状态
+              this.isLoadingLatest = false;
+
+              if (this.isAutoScrollEnabled) {
+                // 如果启用自动滚动,强制滚动到底部并隐藏按钮
+                this.scrollToBottom(true);
+              } else {
+                // 如果禁用自动滚动,检查是否在底部,决定是否显示按钮
+                if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+                  const wrap = this.$refs.manageRightRef.wrap;
+                  const scrollHeight = wrap.scrollHeight;
+                  const clientHeight = wrap.clientHeight;
+                  const currentScrollTop = wrap.scrollTop;
+                  const maxScrollTop = scrollHeight - clientHeight;
+
+                  if (currentScrollTop < maxScrollTop - 50) {
+                    this.showLoadLatestBtn = true;
+                  } else {
+                    this.showLoadLatestBtn = false;
+                  }
+                }
+              }
+            }, 300);
+          });
+        }
+      })
+
+      // 添加滚动监听
+      this.$nextTick(() => {
+        if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+          this.$refs.manageRightRef.wrap.addEventListener("scroll", this.manageRightScroll)
+        }
+      })
+    },
+    // 消息滚动监听(带防抖)
+    manageRightScroll() {
+      // 清除之前的防抖定时器
+      if (this.scrollDebounceTimer) {
+        clearTimeout(this.scrollDebounceTimer);
+      }
+
+      // 设置防抖,300ms内只执行一次
+      this.scrollDebounceTimer = setTimeout(() => {
+        this.saveChatScrollPosition();
+
+        // 检查是否滚动到底部
+        if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+          const wrap = this.$refs.manageRightRef.wrap;
+          const scrollHeight = wrap.scrollHeight;
+          const clientHeight = wrap.clientHeight;
+          const currentScrollTop = wrap.scrollTop;
+          const maxScrollTop = scrollHeight - clientHeight;
+
+          // 如果滚动到底部(距离底部小于50px),隐藏按钮并恢复自动滚动
+          if (currentScrollTop >= maxScrollTop - 50) {
+            this.showLoadLatestBtn = false;
+            this.isAutoScrollEnabled = true;
+          }
+        }
+      }, 300);
+    },
+    loadUserTotals() {
+      if (!this.liveId) return;
+      // 假设后端提供一个接口返回总人数(如果没有,可通过首次加载全量数据后统计)
+      getLiveUserTotals({ liveId: this.liveId }).then(res => {
+        if (res.code === 200) {
+          this.userTotal = res.data; // { online, offline, silenced }
+        }
+      });
+    },
+    toggleBlack(user) {
+      user.isBlack = !user.isBlack;
+    },
+    toggleMute(user) {
+      user.isMuted = !user.isMuted;
+    },
+    toggleVisible(msg) {
+      msg.isVisible = !msg.isVisible;
+    },
+    deleteMessage(msg) {
+      const index = this.messages.indexOf(msg);
+      if (index > -1) {
+        this.messages.splice(index, 1);
+      }
+    },
+    addTimelineItem() {
+      this.timelineItems.push({ time: '00:00', action: '新动作' });
+    },
+    removeTimelineItem(item) {
+      const index = this.timelineItems.indexOf(item);
+      if (index > -1) {
+        this.timelineItems.splice(index, 1);
+      }
+    },
+    resetUserParams() {
+      // 重置各Tab的显示列表和分页参数
+      this.alDisplayList = [];
+      this.onlineDisplayList = [];   // 在线用户显示列表
+      this.offlineDisplayList = [];   // 离线用户显示列表
+      this.silencedDisplayList = [];  // 禁言用户显示列表
+      this.pageParams=  {
+        al: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        },
+        online: {
+          currentPage: 1,       // 当前页(下一页加载用)
+          pageSize: 20,       // 当前页(下一页加载用)
+          prevPage: 0,          // 上一页页码(上一页加载用)
+          totalLoaded: 0,       // 已加载总条数
+          total: 0,             // 总数据量
+          hasMore: true,        // 是否有下一页
+          hasPrev: false        // 是否有上一页
+        },
+        offline: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        },
+        silenced: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        }
+      },
+        this.scrLoading = {
+          al: { next: false, prev: false },
+          online: { next: false, prev: false },
+          offline: { next: false, prev: false },
+          silenced: { next: false, prev: false }
+        }
+    },
+    resetMsgParams() {
+      // 消息参数保留
+      this.msgList = [];
+      this.msgParams = {
+        pageNum: 1,
+        pageSize: 30,
+        liveId: this.liveId
+      };
+      // 重置时不改变按钮状态,由后续的滚动逻辑决定
+      this.taskParams = {
+        currentPage: 1,
+        pageSize: 20,
+        prevPage: 0,
+        totalLoaded: 0,
+        total: 0,
+        hasMore: true,
+        hasPrev: false
+      }
+    },
+    getScrollElement(tabName) {
+      const scrollRefs = {
+        al: this.$refs.manageLeftRef_al,
+        online: this.$refs.manageLeftRef_online,
+        offline: this.$refs.manageLeftRef_offline,
+        silenced: this.$refs.manageLeftRef_silenced
+      };
+      return scrollRefs[tabName]?.wrap;
+    },
+    // 初始化滚动监听(在mounted中调用)
+    initScrollListeners() {
+      // 为每个Tab的滚动容器添加监听
+      this.$nextTick(() => {
+        const scrollRefs = {
+          al: this.$refs.manageLeftRef_al,
+          online: this.$refs.manageLeftRef_online,
+          offline: this.$refs.manageLeftRef_offline,
+          silenced: this.$refs.manageLeftRef_silenced
+        };
+
+        Object.keys(scrollRefs).forEach(tabName => {
+          const scrollEl = scrollRefs[tabName]?.wrap;
+          if (scrollEl) {
+            scrollEl.addEventListener('scroll', () =>
+              this.handleTabScroll(tabName, scrollEl)
+            );
+          }
+        });
+      });
+    },
+    // 处理Tab滚动事件(判断是否触底)
+    handleTabScroll(tabName, scrollEl) {
+      const { scrollTop, scrollHeight, clientHeight } = scrollEl;
+      const bottomThreshold = 50; // 距离底部100px触发下一页
+      const topThreshold = 50;    // 距离顶部100px触发上一页
+
+      // 加载下一页(滚动到底部附近)
+      if (scrollHeight - scrollTop - clientHeight < bottomThreshold) {
+        this.loadNextPage(tabName);
+      }
+
+      // 加载上一页(滚动到顶部附近)
+      if (scrollTop < topThreshold) {
+        this.loadPrevPage(tabName);
+      }
+    },
+    // 恢复聊天滚动位置
+    restoreChatScrollPosition() {
+      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+        this.$refs.manageRightRef.wrap.scrollTop = this.chatScrollTop;
+      }
+    },
+    // 保存聊天滚动位置
+    saveChatScrollPosition() {
+      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+        this.chatScrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight;
+      }
+    },
+    /**
+     * 停止任务检测定时器
+     */
+    stopTaskTimer() {
+      if (this.taskTimer) {
+        clearInterval(this.taskTimer);
+        this.taskTimer = null;
+      }
+    },
+    /**
+     * 启动任务检测定时器
+     */
+    startTaskTimer() {
+      // 先清除已有定时器,避免重复
+      if (this.taskTimer) {
+        clearInterval(this.taskTimer);
+      }
+
+      // 立即执行一次检查
+      this.checkTaskExpiration();
+
+      // 启动定时器,定期检查
+      this.taskTimer = setInterval(() => {
+        this.checkTaskExpiration();
+      }, this.checkInterval);
+    },
+    /**
+     * 检查时间轴第一条任务是否过期
+     */
+    checkTaskExpiration() {
+      // 如果没有任务,直接返回
+      if (!this.timelineItems || this.timelineItems.length === 0) {
+        this.stopTaskTimer()
+        return;
+      };
+
+      // 获取第一条任务的时间
+      const firstTask = this.timelineItems[0];
+      const taskTime = new Date(firstTask.absValue).getTime();
+      const currentTime = new Date().getTime();
+
+      // 如果任务时间已过当前时间(过期),重新加载任务列表
+      if (taskTime < currentTime) {
+        this.timelineItems.shift()
+        this.loadLiveTask(); // 重新加载任务列表
+      }
+    },
+  },
+  beforeDestroy() {
+    if (this.autoMsgTimer != null) {
+      clearInterval(this.autoMsgTimer);
+    }
+    if (this.autoScrollTimer) {
+      clearTimeout(this.autoScrollTimer);
+    }
+    if (this.scrollDebounceTimer) {
+      clearTimeout(this.scrollDebounceTimer);
+    }
+    // 移除滚动监听器
+    if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+      this.$refs.manageRightRef.wrap.removeEventListener("scroll", this.manageRightScroll);
+    }
+  },
+  // 使用 deactivated 和 activated 钩子替代 beforeDestroy 和 destroyed
+  deactivated() {
+    this.saveChatScrollPosition();
+  },
+  activated() {
+    this.$nextTick(() => {
+      this.restoreChatScrollPosition();
+    });
+    // todo yhq
+    // this.$nextTick(() => {
+    //   const video = this.$refs.videoPlayer;
+    //   if (video != null) {
+    //     this.initVideoPlayer(this.liveInfo.startTime)
+    //   }
+    // })
+  },
+};
+</script>
+
+<style scoped>
+.console {
+  display: flex;
+  height: 100vh;
+}
+
+.left-panel, .middle-panel, .right-panel {
+  padding: 20px;
+  box-sizing: border-box;
+}
+
+.left-panel {
+  width: 30%;
+  background: #f8fafc;
+  border-right: 1px solid #e2e8f0;
+}
+
+.middle-panel {
+  width: 40%;
+  background: #f8fafc;
+  border-right: 1px solid #e2e8f0;
+}
+
+.right-panel {
+  width: 30%;
+  background: #f8fafc;
+}
+
+.search {
+  margin: 10px 0;
+}
+
+.search input {
+  width: 70%;
+  padding: 8px;
+  border: 1px solid #cbd5e1;
+  border-radius: 4px;
+}
+
+.search button {
+  padding: 8px 15px;
+  background: #3b82f6;
+  color: #fff;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.tabs {
+  display: flex;
+  margin: 10px 0;
+}
+
+.tabs button {
+  padding: 8px 15px;
+  border: 1px solid #e2e8f0;
+  background: #fff;
+  cursor: pointer;
+}
+
+.tabs button.active {
+  background: #3b82f6;
+  color: #fff;
+  border-color: #3b82f6;
+}
+
+.user-list {
+  max-height: 600px;
+  overflow-y: auto;
+}
+
+.user-item {
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  border-bottom: 1px solid #e2e8f0;
+}
+
+.user-item img {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  margin-right: 10px;
+}
+
+.user-info {
+  flex: 1;
+}
+
+.user-name {
+  font-weight: bold;
+}
+
+.user-status {
+  font-size: 12px;
+  color: #64748b;
+}
+
+.online {
+  color: #10b981;
+}
+
+.offline {
+  color: #94a3b8;
+}
+
+.user-actions {
+  display: flex;
+}
+
+.user-actions button {
+  padding: 5px 10px;
+  margin-left: 5px;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.block {
+  background: #ef4444;
+  color: #fff;
+}
+
+.unblock {
+  background: #10b981;
+  color: #fff;
+}
+
+.mute {
+  background: #f59e0b;
+  color: #fff;
+}
+
+.unmute {
+  background: #3b82f6;
+  color: #fff;
+}
+
+.system-messages, .discussion-messages {
+  margin: 20px 0;
+  background: #fff;
+  padding: 15px;
+  border-radius: 8px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.system-messages textarea {
+  width: 100%;
+  height: 100px;
+  border: 1px solid #e2e8f0;
+  border-radius: 4px;
+  padding: 8px;
+  box-sizing: border-box;
+}
+
+.message-actions {
+  margin-top: 10px;
+}
+
+.message-actions button {
+  padding: 5px 10px;
+  margin-right: 5px;
+  background: #3b82f6;
+  color: #fff;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.message-list {
+  max-height: 300px;
+  overflow-y: auto;
+  margin-top: 10px;
+}
+
+.message-item {
+  display: flex;
+  margin-bottom: 10px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #e2e8f0;
+}
+
+.message-avatar img {
+  width: 30px;
+  height: 30px;
+  border-radius: 50%;
+  margin-right: 10px;
+}
+
+.message-content {
+  flex: 1;
+}
+
+.message-user {
+  font-weight: bold;
+}
+
+.message-text {
+  font-size: 14px;
+  color: #64748b;
+}
+
+.message-actions button {
+  padding: 3px 8px;
+  font-size: 12px;
+  background: #3b82f6;
+  color: #fff;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.live-player, .automation, .watermark {
+  margin: 20px 0;
+  background: #fff;
+  padding: 15px;
+  border-radius: 8px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.timeline-items {
+  margin: 10px 0;
+}
+
+.timeline-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 0;
+  border-bottom: 1px solid #e2e8f0;
+}
+
+.delete {
+  background: #ef4444;
+  color: #fff;
+  border: none;
+  border-radius: 4px;
+  padding: 3px 8px;
+  cursor: pointer;
+}
+
+.add {
+  background: #10b981;
+  color: #fff;
+  border: none;
+  border-radius: 4px;
+  padding: 8px 15px;
+  cursor: pointer;
+}
+
+.watermark-settings textarea {
+  width: 100%;
+  height: 100px;
+  border: 1px solid #e2e8f0;
+  border-radius: 4px;
+  padding: 8px;
+  box-sizing: border-box;
+}
+
+.watermark-options {
+  margin-top: 10px;
+}
+
+.watermark-options label {
+  display: block;
+  margin-bottom: 5px;
+}
+/* 隐藏 el-scrollbar 的横向滚动条 */
+.el-scrollbar__wrap {
+  overflow-x: hidden !important;
+}
+.custom-scrollbar .el-scrollbar__wrap {
+  overflow-x: hidden !important;
+}
+.scrollbar-demo-item{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 50px;
+  margin: 10px;
+  text-align: center;
+  border-radius: 4px;
+}
+.message-container {
+  position: relative;
+}
+.load-latest-btn {
+  position: absolute;
+  bottom: 20px;
+  right: 20px;
+  z-index: 10;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+</style>

+ 462 - 0
src/views/live/liveConsole/LiveDashboard.vue

@@ -0,0 +1,462 @@
+<template>
+  <div class="dashboard-container">
+    <!-- 实时大屏主体(100%占满父容器) -->
+    <div class="dashboard">
+      <!-- 顶部数据卡片区(占满宽度,高度20%) -->
+      <div class="data-cards">
+        <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>
+
+      <!-- 中间内容区(上下分两部分,各占40%高度) -->
+      <div class="middle-content">
+        <!-- 左侧用户分析区(占50%宽度) -->
+        <div class="user-analysis">
+          <div class="new-old">
+            <h3>直播新老用户占比</h3>
+            <EChartsComponent chartId="liveNewOldChart" :option="liveNewOldOption" />
+          </div>
+          <div class="region">
+            <h3>回放新老用户占比</h3>
+            <EChartsComponent chartId="replayNewOldChart" :option="replayNewOldOption" />
+          </div>
+        </div>
+
+        <!-- 右侧来源与榜单区(占50%宽度) -->
+        <div class="source-rank">
+          <div class="source">
+            <h3>观众来源</h3>
+            <EChartsComponent chartId="sourceChart" :option="sourceOption" />
+          </div>
+          <div class="rank">
+            <h3>邀请达人榜</h3>
+            <ul class="rank-list" v-if="rankList.length > 0">
+              <li v-for="(item, index) in rankList" :key="index">
+                <span class="rank-num">{{ index + 1 }}</span>
+                <span class="rank-name">{{ item.userName }}</span>
+                <span class="rank-count">{{ item.inviteNum }}</span>
+              </li>
+            </ul>
+            <p class="no-data" style="text-align: center;" v-if="rankList.length == 0">暂无数据</p>
+          </div>
+        </div>
+      </div>
+
+      <!-- 底部趋势图(占20%高度) -->
+      <!--      <div class="trend">-->
+      <!--        <h3>实时在线人数趋势</h3>-->
+      <!--        <EChartsComponent chartId="trendChart" :option="trendOption" />-->
+      <!--      </div>-->
+    </div>
+  </div>
+</template>
+
+<script>
+import EChartsComponent from './EchartsComponent.vue';
+import {dashboardData} from '@/api/live/liveData'
+
+export default {
+  components: { EChartsComponent },
+  props: {
+    liveId: {
+      type: String,
+      default: null
+    }
+  },
+  data() {
+    return {
+      // 数据保持不变(与原代码一致)
+      dataCards: [
+        { 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 }
+        }
+      ],
+      liveNewOldOption: {
+        tooltip: { trigger: 'item' },
+        series: [{
+          type: 'pie', radius: '70%',
+          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'] }
+        }]
+      },
+      regionOption: {
+        tooltip: { trigger: 'item' },
+        series: [{
+          type: 'map', map: 'china', roam: true,
+          itemStyle: {
+            normal: { areaColor: '#36cfc9', borderColor: '#fff' },
+            emphasis: { areaColor: '#722ed1' }
+          },
+          data: [{ name: '北京', value: 120 }, { name: '上海', value: 150 }, { name: '广州', value: 90 }, { name: '深圳', value: 80 }, { name: '杭州', value: 70 }]
+        }]
+      },
+      sourceOption: {
+        tooltip: { trigger: 'item' },
+        series: [{
+          type: 'pie', radius: '70%',
+          data: [{ value: 45, name: '分享链接' }, { value: 30, name: '直接访问' }, { value: 25, name: '其他' }],
+          itemStyle: { colors: ['#3b82f6', '#10b981', '#f59e0b'] }
+        }]
+      },
+      rankList: [
+        { userName: '达人1', inviteNum: 258 },
+        { userName: '达人2', inviteNum: 186 },
+        { userName: '达人3', inviteNum: 152 },
+        { userName: '达人4', inviteNum: 121 },
+        { userName: '达人5', inviteNum: 98 }
+      ],
+      trendOption: {
+        // 网格:控制图表与容器的边距,避免内容被裁剪
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          top: '10%',
+          containLabel: true // 确保坐标轴标签不被裁剪
+        },
+        // X轴配置(补充样式,适配深色背景)
+        xAxis: {
+          type: 'category',
+          data: ['09:00', '09:10', '09:20', '09:30', '09:40', '09:50'],
+          axisLine: {
+            lineStyle: {
+              color: '#475569' // 坐标轴线条颜色(深色背景可见)
+            }
+          },
+          axisLabel: {
+            color: '#cbd5e1' // 坐标轴文字颜色
+          }
+        },
+        // Y轴配置(同上)
+        yAxis: {
+          type: 'value',
+          axisLine: {
+            lineStyle: {
+              color: '#475569'
+            }
+          },
+          axisLabel: {
+            color: '#cbd5e1'
+          },
+          splitLine: {
+            lineStyle: {
+              color: 'rgba(71, 85, 105, 0.3)' // 网格线半透明
+            }
+          }
+        },
+        // 系列配置(补充线条和区域样式)
+        series: [{
+          data: [1200, 1500, 1800, 2100, 2300, 2500],
+          type: 'line',
+          smooth: true,
+          // 线条样式
+          lineStyle: {
+            color: '#36cfc9',
+            width: 3
+          },
+          // 点样式
+          itemStyle: {
+            color: '#36cfc9',
+            borderWidth: 2,
+            borderColor: '#fff' // 点边缘白色,突出显示
+          },
+          // 填充区域(可选,增强视觉效果)
+          areaStyle: {
+            color: {
+              type: 'linear',
+              x: 0,
+              y: 0,
+              x2: 0,
+              y2: 1,
+              colorStops: [{
+                offset: 0, color: 'rgba(54, 207, 201, 0.3)'
+              }, {
+                offset: 1, color: 'rgba(54, 207, 201, 0)'
+              }]
+            }
+          }
+        }]
+      },
+      socket:null,
+      timer: null
+    };
+  },
+  created() {
+    this.getInitData()
+    this.timer = setInterval(() => {
+      this.getInitData();
+    }, 30000);
+  },
+  beforeDestroy() {
+    clearInterval(this.timer); // 组件销毁时清除定时器
+  },
+  methods: {
+    async getInitData() {
+      dashboardData(this.liveId).then(res => {
+        if(res.code == 200){
+          this.dealData(res.data)
+        }
+      });
+    },
+    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 || 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.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.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 || 0, name: '分享链接' }, 
+            { value: data.directAccessNum || 0, name: '直接访问' }
+          ],
+          itemStyle: { colors: ['#3b82f6', '#10b981'] }
+        }]
+      }
+      
+      // 邀请用户列表
+      this.rankList = data.inviteUserList || [];
+    },
+  }
+};
+</script>
+
+<style scoped>
+/* 容器占满整个屏幕 */
+.dashboard-container {
+  width: 100vw;
+  height: 92vh;
+  overflow: hidden;
+  background-color: #0f172a;
+}
+
+/* 大屏主体:100%宽高,内边距用百分比 */
+.dashboard {
+  width: 100%;
+  height: 100%;
+  padding: 1%; /* 用百分比内边距,适配不同屏幕 */
+  box-sizing: border-box;
+  color: #fff;
+  display: flex;
+  flex-direction: column;
+  gap: 1%; /* 区域间间距 */
+}
+
+/* 顶部数据卡片区:高度20%,横向排列 */
+.data-cards {
+  width: 100%;
+  height: 20%;
+  display: flex;
+  justify-content: space-between;
+  gap: 1%; /* 卡片间间距 */
+}
+
+/* 每个卡片:平均分配宽度(4个卡片各占 ~24%) */
+.card {
+  width: 24%; /* 100% - 3个间距(1%) = 97% → 97%/4 ≈ 24% */
+  height: 100%;
+  background: #1a202c;
+  border-radius: 8px;
+  padding: 1%;
+  box-sizing: border-box;
+  text-align: center;
+}
+
+.card h3 {
+  font-size: 1.2vw; /* 字体用vw单位,随屏幕宽度缩放 */
+  margin: 0 0 5% 0;
+}
+
+.card .value {
+  font-size: 2vw;
+  font-weight: bold;
+  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%;
+  height: 70%;
+  display: flex;
+  gap: 1%;
+}
+
+/* 左侧用户分析区:占50%宽度,内部上下分栏 */
+.user-analysis {
+  width: 50%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 1%;
+}
+
+/* 右侧来源与榜单区:占50%宽度,内部上下分栏 */
+.source-rank {
+  width: 50%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 1%;
+}
+
+/* 中间区域的子模块(新老用户/地域/来源/榜单):各占50%高度 */
+.new-old, .region, .source, .rank {
+  width: 100%;
+  height: 60.5%; /* 100% - 1个间距(1%) = 99% → 99%/2 ≈ 49.5% */
+  background: #1a202c;
+  border-radius: 8px;
+  padding: 1%;
+  box-sizing: border-box;
+}
+
+/* 标题样式:用vw单位适配 */
+.new-old h3, .region h3, .source h3, .rank h3, .trend h3 {
+  font-size: 1.1vw;
+  margin: 0 0 2% 0;
+}
+
+/* 达人榜单列表 */
+.rank-list {
+  list-style: none;
+  padding: 0;
+  height: calc(100% - 15%); /* 减去标题高度 */
+  overflow-y: auto;
+}
+
+.rank-list li {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 2%;
+  margin-bottom: 1%;
+  border-bottom: 1px solid #2d3748;
+  font-size: 1vw;
+}
+
+.rank-num {
+  width: 3vw;
+  height: 3vw;
+  max-width: 30px;
+  max-height: 30px;
+  border-radius: 50%;
+  background: #36cfc9;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 0.8vw;
+}
+
+/* 底部趋势图:高度20% */
+.trend {
+  width: 100%;
+  height: 20%;
+  background: #1a202c;
+  border-radius: 8px;
+  padding: 1%;
+  box-sizing: border-box;
+}
+
+/* ECharts图表容器:占满父元素 */
+::v-deep .chart-container {
+  width: 100% !important;
+  height: calc(100% - 15%) !important; /* 减去标题高度 */
+}
+</style>

+ 257 - 0
src/views/live/liveConsole/LivePlayer.vue

@@ -0,0 +1,257 @@
+<template>
+  <div class="live-player">
+    <!-- 修改:所有录播和回放都使用同一个video元素,MP4和HLS都支持 -->
+    <video
+      v-if="videoParam.liveType == 2 || videoParam.liveType == 3"
+      ref="videoPlayer"
+      :loop="videoParam.liveType == 2"
+      autoplay
+      class="player"
+    >
+      <!-- 去掉type,让浏览器自动检测 -->
+    </video>
+
+    <video
+      v-if="videoParam.liveType == 1"
+      ref="livePlayer"
+      autoplay
+      class="player"
+    >
+    </video>
+  </div>
+</template>
+
+<script>
+import Hls from "hls.js";
+
+export default {
+  name: 'LivePlayer',
+  props: {
+    videoParam: {
+      type: Object,
+      required: true,
+      default: () => ({
+        startTime: '',
+        livingUrl: '',
+        liveType: 1,
+        videoUrl: ''
+      }),
+    }
+  },
+  data() {
+    return {
+      videoDuration: 0,
+      startTime: 0,
+      hls: null,
+      liveHls: null // 分开管理直播和录播的HLS实例
+    };
+  },
+  mounted() {
+    this.initPlayer();
+  },
+  methods: {
+    videoPlay(url) {
+      const videoElement = this.$refs.videoPlayer;
+      if (!videoElement) {
+        console.error('找不到 video 元素');
+        return;
+      }
+
+      // 判断是否是MP4格式
+      const isMp4 = url.toLowerCase().endsWith('.mp4') ||
+        url.toLowerCase().includes('.mp4?');
+
+      // 判断是否是HLS格式(m3u8)
+      const isHls = url.toLowerCase().endsWith('.m3u8') ||
+        url.toLowerCase().includes('.m3u8?');
+
+      if (isHls && Hls.isSupported()) {
+        // HLS格式使用hls.js播放
+        this.hls = new Hls();
+        this.hls.attachMedia(videoElement);
+        this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
+          this.hls.loadSource(url);
+          this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
+            videoElement.play();
+          });
+        });
+        this.hls.on(Hls.Events.ERROR, (event, data) => {
+          console.error('HLS 错误:', data);
+        });
+
+        // 录播的时间位置计算
+        if (this.videoParam.liveType === 2) {
+          videoElement.addEventListener('loadedmetadata', () => {
+            this.videoDuration = videoElement.duration;
+            this.updateVideoPosition(videoElement);
+          });
+        }
+
+      } else if (isMp4 || !isHls) {
+        // MP4格式或非HLS格式直接使用video元素播放
+        videoElement.src = url;
+
+        // 录播的时间位置计算
+        if (this.videoParam.liveType === 2) {
+          videoElement.addEventListener('loadedmetadata', () => {
+            this.videoDuration = videoElement.duration;
+            this.updateVideoPosition(videoElement);
+          });
+        }
+
+        // 触发播放
+        videoElement.load();
+        videoElement.play().catch(e => {
+          console.warn('自动播放失败:', e);
+        });
+
+      } else {
+        // 不支持HLS的浏览器,尝试直接播放
+        console.warn('浏览器不支持HLS,尝试直接播放');
+        videoElement.src = url;
+        videoElement.load();
+        videoElement.play().catch(e => {
+          console.warn('播放失败:', e);
+        });
+      }
+    },
+
+    updateVideoPosition(video) {
+      const currentTime = new Date().getTime();
+      this.startTime = new Date(this.videoParam.startTime).getTime();
+      const elapsedTime = currentTime - this.startTime;
+
+      if (elapsedTime < 0) {
+        video.currentTime = 0;
+        return;
+      }
+
+      const elapsedSeconds = elapsedTime / 1000;
+      if (this.videoDuration > 0) {
+        video.currentTime = elapsedSeconds % this.videoDuration;
+      }
+    },
+
+    livePlay(url) {
+      if (Hls.isSupported()) {
+        const videoElement = this.$refs.livePlayer;
+        if (!videoElement) {
+          console.error('找不到 video 元素');
+          return;
+        }
+
+        this.liveHls = new Hls();
+        this.liveHls.attachMedia(videoElement);
+        this.liveHls.on(Hls.Events.MEDIA_ATTACHED, () => {
+          this.liveHls.loadSource(url);
+          this.liveHls.on(Hls.Events.STREAM_LOADED, (event, data) => {
+            videoElement.play();
+          });
+        });
+        this.liveHls.on(Hls.Events.ERROR, (event, data) => {
+          console.error('HLS 错误:', data);
+        });
+
+      } else {
+        // 浏览器原生支持HLS(如Safari)
+        const videoElement = this.$refs.livePlayer;
+        if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
+          videoElement.src = url;
+          videoElement.play().catch(e => {
+            console.warn('自动播放失败:', e);
+          });
+        } else {
+          console.error('浏览器不支持 HLS');
+        }
+      }
+    },
+
+    initPlayer() {
+      // 清理之前的实例
+      if (this.hls) {
+        this.hls.destroy();
+        this.hls = null;
+      }
+      if (this.liveHls) {
+        this.liveHls.destroy();
+        this.liveHls = null;
+      }
+
+      if (this.videoParam.liveType === 1) {
+        // 直播
+        const isUrl = this.videoParam.livingUrl === null || this.videoParam.livingUrl.trim() === '';
+        if (isUrl) {
+          console.error('直播地址为空,无法初始化播放器');
+          return;
+        }
+        this.$nextTick(() => {
+          this.livePlay(this.videoParam.livingUrl);
+        });
+        return;
+      }
+
+      const viUrl = this.videoParam.videoUrl === null || this.videoParam.videoUrl.trim() === '';
+      if (viUrl) {
+        console.error('播放地址为空,无法初始化播放器');
+        return;
+      }
+
+      if (this.videoParam.liveType === 2 || this.videoParam.liveType === 3) {
+        // 录播或直播回放
+        this.$nextTick(() => {
+          this.videoPlay(this.videoParam.videoUrl);
+        });
+      } else {
+        console.error('直播类型错误,无法初始化播放器');
+      }
+    }
+  },
+
+  watch: {
+    // 监听videoParam变化,重新初始化播放器
+    videoParam: {
+      handler() {
+        this.$nextTick(() => {
+          this.initPlayer();
+        });
+      },
+      deep: true
+    }
+  },
+
+  beforeDestroy() {
+    // 销毁HLS实例
+    this.hls?.destroy();
+    this.liveHls?.destroy();
+
+    // 清理video元素
+    const videoElements = [
+      this.$refs.videoPlayer,
+      this.$refs.livePlayer
+    ];
+
+    videoElements.forEach(video => {
+      if (video) {
+        video.pause();
+        video.src = '';
+        video.load();
+      }
+    });
+  }
+};
+</script>
+
+<style scoped>
+.live-player {
+  margin-bottom: 20px;
+}
+
+.player {
+  width: 100%;
+  height: auto;
+  border-radius: 8px;
+  max-height: 300px; /* 设置最大高度,避免过大 */
+  object-fit: contain; /* 保持比例,不拉伸 */
+  background-color: #000; /* 黑色背景,避免视频加载时显示白色 */
+}
+</style>

+ 55 - 0
src/views/live/liveConsole/ScreenScale.vue

@@ -0,0 +1,55 @@
+<template>
+  <div class="container">
+    <div ref="scaleContainer" class="scale-content">
+      <slot />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ScreenScale',
+  mounted() {
+    this.calculateRatio();
+    window.addEventListener('resize', this.calculateRatio);
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.calculateRatio);
+  },
+  methods: {
+    calculateRatio() {
+      const baseWidth = 1920;
+      const baseHeight = 1080;
+      const el = this.$refs.scaleContainer;
+      // 基于父容器宽高计算缩放比(而非整个窗口)
+      const parentWidth = el.parentElement.clientWidth;
+      const parentHeight = el.parentElement.clientHeight;
+      const widthRatio = parentWidth / baseWidth;
+      const heightRatio = parentHeight / baseHeight;
+      const scale = Math.min(widthRatio, heightRatio); // 取最小缩放比防止溢出
+
+      el.style.transform = `scale(${scale}) translate(-50%, -50%)`;
+      el.style.transformOrigin = 'top left';
+    }
+  }
+};
+</script>
+
+<style scoped>
+.container {
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  justify-content: center;
+  align-items: flex-start;
+  overflow: hidden;
+  background-color: #0f172a;
+}
+
+.scale-content {
+  position: relative;
+  width: 1920px;
+  height: 1080px;
+  transform-origin: top left;
+}
+</style>

+ 1289 - 0
src/views/live/liveConsole/index-backup.vue

@@ -0,0 +1,1289 @@
+<template>
+  <div>
+  <!-- 直播中控台 start -->
+  <el-row type="flex" justify="center" class="live-console" :gutter="10" v-loading="loading">>
+    <!-- 聊天 start -->
+    <el-col class="live-console-col" :span="6">
+      <el-tabs class="live-console-tab-left" v-model="tabRight.activeName" @tab-click="handleClick" :stretch="true">
+        <el-tab-pane label="讨论" name="talk">
+          <el-scrollbar style="height: 500px; width: 100%;" ref="manageRightRef">
+            <el-row v-for="m in msgList" :key="m.msgId">
+              <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>
+                      <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="blockUser(m)">拉黑</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 -->
+
+
+    <!-- 直播/视频 start -->
+    <el-col class="live-console-col" :span="12">
+      <div style="background: #000; border-radius: 5px; overflow: hidden; margin: 10px 5px;">
+        <div style="border-radius: 5px; overflow: hidden;" v-if="!isAudit">
+          <img :src="require('@/assets/images/videoIsAudit.png')" style="width: 100%; height: 45vh;">
+        </div>
+        <div style="border-radius: 5px; overflow: hidden;" v-else-if="status != 2 && status != 4">
+          <img :src="require('@/assets/images/videoNotStart.png')" style="width: 100%; height: 45vh;">
+        </div>
+        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 1">
+          <video
+            controls
+            ref="livingPlayer"
+            autoplay
+            width="100%"
+            @click.prevent
+            @contextmenu.prevent
+            class="custom-video"
+            style="display: block; background: #000; height: 45vh;"
+          ></video>
+          <!-- 时间显示(可选) -->
+          <div ref="liveElapsedTime" class="time-display">
+            已播放:<span id="liveElapsedTime">00:00:00</span>
+          </div>
+        </div>
+        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 2">
+          <video
+            controls
+            ref="videoPlayer"
+            loop
+            autoplay
+            width="100%"
+            muted
+            playsinline
+            @click.prevent
+            @contextmenu.prevent
+            class="custom-video"
+            style="display: block; background: #000; height: 40vh;"
+          >
+            <source :src="videoUrl" type="application/x-mpegURL">
+          </video>
+          <!-- 自定义进度条容器 -->
+          <div ref="progressBar" class="progress-container">
+            <div id="progressBar" class="progress-bar"></div>
+          </div>
+
+          <!-- 时间显示(可选) -->
+          <div ref="elapsedTime" class="time-display">
+            已播放:<span id="elapsedTime">00:00:00</span>
+          </div>
+        </div>
+        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 3">
+          <video
+            controls
+            ref="liveReplay"
+            loop
+            autoplay
+            width="100%"
+            playsinline
+            style="display: block; background: #000; height: 40vh;"
+          >
+            <source :src="videoUrl" type="application/x-mpegURL">
+          </video>
+        </div>
+        <div style="border-radius: 5px; overflow: hidden;" v-else>
+          <img :src="require('@/assets/images/videoNotStart.png')" style="width: 100%; height: 45vh;">
+        </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;" @click="handleClickRed">
+          <i class="el-icon-money" 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;" @click="handleClickLottery">
+          <i class="el-icon-present" 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;" @click="handleClickGoods">
+          <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;" @click="handleClickOrder">
+          <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;" @click="handleClickCoupon">
+          <i class="el-icon-goods" style="font-size: 20px;"></i>
+          <span style="font-size: 12px; margin-top: 4px;">直播优惠券</span>
+        </div>
+      </div>
+      <el-radio-group v-model="tableRadio" >
+        <el-radio-button label="订单数">订单数</el-radio-button>
+      </el-radio-group>
+      <div  style="position: relative;width: 100%; height: 300px;">
+        <div ref="chartContainer" style="width: 100%; height: 100%;"></div>
+        <div style="position: absolute; top: 10px; right: 10px; background: #fff; padding: 5px; z-index: 1;">
+          <el-select v-model="searchQuery.timeOptions" placeholder="请选择" style="width: 150px"  @change="timeChange">
+            <el-option
+              v-for="item in timeOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value">
+            </el-option>
+          </el-select>
+          <el-select v-model="searchQuery.timeGranularity" placeholder="请选择" style="width: 150px"  @change="timeGranularityChange">
+            <el-option
+              v-for="item in timeGranularity"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value">
+            </el-option>
+          </el-select>
+          <!--          <el-button type="primary" @click="applyFilter">搜索</el-button>-->
+        </div>
+      </div>
+    </el-col>
+    <!-- 直播/视频 end -->
+
+    <!-- 用户列表 start -->
+    <el-col class="live-console-col" :span="6">
+      <el-tabs class="live-console-tab-right" v-model="tabLeft.activeName" @tab-click="handleClick" :stretch="true">
+        <el-tab-pane :label="onlineLabel" name="online">
+          <el-scrollbar ref="manageLeftRef_online" style="height: 800px; width: 100%;">
+            <el-row style="margin-top: 10px" type="flex" align="middle" v-for="u in onlineDisplayList" :key="u.userId">
+              <el-col :span="20">
+                <el-row type="flex" align="middle">
+                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
+                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
+                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
+                  <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 offlineDisplayList" :key="u.userId">
+              <el-col :span="20">
+                <el-row type="flex" align="middle">
+                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
+                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
+                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
+                  <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 silencedDisplayList" :key="u.userId">
+              <el-col :span="20">
+                <el-row type="flex" align="middle">
+                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
+                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
+                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
+                </el-row>
+              </el-col>
+              <el-col :span="4" >
+                <el-popover
+                  width="100"
+                  trigger="click">
+                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
+                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
+                  <i class="el-icon-more" slot="reference"></i>
+                </el-popover>
+              </el-col>
+            </el-row>
+          </el-scrollbar>
+        </el-tab-pane>
+      </el-tabs>
+    </el-col>
+    <!-- 用户列表 end -->
+  </el-row>
+  <!-- 直播中控台  end -->
+  </div>
+</template>
+
+<script>
+import { blockUser,changeUserStatus,getLiveUserTotals, watchUserList } from '@/api/live/liveWatchUser'
+import { getLiveVideoByLiveId } from '@/api/live/liveVideo'
+import {getLive, getLivingUrl} from '@/api/live/live'
+import { getLiveOrderTimeGranularity } from '@/api/live/liveOrder'
+import { listLiveMsg } from '@/api/live/liveMsg'
+import Hls from 'hls.js';
+import LiveLotteryConf from '@/views/live/liveConfig/liveLotteryConf.vue'
+import LiveRedConf from '@/views/live/liveConfig/liveRedConf.vue'
+import LiveGoods from '@/views/live/liveConfig/goods.vue'
+import LiveLiveCoupon from '@/views/live/liveConfig/liveCoupon.vue'
+import echarts from 'echarts'
+
+export default {
+  name: "LiveConsole",
+  components: { LiveLotteryConf,LiveRedConf },
+  data() {
+    return {
+      loading:true,
+      tabLeft: {
+        activeName: "online",
+      },
+      tabRight: {
+        activeName: "talk",
+      },
+      livingUrl:"",
+      videoUrl: "",
+      status: 0,
+      loadMsgMaxPage: 2,
+      liveVideo: {},
+      liveWsUrl: process.env.VUE_APP_LIVE_WS_URL + '/app/webSocket',
+      userParams:{
+        pageNum: 1,
+        pageSize: 10,
+        liveId: null
+      },
+      msgParams: {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: null
+      },
+      userList: [],
+      msgList: [],
+      newMsg: '',
+      isAudit: false,
+      myChart: null, // 用于存储 ECharts 实例
+      liveType: 1,
+      tableRadio: '订单数',
+      searchQuery: {timeOptions:'2',timeGranularity:'10',liveId: null},
+      timeOptions: [
+        {value:'2',label:'最近2小时',key:'2'},
+        {value:'4',label:'最近4小时',key:'4'},
+        {value:'all',label:'全场',key:'all'},
+      ],
+      timeGranularity: [
+        {value:'10',label:'10分钟',key:'10'},
+        {value:'30',label:'30分钟',key:'30'},
+        {value:'60',label:'1小时',key:'60'},
+      ],
+      videoDuration: 0,
+      startTime: null,
+      processInterval: null,
+      // ... 其他数据
+      chatScrollTop: 0, // 保存聊天滚动位置
+      socket: null,
+      userTotal: {
+        online: 0,       // 在线总人数
+        offline: 0,      // 离线总人数
+        silenced: 0      // 禁言总人数
+      },
+      // 各Tab的显示列表(仅存储当前需要展示的数据)
+      onlineDisplayList: [],    // 在线用户显示列表
+      offlineDisplayList: [],   // 离线用户显示列表
+      silencedDisplayList: [],  // 禁言用户显示列表
+      // 各Tab的分页参数
+      pageParams: {
+        online: {
+          currentPage: 1,       // 当前页(下一页加载用)
+          pageSize: 20,       // 当前页(下一页加载用)
+          prevPage: 0,          // 上一页页码(上一页加载用)
+          totalLoaded: 0,       // 已加载总条数
+          total: 0,             // 总数据量
+          hasMore: true,        // 是否有下一页
+          hasPrev: false        // 是否有上一页
+        },
+        offline: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        },
+        silenced: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        }
+      },
+      scrLoading: {
+        online: { next: false, prev: false },
+        offline: { next: false, prev: false },
+        silenced: { next: false, prev: false }
+      }
+    }
+  },
+  created() {
+    // this.getLiveVideo()
+    this.getList()
+    this.connectWebSocket()
+    this.getLive()
+    this.searchQuery.liveId = this.liveId
+  },
+  computed: {
+    liveId() {
+      return this.$route.params.liveId;
+    },
+    userId() {
+      return this.$store.state.user.user.userId
+    },
+    companyId() {
+      return this.$store.state.user.user.companyId
+    },
+    onlineLabel() {
+      return `在线(${this.userTotal.online})`;
+    },
+    offlineLabel() {
+      return `离线(${this.userTotal.offline})`;
+    },
+    silencedUserLabel() {
+      return `禁言(${this.userTotal.silenced})`;
+    }
+  },
+  mounted() {
+
+    this.$nextTick(() => {
+      this.restoreChatScrollPosition();
+    });
+    this.getEchartsTables();
+    // 添加滚动事件监听器
+    this.$nextTick(() => {
+      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+        this.$refs.manageRightRef.wrap.addEventListener('scroll', this.saveChatScrollPosition);
+      }
+    });
+    this.initScrollListeners();
+  },
+  beforeDestroy() {
+    this.saveTabScrollPositions()
+    // 移除滚动监听(避免内存泄漏)
+    const scrollRefs = {
+      online: this.$refs.manageLeftRef_online,
+      offline: this.$refs.manageLeftRef_offline,
+      silenced: this.$refs.manageLeftRef_silenced
+    };
+    Object.keys(scrollRefs).forEach(tabName => {
+      const scrollEl = scrollRefs[tabName]?.wrap;
+      if (scrollEl) {
+        scrollEl.removeEventListener('scroll', () =>
+          this.handleTabScroll(tabName, scrollEl)
+        );
+      }
+    })
+  },
+  // 使用 deactivated 和 activated 钩子替代 beforeDestroy 和 destroyed
+  deactivated() {
+    this.saveChatScrollPosition();
+  },
+
+  activated() {
+    this.$nextTick(() => {
+      this.restoreChatScrollPosition();
+    });
+    this.$nextTick(() => {
+      const video = this.$refs.videoPlayer;
+      if (video != null) {
+        this.initVideoPlayer(this.liveInfo.startTime)
+      }
+    })
+  },
+  methods: {
+    updateVideoPosition(video){
+      const currentTime = new Date().getTime(); // 当前时间戳(毫秒)
+      const elapsedTime = currentTime - this.startTime; // 已流逝时间(毫秒)
+
+      if (elapsedTime < 0) {
+        // 未开播:视频停在初始位置
+        video.currentTime = 0;
+        return;
+      }
+
+      // 已开播:计算视频循环后的位置(流逝时间 % 视频时长)
+      const elapsedSeconds = elapsedTime / 1000; // 转换为秒
+      const videoPosition = elapsedSeconds % this.videoDuration; // 视频内的播放位置(秒)
+
+      // 设置视频播放位置
+      video.currentTime = videoPosition;
+    },
+    updateProgress() {
+      const progressBar = this.$refs.progressBar;
+      const elapsedTimeEl = this.$refs.elapsedTime;
+      if (!this.videoDuration) return; // 视频时长未加载时不更新
+
+      const currentTime = new Date().getTime();
+      const elapsedTime = currentTime - this.startTime; // 总流逝时间(毫秒)
+
+      if (elapsedTime < 0) {
+        // 未开播状态
+        progressBar.style.width = '0%';
+        elapsedTimeEl.textContent = '00:00:00';
+        return;
+      }
+
+      // 计算进度百分比(基于视频循环)
+      const elapsedSeconds = elapsedTime / 1000;
+      const videoPosition = elapsedSeconds % this.videoDuration; // 当前在视频中的位置
+      const progressPercent = (videoPosition / this.videoDuration) * 100; // 进度百分比
+
+      // 更新进度条宽度
+      progressBar.style.width = `${progressPercent}%`;
+
+      // 格式化总流逝时间为“时:分:秒”并显示
+      elapsedTimeEl.textContent = this.formatTime(elapsedTime);
+    },
+    formatTime(ms) {
+      const totalSeconds = Math.floor(ms / 1000);
+      const hours = Math.floor(totalSeconds / 3600);
+      const minutes = Math.floor((totalSeconds % 3600) / 60);
+      const seconds = totalSeconds % 60;
+
+      // 补零处理(确保两位数)
+      return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+    },
+    initVideoPlayer: function (startTime) {
+      const video = this.$refs.videoPlayer;
+      this.hls = new Hls();
+      this.hls.attachMedia(video);
+      this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
+        this.hls.loadSource(this.videoUrl);
+        this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
+          video.play();
+        });
+      });
+      this.hls.on(Hls.Events.ERROR, (event, data) => {
+        console.error('HLS 错误:', data);
+      });
+
+      // 1. 初始化开播时间
+      startTime = new Date(startTime).getTime();
+      this.startTime = startTime;
+      // 2. 监听视频元数据加载完成(获取视频时长)
+      video.addEventListener('loadedmetadata', () => {
+        this.videoDuration = video.duration; // 获取视频时长(秒)
+
+        // 初始化视频播放位置
+        this.updateVideoPosition(video);
+
+        // 启动实时进度更新(每秒刷新一次)
+        this.processInterval = setInterval(this.updateProgress, 1000);
+      });
+
+    },
+    // 保存聊天滚动位置
+    saveChatScrollPosition() {
+      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+        this.chatScrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight;
+      }
+    },
+
+    // 恢复聊天滚动位置
+    restoreChatScrollPosition() {
+      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
+        this.$refs.manageRightRef.wrap.scrollTop = this.chatScrollTop;
+      }
+    },
+    // ... 其他方法 ...
+    timeChange(val) {
+      this.searchQuery.timeOptions = val
+      this.getEchartsTables(this.searchQuery)
+      this.initChart()
+    },
+    timeGranularityChange(val) {
+      this.searchQuery.timeGranularity =  val
+      this.getEchartsTables()
+      this.initChart()
+    },
+    getEchartsTables() {
+      getLiveOrderTimeGranularity(this.searchQuery).then(res => {
+        if (res.code == 200) {
+          this.echartsXLine = res.hourlySlots
+          this.echartsXValue = res.hourlySlotsValue
+          this.initChart()
+        }
+      })
+    },
+    initChart() {
+      const chartDom = this.$refs.chartContainer;
+      this.myChart = echarts.init(chartDom);
+      const option = {
+        tooltip: {trigger: 'axis'},
+        legend: {data: ['订单数']},
+        xAxis: {type: 'category', boundaryGap: false, data: this.echartsXLine},
+        yAxis: {type: 'value'},
+        series: [
+          {name: '订单数', type: 'line', data: this.echartsXValue}
+        ],
+      };
+      this.myChart.setOption(option);
+    },
+    handleClickGoods(){
+      this.$router.push({
+        name: 'LiveGoods',
+        query: {
+          liveId: this.liveId
+        }
+      })
+    },
+    updateLiveProgress() {
+      const elapsedTimeEl = this.$refs.liveElapsedTime;
+
+      const currentTime = new Date().getTime();
+      const elapsedTime = currentTime - this.startTime; // 总流逝时间(毫秒)
+
+      if (elapsedTime < 0) {
+        elapsedTimeEl.textContent = '00:00:00';
+        return;
+      }
+
+
+      // 格式化总流逝时间为“时:分:秒”并显示
+      elapsedTimeEl.textContent = this.formatTime(elapsedTime);
+    },
+    handleClickOrder(){
+      this.$router.push({
+        name: 'LiveOrder',
+        query: {
+          liveId: this.liveId
+        }
+      })
+    },
+    handleClickCoupon(){
+      this.$router.push({
+        name: 'LiveLiveCoupon',
+        query: {
+          liveId: this.liveId
+        }
+      })
+    },
+    getLive(){
+      getLive(this.liveId).then(res => {
+        if (res.code == 200) {
+          if (res.data.isAudit != 1) {
+            this.$message.error("当前直播间未经审核");
+            this.loading = false
+            return
+          }
+          this.isAudit = true
+          this.status = res.data.status
+          if(res.data.status == 4){
+            this.liveType = 3
+            this.videoUrl = res.data.videoUrl;
+          }else {
+            if (res.data.status != 2) {
+              this.$message.error("当前直播间未直播");
+              this.loading = false
+              return
+            }
+            if (res.data.liveType == 1) {
+              this.livingUrl = res.data.flvHlsUrl
+              this.livingUrl = this.livingUrl.replace("flv","m3u8")
+              this.$nextTick(() => {
+                this.initPlayer()
+              })
+              this.startTime = new Date(res.data.startTime).getTime()
+              this.processInterval = setInterval(this.updateLiveProgress, 1000);
+            } else {
+              this.liveType = 2
+              this.videoUrl = res.data.videoUrl;
+              this.$nextTick(() => {
+                this.initVideoPlayer(res.data.startTime)
+              })
+            }
+          }
+          this.loading = false
+        } else {
+          this.$message.error(res.msg)
+          this.loading = false
+        }
+        this.liveInfo = res.data
+      })
+    },
+    handleClickLottery(){
+      this.$router.push({
+        name: 'LiveLotteryConf',
+        query: {
+          liveId: this.liveId
+        }
+      })
+    },
+    handleClickRed(){
+      this.$router.push({
+        name: 'LiveRedConf',
+        query: {
+          liveId: this.liveId
+        }
+      })
+    },
+    getLiveUrl(){
+      getLivingUrl(this.liveId).then(res=>{
+        if(res.code === 200){
+          this.livingUrl = res.livingUrl
+          this.initPlayer()
+        }
+      })
+    },
+    initPlayer(){
+      var isUrl = this.livingUrl === null || this.livingUrl.trim() === ''
+      if (isUrl) {
+        console.error('直播地址为空,无法初始化播放器')
+        return
+      }
+      if (Hls.isSupported() && !isUrl) {
+        const videoElement = this.$refs.livingPlayer
+        if (!videoElement) {
+          console.error('找不到 video 元素')
+          return
+        }
+        this.hls = new Hls();
+        this.hls.attachMedia(videoElement);
+        this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
+          this.hls.loadSource(this.livingUrl);
+          this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
+            videoElement.play();
+          });
+        });
+        this.hls.on(Hls.Events.ERROR, (event, data) => {
+          console.error('HLS 错误:', data);
+        });
+      } else {
+        console.error('浏览器不支持 HLS')
+      }
+    },
+    handleClick(tab) {
+      const tabName = tab.name;
+      const params = this.pageParams[tabName];
+      const displayList = this[`${tabName}DisplayList`];
+      // 首次切换到该Tab或列表为空时初始化
+      if (displayList.length < 20) {
+        // 重置分页参数
+        params.currentPage = 1;
+        params.pageSize = 20;
+        params.prevPage = 0;
+        params.totalLoaded = 0;
+        params.hasMore = true;
+        params.hasPrev = false;
+        // 加载第一页
+        this.loadNextPage(tabName);
+      } else {
+        // 非首次切换,恢复滚动位置
+        this.$nextTick(() => {
+          const scrollEl = this.getScrollElement(tabName);
+          if (scrollEl) {
+            scrollEl.scrollTop = this.tabScrollPositions[tabName] || 0;
+          }
+        });
+      }
+    },
+    saveTabScrollPositions() {
+      this.tabScrollPositions = {
+        online: this.getScrollElement('online')?.scrollTop || 0,
+        offline: this.getScrollElement('offline')?.scrollTop || 0,
+        silenced: this.getScrollElement('silenced')?.scrollTop || 0
+      };
+    },
+    // 加载指定Tab的用户列表(核心加载逻辑)
+    loadNextPage(tabName) {
+      const params = this.pageParams[tabName];
+      const displayList = this[`${tabName}DisplayList`];
+      console.log(`加载 ${tabName} 用户列表`)
+      console.log(!params.hasMore || this.scrLoading[tabName].next)
+      console.log(params.currentPage)
+      // 若没有更多数据或正在加载,直接返回
+      if (!params.hasMore || this.scrLoading[tabName].next) {
+        return;
+      }
+
+      this.scrLoading[tabName].next = true;
+      const queryParams = {
+        liveId: this.liveId,
+        pageNum: params.currentPage,
+        pageSize: 20,
+        online: tabName === 'online' ? 0 : 1,
+        msgStatus: tabName === 'silenced' ? 1 : 0
+      };
+      // 调用接口加载对应状态的分页数据(需后端支持按状态筛选)
+      watchUserList(queryParams).then(response => {
+        this.scrLoading[tabName].next = false;
+        if (response.code !== 200) return;
+
+        const { rows, total } = response;
+        params.total = total; // 记录总数据量
+        // 过滤重复数据(基于userId)
+        const newRows = rows.filter(row =>
+          !displayList.some(u => u.userId === row.userId)
+        );
+        displayList.push(...newRows)
+        // 添加新数据并限制最大长度(避免内存占用过大)
+        if (displayList.length >= 40) { // 最大保留100条
+          this[`${tabName}DisplayList`] = displayList.slice(-40);
+          // 记录滚动位置(用于加载后校准)
+          const scrollEl = this.getScrollElement(tabName);
+          // 校准滚动位置(保持视觉连续性)
+          this.$nextTick(() => {
+            if (scrollEl) {
+              scrollEl.scrollTop = scrollEl.scrollHeight * 0.5;
+            }
+          });
+        }
+        // 更新分页状态
+        params.hasMore = params.currentPage * params.pageSize < total;
+        params.currentPage += 1;
+        params.hasPrev = params.currentPage > 2; // 当前页>2时一定有上一页
+        params.prevPage = params.currentPage - 2;
+      }).catch(() => {
+        this.scrLoading[tabName].next = false;
+      });
+    },
+    // 新增:加载上一页(向上滚动时)
+    loadPrevPage(tabName) {
+      const params = this.pageParams[tabName];
+      const displayList = this[`${tabName}DisplayList`];
+      // 边界校验:无上一页/正在加载/当前页<=1
+      console.log(`加载 ${tabName} 上一页`);
+      console.log(!params.hasPrev || this.scrLoading[tabName].prev || params.currentPage <= 1)
+      if (!params.hasPrev || this.scrLoading[tabName].prev || params.currentPage <= 1) {
+        return;
+      }
+      this.scrLoading[tabName].prev = true;
+      const targetPage = params.prevPage > 0 ? params.prevPage : params.currentPage - 2;
+      const queryParams = {
+        liveId: this.liveId,
+        pageNum: targetPage,
+        pageSize: 20,
+        online: tabName === 'online' ? 0 : 1,
+        msgStatus: tabName === 'silenced' ? 1 : 0
+      };
+      watchUserList(queryParams).then(response => {
+        this.scrLoading[tabName].prev = false;
+        if (response.code !== 200) return;
+
+        const { rows } = response;
+        if (rows.length === 0) {
+          params.hasPrev = false;
+          return;
+        }
+
+        // 记录滚动位置(用于加载后校准)
+        const scrollEl = this.getScrollElement(tabName);
+        const scrollTop = scrollEl?.scrollTop || 0;
+        const itemHeight = 80; // 预估行高(根据实际样式调整)
+        const newItemsHeight = rows.length * itemHeight;
+
+        // 过滤重复数据并添加到列表头部
+        const newRows = rows.filter(row => !displayList.some(u => u.userId === row.userId));
+        this[`${tabName}DisplayList`] = [...newRows, ...displayList];
+        params.totalLoaded += newRows.length;
+
+        // 限制最大长度
+        if (this[`${tabName}DisplayList`].length > 40) {
+          this[`${tabName}DisplayList`] = this[`${tabName}DisplayList`].slice(0, 40);
+        }
+
+        // 更新分页状态
+        params.prevPage = targetPage - 1;
+        params.hasPrev = targetPage > 1; // 上一页页码>1时还有更多上一页
+        params.currentPage = params.currentPage - 1;
+        if(params.currentPage * 20 < params.total) params.hasMore = true;
+        // 校准滚动位置(保持视觉连续性)
+        this.$nextTick(() => {
+          if (scrollEl) {
+            scrollEl.scrollTop = scrollEl.scrollHeight * 0.5;
+          }
+        });
+      }).catch(() => {
+        this.scrLoading[tabName].prev = false;
+      });
+    },
+    // 辅助:获取Tab对应的滚动容器
+    getScrollElement(tabName) {
+      const scrollRefs = {
+        online: this.$refs.manageLeftRef_online,
+        offline: this.$refs.manageLeftRef_offline,
+        silenced: this.$refs.manageLeftRef_silenced
+      };
+      return scrollRefs[tabName]?.wrap;
+    },
+    getLiveVideo() {
+      getLiveVideoByLiveId(this.liveId).then(res => {
+        this.liveVideo = res.data
+      })
+    },
+    getList() {
+      this.resetParams()
+      // this.loadUserList()
+      this.loadUserTotals(); // 先加载总人数
+      // this.handleClick('online')
+      this.handleClick({name:'online'})
+      this.loadMsgList()
+    },
+    loadUserTotals() {
+      if (!this.liveId) return;
+      // 假设后端提供一个接口返回总人数(如果没有,可通过首次加载全量数据后统计)
+      getLiveUserTotals({ liveId: this.liveId }).then(res => {
+        if (res.code === 200) {
+          this.userTotal = res.data; // { online, offline, silenced }
+        }
+      });
+    },
+    resetParams() {
+      // 重置各Tab的显示列表和分页参数
+      this.onlineDisplayList = [];
+      this.offlineDisplayList = [];
+      this.silencedDisplayList = [];
+      this.pageParams = {
+        online: {
+          currentPage: 1,       // 当前页(下一页加载用)
+          pageSize: 20,       // 当前页(下一页加载用)
+          prevPage: 0,          // 上一页页码(上一页加载用)
+          totalLoaded: 0,       // 已加载总条数
+          total: 0,             // 总数据量
+          hasMore: true,        // 是否有下一页
+          hasPrev: false        // 是否有上一页
+        },
+        offline: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        },
+        silenced: {
+          currentPage: 1,
+          pageSize: 20,
+          prevPage: 0,
+          totalLoaded: 0,
+          total: 0,
+          hasMore: true,
+          hasPrev: false
+        }
+      };
+      // 消息参数保留
+      this.msgList = [];
+      this.msgParams = {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: this.liveId
+      };
+    },
+    loadUserList() {
+      if(this.liveId == null)  return
+      // 直播间用户
+      watchUserList({
+        liveId: this.liveId,
+        pageNum: this.userParams.pageNum,
+        pageSize: this.userParams.pageSize
+      }).then(response => {
+        let {code,rows,total} = response
+        if (code === 200) {
+          let totalPage = (total % this.userParams.pageSize == 0) ? Math.floor(total / this.userParams.pageSize) : Math.floor(total / this.userParams.pageSize + 1);
+          rows.forEach(row => {
+            if (!this.userList.some(u => u.userId === row.userId)) {
+              this.userList.push(row)
+            }
+          })
+
+          // 没加载完继续加载
+          if (this.userParams.pageNum < totalPage) {
+            this.userParams.pageNum = parseInt(this.userParams.pageNum) + 1;
+            this.loadUserList()
+          }
+        }
+      })
+    },
+    loadMsgList() {
+      // 直播间消息
+      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() {
+      this.saveChatScrollPosition();
+    },
+    blockUser(u){
+      this.$confirm('是否确认封禁用户账号为:"' + u.nickName + '-' + u.userId + '"?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        let msg = {
+          msg: "",
+          liveId: this.liveId,
+          userId: u.userId,
+          userType: 0,
+          cmd: 'blockUser',
+          avatar: this.$store.state.user.user.avatar,
+          nickName: this.$store.state.user.user.nickName
+        }
+        this.socket.send(JSON.stringify(msg))
+        return blockUser(u.userId);
+      }).then(() => {
+        this.msgSuccess("封禁成功");
+      }).catch(() => {});
+    },
+    changeUserState(u) {
+      // 修改状态
+      changeUserStatus({liveId: u.liveId, userId: u.userId}).then(response => {
+        let { code } = response;
+        if (200 === code) {
+          u.msgStatus = u.msgStatus === 0 ? 1 : 0
+          // 同步更新消息列表中相同用户的状态
+          this.msgList.forEach(msg => {
+            if (msg.userId === u.userId) {
+              msg.msgStatus = u.msgStatus;
+            }
+          });
+
+          this.userList.forEach(user => {
+            if (user.userId === u.userId) {
+              user.msgStatus = u.msgStatus;
+            }
+          });
+          // 4. 关键:重新筛选所有Tab的显示列表,确保状态同步
+          this.refreshUserDisplayLists(u);
+
+          let msg = u.msgStatus === 0 ? "已解禁" : "已禁言"
+          this.msgSuccess(msg);
+          return
+        }
+        this.msgError("操作失败");
+      })
+    },
+    // 新增:重新筛选所有Tab的显示列表
+    refreshUserDisplayLists(user) {
+      const { userId, msgStatus: newStatus, online } = user;
+      const oldStatus = newStatus === 1 ? 0 : 1; // 操作前的状态(反向)
+
+      // 1. 禁言操作(newStatus=1):从原在线/离线列表移除,加入禁言列表
+      if (newStatus === 1) {
+        // 从在线/离线列表中移除该用户(根据当前在线状态)
+        if (online === 0) {
+          this.onlineDisplayList = this.onlineDisplayList.filter(u => u.userId !== userId);
+          this.userTotal.online = Math.max(0, this.userTotal.online - 1);
+        } else {
+          this.offlineDisplayList = this.offlineDisplayList.filter(u => u.userId !== userId);
+          this.userTotal.offline = Math.max(0, this.userTotal.offline - 1);
+        }
+        this.userTotal.silenced = this.userTotal.silenced + 1;
+        // 添加到禁言列表(去重+限制长度)
+        const silencedList = this.silencedDisplayList.filter(u => u.userId !== userId);
+        silencedList.push(user);
+        this.silencedDisplayList = silencedList.slice(-40);
+      }
+
+      // 2. 解禁操作(newStatus=0):从禁言列表移除,回到原在线/离线列表
+      else {
+        // 从禁言列表移除
+        this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== userId);
+        this.userTotal.silenced = Math.max(0, this.userTotal.silenced - 1);
+        // 回到对应的在线/离线列表(根据当前在线状态)
+        if (online === 0) {
+          // 加入在线列表(去重+限制长度)
+          const onlineList = this.onlineDisplayList.filter(u => u.userId !== userId);
+          onlineList.push(user);
+          this.onlineDisplayList = onlineList.slice(-40);
+          this.userTotal.online = this.userTotal.online + 1;
+        } else {
+          // 加入离线列表(去重+限制长度)
+          const offlineList = this.offlineDisplayList.filter(u => u.userId !== userId);
+          offlineList.push(user);
+          this.offlineDisplayList = offlineList.slice(-40);
+          this.userTotal.offline = this.userTotal.offline + 1;
+        }
+      }
+    },
+    connectWebSocket() {
+      this.$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)
+    },
+    handleWsMessage(event) {
+      let { code, data } = JSON.parse(event.data)
+      if (code === 200) {
+        let { cmd } = data
+        if (cmd === 'sendMsg') {
+          let message = JSON.parse(data.data)
+
+          let user = this.userList.find(u => u.userId === message.userId)
+          if (user) {
+            message.msgStatus = user.msgStatus
+          } else {
+            message.msgStatus = 0
+          }
+          delete message.params
+          if(this.msgList.length > 50){
+            this.msgList.shift()
+          }
+          this.msgList.push(message)
+          // 移动到底部
+          this.$nextTick(() => {
+            setTimeout(() => {
+              this.$refs.manageRightRef.wrap.scrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
+            }, 200)
+          })
+        }
+        else if (cmd === 'entry' || cmd === 'out') {
+          const user = data;
+          const online = cmd === 'entry' ? 0 : 1; // 0=在线,1=离线
+          const info = {
+            online:online,
+            msgStatus: user.msgStatus || 0,
+            nickName: user.nickName || '',
+            userType: user.userType || 0,
+            userId: user.userId || '',
+          };
+
+          // 1. 更新总人数(在线/离线互转)
+          if (cmd === 'entry') {
+            this.userTotal.online += 1;
+            this.userTotal.offline = Math.max(0, this.userTotal.offline - 1); // 确保不小于0
+          } else {
+            this.userTotal.offline += 1;
+            this.userTotal.online = Math.max(0, this.userTotal.online - 1); // 确保不小于0
+          }
+          // 2. 强制更新相关列表(无论当前激活哪个Tab)
+          if (cmd === 'entry') {
+            // 用户进入:从离线列表删除,添加到在线列表
+            this.offlineDisplayList = this.offlineDisplayList.filter(u => u.userId !== user.userId);
+            const newOnlineList = this.onlineDisplayList.filter(u => u.userId !== user.userId);
+            newOnlineList.push(info);
+            this.onlineDisplayList = newOnlineList.slice(-40); // 限制最大50条
+          } else {
+            // 用户离开:从在线列表删除,添加到离线列表
+            this.onlineDisplayList = this.onlineDisplayList.filter(u => u.userId !== user.userId);
+            const newOfflineList = this.offlineDisplayList.filter(u => u.userId !== user.userId);
+            newOfflineList.push(info);
+            this.offlineDisplayList = newOfflineList.slice(-40); // 限制最大50条
+          }
+          // 3. 处理禁言列表(如果用户是禁言状态,无论进出都要同步)
+          if (info.msgStatus === 1) {
+            // 禁言用户:从禁言列表删除旧数据,添加新数据
+            const newSilencedList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
+            newSilencedList.push(info);
+            this.silencedDisplayList = newSilencedList.slice(-40);
+          } else {
+            // 非禁言用户:从禁言列表删除(如果存在)
+            this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
+          }
+        } else if (cmd === 'live_start') {
+
+        } else if (cmd === 'live_end') {
+
+        }
+      }
+    },
+    sendMessage() {
+      // 发送前简单校验
+      if (this.newMsg.trim() === '') {
+        return;
+      }
+
+      let msg = {
+        msg: this.newMsg,
+        liveId: this.liveId,
+        userId: this.userId,
+        userType: 1,
+        cmd: 'sendMsg',
+        avatar: this.$store.state.user.user.avatar,
+        nickName: this.$store.state.user.user.nickName
+      }
+
+      this.socket.send(JSON.stringify(msg))
+
+      this.newMsg = '';
+    },
+    // 初始化滚动监听(在mounted中调用)
+    initScrollListeners() {
+      // 为每个Tab的滚动容器添加监听
+      this.$nextTick(() => {
+        const scrollRefs = {
+          online: this.$refs.manageLeftRef_online,
+          offline: this.$refs.manageLeftRef_offline,
+          silenced: this.$refs.manageLeftRef_silenced
+        };
+
+        Object.keys(scrollRefs).forEach(tabName => {
+          const scrollEl = scrollRefs[tabName]?.wrap;
+          if (scrollEl) {
+            scrollEl.addEventListener('scroll', () =>
+              this.handleTabScroll(tabName, scrollEl)
+            );
+          }
+        });
+      });
+    },
+    handleTabScroll(tabName, scrollEl) {
+      const { scrollTop, scrollHeight, clientHeight } = scrollEl;
+      const bottomThreshold = 50; // 距离底部100px触发下一页
+      const topThreshold = 50;    // 距离顶部100px触发上一页
+
+      // 加载下一页(滚动到底部附近)
+      if (scrollHeight - scrollTop - clientHeight < bottomThreshold) {
+        this.loadNextPage(tabName);
+      }
+
+      // 加载上一页(滚动到顶部附近)
+      if (scrollTop < topThreshold) {
+        this.loadPrevPage(tabName);
+      }
+    },
+  },
+  destroyed() {
+    this.hls?.destroy();
+    clearInterval(this.processInterval)
+  }
+}
+</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>

+ 37 - 1269
src/views/live/liveConsole/index.vue

@@ -1,1289 +1,57 @@
 <template>
-  <div>
-  <!-- 直播中控台 start -->
-  <el-row type="flex" justify="center" class="live-console" :gutter="10" v-loading="loading">>
-    <!-- 聊天 start -->
-    <el-col class="live-console-col" :span="6">
-      <el-tabs class="live-console-tab-left" v-model="tabRight.activeName" @tab-click="handleClick" :stretch="true">
-        <el-tab-pane label="讨论" name="talk">
-          <el-scrollbar style="height: 500px; width: 100%;" ref="manageRightRef">
-            <el-row v-for="m in msgList" :key="m.msgId">
-              <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>
-                      <a style="cursor: pointer;color: #ff0000;padding: 8px 8px 0 0;font-size: 12px;" @click="blockUser(m)">拉黑</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 -->
-
-
-    <!-- 直播/视频 start -->
-    <el-col class="live-console-col" :span="12">
-      <div style="background: #000; border-radius: 5px; overflow: hidden; margin: 10px 5px;">
-        <div style="border-radius: 5px; overflow: hidden;" v-if="!isAudit">
-          <img :src="require('@/assets/images/videoIsAudit.png')" style="width: 100%; height: 45vh;">
-        </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else-if="status != 2 && status != 4">
-          <img :src="require('@/assets/images/videoNotStart.png')" style="width: 100%; height: 45vh;">
-        </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 1">
-          <video
-            controls
-            ref="livingPlayer"
-            autoplay
-            width="100%"
-            @click.prevent
-            @contextmenu.prevent
-            class="custom-video"
-            style="display: block; background: #000; height: 45vh;"
-          ></video>
-          <!-- 时间显示(可选) -->
-          <div ref="liveElapsedTime" class="time-display">
-            已播放:<span id="liveElapsedTime">00:00:00</span>
-          </div>
-        </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 2">
-          <video
-            controls
-            ref="videoPlayer"
-            loop
-            autoplay
-            width="100%"
-            muted
-            playsinline
-            @click.prevent
-            @contextmenu.prevent
-            class="custom-video"
-            style="display: block; background: #000; height: 40vh;"
-          >
-            <source :src="videoUrl" type="application/x-mpegURL">
-          </video>
-          <!-- 自定义进度条容器 -->
-          <div ref="progressBar" class="progress-container">
-            <div id="progressBar" class="progress-bar"></div>
-          </div>
-
-          <!-- 时间显示(可选) -->
-          <div ref="elapsedTime" class="time-display">
-            已播放:<span id="elapsedTime">00:00:00</span>
-          </div>
-        </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else-if="liveType == 3">
-          <video
-            controls
-            ref="liveReplay"
-            loop
-            autoplay
-            width="100%"
-            playsinline
-            style="display: block; background: #000; height: 40vh;"
-          >
-            <source :src="videoUrl" type="application/x-mpegURL">
-          </video>
-        </div>
-        <div style="border-radius: 5px; overflow: hidden;" v-else>
-          <img :src="require('@/assets/images/videoNotStart.png')" style="width: 100%; height: 45vh;">
-        </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;" @click="handleClickRed">
-          <i class="el-icon-money" 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;" @click="handleClickLottery">
-          <i class="el-icon-present" 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;" @click="handleClickGoods">
-          <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;" @click="handleClickOrder">
-          <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;" @click="handleClickCoupon">
-          <i class="el-icon-goods" style="font-size: 20px;"></i>
-          <span style="font-size: 12px; margin-top: 4px;">直播优惠券</span>
-        </div>
-      </div>
-      <el-radio-group v-model="tableRadio" >
-        <el-radio-button label="订单数">订单数</el-radio-button>
-      </el-radio-group>
-      <div  style="position: relative;width: 100%; height: 300px;">
-        <div ref="chartContainer" style="width: 100%; height: 100%;"></div>
-        <div style="position: absolute; top: 10px; right: 10px; background: #fff; padding: 5px; z-index: 1;">
-          <el-select v-model="searchQuery.timeOptions" placeholder="请选择" style="width: 150px"  @change="timeChange">
-            <el-option
-              v-for="item in timeOptions"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value">
-            </el-option>
-          </el-select>
-          <el-select v-model="searchQuery.timeGranularity" placeholder="请选择" style="width: 150px"  @change="timeGranularityChange">
-            <el-option
-              v-for="item in timeGranularity"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value">
-            </el-option>
-          </el-select>
-          <!--          <el-button type="primary" @click="applyFilter">搜索</el-button>-->
-        </div>
-      </div>
-    </el-col>
-    <!-- 直播/视频 end -->
-
-    <!-- 用户列表 start -->
-    <el-col class="live-console-col" :span="6">
-      <el-tabs class="live-console-tab-right" v-model="tabLeft.activeName" @tab-click="handleClick" :stretch="true">
-        <el-tab-pane :label="onlineLabel" name="online">
-          <el-scrollbar ref="manageLeftRef_online" style="height: 800px; width: 100%;">
-            <el-row style="margin-top: 10px" type="flex" align="middle" v-for="u in onlineDisplayList" :key="u.userId">
-              <el-col :span="20">
-                <el-row type="flex" align="middle">
-                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
-                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
-                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
-                </el-row>
-              </el-col>
-              <el-col :span="4" >
-                <el-popover
-                  width="100"
-                  trigger="click">
-                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
-                  <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 offlineDisplayList" :key="u.userId">
-              <el-col :span="20">
-                <el-row type="flex" align="middle">
-                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
-                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
-                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
-                </el-row>
-              </el-col>
-              <el-col :span="4" >
-                <el-popover
-                  width="100"
-                  trigger="click">
-                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
-                  <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 silencedDisplayList" :key="u.userId">
-              <el-col :span="20">
-                <el-row type="flex" align="middle">
-                  <el-col :span="4" style="padding-left: 10px;"><el-avatar :src="u.avatar"></el-avatar></el-col>
-                  <el-col :span="19" :offset="1">{{ u.nickName }}</el-col>
-                  <el-col :span="19" :offset="1">{{ u.userId }}</el-col>
-                </el-row>
-              </el-col>
-              <el-col :span="4" >
-                <el-popover
-                  width="100"
-                  trigger="click">
-                  <a style="cursor: pointer;color: #ff0000;" @click="changeUserState(u)">{{ u.msgStatus === 1 ? '解禁' : '禁言' }}</a>
-                  <a style="cursor: pointer;color: #ff0000;margin-left:10px" @click="blockUser(u)">拉黑</a>
-                  <i class="el-icon-more" slot="reference"></i>
-                </el-popover>
-              </el-col>
-            </el-row>
-          </el-scrollbar>
-        </el-tab-pane>
-      </el-tabs>
-    </el-col>
-    <!-- 用户列表 end -->
-  </el-row>
-  <!-- 直播中控台  end -->
+  <div id="app">
+    <div class="nav">
+      <button @click="currentView = 'dashboard'">实时大屏</button>
+      <button @click="currentView = 'console'">中控台</button>
+    </div>
+    <component :is="currentView" :liveId="liveId"></component>
   </div>
 </template>
 
 <script>
-import { blockUser,changeUserStatus,getLiveUserTotals, watchUserList } from '@/api/live/liveWatchUser'
-import { getLiveVideoByLiveId } from '@/api/live/liveVideo'
-import {getLive, getLivingUrl} from '@/api/live/live'
-import { getLiveOrderTimeGranularity } from '@/api/live/liveOrder'
-import { listLiveMsg } from '@/api/live/liveMsg'
-import Hls from 'hls.js';
-import LiveLotteryConf from '@/views/live/liveConfig/liveLotteryConf.vue'
-import LiveRedConf from '@/views/live/liveConfig/liveRedConf.vue'
-import LiveGoods from '@/views/live/liveConfig/goods.vue'
-import LiveLiveCoupon from '@/views/live/liveConfig/liveCoupon.vue'
-import echarts from 'echarts'
+import LiveDashboard from './LiveDashboard.vue';
+import LiveConsole from './LiveConsole.vue';
 
 export default {
-  name: "LiveConsole",
-  components: { LiveLotteryConf,LiveRedConf },
+  name: 'LiveConsole',
+  components: {
+    dashboard: LiveDashboard,
+    console: LiveConsole
+  },
   data() {
     return {
-      loading:true,
-      tabLeft: {
-        activeName: "online",
-      },
-      tabRight: {
-        activeName: "talk",
-      },
-      livingUrl:"",
-      videoUrl: "",
-      status: 0,
-      loadMsgMaxPage: 2,
-      liveVideo: {},
-      liveWsUrl: process.env.VUE_APP_LIVE_WS_URL + '/app/webSocket',
-      userParams:{
-        pageNum: 1,
-        pageSize: 10,
-        liveId: null
-      },
-      msgParams: {
-        pageNum: 1,
-        pageSize: 10,
-        liveId: null
-      },
-      userList: [],
-      msgList: [],
-      newMsg: '',
-      isAudit: false,
-      myChart: null, // 用于存储 ECharts 实例
-      liveType: 1,
-      tableRadio: '订单数',
-      searchQuery: {timeOptions:'2',timeGranularity:'10',liveId: null},
-      timeOptions: [
-        {value:'2',label:'最近2小时',key:'2'},
-        {value:'4',label:'最近4小时',key:'4'},
-        {value:'all',label:'全场',key:'all'},
-      ],
-      timeGranularity: [
-        {value:'10',label:'10分钟',key:'10'},
-        {value:'30',label:'30分钟',key:'30'},
-        {value:'60',label:'1小时',key:'60'},
-      ],
-      videoDuration: 0,
-      startTime: null,
-      processInterval: null,
-      // ... 其他数据
-      chatScrollTop: 0, // 保存聊天滚动位置
-      socket: null,
-      userTotal: {
-        online: 0,       // 在线总人数
-        offline: 0,      // 离线总人数
-        silenced: 0      // 禁言总人数
-      },
-      // 各Tab的显示列表(仅存储当前需要展示的数据)
-      onlineDisplayList: [],    // 在线用户显示列表
-      offlineDisplayList: [],   // 离线用户显示列表
-      silencedDisplayList: [],  // 禁言用户显示列表
-      // 各Tab的分页参数
-      pageParams: {
-        online: {
-          currentPage: 1,       // 当前页(下一页加载用)
-          pageSize: 20,       // 当前页(下一页加载用)
-          prevPage: 0,          // 上一页页码(上一页加载用)
-          totalLoaded: 0,       // 已加载总条数
-          total: 0,             // 总数据量
-          hasMore: true,        // 是否有下一页
-          hasPrev: false        // 是否有上一页
-        },
-        offline: {
-          currentPage: 1,
-          pageSize: 20,
-          prevPage: 0,
-          totalLoaded: 0,
-          total: 0,
-          hasMore: true,
-          hasPrev: false
-        },
-        silenced: {
-          currentPage: 1,
-          pageSize: 20,
-          prevPage: 0,
-          totalLoaded: 0,
-          total: 0,
-          hasMore: true,
-          hasPrev: false
-        }
-      },
-      scrLoading: {
-        online: { next: false, prev: false },
-        offline: { next: false, prev: false },
-        silenced: { next: false, prev: false }
-      }
-    }
-  },
-  created() {
-    // this.getLiveVideo()
-    this.getList()
-    this.connectWebSocket()
-    this.getLive()
-    this.searchQuery.liveId = this.liveId
-  },
-  computed: {
-    liveId() {
-      return this.$route.params.liveId;
-    },
-    userId() {
-      return this.$store.state.user.user.userId
-    },
-    companyId() {
-      return this.$store.state.user.user.companyId
-    },
-    onlineLabel() {
-      return `在线(${this.userTotal.online})`;
-    },
-    offlineLabel() {
-      return `离线(${this.userTotal.offline})`;
-    },
-    silencedUserLabel() {
-      return `禁言(${this.userTotal.silenced})`;
-    }
-  },
-  mounted() {
-
-    this.$nextTick(() => {
-      this.restoreChatScrollPosition();
-    });
-    this.getEchartsTables();
-    // 添加滚动事件监听器
-    this.$nextTick(() => {
-      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
-        this.$refs.manageRightRef.wrap.addEventListener('scroll', this.saveChatScrollPosition);
-      }
-    });
-    this.initScrollListeners();
-  },
-  beforeDestroy() {
-    this.saveTabScrollPositions()
-    // 移除滚动监听(避免内存泄漏)
-    const scrollRefs = {
-      online: this.$refs.manageLeftRef_online,
-      offline: this.$refs.manageLeftRef_offline,
-      silenced: this.$refs.manageLeftRef_silenced
+      liveId: this.$route.params.liveId,
+      currentView: 'dashboard'
     };
-    Object.keys(scrollRefs).forEach(tabName => {
-      const scrollEl = scrollRefs[tabName]?.wrap;
-      if (scrollEl) {
-        scrollEl.removeEventListener('scroll', () =>
-          this.handleTabScroll(tabName, scrollEl)
-        );
-      }
-    })
-  },
-  // 使用 deactivated 和 activated 钩子替代 beforeDestroy 和 destroyed
-  deactivated() {
-    this.saveChatScrollPosition();
-  },
-
-  activated() {
-    this.$nextTick(() => {
-      this.restoreChatScrollPosition();
-    });
-    this.$nextTick(() => {
-      const video = this.$refs.videoPlayer;
-      if (video != null) {
-        this.initVideoPlayer(this.liveInfo.startTime)
-      }
-    })
   },
-  methods: {
-    updateVideoPosition(video){
-      const currentTime = new Date().getTime(); // 当前时间戳(毫秒)
-      const elapsedTime = currentTime - this.startTime; // 已流逝时间(毫秒)
-
-      if (elapsedTime < 0) {
-        // 未开播:视频停在初始位置
-        video.currentTime = 0;
-        return;
-      }
-
-      // 已开播:计算视频循环后的位置(流逝时间 % 视频时长)
-      const elapsedSeconds = elapsedTime / 1000; // 转换为秒
-      const videoPosition = elapsedSeconds % this.videoDuration; // 视频内的播放位置(秒)
-
-      // 设置视频播放位置
-      video.currentTime = videoPosition;
-    },
-    updateProgress() {
-      const progressBar = this.$refs.progressBar;
-      const elapsedTimeEl = this.$refs.elapsedTime;
-      if (!this.videoDuration) return; // 视频时长未加载时不更新
-
-      const currentTime = new Date().getTime();
-      const elapsedTime = currentTime - this.startTime; // 总流逝时间(毫秒)
-
-      if (elapsedTime < 0) {
-        // 未开播状态
-        progressBar.style.width = '0%';
-        elapsedTimeEl.textContent = '00:00:00';
-        return;
-      }
-
-      // 计算进度百分比(基于视频循环)
-      const elapsedSeconds = elapsedTime / 1000;
-      const videoPosition = elapsedSeconds % this.videoDuration; // 当前在视频中的位置
-      const progressPercent = (videoPosition / this.videoDuration) * 100; // 进度百分比
-
-      // 更新进度条宽度
-      progressBar.style.width = `${progressPercent}%`;
-
-      // 格式化总流逝时间为“时:分:秒”并显示
-      elapsedTimeEl.textContent = this.formatTime(elapsedTime);
-    },
-    formatTime(ms) {
-      const totalSeconds = Math.floor(ms / 1000);
-      const hours = Math.floor(totalSeconds / 3600);
-      const minutes = Math.floor((totalSeconds % 3600) / 60);
-      const seconds = totalSeconds % 60;
-
-      // 补零处理(确保两位数)
-      return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
-    },
-    initVideoPlayer: function (startTime) {
-      const video = this.$refs.videoPlayer;
-      this.hls = new Hls();
-      this.hls.attachMedia(video);
-      this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
-        this.hls.loadSource(this.videoUrl);
-        this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
-          video.play();
-        });
-      });
-      this.hls.on(Hls.Events.ERROR, (event, data) => {
-        console.error('HLS 错误:', data);
-      });
-
-      // 1. 初始化开播时间
-      startTime = new Date(startTime).getTime();
-      this.startTime = startTime;
-      // 2. 监听视频元数据加载完成(获取视频时长)
-      video.addEventListener('loadedmetadata', () => {
-        this.videoDuration = video.duration; // 获取视频时长(秒)
-
-        // 初始化视频播放位置
-        this.updateVideoPosition(video);
-
-        // 启动实时进度更新(每秒刷新一次)
-        this.processInterval = setInterval(this.updateProgress, 1000);
-      });
-
-    },
-    // 保存聊天滚动位置
-    saveChatScrollPosition() {
-      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
-        this.chatScrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight;
-      }
-    },
-
-    // 恢复聊天滚动位置
-    restoreChatScrollPosition() {
-      if (this.$refs.manageRightRef && this.$refs.manageRightRef.wrap) {
-        this.$refs.manageRightRef.wrap.scrollTop = this.chatScrollTop;
-      }
-    },
-    // ... 其他方法 ...
-    timeChange(val) {
-      this.searchQuery.timeOptions = val
-      this.getEchartsTables(this.searchQuery)
-      this.initChart()
-    },
-    timeGranularityChange(val) {
-      this.searchQuery.timeGranularity =  val
-      this.getEchartsTables()
-      this.initChart()
-    },
-    getEchartsTables() {
-      getLiveOrderTimeGranularity(this.searchQuery).then(res => {
-        if (res.code == 200) {
-          this.echartsXLine = res.hourlySlots
-          this.echartsXValue = res.hourlySlotsValue
-          this.initChart()
-        }
-      })
-    },
-    initChart() {
-      const chartDom = this.$refs.chartContainer;
-      this.myChart = echarts.init(chartDom);
-      const option = {
-        tooltip: {trigger: 'axis'},
-        legend: {data: ['订单数']},
-        xAxis: {type: 'category', boundaryGap: false, data: this.echartsXLine},
-        yAxis: {type: 'value'},
-        series: [
-          {name: '订单数', type: 'line', data: this.echartsXValue}
-        ],
-      };
-      this.myChart.setOption(option);
-    },
-    handleClickGoods(){
-      this.$router.push({
-        name: 'LiveGoods',
-        query: {
-          liveId: this.liveId
-        }
-      })
-    },
-    updateLiveProgress() {
-      const elapsedTimeEl = this.$refs.liveElapsedTime;
-
-      const currentTime = new Date().getTime();
-      const elapsedTime = currentTime - this.startTime; // 总流逝时间(毫秒)
-
-      if (elapsedTime < 0) {
-        elapsedTimeEl.textContent = '00:00:00';
-        return;
-      }
-
-
-      // 格式化总流逝时间为“时:分:秒”并显示
-      elapsedTimeEl.textContent = this.formatTime(elapsedTime);
-    },
-    handleClickOrder(){
-      this.$router.push({
-        name: 'LiveOrder',
-        query: {
-          liveId: this.liveId
-        }
-      })
-    },
-    handleClickCoupon(){
-      this.$router.push({
-        name: 'LiveLiveCoupon',
-        query: {
-          liveId: this.liveId
-        }
-      })
-    },
-    getLive(){
-      getLive(this.liveId).then(res => {
-        if (res.code == 200) {
-          if (res.data.isAudit != 1) {
-            this.$message.error("当前直播间未经审核");
-            this.loading = false
-            return
-          }
-          this.isAudit = true
-          this.status = res.data.status
-          if(res.data.status == 4){
-            this.liveType = 3
-            this.videoUrl = res.data.videoUrl;
-          }else {
-            if (res.data.status != 2) {
-              this.$message.error("当前直播间未直播");
-              this.loading = false
-              return
-            }
-            if (res.data.liveType == 1) {
-              this.livingUrl = res.data.flvHlsUrl
-              this.livingUrl = this.livingUrl.replace("flv","m3u8")
-              this.$nextTick(() => {
-                this.initPlayer()
-              })
-              this.startTime = new Date(res.data.startTime).getTime()
-              this.processInterval = setInterval(this.updateLiveProgress, 1000);
-            } else {
-              this.liveType = 2
-              this.videoUrl = res.data.videoUrl;
-              this.$nextTick(() => {
-                this.initVideoPlayer(res.data.startTime)
-              })
-            }
-          }
-          this.loading = false
-        } else {
-          this.$message.error(res.msg)
-          this.loading = false
-        }
-        this.liveInfo = res.data
-      })
-    },
-    handleClickLottery(){
-      this.$router.push({
-        name: 'LiveLotteryConf',
-        query: {
-          liveId: this.liveId
-        }
-      })
-    },
-    handleClickRed(){
-      this.$router.push({
-        name: 'LiveRedConf',
-        query: {
-          liveId: this.liveId
-        }
-      })
-    },
-    getLiveUrl(){
-      getLivingUrl(this.liveId).then(res=>{
-        if(res.code === 200){
-          this.livingUrl = res.livingUrl
-          this.initPlayer()
-        }
-      })
-    },
-    initPlayer(){
-      var isUrl = this.livingUrl === null || this.livingUrl.trim() === ''
-      if (isUrl) {
-        console.error('直播地址为空,无法初始化播放器')
-        return
-      }
-      if (Hls.isSupported() && !isUrl) {
-        const videoElement = this.$refs.livingPlayer
-        if (!videoElement) {
-          console.error('找不到 video 元素')
-          return
-        }
-        this.hls = new Hls();
-        this.hls.attachMedia(videoElement);
-        this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
-          this.hls.loadSource(this.livingUrl);
-          this.hls.on(Hls.Events.STREAM_LOADED, (event, data) => {
-            videoElement.play();
-          });
-        });
-        this.hls.on(Hls.Events.ERROR, (event, data) => {
-          console.error('HLS 错误:', data);
-        });
-      } else {
-        console.error('浏览器不支持 HLS')
-      }
-    },
-    handleClick(tab) {
-      const tabName = tab.name;
-      const params = this.pageParams[tabName];
-      const displayList = this[`${tabName}DisplayList`];
-      // 首次切换到该Tab或列表为空时初始化
-      if (displayList.length < 20) {
-        // 重置分页参数
-        params.currentPage = 1;
-        params.pageSize = 20;
-        params.prevPage = 0;
-        params.totalLoaded = 0;
-        params.hasMore = true;
-        params.hasPrev = false;
-        // 加载第一页
-        this.loadNextPage(tabName);
-      } else {
-        // 非首次切换,恢复滚动位置
-        this.$nextTick(() => {
-          const scrollEl = this.getScrollElement(tabName);
-          if (scrollEl) {
-            scrollEl.scrollTop = this.tabScrollPositions[tabName] || 0;
-          }
-        });
-      }
-    },
-    saveTabScrollPositions() {
-      this.tabScrollPositions = {
-        online: this.getScrollElement('online')?.scrollTop || 0,
-        offline: this.getScrollElement('offline')?.scrollTop || 0,
-        silenced: this.getScrollElement('silenced')?.scrollTop || 0
-      };
-    },
-    // 加载指定Tab的用户列表(核心加载逻辑)
-    loadNextPage(tabName) {
-      const params = this.pageParams[tabName];
-      const displayList = this[`${tabName}DisplayList`];
-      console.log(`加载 ${tabName} 用户列表`)
-      console.log(!params.hasMore || this.scrLoading[tabName].next)
-      console.log(params.currentPage)
-      // 若没有更多数据或正在加载,直接返回
-      if (!params.hasMore || this.scrLoading[tabName].next) {
-        return;
-      }
-
-      this.scrLoading[tabName].next = true;
-      const queryParams = {
-        liveId: this.liveId,
-        pageNum: params.currentPage,
-        pageSize: 20,
-        online: tabName === 'online' ? 0 : 1,
-        msgStatus: tabName === 'silenced' ? 1 : 0
-      };
-      // 调用接口加载对应状态的分页数据(需后端支持按状态筛选)
-      watchUserList(queryParams).then(response => {
-        this.scrLoading[tabName].next = false;
-        if (response.code !== 200) return;
-
-        const { rows, total } = response;
-        params.total = total; // 记录总数据量
-        // 过滤重复数据(基于userId)
-        const newRows = rows.filter(row =>
-          !displayList.some(u => u.userId === row.userId)
-        );
-        displayList.push(...newRows)
-        // 添加新数据并限制最大长度(避免内存占用过大)
-        if (displayList.length >= 40) { // 最大保留100条
-          this[`${tabName}DisplayList`] = displayList.slice(-40);
-          // 记录滚动位置(用于加载后校准)
-          const scrollEl = this.getScrollElement(tabName);
-          // 校准滚动位置(保持视觉连续性)
-          this.$nextTick(() => {
-            if (scrollEl) {
-              scrollEl.scrollTop = scrollEl.scrollHeight * 0.5;
-            }
-          });
-        }
-        // 更新分页状态
-        params.hasMore = params.currentPage * params.pageSize < total;
-        params.currentPage += 1;
-        params.hasPrev = params.currentPage > 2; // 当前页>2时一定有上一页
-        params.prevPage = params.currentPage - 2;
-      }).catch(() => {
-        this.scrLoading[tabName].next = false;
-      });
-    },
-    // 新增:加载上一页(向上滚动时)
-    loadPrevPage(tabName) {
-      const params = this.pageParams[tabName];
-      const displayList = this[`${tabName}DisplayList`];
-      // 边界校验:无上一页/正在加载/当前页<=1
-      console.log(`加载 ${tabName} 上一页`);
-      console.log(!params.hasPrev || this.scrLoading[tabName].prev || params.currentPage <= 1)
-      if (!params.hasPrev || this.scrLoading[tabName].prev || params.currentPage <= 1) {
-        return;
-      }
-      this.scrLoading[tabName].prev = true;
-      const targetPage = params.prevPage > 0 ? params.prevPage : params.currentPage - 2;
-      const queryParams = {
-        liveId: this.liveId,
-        pageNum: targetPage,
-        pageSize: 20,
-        online: tabName === 'online' ? 0 : 1,
-        msgStatus: tabName === 'silenced' ? 1 : 0
-      };
-      watchUserList(queryParams).then(response => {
-        this.scrLoading[tabName].prev = false;
-        if (response.code !== 200) return;
-
-        const { rows } = response;
-        if (rows.length === 0) {
-          params.hasPrev = false;
-          return;
-        }
-
-        // 记录滚动位置(用于加载后校准)
-        const scrollEl = this.getScrollElement(tabName);
-        const scrollTop = scrollEl?.scrollTop || 0;
-        const itemHeight = 80; // 预估行高(根据实际样式调整)
-        const newItemsHeight = rows.length * itemHeight;
-
-        // 过滤重复数据并添加到列表头部
-        const newRows = rows.filter(row => !displayList.some(u => u.userId === row.userId));
-        this[`${tabName}DisplayList`] = [...newRows, ...displayList];
-        params.totalLoaded += newRows.length;
-
-        // 限制最大长度
-        if (this[`${tabName}DisplayList`].length > 40) {
-          this[`${tabName}DisplayList`] = this[`${tabName}DisplayList`].slice(0, 40);
-        }
-
-        // 更新分页状态
-        params.prevPage = targetPage - 1;
-        params.hasPrev = targetPage > 1; // 上一页页码>1时还有更多上一页
-        params.currentPage = params.currentPage - 1;
-        if(params.currentPage * 20 < params.total) params.hasMore = true;
-        // 校准滚动位置(保持视觉连续性)
-        this.$nextTick(() => {
-          if (scrollEl) {
-            scrollEl.scrollTop = scrollEl.scrollHeight * 0.5;
-          }
-        });
-      }).catch(() => {
-        this.scrLoading[tabName].prev = false;
-      });
-    },
-    // 辅助:获取Tab对应的滚动容器
-    getScrollElement(tabName) {
-      const scrollRefs = {
-        online: this.$refs.manageLeftRef_online,
-        offline: this.$refs.manageLeftRef_offline,
-        silenced: this.$refs.manageLeftRef_silenced
-      };
-      return scrollRefs[tabName]?.wrap;
-    },
-    getLiveVideo() {
-      getLiveVideoByLiveId(this.liveId).then(res => {
-        this.liveVideo = res.data
-      })
-    },
-    getList() {
-      this.resetParams()
-      // this.loadUserList()
-      this.loadUserTotals(); // 先加载总人数
-      // this.handleClick('online')
-      this.handleClick({name:'online'})
-      this.loadMsgList()
-    },
-    loadUserTotals() {
-      if (!this.liveId) return;
-      // 假设后端提供一个接口返回总人数(如果没有,可通过首次加载全量数据后统计)
-      getLiveUserTotals({ liveId: this.liveId }).then(res => {
-        if (res.code === 200) {
-          this.userTotal = res.data; // { online, offline, silenced }
-        }
-      });
-    },
-    resetParams() {
-      // 重置各Tab的显示列表和分页参数
-      this.onlineDisplayList = [];
-      this.offlineDisplayList = [];
-      this.silencedDisplayList = [];
-      this.pageParams = {
-        online: {
-          currentPage: 1,       // 当前页(下一页加载用)
-          pageSize: 20,       // 当前页(下一页加载用)
-          prevPage: 0,          // 上一页页码(上一页加载用)
-          totalLoaded: 0,       // 已加载总条数
-          total: 0,             // 总数据量
-          hasMore: true,        // 是否有下一页
-          hasPrev: false        // 是否有上一页
-        },
-        offline: {
-          currentPage: 1,
-          pageSize: 20,
-          prevPage: 0,
-          totalLoaded: 0,
-          total: 0,
-          hasMore: true,
-          hasPrev: false
-        },
-        silenced: {
-          currentPage: 1,
-          pageSize: 20,
-          prevPage: 0,
-          totalLoaded: 0,
-          total: 0,
-          hasMore: true,
-          hasPrev: false
-        }
-      };
-      // 消息参数保留
-      this.msgList = [];
-      this.msgParams = {
-        pageNum: 1,
-        pageSize: 10,
-        liveId: this.liveId
-      };
-    },
-    loadUserList() {
-      if(this.liveId == null)  return
-      // 直播间用户
-      watchUserList({
-        liveId: this.liveId,
-        pageNum: this.userParams.pageNum,
-        pageSize: this.userParams.pageSize
-      }).then(response => {
-        let {code,rows,total} = response
-        if (code === 200) {
-          let totalPage = (total % this.userParams.pageSize == 0) ? Math.floor(total / this.userParams.pageSize) : Math.floor(total / this.userParams.pageSize + 1);
-          rows.forEach(row => {
-            if (!this.userList.some(u => u.userId === row.userId)) {
-              this.userList.push(row)
-            }
-          })
-
-          // 没加载完继续加载
-          if (this.userParams.pageNum < totalPage) {
-            this.userParams.pageNum = parseInt(this.userParams.pageNum) + 1;
-            this.loadUserList()
-          }
-        }
-      })
-    },
-    loadMsgList() {
-      // 直播间消息
-      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() {
-      this.saveChatScrollPosition();
-    },
-    blockUser(u){
-      this.$confirm('是否确认封禁用户账号为:"' + u.nickName + '-' + u.userId + '"?', "警告", {
-        confirmButtonText: "确定",
-        cancelButtonText: "取消",
-        type: "warning"
-      }).then(function() {
-        let msg = {
-          msg: "",
-          liveId: this.liveId,
-          userId: u.userId,
-          userType: 0,
-          cmd: 'blockUser',
-          avatar: this.$store.state.user.user.avatar,
-          nickName: this.$store.state.user.user.nickName
-        }
-        this.socket.send(JSON.stringify(msg))
-        return blockUser(u.userId);
-      }).then(() => {
-        this.msgSuccess("封禁成功");
-      }).catch(() => {});
-    },
-    changeUserState(u) {
-      // 修改状态
-      changeUserStatus({liveId: u.liveId, userId: u.userId}).then(response => {
-        let { code } = response;
-        if (200 === code) {
-          u.msgStatus = u.msgStatus === 0 ? 1 : 0
-          // 同步更新消息列表中相同用户的状态
-          this.msgList.forEach(msg => {
-            if (msg.userId === u.userId) {
-              msg.msgStatus = u.msgStatus;
-            }
-          });
-
-          this.userList.forEach(user => {
-            if (user.userId === u.userId) {
-              user.msgStatus = u.msgStatus;
-            }
-          });
-          // 4. 关键:重新筛选所有Tab的显示列表,确保状态同步
-          this.refreshUserDisplayLists(u);
-
-          let msg = u.msgStatus === 0 ? "已解禁" : "已禁言"
-          this.msgSuccess(msg);
-          return
-        }
-        this.msgError("操作失败");
-      })
-    },
-    // 新增:重新筛选所有Tab的显示列表
-    refreshUserDisplayLists(user) {
-      const { userId, msgStatus: newStatus, online } = user;
-      const oldStatus = newStatus === 1 ? 0 : 1; // 操作前的状态(反向)
-
-      // 1. 禁言操作(newStatus=1):从原在线/离线列表移除,加入禁言列表
-      if (newStatus === 1) {
-        // 从在线/离线列表中移除该用户(根据当前在线状态)
-        if (online === 0) {
-          this.onlineDisplayList = this.onlineDisplayList.filter(u => u.userId !== userId);
-          this.userTotal.online = Math.max(0, this.userTotal.online - 1);
-        } else {
-          this.offlineDisplayList = this.offlineDisplayList.filter(u => u.userId !== userId);
-          this.userTotal.offline = Math.max(0, this.userTotal.offline - 1);
-        }
-        this.userTotal.silenced = this.userTotal.silenced + 1;
-        // 添加到禁言列表(去重+限制长度)
-        const silencedList = this.silencedDisplayList.filter(u => u.userId !== userId);
-        silencedList.push(user);
-        this.silencedDisplayList = silencedList.slice(-40);
-      }
-
-      // 2. 解禁操作(newStatus=0):从禁言列表移除,回到原在线/离线列表
-      else {
-        // 从禁言列表移除
-        this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== userId);
-        this.userTotal.silenced = Math.max(0, this.userTotal.silenced - 1);
-        // 回到对应的在线/离线列表(根据当前在线状态)
-        if (online === 0) {
-          // 加入在线列表(去重+限制长度)
-          const onlineList = this.onlineDisplayList.filter(u => u.userId !== userId);
-          onlineList.push(user);
-          this.onlineDisplayList = onlineList.slice(-40);
-          this.userTotal.online = this.userTotal.online + 1;
-        } else {
-          // 加入离线列表(去重+限制长度)
-          const offlineList = this.offlineDisplayList.filter(u => u.userId !== userId);
-          offlineList.push(user);
-          this.offlineDisplayList = offlineList.slice(-40);
-          this.userTotal.offline = this.userTotal.offline + 1;
-        }
-      }
-    },
-    connectWebSocket() {
-      this.$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)
-    },
-    handleWsMessage(event) {
-      let { code, data } = JSON.parse(event.data)
-      if (code === 200) {
-        let { cmd } = data
-        if (cmd === 'sendMsg') {
-          let message = JSON.parse(data.data)
-
-          let user = this.userList.find(u => u.userId === message.userId)
-          if (user) {
-            message.msgStatus = user.msgStatus
-          } else {
-            message.msgStatus = 0
-          }
-          delete message.params
-          if(this.msgList.length > 50){
-            this.msgList.shift()
-          }
-          this.msgList.push(message)
-          // 移动到底部
-          this.$nextTick(() => {
-            setTimeout(() => {
-              this.$refs.manageRightRef.wrap.scrollTop = this.$refs.manageRightRef.wrap.scrollHeight - this.$refs.manageRightRef.wrap.clientHeight
-            }, 200)
-          })
-        }
-        else if (cmd === 'entry' || cmd === 'out') {
-          const user = data;
-          const online = cmd === 'entry' ? 0 : 1; // 0=在线,1=离线
-          const info = {
-            online:online,
-            msgStatus: user.msgStatus || 0,
-            nickName: user.nickName || '',
-            userType: user.userType || 0,
-            userId: user.userId || '',
-          };
-
-          // 1. 更新总人数(在线/离线互转)
-          if (cmd === 'entry') {
-            this.userTotal.online += 1;
-            this.userTotal.offline = Math.max(0, this.userTotal.offline - 1); // 确保不小于0
-          } else {
-            this.userTotal.offline += 1;
-            this.userTotal.online = Math.max(0, this.userTotal.online - 1); // 确保不小于0
-          }
-          // 2. 强制更新相关列表(无论当前激活哪个Tab)
-          if (cmd === 'entry') {
-            // 用户进入:从离线列表删除,添加到在线列表
-            this.offlineDisplayList = this.offlineDisplayList.filter(u => u.userId !== user.userId);
-            const newOnlineList = this.onlineDisplayList.filter(u => u.userId !== user.userId);
-            newOnlineList.push(info);
-            this.onlineDisplayList = newOnlineList.slice(-40); // 限制最大50条
-          } else {
-            // 用户离开:从在线列表删除,添加到离线列表
-            this.onlineDisplayList = this.onlineDisplayList.filter(u => u.userId !== user.userId);
-            const newOfflineList = this.offlineDisplayList.filter(u => u.userId !== user.userId);
-            newOfflineList.push(info);
-            this.offlineDisplayList = newOfflineList.slice(-40); // 限制最大50条
-          }
-          // 3. 处理禁言列表(如果用户是禁言状态,无论进出都要同步)
-          if (info.msgStatus === 1) {
-            // 禁言用户:从禁言列表删除旧数据,添加新数据
-            const newSilencedList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
-            newSilencedList.push(info);
-            this.silencedDisplayList = newSilencedList.slice(-40);
-          } else {
-            // 非禁言用户:从禁言列表删除(如果存在)
-            this.silencedDisplayList = this.silencedDisplayList.filter(u => u.userId !== user.userId);
-          }
-        } else if (cmd === 'live_start') {
-
-        } else if (cmd === 'live_end') {
-
-        }
-      }
-    },
-    sendMessage() {
-      // 发送前简单校验
-      if (this.newMsg.trim() === '') {
-        return;
-      }
-
-      let msg = {
-        msg: this.newMsg,
-        liveId: this.liveId,
-        userId: this.userId,
-        userType: 1,
-        cmd: 'sendMsg',
-        avatar: this.$store.state.user.user.avatar,
-        nickName: this.$store.state.user.user.nickName
-      }
-
-      this.socket.send(JSON.stringify(msg))
-
-      this.newMsg = '';
-    },
-    // 初始化滚动监听(在mounted中调用)
-    initScrollListeners() {
-      // 为每个Tab的滚动容器添加监听
-      this.$nextTick(() => {
-        const scrollRefs = {
-          online: this.$refs.manageLeftRef_online,
-          offline: this.$refs.manageLeftRef_offline,
-          silenced: this.$refs.manageLeftRef_silenced
-        };
-
-        Object.keys(scrollRefs).forEach(tabName => {
-          const scrollEl = scrollRefs[tabName]?.wrap;
-          if (scrollEl) {
-            scrollEl.addEventListener('scroll', () =>
-              this.handleTabScroll(tabName, scrollEl)
-            );
-          }
-        });
-      });
-    },
-    handleTabScroll(tabName, scrollEl) {
-      const { scrollTop, scrollHeight, clientHeight } = scrollEl;
-      const bottomThreshold = 50; // 距离底部100px触发下一页
-      const topThreshold = 50;    // 距离顶部100px触发上一页
-
-      // 加载下一页(滚动到底部附近)
-      if (scrollHeight - scrollTop - clientHeight < bottomThreshold) {
-        this.loadNextPage(tabName);
-      }
+  created() {
 
-      // 加载上一页(滚动到顶部附近)
-      if (scrollTop < topThreshold) {
-        this.loadPrevPage(tabName);
-      }
-    },
-  },
-  destroyed() {
-    this.hls?.destroy();
-    clearInterval(this.processInterval)
   }
-}
+};
 </script>
 
-<style scoped>
-.talk-list{
-  display: flex;
+<style>
+body {
+  margin: 0;
+  font-family: 'Arial', sans-serif;
 }
-  .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;
-  }
+.nav {
+  display: flex;
+  background: #1e3a8a;
+  color: white;
+}
 
-  .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;
-  }
+.nav button {
+  padding: 15px 20px;
+  border: none;
+  background: none;
+  color: white;
+  cursor: pointer;
+  font-size: 16px;
+}
 
+.nav button:hover {
+  background: #3b82f6;
+}
 </style>

+ 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/liveCouponIssue/index.vue

@@ -65,7 +65,7 @@
       <el-table-column label="最低消费" align="center" prop="useMinPrice" />
       <el-table-column label="优惠券有效期限(天)" align="center" prop="couponTime" />
       <el-table-column label="总数量" align="center" prop="totalCount" />
-      <el-table-column label="领取数量" align="center" prop="remainCount" />
+      <el-table-column label="领取数量" align="center" prop="remainCount" />
       <el-table-column label="类型" align="center" prop="couponType" >
           <template slot-scope="scope">
               <el-tag prop="couponType" v-for="(item, index) in couponTypeOptions"    v-if="scope.row.couponType==item.dictValue">{{item.dictLabel}}</el-tag>

+ 659 - 0
src/views/live/liveData/index-old.vue

@@ -0,0 +1,659 @@
+<template>
+  <div class="el-container-md">
+    <!-- 数据统计指标展示区域 -->
+    <el-card class="statistics-card" shadow="never">
+      <el-row :gutter="20">
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">累计观看人数</div>
+            <div class="statistics-value">{{ statisticsData.totalViewers || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">
+              累计完课人数
+              <el-popover placement="top" width="200" trigger="click">
+                <div>
+                  <el-input-number v-model="completeWatchTime" :min="0" :precision="0" placeholder="看课时长(分钟)" style="width: 100%;"></el-input-number>
+                  <el-button type="primary" size="mini" style="width: 100%; margin-top: 10px;" @click="updateCompleteWatchTime('total')">确定</el-button>
+                </div>
+                <el-button slot="reference" size="mini" type="text" icon="el-icon-setting" style="margin-left: 5px;">设置</el-button>
+              </el-popover>
+            </div>
+            <div class="statistics-value">{{ statisticsData.totalCompletedCourses || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">直播观看人数</div>
+            <div class="statistics-value">{{ statisticsData.liveViewers || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">
+              直播完课人数
+              <el-popover placement="top" width="200" trigger="click">
+                <div>
+                  <el-input-number v-model="liveCompleteWatchTime" :min="0" :precision="0" placeholder="看课时长(分钟)" style="width: 100%;"></el-input-number>
+                  <el-button type="primary" size="mini" style="width: 100%; margin-top: 10px;" @click="updateCompleteWatchTime('live')">确定</el-button>
+                </div>
+                <el-button slot="reference" size="mini" type="text" icon="el-icon-setting" style="margin-left: 5px;">设置</el-button>
+              </el-popover>
+            </div>
+            <div class="statistics-value">{{ statisticsData.liveCompletedCourses || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">回放观看人数</div>
+            <div class="statistics-value">{{ statisticsData.playbackViewers || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">
+              回放完课人数
+              <el-popover placement="top" width="200" trigger="click">
+                <div>
+                  <el-input-number v-model="replayCompleteWatchTime" :min="0" :precision="0" placeholder="看课时长(分钟)" style="width: 100%;"></el-input-number>
+                  <el-button type="primary" size="mini" style="width: 100%; margin-top: 10px;" @click="updateCompleteWatchTime('replay')">确定</el-button>
+                </div>
+                <el-button slot="reference" size="mini" type="text" icon="el-icon-setting" style="margin-left: 5px;">设置</el-button>
+              </el-popover>
+            </div>
+            <div class="statistics-value">{{ statisticsData.playbackCompletedCourses || 0 }}</div>
+          </div>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20" style="margin-top: 20px;">
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">直播峰值</div>
+            <div class="statistics-value">{{ statisticsData.livePeak || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">直播平均时长</div>
+            <div class="statistics-value">{{ formatDuration(statisticsData.liveAvgDuration) }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">回放平均时长</div>
+            <div class="statistics-value">{{ formatDuration(statisticsData.playbackAvgDuration) }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">GMV</div>
+            <div class="statistics-value">¥{{ statisticsData.gmv || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">付费人数</div>
+            <div class="statistics-value">{{ statisticsData.paidUsers || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">付费单数</div>
+            <div class="statistics-value">{{ statisticsData.paidOrders || 0 }}</div>
+          </div>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20" style="margin-top: 20px;">
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">销量统计</div>
+            <div class="statistics-value">{{ statisticsData.salesCount || 0 }}</div>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 筛选条件区域 -->
+    <el-form :model="queryParams" class="live-data-css" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="直播名称" prop="liveName">
+        <el-input
+          v-model="queryParams.liveName"
+          placeholder="请输入直播名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="观看类型" prop="watchType">
+        <el-select v-model="queryParams.watchType" placeholder="请选择观看类型" clearable size="small">
+          <el-option label="直播" value="live"></el-option>
+          <el-option label="回放" value="replay"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="完课状态" prop="completeStatus">
+        <el-select v-model="queryParams.completeStatus" placeholder="请选择完课状态" clearable size="small">
+          <el-option label="已完课" value="1"></el-option>
+          <el-option label="未完课" value="0"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="付费状态" prop="payStatus">
+        <el-select v-model="queryParams.payStatus" placeholder="请选择付费状态" clearable size="small">
+          <el-option label="已付费" value="1"></el-option>
+          <el-option label="未付费" value="0"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="观看时长" prop="watchDuration">
+        <el-input-number
+          v-model="queryParams.watchDuration"
+          :min="0"
+          :precision="0"
+          placeholder="观看时长(分钟)"
+          size="small"
+        ></el-input-number>
+      </el-form-item>
+      <el-form-item label="时间范围" prop="dateRange">
+        <el-date-picker
+          v-model="dateRange"
+          type="datetimerange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          size="small"
+          value-format="yyyy-MM-dd HH:mm:ss"
+        ></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>
+
+    <!-- 操作工具栏 -->
+    <div class="selection-toolbar">
+      <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
+        {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
+      </el-checkbox>
+      <!--      <el-button plain size="mini" @click="handleAutoTag">自动打标签</el-button>-->
+      <!--      <el-button plain size="mini" @click="handleAutoRemark">自动备注</el-button>-->
+      <el-button plain type="primary" size="mini" icon="el-icon-download" :loading="exportLoading" @click="handleExport">导出</el-button>
+    </div>
+
+    <!-- 数据表格 -->
+    <el-table
+      ref="dataTable"
+      :data="dataList"
+      style="width: 100%"
+      v-loading="loading"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55"></el-table-column>
+      <el-table-column type="index" label="序号" width="55" align="center"></el-table-column>
+      <el-table-column prop="liveName" label="直播间名称" min-width="160" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="liveType" label="直播类型" width="100" align="center">
+        <template slot-scope="scope">
+          <el-tag type="danger" v-if="scope.row.liveType == 1">直播</el-tag>
+          <el-tag type="success" v-else-if="scope.row.liveType == 2">录播</el-tag>
+          <el-tag v-else-if="scope.row.liveType == 3">直播回放</el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="status" label="状态" width="100" align="center">
+        <template slot-scope="scope">
+          <el-tag type="info" v-if="scope.row.status == 1">未开始</el-tag>
+          <el-tag type="warning" v-else-if="scope.row.status == 2">进行中</el-tag>
+          <el-tag type="success" v-else-if="scope.row.status == 3">已结束</el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="startTime" label="开始时间" width="180" align="center"></el-table-column>
+      <el-table-column prop="finishTime" label="结束时间" width="180" align="center"></el-table-column>
+      <el-table-column prop="totalViewers" label="累计观看" width="110" align="center"></el-table-column>
+      <el-table-column prop="liveViewers" label="直播观看" width="110" align="center"></el-table-column>
+      <el-table-column prop="playbackViewers" label="回放观看" width="110" align="center"></el-table-column>
+      <el-table-column prop="liveAvgDuration" label="直播平均时长" width="140" align="center">
+        <template slot-scope="scope">{{ formatDuration(scope.row.liveAvgDuration) }}</template>
+      </el-table-column>
+      <el-table-column prop="playbackAvgDuration" label="回放平均时长" width="140" align="center">
+        <template slot-scope="scope">{{ formatDuration(scope.row.playbackAvgDuration) }}</template>
+      </el-table-column>
+      <el-table-column prop="totalCompletedCourses" label="累计完课" width="110" align="center"></el-table-column>
+      <el-table-column prop="liveCompletedCourses" label="直播完课" width="110" align="center"></el-table-column>
+      <el-table-column prop="playbackCompletedCourses" label="回放完课" width="110" align="center"></el-table-column>
+      <el-table-column prop="gmv" label="GMV" width="120" align="center">
+        <template slot-scope="scope">¥{{ scope.row.gmv || 0 }}</template>
+      </el-table-column>
+      <el-table-column prop="paidUsers" label="付费人数" width="100" align="center"></el-table-column>
+      <el-table-column prop="paidOrders" label="付费单数" width="100" align="center"></el-table-column>
+      <el-table-column prop="salesCount" label="销量统计" width="100" align="center"></el-table-column>
+      <el-table-column label="操作" width="120" fixed="right">
+        <template slot-scope="scope">
+          <el-button type="text" size="small" @click="handleViewDetail(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="getList"
+      style="margin-top: 20px;"
+    />
+
+    <!-- 自动打标签弹窗 -->
+    <el-dialog
+      title="自动打标签"
+      :visible.sync="tagDialogVisible"
+      width="600px"
+      :close-on-click-modal="false"
+    >
+      <el-form :model="tagForm" label-width="120px">
+        <el-form-item label="打标签规则">
+          <el-checkbox-group v-model="tagForm.rules">
+            <el-checkbox label="liveComplete">直播完课</el-checkbox>
+            <el-checkbox label="replayComplete">回放完课</el-checkbox>
+            <el-checkbox label="pay">付费行为</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+        <el-form-item label="标签名称">
+          <el-input v-model="tagForm.tagName" placeholder="请输入标签名称"></el-input>
+        </el-form-item>
+        <el-form-item label="备注格式">
+          <el-radio-group v-model="tagForm.remarkFormat">
+            <el-radio label="time">时间—行为(如:2024-01-01—直播完课)</el-radio>
+            <el-radio label="simple">仅行为(如:直播完课)</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="tagDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmAutoTag">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 自动备注弹窗 -->
+    <el-dialog
+      title="自动备注"
+      :visible.sync="remarkDialogVisible"
+      width="600px"
+      :close-on-click-modal="false"
+    >
+      <el-form :model="remarkForm" label-width="120px">
+        <el-form-item label="备注规则">
+          <el-checkbox-group v-model="remarkForm.rules">
+            <el-checkbox label="liveComplete">直播完课</el-checkbox>
+            <el-checkbox label="replayComplete">回放完课</el-checkbox>
+            <el-checkbox label="pay">付费行为</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+        <el-form-item label="备注格式">
+          <el-radio-group v-model="remarkForm.remarkFormat">
+            <el-radio label="time">时间—行为(如:2024-01-01—直播完课)</el-radio>
+            <el-radio label="simple">仅行为(如:直播完课)</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注位置">
+          <el-radio-group v-model="remarkForm.position">
+            <el-radio :label="1">添加在最前面</el-radio>
+            <el-radio :label="2">添加在最后面</el-radio>
+            <el-radio :label="3">替换所有备注</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="remarkDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmAutoRemark">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listLiveData, exportLiveData, autoTagAndRemark, dashboardData } from "@/api/live/liveData";
+import { batchUpdateExternalContactNotes } from "@/api/qw/externalContact";
+import { addTag } from "@/api/qw/externalContact";
+
+export default {
+  name: "LiveData",
+  data() {
+    return {
+      liveId: '',
+      loading: true,
+      exportLoading: false,
+      showSearch: true,
+      dataList: [],
+      total: 0,
+      multipleSelection: [],
+      allChecked: false,
+      isIndeterminate: false,
+      dateRange: [],
+      // 统计数据
+      statisticsData: {
+        gmv: 0,
+        liveAvgDuration: 0,
+        liveCompletedCourses: 0,
+        livePeak: 0,
+        liveViewers: 0,
+        paidOrders: 0,
+        paidUsers: 0,
+        playbackAvgDuration: 0,
+        playbackCompletedCourses: 0,
+        playbackViewers: 0,
+        salesCount: 0,
+        totalCompletedCourses: 0,
+        totalViewers: 1
+      },
+      // 完课时长设置
+      completeWatchTime: 0,
+      liveCompleteWatchTime: 0,
+      replayCompleteWatchTime: 0,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: null,
+        liveName: null,
+        watchType: null,
+        completeStatus: null,
+        payStatus: null,
+        watchDuration: null,
+        startTime: null,
+        endTime: null
+      },
+      // 打标签弹窗
+      tagDialogVisible: false,
+      tagForm: {
+        rules: [],
+        tagName: '',
+        remarkFormat: 'time'
+      },
+      // 备注弹窗
+      remarkDialogVisible: false,
+      remarkForm: {
+        rules: [],
+        remarkFormat: 'time',
+        position: 1
+      }
+    };
+  },
+  created() {
+    this.liveId = this.$route.query.liveId;
+    this.queryParams.liveId = this.liveId;
+
+    this.getList();
+  },
+  methods: {
+    /** 获取统计数据 */
+    getStatistics() {
+      if (!this.liveId) return;
+      dashboardData(this.liveId).then(response => {
+        if (response.code === 200) {
+          this.statisticsData = response.data || {};
+        }
+      });
+    },
+    /** 获取列表数据 */
+    getList() {
+      this.loading = true;
+      // 处理时间范围
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.startTime = this.dateRange[0];
+        this.queryParams.endTime = this.dateRange[1];
+      } else {
+        this.queryParams.startTime = null;
+        this.queryParams.endTime = null;
+      }
+      listLiveData(this.queryParams).then(response => {
+        if (response.code === 200) {
+          this.statisticsData = response.data || {};
+          this.dataList = response.list || [];
+          this.total = response.total || 0;
+        }
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRange = [];
+      this.$refs.queryForm.resetFields();
+      this.handleQuery();
+    },
+    /** 全选或取消全选 */
+    toggleSelectAll(val) {
+      if (val) {
+        this.toggleSelection(this.dataList);
+      } else {
+        this.toggleSelection();
+      }
+    },
+    toggleSelection(rows) {
+      if (rows) {
+        rows.forEach(row => {
+          this.$refs.dataTable.toggleRowSelection(row);
+        });
+      } else {
+        this.$refs.dataTable.clearSelection();
+      }
+    },
+    /** 多选框选中数据 */
+    handleSelectionChange(val) {
+      this.multipleSelection = val;
+      this.allChecked = val.length === this.dataList.length;
+      this.isIndeterminate = val.length > 0 && val.length < this.dataList.length;
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.startTime = this.dateRange[0];
+        this.queryParams.endTime = this.dateRange[1];
+      } else {
+        this.queryParams.startTime = null;
+        this.queryParams.endTime = null;
+      }
+      this.$confirm('是否确认导出所有直播数据?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportLiveData(this.queryParams);
+      }).then(response => {
+        if (response.code === 200) {
+          this.download(response.msg);
+        }
+        this.exportLoading = false;
+      }).catch(() => {
+        this.exportLoading = false;
+      });
+    },
+    /** 自动打标签 */
+    // handleAutoTag() {
+    //   if (this.multipleSelection.length === 0) {
+    //     this.$message.warning('请选择要打标签的数据');
+    //     return;
+    //   }
+    //   this.tagDialogVisible = true;
+    // },
+    /** 确认自动打标签 */
+    confirmAutoTag() {
+      if (this.tagForm.rules.length === 0) {
+        this.$message.warning('请选择打标签规则');
+        return;
+      }
+      const userIds = this.multipleSelection.map(item => item.userId).filter(id => id);
+      if (userIds.length === 0) {
+        this.$message.warning('所选数据中没有有效的用户ID');
+        return;
+      }
+      const data = {
+        userIds: userIds,
+        rules: this.tagForm.rules,
+        tagName: this.tagForm.tagName,
+        remarkFormat: this.tagForm.remarkFormat,
+        liveId: this.liveId
+      };
+      autoTagAndRemark(data).then(response => {
+        if (response.code === 200) {
+          this.$message.success('打标签成功');
+          this.tagDialogVisible = false;
+          this.tagForm = {
+            rules: [],
+            tagName: '',
+            remarkFormat: 'time'
+          };
+          this.getList();
+        }
+      });
+    },
+    /** 自动备注 */
+    // handleAutoRemark() {
+    //   if (this.multipleSelection.length === 0) {
+    //     this.$message.warning('请选择要备注的数据');
+    //     return;
+    //   }
+    //   this.remarkDialogVisible = true;
+    // },
+    /** 确认自动备注 */
+    confirmAutoRemark() {
+      if (this.remarkForm.rules.length === 0) {
+        this.$message.warning('请选择备注规则');
+        return;
+      }
+      const userIds = this.multipleSelection.map(item => item.userId).filter(id => id);
+      if (userIds.length === 0) {
+        this.$message.warning('所选数据中没有有效的用户ID');
+        return;
+      }
+      // 生成备注内容
+      let notes = '';
+      const now = new Date();
+      const dateStr = now.getFullYear() + '-' +
+        String(now.getMonth() + 1).padStart(2, '0') + '-' +
+        String(now.getDate()).padStart(2, '0');
+
+      this.remarkForm.rules.forEach((rule, index) => {
+        let ruleText = '';
+        if (rule === 'liveComplete') {
+          ruleText = '直播完课';
+        } else if (rule === 'replayComplete') {
+          ruleText = '回放完课';
+        } else if (rule === 'pay') {
+          ruleText = '付费行为';
+        }
+
+        if (this.remarkForm.remarkFormat === 'time') {
+          notes += (index > 0 ? ';' : '') + dateStr + '—' + ruleText;
+        } else {
+          notes += (index > 0 ? ';' : '') + ruleText;
+        }
+      });
+
+      const data = {
+        userIds: userIds,
+        notes: notes,
+        type: this.remarkForm.position,
+        nameType: 3, // 不添加客户名称
+        filter: false
+      };
+
+      batchUpdateExternalContactNotes(data).then(response => {
+        if (response.code === 200) {
+          this.$message.success('备注成功');
+          this.remarkDialogVisible = false;
+          this.remarkForm = {
+            rules: [],
+            remarkFormat: 'time',
+            position: 1
+          };
+          this.getList();
+        }
+      });
+    },
+    /** 查看详情 */
+    handleViewDetail(row) {
+      // 可以跳转到详情页面或打开详情弹窗
+      this.$message.info('查看详情功能待实现');
+    },
+    /** 格式化时长 */
+    formatDuration(seconds) {
+      if (!seconds) return '00:00:00';
+      const hours = Math.floor(seconds / 3600);
+      const minutes = Math.floor((seconds % 3600) / 60);
+      const secs = seconds % 60;
+      return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
+    },
+    /** 更新完课时长设置 */
+    updateCompleteWatchTime(type) {
+      // 这里可以调用API保存设置
+      let watchTime = 0;
+      if (type === 'total') {
+        watchTime = this.completeWatchTime;
+      } else if (type === 'live') {
+        watchTime = this.liveCompleteWatchTime;
+      } else if (type === 'replay') {
+        watchTime = this.replayCompleteWatchTime;
+      }
+      // 更新查询参数并重新获取数据
+      this.queryParams.completeWatchTime = watchTime;
+      // this.getStatistics();
+      this.getList();
+      this.$message.success('设置成功');
+    }
+  }
+};
+</script>
+
+<style scoped>
+.statistics-card {
+  margin-bottom: 20px;
+}
+
+.statistics-item {
+  text-align: center;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+}
+
+.statistics-title {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.statistics-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.selection-toolbar {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  padding-left: 10px;
+}
+
+.selection-toolbar .el-checkbox {
+  margin-right: 10px;
+}
+
+.live-data-css {
+  padding-left: 10px;
+  padding-top: 30px;
+}
+</style>

+ 976 - 727
src/views/live/liveData/index.vue

@@ -1,760 +1,1009 @@
 <template>
-  <div class="app-container">
-    <div class="title">近期直播</div>
-    <div class="live-container">
-      <div class="live-card" v-for="live in displayLives" :key="live.id">
-        <!-- 直播状态 -->
-        <div class="status">已结束</div>
-
-        <!-- 直播信息 -->
-        <div class="content">
-          <img :src="live.image" alt="直播封面" class="cover-image" />
-          <div class="info">
-            <h3 class="live-title">{{ live.title }}</h3>
-            <p class="time">开播时间: {{ live.time }}</p>
+  <div class="el-container-md">
+    <!-- 数据统计指标展示区域 -->
+    <el-card class="statistics-card" shadow="never">
+      <el-row :gutter="20">
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">累计观看人数</div>
+            <div class="statistics-value">{{ statisticsData.totalViewers || 0 }}</div>
           </div>
-        </div>
-
-        <!-- 直播数据 -->
-        <div class="stats">
-          <div class="stat-item">
-            <p class="label">直播间浏览量</p>
-            <p class="value">{{ live.views }}</p>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">
+              累计完课人数
+              <el-popover placement="top" width="200" trigger="click">
+                <div>
+                  <el-input-number v-model="completeWatchTime" :min="0" :precision="0" placeholder="看课时长(分钟)" style="width: 100%;"></el-input-number>
+                  <el-button type="primary" size="mini" style="width: 100%; margin-top: 10px;" @click="updateCompleteWatchTime('total')">确定</el-button>
+                </div>
+                <el-button slot="reference" size="mini" type="text" icon="el-icon-setting" style="margin-left: 5px;">设置</el-button>
+              </el-popover>
+            </div>
+            <div class="statistics-value">{{ statisticsData.totalCompletedCourses || 0 }}</div>
           </div>
-          <div class="stat-item">
-            <p class="label">直播间访客数</p>
-            <p class="value">{{ live.visitors }}</p>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">直播观看人数</div>
+            <div class="statistics-value">{{ statisticsData.liveViewers || 0 }}</div>
           </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
-            v-model="selectedDate"
-            :type="datePickerType"
-            :format="selectedTimeRange === 'week' ? weekRange : dateFormat"
-            :value-format="valueFormat"
-            placeholder="选择日期"
-            style="width: 280px"
-            @change="changeDate"
-          ></el-date-picker>
-        </div>
-      </div>
-
-      <div class="trend-cards">
-        <div
-          class="trend-card"
-          v-for="item in trendData"
-          :key="item.id"
-          :class="{ 'active': selectedMetric.id === item.id }"
-          @click="changeMetric(item)"
-        >
-          <div class="trend-header">
-            <p class="trend-title">{{ item.title }}</p>
-            <el-tooltip class="item" effect="dark" :content="item.tip" placement="top">
-              <i class="el-icon-info"></i>
-            </el-tooltip>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">
+              直播完课人数
+              <el-popover placement="top" width="200" trigger="click">
+                <div>
+                  <el-input-number v-model="liveCompleteWatchTime" :min="0" :precision="0" placeholder="看课时长(分钟)" style="width: 100%;"></el-input-number>
+                  <el-button type="primary" size="mini" style="width: 100%; margin-top: 10px;" @click="updateCompleteWatchTime('live')">确定</el-button>
+                </div>
+                <el-button slot="reference" size="mini" type="text" icon="el-icon-setting" style="margin-left: 5px;">设置</el-button>
+              </el-popover>
+            </div>
+            <div class="statistics-value">{{ statisticsData.liveCompletedCourses || 0 }}</div>
           </div>
-          <p class="trend-value">{{ item.value }}</p>
-          <p class="trend-change" :class="{ 'up': computedChange(item) > 0, 'down': computedChange(item) < 0 }">
-            {{ changeLabel }} {{ computedChange(item) }}%
-          </p>
-        </div>
-      </div>
-
-      <!-- 直播趋势折线图 -->
-      <div id="liveChart" class="chart"></div>
-    </div>
-    <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>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">回放观看人数</div>
+            <div class="statistics-value">{{ statisticsData.playbackViewers || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">
+              回放完课人数
+              <el-popover placement="top" width="200" trigger="click">
+                <div>
+                  <el-input-number v-model="replayCompleteWatchTime" :min="0" :precision="0" placeholder="看课时长(分钟)" style="width: 100%;"></el-input-number>
+                  <el-button type="primary" size="mini" style="width: 100%; margin-top: 10px;" @click="updateCompleteWatchTime('replay')">确定</el-button>
+                </div>
+                <el-button slot="reference" size="mini" type="text" icon="el-icon-setting" style="margin-left: 5px;">设置</el-button>
+              </el-popover>
             </div>
+            <div class="statistics-value">{{ statisticsData.playbackCompletedCourses || 0 }}</div>
+          </div>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20" style="margin-top: 20px;">
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">直播峰值</div>
+            <div class="statistics-value">{{ statisticsData.livePeak || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">直播平均时长</div>
+            <div class="statistics-value">{{ formatDuration(statisticsData.liveAvgDuration) }}</div>
           </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">回放平均时长</div>
+            <div class="statistics-value">{{ formatDuration(statisticsData.playbackAvgDuration) }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">GMV</div>
+            <div class="statistics-value">¥{{ statisticsData.gmv || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">付费人数</div>
+            <div class="statistics-value">{{ statisticsData.paidUsers || 0 }}</div>
+          </div>
+        </el-col>
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">付费单数</div>
+            <div class="statistics-value">{{ statisticsData.paidOrders || 0 }}</div>
+          </div>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20" style="margin-top: 20px;">
+        <el-col :span="4">
+          <div class="statistics-item">
+            <div class="statistics-title">销量统计</div>
+            <div class="statistics-value">{{ statisticsData.salesCount || 0 }}</div>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 筛选条件区域 -->
+    <el-form :model="queryParams" class="live-data-css" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="直播名称" prop="liveName">
+        <el-input
+          v-model="queryParams.liveName"
+          placeholder="请输入直播名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="观看类型" prop="watchType">
+        <el-select v-model="queryParams.watchType" placeholder="请选择观看类型" clearable size="small">
+          <el-option label="直播" value="live"></el-option>
+          <el-option label="回放" value="replay"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="完课状态" prop="completeStatus">
+        <el-select v-model="queryParams.completeStatus" placeholder="请选择完课状态" clearable size="small">
+          <el-option label="已完课" value="1"></el-option>
+          <el-option label="未完课" value="0"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="付费状态" prop="payStatus">
+        <el-select v-model="queryParams.payStatus" placeholder="请选择付费状态" clearable size="small">
+          <el-option label="已付费" value="1"></el-option>
+          <el-option label="未付费" value="0"></el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="观看时长" prop="watchDuration">
+        <el-input-number
+          v-model="queryParams.watchDuration"
+          :min="0"
+          :precision="0"
+          placeholder="观看时长(分钟)"
+          size="small"
+        ></el-input-number>
+      </el-form-item>
+      <el-form-item label="开始时间范围" prop="dateRange">
+        <el-date-picker
+          v-model="dateRange"
+          type="datetimerange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          size="small"
+          value-format="yyyy-MM-dd HH:mm:ss"
+        ></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>
+
+    <!-- 操作工具栏 -->
+    <div class="selection-toolbar">
+      <el-checkbox :indeterminate="isIndeterminate" v-model="allChecked" @change="toggleSelectAll">
+        {{ multipleSelection.length > 0 ? `已选 ${multipleSelection.length} 条` : '选中本页' }}
+      </el-checkbox>
+      <!--      <el-button plain size="mini" @click="handleAutoTag">自动打标签</el-button>-->
+      <!--      <el-button plain size="mini" @click="handleAutoRemark">自动备注</el-button>-->
+      <el-button plain type="primary" size="mini" icon="el-icon-download" :loading="exportLoading" @click="handleExport">导出</el-button>
+    </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>
+    <!-- 数据表格 -->
+    <el-table
+      ref="dataTable"
+      :data="dataList"
+      style="width: 100%"
+      v-loading="loading"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55"></el-table-column>
+      <el-table-column type="index" label="序号" width="55" align="center"></el-table-column>
+      <el-table-column prop="liveName" label="直播间名称" min-width="160" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="liveType" label="直播类型" width="100" align="center">
+        <template slot-scope="scope">
+          <el-tag type="danger" v-if="scope.row.liveType == 1">直播</el-tag>
+          <el-tag type="success" v-else-if="scope.row.liveType == 2">录播</el-tag>
+          <el-tag v-else-if="scope.row.liveType == 3">直播回放</el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="status" label="状态" width="100" align="center">
+        <template slot-scope="scope">
+          <el-tag type="info" v-if="scope.row.status == 1">未开始</el-tag>
+          <el-tag type="warning" v-else-if="scope.row.status == 2">进行中</el-tag>
+          <el-tag type="success" v-else-if="scope.row.status == 3">已结束</el-tag>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="startTime" label="开始时间" width="180" align="center"></el-table-column>
+      <el-table-column prop="finishTime" label="结束时间" width="180" align="center"></el-table-column>
+      <el-table-column prop="totalViewers" label="累计观看" width="110" align="center"></el-table-column>
+      <el-table-column prop="liveViewers" label="直播观看" width="110" align="center"></el-table-column>
+      <el-table-column prop="playbackViewers" label="回放观看" width="110" align="center"></el-table-column>
+      <el-table-column prop="liveAvgDuration" label="直播平均时长" width="140" align="center">
+        <template slot-scope="scope">{{ formatDuration(scope.row.liveAvgDuration) }}</template>
+      </el-table-column>
+      <el-table-column prop="playbackAvgDuration" label="回放平均时长" width="140" align="center">
+        <template slot-scope="scope">{{ formatDuration(scope.row.playbackAvgDuration) }}</template>
+      </el-table-column>
+      <el-table-column prop="totalCompletedCourses" label="累计完课" width="110" align="center"></el-table-column>
+      <el-table-column prop="liveCompletedCourses" label="直播完课" width="110" align="center"></el-table-column>
+      <el-table-column prop="playbackCompletedCourses" label="回放完课" width="110" align="center"></el-table-column>
+      <el-table-column prop="gmv" label="GMV" width="120" align="center">
+        <template slot-scope="scope">¥{{ scope.row.gmv || 0 }}</template>
+      </el-table-column>
+      <el-table-column prop="paidUsers" label="付费人数" width="100" align="center"></el-table-column>
+      <el-table-column prop="paidOrders" label="付费单数" width="100" align="center"></el-table-column>
+      <el-table-column prop="salesCount" label="销量统计" width="100" align="center"></el-table-column>
+      <el-table-column label="操作" width="120" fixed="right">
+        <template slot-scope="scope">
+          <el-button type="text" size="small" @click="handleViewDetail(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="getList"
+      style="margin-top: 20px;"
+    />
+
+    <!-- 自动打标签弹窗 -->
+    <el-dialog
+      title="自动打标签"
+      :visible.sync="tagDialogVisible"
+      width="600px"
+      :close-on-click-modal="false"
+    >
+      <el-form :model="tagForm" label-width="120px">
+        <el-form-item label="打标签规则">
+          <el-checkbox-group v-model="tagForm.rules">
+            <el-checkbox label="liveComplete">直播完课</el-checkbox>
+            <el-checkbox label="replayComplete">回放完课</el-checkbox>
+            <el-checkbox label="pay">付费行为</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+        <el-form-item label="标签名称">
+          <el-input v-model="tagForm.tagName" placeholder="请输入标签名称"></el-input>
+        </el-form-item>
+        <el-form-item label="备注格式">
+          <el-radio-group v-model="tagForm.remarkFormat">
+            <el-radio label="time">时间—行为(如:2024-01-01—直播完课)</el-radio>
+            <el-radio label="simple">仅行为(如:直播完课)</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="tagDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmAutoTag">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 自动备注弹窗 -->
+    <el-dialog
+      title="自动备注"
+      :visible.sync="remarkDialogVisible"
+      width="600px"
+      :close-on-click-modal="false"
+    >
+      <el-form :model="remarkForm" label-width="120px">
+        <el-form-item label="备注规则">
+          <el-checkbox-group v-model="remarkForm.rules">
+            <el-checkbox label="liveComplete">直播完课</el-checkbox>
+            <el-checkbox label="replayComplete">回放完课</el-checkbox>
+            <el-checkbox label="pay">付费行为</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+        <el-form-item label="备注格式">
+          <el-radio-group v-model="remarkForm.remarkFormat">
+            <el-radio label="time">时间—行为(如:2024-01-01—直播完课)</el-radio>
+            <el-radio label="simple">仅行为(如:直播完课)</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注位置">
+          <el-radio-group v-model="remarkForm.position">
+            <el-radio :label="1">添加在最前面</el-radio>
+            <el-radio :label="2">添加在最后面</el-radio>
+            <el-radio :label="3">替换所有备注</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="remarkDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmAutoRemark">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 详情侧边栏 -->
+    <el-drawer
+      title="直播间详情"
+      :visible.sync="detailDrawerVisible"
+      direction="rtl"
+      size="60%"
+      :before-close="closeDetailDrawer"
+    >
+      <div v-if="!showUserDetail" class="detail-content">
+        <el-card shadow="never" style="margin-bottom: 20px;">
+          <div slot="header">
+            <span>总体数据</span>
+            <el-button style="float: right; padding: 3px 0" type="text" @click="handleViewUserDetail">查看用户详情</el-button>
           </div>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">视频时长:</span>
+                <span class="detail-value">{{ formatDuration(detailData.videoDuration || 0) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">累计观看人数:</span>
+                <span class="detail-value">{{ detailData.totalViewers || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">累计完课人数:</span>
+                <span class="detail-value">{{ detailData.totalCompletedCourses || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">到课完课率:</span>
+                <span class="detail-value">{{ formatPercent(detailData.totalCompletionRate) }}</span>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+
+        <el-card shadow="never" style="margin-bottom: 20px;">
+          <div slot="header">直播数据</div>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">直播观看人数:</span>
+                <span class="detail-value">{{ detailData.liveViewers || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">>20分钟人数(直播):</span>
+                <span class="detail-value">{{ detailData.liveOver20Minutes || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">>30分钟人数(直播):</span>
+                <span class="detail-value">{{ detailData.liveOver30Minutes || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">到课完课率直播(>20分钟):</span>
+                <span class="detail-value">{{ formatPercent(detailData.liveCompletionRate20) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">到课完课率直播(>30分钟):</span>
+                <span class="detail-value">{{ formatPercent(detailData.liveCompletionRate30) }}</span>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+
+        <el-card shadow="never" style="margin-bottom: 20px;">
+          <div slot="header">回放数据</div>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">回放观看人数:</span>
+                <span class="detail-value">{{ detailData.playbackViewers || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">>20分钟人数(回放):</span>
+                <span class="detail-value">{{ detailData.playbackOver20Minutes || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">>30分钟人数(回放):</span>
+                <span class="detail-value">{{ detailData.playbackOver30Minutes || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">到课完课率回放(>20分钟):</span>
+                <span class="detail-value">{{ formatPercent(detailData.playbackCompletionRate20) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">到课完课率回放(>30分钟):</span>
+                <span class="detail-value">{{ formatPercent(detailData.playbackCompletionRate30) }}</span>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+
+        <el-card shadow="never" style="margin-bottom: 20px;">
+          <div slot="header">时长统计</div>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">直播峰值:</span>
+                <span class="detail-value">{{ detailData.livePeak || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">直播平均时长:</span>
+                <span class="detail-value">{{ formatDuration(detailData.liveAvgDuration || 0) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">回放平均时长:</span>
+                <span class="detail-value">{{ formatDuration(detailData.playbackAvgDuration || 0) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">回放完播率:</span>
+                <span class="detail-value">{{ formatPercent(detailData.playbackFinishRate) }}</span>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+
+        <el-card shadow="never" style="margin-bottom: 20px;">
+          <div slot="header">订单数据</div>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">GMV:</span>
+                <span class="detail-value">{{ formatMoney(detailData.gmv) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">付费人数:</span>
+                <span class="detail-value">{{ detailData.paidUsers || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">付费单数:</span>
+                <span class="detail-value">{{ detailData.paidOrders || 0 }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">峰值转化率:</span>
+                <span class="detail-value">{{ formatPercent(detailData.peakConversionRate) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">总到课转化率:</span>
+                <span class="detail-value">{{ formatPercent(detailData.totalViewerConversionRate) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">30min完课转化率:</span>
+                <span class="detail-value">{{ formatPercent(detailData.completion30MinConversionRate) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">峰值R值:</span>
+                <span class="detail-value">{{ formatMoney(detailData.peakRValue) }}</span>
+              </div>
+            </el-col>
+            <el-col :span="12">
+              <div class="detail-item">
+                <span class="detail-label">完课R值:</span>
+                <span class="detail-value">{{ formatMoney(detailData.completionRValue) }}</span>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+
+        <el-card shadow="never" v-if="detailData.productSalesList && detailData.productSalesList.length > 0">
+          <div slot="header">单品销量统计</div>
+          <el-table :data="detailData.productSalesList" border>
+            <el-table-column prop="productName" label="商品名称" min-width="200"></el-table-column>
+            <el-table-column prop="salesCount" label="销量" width="100" align="center"></el-table-column>
+            <el-table-column prop="salesAmount" label="销售额" width="150" align="center">
+              <template slot-scope="scope">
+                {{ formatMoney(scope.row.salesAmount) }}
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
       </div>
-      <el-table :data="top10List" border style="width: 100%">
-        <el-table-column prop="rank" label="排名" width="80"></el-table-column>
-        <el-table-column prop="name" label="直播名称">
-          <template #default="scope">
-            <div class="live-name">
-              <img :src="scope.row.cover" class="live-cover" alt="封面">
-              <span class="live-title">{{ scope.row.name }}</span>
-            </div>
-          </template>
-        </el-table-column>
-        <el-table-column prop="pv" label="直播间浏览量(PV)"></el-table-column>
-        <el-table-column prop="uv" label="直播间访客数(UV)"></el-table-column>
-        <el-table-column prop="watchPV" label="累计观看人次(PV)"></el-table-column>
-        <el-table-column prop="watchUV" label="累计观看人数(UV)"></el-table-column>
-        <el-table-column prop="avgTime" label="人均观看时长"></el-table-column>
-        <el-table-column prop="maxOnline" label="最高在线人数"></el-table-column>
-      </el-table>
-    </div>
+
+      <!-- 用户详情列表 -->
+      <div v-else class="user-detail-content">
+        <div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
+          <el-button type="text" icon="el-icon-arrow-left" @click="showUserDetail = false">返回</el-button>
+          <el-button type="primary" size="small" icon="el-icon-download" :loading="userDetailExportLoading" @click="handleExportUserDetail">导出用户详情</el-button>
+        </div>
+        <el-table
+          :data="userDetailList"
+          v-loading="userDetailLoading"
+          border
+          style="width: 100%"
+        >
+          <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
+          <el-table-column prop="userName" label="用户名称" min-width="120"></el-table-column>
+          <el-table-column prop="liveWatchDuration" label="直播观看时长" width="140" align="center">
+            <template slot-scope="scope">
+              {{ formatDuration(scope.row.liveWatchDuration || 0) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="playbackWatchDuration" label="回放观看时长" width="140" align="center">
+            <template slot-scope="scope">
+              {{ formatDuration(scope.row.playbackWatchDuration || 0) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="orderCount" label="订单数" width="100" align="center"></el-table-column>
+          <el-table-column prop="orderAmount" label="订单金额" width="120" align="center">
+            <template slot-scope="scope">
+              {{ formatMoney(scope.row.orderAmount) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="companyName" label="分公司" min-width="150"></el-table-column>
+          <el-table-column prop="salesName" label="销售" min-width="120"></el-table-column>
+        </el-table>
+      </div>
+    </el-drawer>
   </div>
 </template>
 
-
 <script>
-  import * as echarts from "echarts";
-  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 {
-        top10List: [
-          {
-            rank: 1,
-            name: "御君方年末会员福利专场!",
-            cover:"https://cos.his.cdwjyyh.com/fs/20250304/710ea5b1896749b58438b76baf881d05.jpeg",
-            pv: 88332,
-            uv: 32674,
-            watchPV: 41866,
-            watchUV: 24714,
-            avgTime: "00:13:12",
-            maxOnline: 4317,
-          },
-          {
-            rank: 2,
-            name: "立秋养生大作战",
-            cover:"https://cos.his.cdwjyyh.com/fs/20250304/710ea5b1896749b58438b76baf881d05.jpeg",
-            pv: 55092,
-            uv: 15066,
-            watchPV: 33522,
-            watchUV: 12343,
-            avgTime: "01:18:30",
-            maxOnline: 3160,
-          },
-          {
-            rank: 3,
-            name: "寒露养生大挑战:防寒防燥防疾病",
-            cover:"https://cos.his.cdwjyyh.com/fs/20250304/710ea5b1896749b58438b76baf881d05.jpeg",
-            pv: 36044,
-            uv: 12205,
-            watchPV: 26056,
-            watchUV: 11086,
-            avgTime: "01:16:08",
-            maxOnline: 3232,
-          },
-        ],
-        selectedRank: 'highFlow',
-        redRankTypes: [
-          { label: '高流量', value: 'highFlow' },
-          { label: '高观看', value: 'highView' },
-          { label: '高时长', value: 'highTime' },
-        ],
-        blackRankTypes: [
-          { label: '低时长', value: 'lowTime' },
-          { label: '低观看', value: 'lowView' },
-          { label: '低流量', value: 'lowFlow' },
-        ],
-        selectedTimeRange: "day", // 默认选中“自然天”
-        datePickerType: "date", // 默认 date 类型
-        weekRange: "", // 存储选择周的时间范围
-        selectedDate: new Date().toISOString().split("T")[0], // 默认今天
-        lives: [
-          {
-            id: 1,
-            image: "https://cos.his.cdwjyyh.com/fs/20250117/d9e3b268e3834a2497e0bb1f900eac90.jpg",
-            title: "李悦的直播",
-            time: "2024-10-10 15:00:00",
-            views: 352821,
-            visitors: 14505,
-          },
-          {
-            id: 2,
-            image: "https://cos.his.cdwjyyh.com/fs/20250117/d9e3b268e3834a2497e0bb1f900eac90.jpg",
-            title: "小付的直播",
-            time: "2024-10-12 18:00:00",
-            views: 284210,
-            visitors: 10234,
-          },
-          {
-            id: 3,
-            image: "https://cos.his.cdwjyyh.com/fs/20250117/d9e3b268e3834a2497e0bb1f900eac90.jpg",
-            title: "生活妙招分享",
-            time: "2024-11-05 20:00:00",
-            views: 182401,
-            visitors: 8734,
-          },
-          {
-            id: 4,
-            image: "https://cos.his.cdwjyyh.com/fs/20250117/d9e3b268e3834a2497e0bb1f900eac90.jpg",
-            title: "家庭健康指南",
-            time: "2024-12-01 14:00:00",
-            views: 254321,
-            visitors: 12345,
-          },
-          {
-            id: 5,
-            image: "https://cos.his.cdwjyyh.com/fs/20250117/d9e3b268e3834a2497e0bb1f900eac90.jpg",
-            title: "额外的直播",
-            time: "2024-12-15 19:00:00",
-            views: 100000,
-            visitors: 5000,
-          }
-        ],
-        trendData: [
-          {
-            id: "views",
-            title: "直播间浏览量",
-            tip: "直播间总浏览量",
-            value: 352421,
-            dailyChange:1,
-            weeklyChange:2,
-            monthlyChange:3,
-            change: 5.2,
-            dates: ["02-02", "02-04", "02-06", "02-08", "02-10"],
-            data: [10, 20, 15, 25, 30],
-          },
-          {
-            id: "visitors",
-            title: "直播间访客数",
-            tip: "直播间访客数",
-            value: 14505,
-            dailyChange:1,
-            weeklyChange:2,
-            monthlyChange:3,
-            change: 5.2,
-            dates: ["02-02", "02-04", "02-06", "02-08", "02-10"],
-            data: [5, 8, 6, 10, 12],
-          },
-          {
-            id: "streams",
-            title: "创建直播数",
-            tip: "每日直播场次",
-            value: 20,
-            dailyChange:1,
-            weeklyChange:2,
-            monthlyChange:3,
-            change: 5.2,
-            dates: ["02-02", "02-04", "02-06", "02-08", "02-10"],
-            data: [1, 2, 1, 3, 2],
-          },
-          {
-            id: "pv",
-            title: "累计观看人次(PV)",
-            tip: "累计观看人次",
-            value: 5000,
-            dailyChange:1,
-            weeklyChange:2,
-            monthlyChange:3,
-            change: 5.2,
-            dates: ["02-02", "02-04", "02-06", "02-08", "02-10"],
-            data: [0, 0, 1, 0, 0],
-          },
-          {
-            id: "uv",
-            title: "累计观看人数(UV)",
-            tip: "累计观看人数",
-            value: 3000,
-            dailyChange:1,
-            weeklyChange:2,
-            monthlyChange:3,
-            change: 5.2,
-            dates: ["02-02", "02-04", "02-06", "02-08", "02-10"],
-            data: [0, 0, 0, 1, 0],
-          },
-        ],
-        selectedMetric: {}, // 选中的卡片数据
-        chart: null, // ECharts 实例
-      };
-    },
-    computed: {
-      changeLabel() {
-        switch (this.selectedTimeRange) {
-          case 'day': return '比前一天';
-          case 'week': return '比上周';
-          case 'month': return '比上月';
-          default: return '变化';
-        }
+import { listLiveData, exportLiveData, autoTagAndRemark, dashboardData, getLiveDataDetailBySql, getLiveUserDetailListBySql, exportLiveUserDetail } from "@/api/live/liveData";
+import { batchUpdateExternalContactNotes } from "@/api/qw/externalContact";
+import { addTag } from "@/api/qw/externalContact";
+
+export default {
+  name: "LiveData",
+  data() {
+    return {
+      liveId: '',
+      loading: true,
+      exportLoading: false,
+      showSearch: true,
+      dataList: [],
+      total: 0,
+      multipleSelection: [],
+      allChecked: false,
+      isIndeterminate: false,
+      dateRange: [],
+      // 统计数据
+      statisticsData: {
+        gmv: 0,
+        liveAvgDuration: 0,
+        liveCompletedCourses: 0,
+        livePeak: 0,
+        liveViewers: 0,
+        paidOrders: 0,
+        paidUsers: 0,
+        playbackAvgDuration: 0,
+        playbackCompletedCourses: 0,
+        playbackViewers: 0,
+        salesCount: 0,
+        totalCompletedCourses: 0,
+        totalViewers: 1
       },
-      displayLives() {
-        return this.lives.slice(0, 4); // 最多只显示前 4 条数据
+      // 完课时长设置
+      completeWatchTime: 0,
+      liveCompleteWatchTime: 0,
+      replayCompleteWatchTime: 0,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        liveId: null,
+        liveName: null,
+        watchType: null,
+        completeStatus: null,
+        payStatus: null,
+        watchDuration: null,
+        startTime: null,
+        endTime: null
       },
-      /*datePickerType() {
-        return this.selectedTimeRange === "day"
-          ? "date"
-          : this.selectedTimeRange === "week"
-            ? "week"
-            : "month";
-      },*/
-      dateFormat() {
-        return this.selectedTimeRange === "day"
-          ? "yyyy-MM-dd"
-          : this.selectedTimeRange === "week"
-            ? "yyyy-MM-dd 至 yyyy-MM-dd"
-            : "yyyy-MM";
+      // 打标签弹窗
+      tagDialogVisible: false,
+      tagForm: {
+        rules: [],
+        tagName: '',
+        remarkFormat: 'time'
       },
-      valueFormat() {
-        return this.selectedTimeRange === "day"
-          ? "yyyy-MM-dd"
-          : this.selectedTimeRange === "week"
-            ? "yyyy-MM-dd"
-            : "yyyy-MM";
+      // 备注弹窗
+      remarkDialogVisible: false,
+      remarkForm: {
+        rules: [],
+        remarkFormat: 'time',
+        position: 1
       },
+      // 详情侧边栏
+      detailDrawerVisible: false,
+      currentLiveId: null,
+      detailData: {},
+      userDetailList: [],
+      userDetailLoading: false,
+      showUserDetail: false,
+      userDetailExportLoading: false
+    };
+  },
+  created() {
+    this.liveId = this.$route.query.liveId;
+    this.queryParams.liveId = this.liveId;
+
+    this.getList();
+  },
+  methods: {
+    /** 获取统计数据 */
+    getStatistics() {
+      if (!this.liveId) return;
+      dashboardData(this.liveId).then(response => {
+        if (response.code === 200) {
+          this.statisticsData = response.data || {};
+        }
+      });
     },
-    created() {
-      //this.getList();
+    /** 获取列表数据 */
+    getList() {
+      this.loading = true;
+      // 处理时间范围
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.startTime = this.dateRange[0];
+        this.queryParams.endTime = this.dateRange[1];
+      } else {
+        this.queryParams.startTime = null;
+        this.queryParams.endTime = null;
+      }
+      listLiveData(this.queryParams).then(response => {
+        if (response.code === 200) {
+          this.statisticsData = response.data || {};
+          this.dataList = response.list || [];
+          this.total = response.total || 0;
+        }
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      });
     },
-    mounted() {
-      this.selectedMetric = this.trendData[0]; // 默认选中第一个
-      this.initChart(); // 初始化折线图
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
     },
-    methods: {
-      changeDate(value) {
-        if (this.selectedTimeRange === "week" && value) {
-          /*console.log("🟢 监听到 selectedWeek 变化:", newVal);*/
-          this.weekRange = this.getWeekRange(value);
-        } else {
-          this.weekRange = "";
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRange = [];
+      this.$refs.queryForm.resetFields();
+      this.handleQuery();
+    },
+    /** 全选或取消全选 */
+    toggleSelectAll(val) {
+      if (val) {
+        this.toggleSelection(this.dataList);
+      } else {
+        this.toggleSelection();
+      }
+    },
+    toggleSelection(rows) {
+      if (rows) {
+        rows.forEach(row => {
+          this.$refs.dataTable.toggleRowSelection(row);
+        });
+      } else {
+        this.$refs.dataTable.clearSelection();
+      }
+    },
+    /** 多选框选中数据 */
+    handleSelectionChange(val) {
+      this.multipleSelection = val;
+      this.allChecked = val.length === this.dataList.length;
+      this.isIndeterminate = val.length > 0 && val.length < this.dataList.length;
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.startTime = this.dateRange[0];
+        this.queryParams.endTime = this.dateRange[1];
+      } else {
+        this.queryParams.startTime = null;
+        this.queryParams.endTime = null;
+      }
+      this.$confirm('是否确认导出所有直播数据?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportLiveData(this.queryParams);
+      }).then(response => {
+        if (response.code === 200) {
+          this.download(response.msg);
         }
-        console.log("选择的时间:", value, "筛选方式:", this.selectedTimeRange);
-      },
-      getWeekRange(selectedWeek) {
-        console.log("🔹 selectedWeek 输入值:", selectedWeek); // 检查传入值
-        let date = new Date(selectedWeek);
-
-        if (isNaN(date.getTime())) {
-          console.error("Invalid Date:", selectedWeek);
-          return "日期错误";
+        this.exportLoading = false;
+      }).catch(() => {
+        this.exportLoading = false;
+      });
+    },
+    /** 自动打标签 */
+    // handleAutoTag() {
+    //   if (this.multipleSelection.length === 0) {
+    //     this.$message.warning('请选择要打标签的数据');
+    //     return;
+    //   }
+    //   this.tagDialogVisible = true;
+    // },
+    /** 确认自动打标签 */
+    confirmAutoTag() {
+      if (this.tagForm.rules.length === 0) {
+        this.$message.warning('请选择打标签规则');
+        return;
+      }
+      const userIds = this.multipleSelection.map(item => item.userId).filter(id => id);
+      if (userIds.length === 0) {
+        this.$message.warning('所选数据中没有有效的用户ID');
+        return;
+      }
+      const data = {
+        userIds: userIds,
+        rules: this.tagForm.rules,
+        tagName: this.tagForm.tagName,
+        remarkFormat: this.tagForm.remarkFormat,
+        liveId: this.liveId
+      };
+      autoTagAndRemark(data).then(response => {
+        if (response.code === 200) {
+          this.$message.success('打标签成功');
+          this.tagDialogVisible = false;
+          this.tagForm = {
+            rules: [],
+            tagName: '',
+            remarkFormat: 'time'
+          };
+          this.getList();
+        }
+      });
+    },
+    /** 自动备注 */
+    // handleAutoRemark() {
+    //   if (this.multipleSelection.length === 0) {
+    //     this.$message.warning('请选择要备注的数据');
+    //     return;
+    //   }
+    //   this.remarkDialogVisible = true;
+    // },
+    /** 确认自动备注 */
+    confirmAutoRemark() {
+      if (this.remarkForm.rules.length === 0) {
+        this.$message.warning('请选择备注规则');
+        return;
+      }
+      const userIds = this.multipleSelection.map(item => item.userId).filter(id => id);
+      if (userIds.length === 0) {
+        this.$message.warning('所选数据中没有有效的用户ID');
+        return;
+      }
+      // 生成备注内容
+      let notes = '';
+      const now = new Date();
+      const dateStr = now.getFullYear() + '-' +
+        String(now.getMonth() + 1).padStart(2, '0') + '-' +
+        String(now.getDate()).padStart(2, '0');
+
+      this.remarkForm.rules.forEach((rule, index) => {
+        let ruleText = '';
+        if (rule === 'liveComplete') {
+          ruleText = '直播完课';
+        } else if (rule === 'replayComplete') {
+          ruleText = '回放完课';
+        } else if (rule === 'pay') {
+          ruleText = '付费行为';
         }
 
-        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);
-
-        console.log("周一:", this.formatDate(startDate));
-        console.log("周日:", this.formatDate(endDate));
+        if (this.remarkForm.remarkFormat === 'time') {
+          notes += (index > 0 ? ';' : '') + dateStr + '—' + ruleText;
+        } else {
+          notes += (index > 0 ? ';' : '') + ruleText;
+        }
+      });
+
+      const data = {
+        userIds: userIds,
+        notes: notes,
+        type: this.remarkForm.position,
+        nameType: 3, // 不添加客户名称
+        filter: false
+      };
 
-        return `${this.formatDate(startDate)} 至 ${this.formatDate(endDate)}`;
-      },
-      formatDate(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}`;
-      },
-      computedChange(item) {
-        switch (this.selectedTimeRange) {
-          case 'day': return item.dailyChange;
-          case 'week': return item.weeklyChange;
-          case 'month': return item.monthlyChange;
-          default: return 0;
+      batchUpdateExternalContactNotes(data).then(response => {
+        if (response.code === 200) {
+          this.$message.success('备注成功');
+          this.remarkDialogVisible = false;
+          this.remarkForm = {
+            rules: [],
+            remarkFormat: 'time',
+            position: 1
+          };
+          this.getList();
         }
-      },
-      selectRank(type) {
-        this.selectedRank = type; // 只允许一个按钮被选中
-        console.log(`选中的排行方式: ${type}`);
-      },
-      fetchRankingData() {
-        // 这里根据 `selectedRedRank` 和 `selectedBlackRank` 来请求不同的排行榜数据
-        console.log(`获取排行榜数据: 红榜-${this.selectedRedRank}, 黑榜-${this.selectedBlackRank}`);
-      },
-      // 切换时间范围
-      changeTimeRange() {
-        // 根据选中的时间范围修改 datePicker 类型
-        if (this.selectedTimeRange === "day") {
-          this.datePickerType = "date";
-        } else if (this.selectedTimeRange === "month") {
-          this.datePickerType = "month";
-        } else if (this.selectedTimeRange === "week") {
-          this.datePickerType = "week";
+      });
+    },
+    /** 查看详情 */
+    handleViewDetail(row) {
+      this.currentLiveId = row.liveId;
+      this.detailDrawerVisible = true;
+      this.loadDetailData();
+    },
+    /** 格式化时长 */
+    formatDuration(seconds) {
+      if (!seconds) return '00:00:00';
+      const hours = Math.floor(seconds / 3600);
+      const minutes = Math.floor((seconds % 3600) / 60);
+      const secs = seconds % 60;
+      return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
+    },
+    /** 更新完课时长设置 */
+    updateCompleteWatchTime(type) {
+      // 这里可以调用API保存设置
+      let watchTime = 0;
+      if (type === 'total') {
+        watchTime = this.completeWatchTime;
+      } else if (type === 'live') {
+        watchTime = this.liveCompleteWatchTime;
+      } else if (type === 'replay') {
+        watchTime = this.replayCompleteWatchTime;
+      }
+      // 更新查询参数并重新获取数据
+      this.queryParams.completeWatchTime = watchTime;
+      // this.getStatistics();
+      this.getList();
+      this.$message.success('设置成功');
+    },
+    /** 加载详情数据 */
+    loadDetailData() {
+      if (!this.currentLiveId) return;
+      // 使用SQL方式获取详情数据
+      getLiveDataDetailBySql(this.currentLiveId).then(response => {
+        if (response.code === 200) {
+          this.detailData = response.data || {};
         }
-        this.selectedDate = null; // 重置已选日期
-      },
-      // 切换时间(但目前只是选日期,没有请求后端)
-      /*changeDate() {
-        console.log("选择日期:", this.selectedDate);
-        // 这里可以做后端请求:this.fetchTrendData();
-      },*/
-      // 获取直播趋势数据
-      async fetchTrendData() {
-        try {
-          const response = await axios.get("/api/trend-data", { params: { date: this.selectedDate } });
-          this.trendData = response.data;
-
-          // 默认选择第一个指标
-          if (this.trendData.length > 0) {
-            this.changeMetric(this.trendData[0]);
-          }
-        } catch (error) {
-          console.error("获取直播数据失败:", error);
+      });
+    },
+    /** 查看用户详情 */
+    handleViewUserDetail() {
+      if (!this.currentLiveId) return;
+      this.showUserDetail = true;
+      this.userDetailLoading = true;
+      getLiveUserDetailListBySql(this.currentLiveId).then(response => {
+        if (response.code === 200) {
+          this.userDetailList = response.data || [];
         }
-      },
-      // 切换日期时更新数据
-      updateTrendData() {
-        console.log(`更新 ${this.selectedDate} 的直播数据`);
-        // 这里可以加接口请求,获取新的数据
-        this.updateChart();
-      },
-      // 切换指标
-      // changeMetric(metric) {
-      //   this.selectedMetric = metric;
-      //   this.updateChart();
-      // },
-      // updateTrendData() {
-      //   console.log("选择的时间:", this.selectedDate);
-      //   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 options = {
-          tooltip: { trigger: "axis", backgroundColor: "rgba(0, 0, 0, 0.7)", textStyle: { color: "#fff" } },
-          grid: { left: "5%", right: "5%", bottom: "10%", top: "10%", containLabel: true },
-          xAxis: { type: "category", data: this.selectedMetric.dates || [], axisLine: { lineStyle: { color: "#ddd" } } },
-          yAxis: { type: "value", splitLine: { lineStyle: { type: "dashed", color: "#ddd" } } },
-          series: [
-            {
-              name: this.selectedMetric.title,
-              data: this.selectedMetric.data || [],
-              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);
-      },
-      changeMetric(metric) {
-        this.selectedMetric = metric;
-        this.updateChart();
-      },
+        this.userDetailLoading = false;
+      }).catch(() => {
+        this.userDetailLoading = false;
+      });
+    },
+    /** 关闭详情侧边栏 */
+    closeDetailDrawer() {
+      this.detailDrawerVisible = false;
+      this.showUserDetail = false;
+      this.detailData = {};
+      this.userDetailList = [];
+      this.currentLiveId = null;
+    },
+    /** 格式化百分比 */
+    formatPercent(value) {
+      if (value == null || value === undefined) return '0%';
+      return (typeof value === 'number' ? value : parseFloat(value)).toFixed(2) + '%';
+    },
+    /** 格式化金额 */
+    formatMoney(value) {
+      if (value == null || value === undefined) return '¥0.00';
+      return '¥' + (typeof value === 'number' ? value : parseFloat(value)).toFixed(2);
+    },
+    /** 导出用户详情数据 */
+    handleExportUserDetail() {
+      if (!this.currentLiveId) {
+        this.$message.warning('无法获取直播间ID');
+        return;
+      }
+      if (!this.userDetailList || this.userDetailList.length === 0) {
+        this.$message.warning('暂无用户详情数据可导出');
+        return;
+      }
+      this.$confirm('是否确认导出当前直播间的用户详情数据?', "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.userDetailExportLoading = true;
+        return exportLiveUserDetail(this.currentLiveId);
+      }).then(response => {
+        if (response.code === 200) {
+          this.download(response.msg);
+          this.$message.success('导出成功');
+        }
+        this.userDetailExportLoading = false;
+      }).catch(() => {
+        this.userDetailExportLoading = false;
+      });
     }
-  };
+  }
+};
 </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;
-
-
-  }
-
-  /* 直播信息部分 */
-  .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: -40px;
-  }
-
-  .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; /* 防止换行 */
-  }
+.statistics-card {
+  margin-bottom: 20px;
+}
+
+.statistics-item {
+  text-align: center;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+}
+
+.statistics-title {
+  font-size: 14px;
+  color: #606266;
+  margin-bottom: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.statistics-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.selection-toolbar {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  padding-left: 10px;
+}
+
+.selection-toolbar .el-checkbox {
+  margin-right: 10px;
+}
+
+.live-data-css {
+  padding-left: 10px;
+  padding-top: 30px;
+}
+
+.detail-content {
+  padding: 20px;
+}
+
+.detail-item {
+  margin-bottom: 15px;
+  line-height: 32px;
+}
+
+.detail-label {
+  color: #606266;
+  font-size: 14px;
+}
+
+.detail-value {
+  color: #303133;
+  font-size: 14px;
+  font-weight: bold;
+}
+
+.user-detail-content {
+  padding: 20px;
+}
 </style>
-

+ 384 - 129
src/views/live/liveOrder/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
       <el-form-item label="公司名" prop="companyId">
         <el-select filterable v-model="queryParams.companyId" placeholder="请选择公司名" @change="companyChange" clearable size="small">
           <el-option
@@ -15,122 +15,145 @@
       <el-form-item>
         <treeselect style="width: 220px" :clearable="false" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择归属部门" />
       </el-form-item>
-      <el-form-item label="订单号" prop="orderCode">
+
+      <el-form-item label="商品名称" prop="productName">
         <el-input
-          v-model="queryParams.orderCode"
-          placeholder="请输入订单号"
+          v-model="queryParams.productName"
+          placeholder="请输入商品名称"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="用户ID" prop="userId">
+      <el-form-item label="销售名称" prop="companyUserName">
         <el-input
-          v-model="queryParams.userId"
-          placeholder="请输入用户ID"
+          v-model="queryParams.companyUserName"
+          placeholder="请输入销售名称"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="收货人" prop="userName">
+      <el-form-item label="订单号" prop="orderCode">
         <el-input
-          v-model="queryParams.userName"
-          placeholder="请输入收货人"
+          v-model="queryParams.orderCode"
+          placeholder="请输入订单号"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="手机号" prop="userPhone">
+      <el-form-item label="银行交易流水号" prop="bankTransactionId">
         <el-input
-          v-model="queryParams.userPhone"
-          placeholder="请输入手机号"
+          v-model="queryParams.bankTransactionId"
+          placeholder="请输入银行交易流水号"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="地址" prop="userAddress">
+
+      <el-form-item label="收货人电话" prop="userPhone">
         <el-input
-          v-model="queryParams.userAddress"
-          placeholder="请输入地址"
+          v-model="queryParams.userPhone"
+          placeholder="请输入收货人电话"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="支付金额" prop="payMoney">
+
+      <el-form-item label="收货人姓名" prop="userName">
         <el-input
-          v-model="queryParams.payMoney"
-          placeholder="请输入支付金额"
+          v-model="queryParams.userName"
+          placeholder="请输入收货人姓名"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="订单金额" prop="totalPrice">
+
+      <el-form-item label="商品规格" prop="productSpec">
         <el-input
-          v-model="queryParams.totalPrice"
-          placeholder="请输入订单金额"
+          v-model="queryParams.productSpec"
+          placeholder="请输入商品规格"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="物流编号" prop="deliveryCode">
+
+<!--      <el-form-item label="商品数量" prop="totalNum">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.totalNum"-->
+<!--          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="userAddress">
         <el-input
-          v-model="queryParams.deliveryCode"
-          placeholder="请输入物流编号"
+          v-model="queryParams.userAddress"
+          placeholder="请输入收货地址"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="物流名称" prop="deliveryName">
+
+      <el-form-item label="商品ID" prop="productId">
         <el-input
-          v-model="queryParams.deliveryName"
-          placeholder="请输入物流名称"
+          v-model="queryParams.productId"
+          placeholder="请输入商品ID"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="物流单号" prop="deliverySn">
+
+      <el-form-item label="成本价格" prop="cost">
         <el-input
-          v-model="queryParams.deliverySn"
-          placeholder="请输入物流单号"
+          v-model="queryParams.cost"
+          placeholder="请输入成本价格"
           clearable
           size="small"
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
 
-      <!-- 修改支付时间为范围选择 -->
-      <el-form-item label="支付时间" prop="payTimeRange">
-        <el-date-picker
-          v-model="payTimeRange"
-          type="daterange"
-          range-separator="至"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          value-format="yyyy-MM-dd"
-          size="small"
-          @change="handlePayTimeChange"
-        />
-      </el-form-item>
+<!--      <el-form-item label="供应商" prop="supplierName">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.supplierName"-->
+<!--          placeholder="请输入供应商名称"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          @keyup.enter.native="handleQuery"-->
+<!--        />-->
+<!--      </el-form-item>-->
 
-      <!-- 修改完成时间为范围选择 -->
-      <el-form-item label="完成时间" prop="finishTimeRange">
+      <!-- 下单时间范围选择 -->
+      <el-form-item label="下单时间" prop="orderTimeRange">
         <el-date-picker
-          v-model="finishTimeRange"
-          type="daterange"
+          v-model="orderTimeRange"
+          type="datetimerange"
           range-separator="至"
           start-placeholder="开始日期"
           end-placeholder="结束日期"
-          value-format="yyyy-MM-dd"
+          value-format="yyyy-MM-dd HH:mm:ss"
           size="small"
-          @change="handleFinishTimeChange"
+          @change="handleOrderTimeChange"
         />
       </el-form-item>
 
@@ -150,7 +173,26 @@
           :loading="exportLoading"
           @click="handleExport"
           v-hasPermi="['live:liveOrder:export']"
-        >导出</el-button>
+        >导出订单</el-button>
+        <el-col :span="1.5">
+          <el-button
+            icon="el-icon-s-order"
+            size="mini"
+            type="warning"
+            @click="openDeliveryNote"
+          >批量导入物流单号
+          </el-button>
+        </el-col>
+        <el-col :span="1.5">
+          <el-button
+            v-hasPermi="['store:storeOrder:healthExportShippingOrder']"
+            icon="el-icon-tickets"
+            size="mini"
+            type="success"
+            @click="handleExportShippingOrder"
+          >导出发货单
+          </el-button>
+        </el-col>
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
@@ -163,39 +205,66 @@
     <el-table border v-loading="loading" :data="liveOrderList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="订单号" align="center" prop="orderCode" />
-      <el-table-column label="所属公司" align="center" prop="companyName" />
-      <el-table-column label="所属员工" align="center" prop="companyUserName" />
-      <el-table-column label="用户ID" align="center" prop="userId" />
-      <el-table-column label="收货人" align="center" prop="userName" />
-      <el-table-column label="手机号" align="center" prop="userPhone" />
-      <el-table-column label="地址" align="center" prop="userAddress" />
-      <el-table-column label="支付金额" align="center" prop="payMoney" />
-      <el-table-column label="订单金额" align="center" prop="totalPrice" />
-      <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="deliveryStatus">
-        <template slot-scope="item">
-          <dict-tag :options="deliveryStatusOptions" :value="item.row.deliveryStatus"/>
+      <el-table-column label="银行交易流水号" align="center" prop="bankTransactionId" width="180" />
+      <el-table-column label="销售ID" align="center" prop="companyUserId" >
+        <template slot-scope="scope">
+          <span v-if="scope.row.companyUserId > 0" >{{ scope.row.companyUserId }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="绑定销售昵称" align="center" prop="companyUserNickName" />
+      <el-table-column label="客户编码" align="center" prop="userId" />
+      <el-table-column label="会员等级" align="center" prop="userLevel">
+      </el-table-column>
+      <el-table-column label="销售绑定手机号" align="center" prop="companyUserPhone" />
+      <el-table-column label="销售创建时间" align="center" prop="companyUserCreateTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ scope.row.companyUserCreateTime }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="物流跟踪状态" align="center" prop="deliveryType">
-        <template slot-scope="item">
-          <dict-tag :options="deliveryTypeOptions" :value="item.row.deliveryType"/>
+      <el-table-column label="客户ID" align="center" prop="userId" />
+      <el-table-column label="客户昵称" align="center" prop="nickName" />
+      <el-table-column label="客户绑定手机号" align="center" prop="userBindPhone" />
+      <el-table-column label="收货人电话" align="center" prop="userPhone" />
+      <el-table-column label="收货人姓名" align="center" prop="userName" />
+      <el-table-column label="累计成交笔数" align="center" prop="totalOrderCount" />
+      <el-table-column label="累计成交总额" align="center" prop="totalOrderAmount" />
+      <el-table-column label="最新绑定时间" align="center" prop="latestBindTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ scope.row.latestBindTime }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="支付时间" align="center" prop="payTime" width="180">
+      <el-table-column label="客户状态" align="center" prop="customerStatus">
         <template slot-scope="scope">
-          <span>{{ scope.row.payTime }}</span>
+          <dict-tag :options="customerStatusOptions" :value="scope.row.customerStatus"/>
         </template>
       </el-table-column>
-      <el-table-column label="状态" align="center" prop="status" >
-        <template slot-scope="item">
-          <dict-tag :options="orderStatusOptions" :value="item.row.status"/>
+      <el-table-column label="所属店铺" align="center" prop="storeId" />
+      <el-table-column label="所属店铺名称" align="center" prop="storeName" />
+      <el-table-column label="商品ID" align="center" prop="productId" />
+      <el-table-column label="商品名称" align="center" prop="productName" width="150" />
+      <el-table-column label="商品规格" align="center" prop="productSpec" width="120" />
+      <el-table-column label="商品数量" align="center" prop="totalNum" />
+      <el-table-column label="销售价格" align="center" prop="totalPrice" />
+      <el-table-column label="成本价格" align="center" prop="costPrice" v-if="showFinanceTableField"/>
+      <el-table-column label="结算价格" align="center" prop="fprice"  v-if="showFinanceTableField"/>
+      <el-table-column label="额外运费" align="center" prop="payDelivery" v-if="showFinanceTableField"/>
+      <el-table-column label="商品编码" align="center" prop="barCode"  v-if="showFinanceTableField"/>
+      <el-table-column label="商品分类" align="center" prop="cateName" v-if="showFinanceTableField"/>
+      <el-table-column label="成交价" align="center" prop="payMoney"/>
+      <el-table-column label="收货地址" align="center" prop="userAddress" width="200" />
+<!--      <el-table-column label="对应供应商" align="center" prop="supplierName" width="120" />-->
+      <el-table-column label="下单时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="订单状态" align="center" prop="status">
+        <template slot-scope="scope">
+          <dict-tag :options="orderStatusOptions" :value="scope.row.status"/>
         </template>
       </el-table-column>
-      <el-table-column label="备注" align="center" prop="remark" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
         <template slot-scope="scope">
           <el-button
             size="mini"
@@ -222,25 +291,104 @@
       :title="show.title" :visible.sync="show.open">
       <liveOrderDetails ref="Details" />
     </el-drawer>
+
+    <el-dialog
+      :before-close="handleClose"
+      :visible.sync="deliveryNoteOpen"
+      center
+      title="批量发货"
+      width="35%"
+    >
+      <span slot="footer" class="dialog-footer">
+        <!-- 小程序Appid选择 -->
+        <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
+          <el-form-item label="小程序:" prop="miniAppId">
+        <el-select
+          v-model="ruleForm.miniAppId"
+          clearable
+          placeholder="请选择发货小程序"
+          style="width: 100%"
+        >
+          <el-option
+            v-for="item in miniAppList"
+            :key="item.appId"
+            :label="item.appName"
+            :value="item.appId"
+          />
+        </el-select>
+      </el-form-item>
+        </el-form>
+
+        <el-upload ref="upload" :action="uploadUrl" :auto-upload="false" :disabled="orderUpload.isUploading" :headers="orderUpload.headers"
+                   :limit="1" :on-progress="handleFileUploadProgress"
+                   :on-success="handleFileSuccess" accept=".xlsx, .xls" drag
+        >
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div slot="tip" class="el-upload__tip">
+          <el-link style="font-size:12px" type="info" @click="importDeliveryNoteTemplate">下载模板</el-link>
+        </div>
+        <div slot="tip" class="el-upload__tip" style="color:red">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
+      </el-upload>
+        <el-divider></el-divider>
+        <el-button @click="cancelResetDeliveryNote">取 消</el-button>
+        <el-button type="primary" @click="submitDeliveryNote('ruleForm')">确 定</el-button>
+      </span>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { listLiveOrder, exportLiveOrder } from "@/api/live/liveOrder";
+import { listLiveOrderZm, exportLiveOrderZm,healthExportShippingOrder,importDeliveryNoteExpressTemplate,exportZmNew } from "@/api/live/liveOrder";
 import liveOrderDetails from './liveOrderDetails.vue';
 import {getCompanyList} from "@/api/company/company";
 import Treeselect from "@riophae/vue-treeselect";
 import {treeselect} from "@/api/company/companyDept";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import { getToken } from '@/utils/auth'
 import {parseTime} from "../../../utils/common";
+import { checkPermi } from "@/utils/permission";
 
 export default {
   name: "LiveOrder",
   components: {Treeselect, liveOrderDetails },
   data() {
     return {
+      upload: {
+        // 是否显示弹出层(用户导入)
+        open: false,
+        // 弹出层标题(用户导入)
+        title: '',
+        // 是否禁用上传
+        isUploading: false,
+        // 是否更新已经存在的用户数据
+        updateSupport: 0,
+        // 设置上传的请求头部
+        headers: { Authorization: 'Bearer ' + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + '/live/liveOrder/importExpress'
+      },
+      orderUpload: {
+        // 是否显示弹出层(用户导入)
+        open: false,
+        // 弹出层标题(用户导入)
+        title: '',
+        // 是否禁用上传
+        isUploading: false,
+        // 是否更新已经存在的用户数据
+        updateSupport: 0,
+        // 设置上传的请求头部
+        headers: { Authorization: 'Bearer ' + getToken() },
+      },
+      deliveryNoteOpen: false,
+      miniAppList: [],
       // 字典
       orderStatusOptions: [],
+      memberLevelOptions: [],
+      customerStatusOptions: [],
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -257,35 +405,38 @@ export default {
       total: 0,
       // 订单表格数据
       liveOrderList: [],
-      deliveryStatusOptions: [],
-      deliveryTypeOptions: [],
+      // 公司列表
+      companys: [],
+      companyId: null,
+      // 部门树选项
+      deptOptions: [],
 
-      // 支付时间范围选择器绑定的值
-      payTimeRange: [],
-      // 完成时间范围选择器绑定的值
-      finishTimeRange: [],
+      // 下单时间范围选择器绑定的值
+      orderTimeRange: [],
 
       // 查询参数
       queryParams: {
         pageNum: 1,
         liveId: null,
         pageSize: 10,
-        orderSn: null,
-        userId: null,
-        userName: null,
-        mobile: null,
-        address: null,
-        payMoney: null,
-        orderMoney: null,
-        deliveryCode: null,
-        deliveryName: null,
-        deliverySn: null,
-        // 移除单个时间字段,改为范围字段
-        payStartTime: null,
-        payEndTime: null,
-        finishTimeStart: null,
-        finishTimeEnd: null,
+        companyId: null,
+        companyUserName: null,
+        deptId: null,
+        productName: null,
+        productSpec: null,
+        productNum: null,
+        salePrice: null,
+        userAddress: null,
+        productId: null,
+        costPrice: null,
+        supplierName: null,
+        orderStartTime: null,
+        orderEndTime: null,
         status: null,
+        bankTransactionId: null,
+      },
+      ruleForm:{
+        miniAppId: null,
       },
       orderOptions: [],
       actName: "10",
@@ -293,6 +444,18 @@ export default {
         title: "订单详情",
         open: false,
       },
+      rules: {
+        userId: [
+          { required: true, message: '会员信息不能为空' }
+        ],
+        addressId: [
+          { required: true, message: '收货信息不能为空' }
+        ],
+        miniAppId: [
+          { required: true, message: '发货小程序不能为空' }
+        ],
+      },
+      showFinanceTableField: false,
     };
   },
   created() {
@@ -303,43 +466,91 @@ export default {
         this.getTreeselect();
       }
     });
+    if (checkPermi(['live:liveOrder:finance'])) {
+      this.showFinanceTableField = true;
+    }
     this.queryParams.liveId = this.$route.query.liveId
     this.getList();
     this.getDicts("sys_live_order_status").then(response => {
       this.orderStatusOptions = response.data;
+      this.orderOptions = this.orderStatusOptions;
     });
-    this.getDicts("sys_order_status").then(response => {
-      this.orderOptions = response.data;
-    });
-    this.getDicts("store_order_delivery_status").then(response => {
-      this.deliveryStatusOptions = response.data;
+    // this.getDicts("sys_order_status").then(response => {
+
+    // });
+    this.getDicts("sys_user_level").then(response => {
+      this.memberLevelOptions = response.data;
     });
-    this.getDicts("logistics_status").then(response => {
-      this.deliveryTypeOptions = response.data;
+    this.getDicts("sys_customer_status").then(response => {
+      this.customerStatusOptions = response.data;
     });
+
   },
-  methods: {
-    parseTime,
+  computed: {
 
-    // 支付时间范围选择变化处理
-    handlePayTimeChange(value) {
-      if (value && value.length === 2) {
-        this.queryParams.payStartTime = value[0] + ' 00:00:00';
-        this.queryParams.payEndTime = value[1] + ' 23:59:59';
-      } else {
-        this.queryParams.payStartTime = null;
-        this.queryParams.payEndTime = null;
-      }
+    uploadUrl() {
+      return process.env.VUE_APP_BASE_API +
+        '/live/liveOrder/importDeliveryNoteExpress?miniAppId=' +
+        this.ruleForm.miniAppId;
+    },
+    userId() {
+      return this.$store.state.user.user.userId
+    },
+  },
+  methods: {
+    // 提交发货单
+    submitDeliveryNote(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          const uploadFiles = this.$refs.upload.uploadFiles
+          if (uploadFiles.length === 0) {
+            this.$message.error('请选择要上传的文件')
+            return
+          }
+          this.$refs.upload.submit()
+        } else {
+          return false
+        }
+      })
+    },
+    //发货单模板下载
+    importDeliveryNoteTemplate() {
+      importDeliveryNoteExpressTemplate().then((response) => {
+        this.download(response.msg)
+      })
+    },
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false
+      this.upload.isUploading = false
+      this.$refs.upload.clearFiles()
+      this.importMsgOpen = true
+      this.importMsg = response.msg
+      // this.$alert(response.msg, '导入结果', {
+      //     dangerouslyUseHTMLString: true
+      //   });
+      this.getList()
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true
+    },
+    handleClose(done) {
+      this.$confirm('确认关闭?')
+        .then(_ => {
+          done()
+        })
+        .catch(_ => {
+        })
     },
 
-    // 完成时间范围选择变化处理
-    handleFinishTimeChange(value) {
+    // 下单时间范围选择变化处理
+    handleOrderTimeChange(value) {
       if (value && value.length === 2) {
-        this.queryParams.finishTimeStart = value[0] + ' 00:00:00';
-        this.queryParams.finishTimeEnd = value[1] + ' 23:59:59';
+        this.queryParams.createTimeStart = value[0];
+        this.queryParams.createTimeEnd = value[1];
       } else {
-        this.queryParams.finishTimeStart = null;
-        this.queryParams.finishTimeEnd = null;
+        this.queryParams.createTimeStart = null;
+        this.queryParams.createTimeEnd = null;
       }
     },
 
@@ -365,7 +576,7 @@ export default {
     /** 查询订单列表 */
     getList() {
       this.loading = true;
-      listLiveOrder(this.queryParams).then(response => {
+      listLiveOrderZm(this.queryParams).then(response => {
         this.liveOrderList = response.rows;
         this.total = response.total;
         this.loading = false;
@@ -386,13 +597,10 @@ export default {
     resetQuery() {
       this.resetForm("queryForm");
       // 同时清空范围选择器的值
-      this.payTimeRange = [];
-      this.finishTimeRange = [];
+      this.orderTimeRange = [];
       // 清空时间范围参数
-      this.queryParams.payStartTime = null;
-      this.queryParams.payEndTime = null;
-      this.queryParams.finishTimeStart = null;
-      this.queryParams.finishTimeEnd = null;
+      this.queryParams.orderStartTime = null;
+      this.queryParams.orderEndTime = null;
       this.handleQuery();
     },
 
@@ -412,7 +620,8 @@ export default {
         type: "warning"
       }).then(() => {
         this.exportLoading = true;
-        return exportLiveOrder(queryParams);
+        return exportZmNew(queryParams);
+        // return exportLiveOrderZm(queryParams);
       }).then(response => {
         this.download(response.msg);
         this.exportLoading = false;
@@ -438,6 +647,52 @@ export default {
       this.queryParams.deptId = val;
       this.getList();
     },
+    handleExportShippingOrder() {
+      if (this.queryParams.status == '00') {
+        this.queryParams.status = null
+      }
+      if (this.createTimeRange != null && this.createTimeRange.length == 2) {
+        this.queryParams.createTimeRange = this.createTimeRange[0] + '--' + this.createTimeRange[1]
+      } else {
+        this.queryParams.createTimeRange = null
+      }
+      if (this.payTimeRange != null && this.payTimeRange.length == 2) {
+        this.queryParams.payTimeRange = this.payTimeRange[0] + '--' + this.payTimeRange[1]
+      } else {
+        this.queryParams.payTimeRange = null
+      }
+      if (this.deliveryImportTimeRange != null && this.deliveryImportTimeRange.length == 2) {
+        this.queryParams.deliveryImportTimeRange = this.deliveryImportTimeRange[0] + '--' + this.deliveryImportTimeRange[1]
+      } else {
+        this.queryParams.deliveryImportTimeRange = null
+      }
+      if (this.deliverySendTimeRange != null && this.deliverySendTimeRange.length == 2) {
+        this.queryParams.deliverySendTimeRange = this.deliverySendTimeRange[0] + '--' + this.deliverySendTimeRange[1]
+      } else {
+        this.queryParams.deliverySendTimeRange = null
+      }
+      const queryParams = this.addDateRange(this.queryParams, this.dateRange)
+      this.$confirm('是否确认导出所有订单明细数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(function() {
+        return healthExportShippingOrder(queryParams)
+      }).then(response => {
+        this.download(response.msg)
+      }).catch(function() {
+      })
+    },
+    //打开发货单
+    openDeliveryNote() {
+      this.deliveryNoteOpen = true
+      this.getAppList();
+    },
+    //取消重置
+    cancelResetDeliveryNote(){
+      this.deliveryNoteOpen = false;
+      this.resetForm('ruleForm')
+    },
   }
 };
 </script>

+ 56 - 53
src/views/live/liveOrder/liveOrderDetails.vue

@@ -6,7 +6,7 @@
     <div class="contentx" v-if="item!=null">
       <div class="desct"></div>
       <div class="order-status" v-if="item!=null" >
-        <el-steps  :active="item.status==4?item.status:item.status" align-center finish-status="success">
+        <el-steps  :active="item.status+1" align-center>
           <el-step title="待支付"></el-step>
           <el-step title="待发货"></el-step>
           <el-step title="待收货"></el-step>
@@ -76,57 +76,57 @@
         </el-descriptions>
       </el-card>
     </div>
-    <div class="contentx" v-if="item!=null" style="padding-bottom: 70px;">
-      <div style="margin-top: 20px">
-        <div class="desct">
-          物流信息
-        </div>
-        &nbsp;
-        <el-link  type="primary" @click="editDelivery(null)">添加物流信息</el-link>
-      </div>
-      <el-table
-        border
-        :data="deliverList"
-        size="small"
-        style="width: 100%;margin-top: 20px" >
-        <el-table-column label="物流公司编码" width="150" align="center">
-          <template slot-scope="scope">
-            <p>{{scope.row.deliverSn}}</p>
-          </template>
-        </el-table-column>
-        <el-table-column label="物流公司名称" width="300" align="center">
-          <template slot-scope="scope">
-            <p>{{scope.row.deliverName}}</p>
-          </template>
-        </el-table-column>
-        <el-table-column label="物流单号" width="300" align="center">
-          <template slot-scope="scope">
-            <p>{{scope.row.deliverId}}</p>
-          </template>
-        </el-table-column>
-        <el-table-column label="物流状态" width="300" align="center">
-          <template slot-scope="scope">
-              <span>
-              <el-tag v-for="(item, index) in deliveryStatusOptions"    v-if="scope.row!=null&&scope.row.status==item.dictValue">
-              {{item.dictLabel}}
-              </el-tag>
-              </span>
-          </template>
-        </el-table-column>
-        <el-table-column label="发货时间" width="240" align="center">
-          <template slot-scope="scope">
-            {{scope.row.deliverySendTime}}
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" width="240" align="center">
-          <template slot-scope="scope">
-            <el-link  type="primary" @click="showExpress(scope)">查看物流跟踪</el-link>
-            &nbsp;&nbsp;
-            <el-link  type="primary" @click="editDelivery(scope)">修改物流</el-link>
-          </template>
-        </el-table-column>
-      </el-table>
-    </div>
+<!--    <div class="contentx" v-if="item!=null" style="padding-bottom: 70px;">-->
+<!--      <div style="margin-top: 20px">-->
+<!--        <div class="desct">-->
+<!--          物流信息-->
+<!--        </div>-->
+<!--        &nbsp;-->
+<!--        <el-link  type="primary" @click="editDelivery(null)">添加物流信息</el-link>-->
+<!--      </div>-->
+<!--      <el-table-->
+<!--        border-->
+<!--        :data="deliverList"-->
+<!--        size="small"-->
+<!--        style="width: 100%;margin-top: 20px" >-->
+<!--        <el-table-column label="物流公司编码" width="150" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            <p>{{scope.row.deliverSn}}</p>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="物流公司名称" width="300" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            <p>{{scope.row.deliverName}}</p>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="物流单号" width="300" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            <p>{{scope.row.deliverId}}</p>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="物流状态" width="300" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--              <span>-->
+<!--              <el-tag v-for="(item, index) in deliveryStatusOptions"    v-if="scope.row!=null&&scope.row.status==item.dictValue">-->
+<!--              {{item.dictLabel}}-->
+<!--              </el-tag>-->
+<!--              </span>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="发货时间" width="240" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            {{scope.row.deliverySendTime}}-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="操作" width="240" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            <el-link  type="primary" @click="showExpress(scope)">查看物流跟踪</el-link>-->
+<!--            &nbsp;&nbsp;-->
+<!--            <el-link  type="primary" @click="editDelivery(scope)">修改物流</el-link>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--      </el-table>-->
+<!--    </div>-->
     <div class="contentx" v-if="item!=null" style="padding-bottom: 70px;">
       <div class="desct">
         商品信息
@@ -355,9 +355,12 @@
             <el-option key="SF"  label="顺丰" value="SF" />
             <el-option key="EMS"  label="邮政" value="EMS" />
             <el-option key="ZTO"  label="中通" value="ZTO" />
+            <el-option key="STO"  label="申通" value="STO" />
             <el-option key="JD"  label="京东" value="JD" />
             <el-option key="DBL"  label="德邦" value="DBL" />
+            <el-option key="JTSD"  label="极兔" value="JTSD" />
             <el-option key="YD"  label="韵达" value="YD" />
+            <el-option key="YTO"  label="圆通" value="YTO" />
           </el-select>
         </el-form-item>
         <el-form-item label="物流单号" prop="deliverySn"  >
@@ -537,7 +540,7 @@ export default {
     this.getDicts("sys_store_pay_type").then(response => {
       this.PayOptions = response.data;
     });
-    this.getDicts("sys_order_status").then(response => {
+    this.getDicts("sys_live_order_status").then(response => {
       this.orderOptions = response.data;
     });
     this.getDicts("sys_order_pay").then(response => {

+ 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;

+ 1292 - 0
src/views/live/order/index.vue

@@ -0,0 +1,1292 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="公司名" prop="companyId">
+        <el-select filterable v-model="queryParams.companyId" placeholder="请选择公司名" @change="companyChange" clearable size="small">
+          <el-option
+            v-for="item in companys"
+            :key="item.companyId"
+            :label="item.companyName"
+            :value="item.companyId"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item>
+        <treeselect style="width: 220px" :clearable="false" v-model="queryParams.deptId" :options="deptOptions" :show-count="true" placeholder="请选择归属部门" />
+      </el-form-item>
+
+      <el-form-item label="订单类型" prop="orderTypeFilter">
+        <el-select v-model="queryParams.orderTypeFilter" placeholder="请选择订单类型" clearable size="small">
+          <el-option label="全部" :value="null" />
+          <el-option label="销售订单" :value="1" />
+          <el-option label="商城订单" :value="2" />
+          <el-option label="直播订单" :value="3" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="销售名称" prop="salesName">
+        <el-input
+          v-model="queryParams.salesName"
+          placeholder="请输入销售名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="订单号" prop="orderCodes">
+        <div class="tag-input-container">
+          <div class="tags-wrapper" @click="focusInput">
+            <el-tag
+              v-for="(code, index) in queryParams.orderCodes"
+              :key="index"
+              closable
+              size="small"
+              @close="removeOrderCode(index)"
+              class="order-tag"
+            >
+              {{ code }}
+            </el-tag>
+            <el-input
+              ref="tagInput"
+              v-model="currentInput"
+              v-show="inputVisible || queryParams.orderCodes.length === 0"
+              :placeholder="queryParams.orderCodes.length === 0 ? '请输入订单号,按回车或逗号分隔' : '继续输入...'"
+              size="small"
+              class="tag-input"
+              @keydown.native="handleKeyDown"
+              @keyup.native="handleKeyUp"
+              @blur="handleInputConfirm"
+              @focus="inputVisible = true"
+              clearable
+            />
+            <el-button
+              v-if="!inputVisible && queryParams.orderCodes.length > 0"
+              class="button-new-tag"
+              size="small"
+              @click="showInput"
+              icon="el-icon-plus"
+              type="text"
+            >
+              添加订单号
+            </el-button>
+          </div>
+          <div class="input-tips">
+            <span class="tip-text">
+              支持:回车、逗号、空格分隔 |
+              已添加 {{ queryParams.orderCodes.length }} 个订单号
+            </span>
+          </div>
+        </div>
+      </el-form-item>
+
+      <el-form-item label="运单号" prop="deliveryId">
+        <el-input
+          v-model="queryParams.deliveryId"
+          placeholder="请输入运单号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="银行交易流水号" prop="bankTransactionId">
+        <el-input
+          v-model="queryParams.bankTransactionId"
+          placeholder="请输入银行交易流水号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="产品名称" prop="productName">
+        <el-input
+          v-model="queryParams.productName"
+          placeholder="请输入产品名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="手机号" prop="userPhone">
+        <el-input
+          v-model="queryParams.userPhone"
+          placeholder="请输入手机号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="收件人" prop="realName">
+        <el-input
+          v-model="queryParams.realName"
+          placeholder="请输入收件人姓名"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+
+      <el-form-item label="支付方式" prop="payType">
+        <el-select v-model="queryParams.payType" placeholder="请选择支付方式" clearable size="small">
+          <el-option
+            v-for="item in payTypeOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+
+<!--      <el-form-item label="上传凭证" prop="isUpload">-->
+<!--        <el-select v-model="queryParams.isUpload" placeholder="请选择" clearable size="small">-->
+<!--          <el-option key="0" label="未上传" value="0" />-->
+<!--          <el-option key="1" label="已上传" value="1" />-->
+<!--        </el-select>-->
+<!--      </el-form-item>-->
+
+<!--      <el-form-item label="物流状态" prop="deliveryStatus">-->
+<!--        <el-select v-model="queryParams.deliveryStatus" placeholder="请选择物流状态" clearable size="small">-->
+<!--          <el-option-->
+<!--            v-for="item in deliveryStatusOptions"-->
+<!--            :key="item.dictValue"-->
+<!--            :label="item.dictLabel"-->
+<!--            :value="item.dictValue"-->
+<!--          />-->
+<!--        </el-select>-->
+<!--      </el-form-item>-->
+
+      <el-form-item label="结算状态" prop="deliveryPayStatus">
+        <el-select v-model="queryParams.deliveryPayStatus" placeholder="请选择物流结算状态" clearable size="small">
+          <el-option
+            v-for="item in deliveryPayStatusOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="小程序" prop="appId">
+        <el-select v-model="queryParams.appId" placeholder="请选择所属小程序" clearable size="small">
+          <el-option
+            v-for="dict in appMallOptions"
+            :key="dict.appid"
+            :label="dict.name + '(' + dict.appid + ')'"
+            :value="dict.appid"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="商品规格" prop="productSpec">
+        <el-input
+          v-model="queryParams.productSpec"
+          placeholder="请输入商品规格"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+<!--      <el-form-item label="商品数量" prop="totalNum">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.totalNum"-->
+<!--          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="userAddress">
+        <el-input
+          v-model="queryParams.userAddress"
+          placeholder="请输入收货地址"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="商品ID" prop="productId">
+        <el-input
+          v-model="queryParams.productId"
+          placeholder="请输入商品ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+<!--      <el-form-item label="成本价格" prop="cost">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.cost"-->
+<!--          placeholder="请输入成本价格"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          @keyup.enter.native="handleQuery"-->
+<!--        />-->
+<!--      </el-form-item>-->
+
+<!--      <el-form-item label="供应商" prop="supplierName">-->
+<!--        <el-input-->
+<!--          v-model="queryParams.supplierName"-->
+<!--          placeholder="请输入供应商名称"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          @keyup.enter.native="handleQuery"-->
+<!--        />-->
+<!--      </el-form-item>-->
+
+      <el-form-item label="档期归属" prop="scheduleId">
+        <el-select filterable style="width: 215px" v-model="queryParams.scheduleId" placeholder="请选择档期" clearable size="small">
+          <el-option
+            v-for="item in scheduleOptions"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="代服账户" prop="erpAccount" v-if="SFDFopen">
+        <el-select v-model="queryParams.erpAccount" style="width: 215px" placeholder="ERP账户" clearable size="small">
+          <el-option
+            v-for="dict in erpAccountQueryList"
+            :key="dict"
+            :label="dict"
+            :value="dict"
+          />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="代服电话" prop="erpPhoneNumber" v-if="SFDFopen">
+        <el-input
+          v-model="queryParams.erpPhoneNumber"
+          placeholder="ERP电话"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="下单时间" prop="createTimeRange">
+        <el-date-picker
+          style="width:215px"
+          clearable
+          size="small"
+          v-model="createTimeRange"
+          type="datetimerange"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          @change="handleOrderTimeChange"
+        />
+      </el-form-item>
+
+      <el-form-item label="支付时间" prop="payTimeRange">
+        <el-date-picker
+          style="width: 215px"
+          clearable
+          size="small"
+          v-model="payTimeRange"
+          type="datetimerange"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        />
+      </el-form-item>
+
+<!--      <el-form-item label="发货时间" prop="deliverySendTimeRange">-->
+<!--        <el-date-picker-->
+<!--          style="width:215px"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          v-model="deliverySendTimeRange"-->
+<!--          type="daterange"-->
+<!--          value-format="yyyy-MM-dd"-->
+<!--          start-placeholder="开始日期"-->
+<!--          end-placeholder="结束日期"-->
+<!--        />-->
+<!--      </el-form-item>-->
+
+<!--      <el-form-item label="回单时间" prop="deliveryImportTimeRange">-->
+<!--        <el-date-picker-->
+<!--          style="width:215px"-->
+<!--          clearable-->
+<!--          size="small"-->
+<!--          v-model="deliveryImportTimeRange"-->
+<!--          type="daterange"-->
+<!--          value-format="yyyy-MM-dd"-->
+<!--          start-placeholder="开始日期"-->
+<!--          end-placeholder="结束日期"-->
+<!--        />-->
+<!--      </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-->
+<!--          plain-->
+<!--          type="info"-->
+<!--          icon="el-icon-upload2"-->
+<!--          size="mini"-->
+<!--          @click="handleImport"-->
+<!--        >导入银行回单</el-button>-->
+<!--      </el-col>-->
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          icon="el-icon-download"
+          size="mini"
+          v-hasPermi="['live:order:export']"
+          :loading="exportLoading"
+          @click="handleExport"
+        >导出订单</el-button>
+      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          icon="el-icon-download"-->
+<!--          size="mini"-->
+<!--          @click="handleExportItems"-->
+<!--        >导出订单明细</el-button>-->
+<!--      </el-col>-->
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          icon="el-icon-s-order"-->
+<!--          size="mini"-->
+<!--          @click="openDeliveryNote"-->
+<!--        >批量导入物流单号</el-button>-->
+<!--      </el-col>-->
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          icon="el-icon-download"
+          size="mini"
+          v-hasPermi="['live:order:exportAll']"
+          @click="handleExportDetails"
+        >导出订单(明文)</el-button>
+      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          icon="el-icon-download"-->
+<!--          size="mini"-->
+<!--          @click="handleExportItemsDetails"-->
+<!--        >导出订单明细(明文)</el-button>-->
+<!--      </el-col>-->
+      <el-col :span="1.5">
+        <el-button
+          icon="el-icon-tickets"
+          size="mini"
+          type="success"
+          :loading="exportShippingLoading"
+          @click="handleExportShippingOrder"
+        >导出发货单</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-tabs type="card" v-model="activeName" @tab-click="handleClick">
+      <el-tab-pane label="全部订单" name="00"></el-tab-pane>
+      <el-tab-pane label="待支付" name="0"></el-tab-pane>
+      <el-tab-pane label="待发货" name="1"></el-tab-pane>
+      <el-tab-pane label="待收货" name="2"></el-tab-pane>
+      <el-tab-pane label="交易完成" name="3"></el-tab-pane>
+      <el-tab-pane label="退款中" name="-1"></el-tab-pane>
+      <el-tab-pane label="已退款" name="-2"></el-tab-pane>
+      <el-tab-pane label="已取消" name="-3"></el-tab-pane>
+    </el-tabs>
+
+    <el-table
+      ref="orderTable"
+      border
+      v-loading="loading"
+      :data="orderList"
+      @selection-change="handleSelectionChange"
+      :default-sort="{prop: 'createTime', order: 'descending'}"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="订单类型" align="center" prop="orderTypeName" width="100px">
+        <template slot-scope="scope">
+          <el-tag :type="getOrderTypeTagType(scope.row.orderType)">
+            {{ scope.row.orderTypeName }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="订单号" align="center" prop="orderCode" width="200px" />
+      <el-table-column label="银行交易流水号" align="center" prop="bankTransactionId" width="200px" />
+      <el-table-column label="所属公司" align="center" prop="companyName" />
+      <el-table-column label="销售名称" align="center" prop="salesName" width="120px" />
+      <el-table-column label="员工昵称" align="center" prop="companyUserNickName" width="120px" />
+      <el-table-column label="用户昵称" align="center" prop="nickname" width="150px" />
+      <el-table-column label="收件人" align="center" prop="realName" width="120px" />
+      <el-table-column label="手机号" align="center" prop="userPhone" width="120px" />
+      <el-table-column label="商品规格" align="center" prop="productSpec" width="120px" />
+      <el-table-column label="商品数量" align="center" prop="totalNum" width="100px" />
+      <el-table-column label="订单金额" align="center" prop="totalPrice" width="100px">
+        <template slot-scope="scope">
+          <span v-if="scope.row.totalPrice != null">{{ scope.row.totalPrice.toFixed(2) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="应付金额" align="center" prop="payPrice" width="100px">
+        <template slot-scope="scope">
+          <span v-if="scope.row.payPrice != null">{{ scope.row.payPrice.toFixed(2) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="实付金额" align="center" prop="payMoney" width="100px">
+        <template slot-scope="scope">
+          <span v-if="scope.row.payMoney != null">{{ scope.row.payMoney.toFixed(2) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="下单时间" align="center" prop="createTime" width="180" />
+      <el-table-column label="支付时间" align="center" prop="payTime" width="180" />
+      <el-table-column label="支付方式" align="center" prop="payType" width="100px">
+        <template slot-scope="scope">
+          <el-tag v-for="(item, index) in payTypeOptions" :key="index" v-if="scope.row.payType == item.dictValue" size="mini">
+            {{ item.dictLabel }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="100px">
+        <template slot-scope="scope">
+          <el-tag v-for="(item, index) in statusOptions" :key="index" v-if="scope.row.status == item.dictValue" size="mini">
+            {{ item.dictLabel }}
+          </el-tag>
+        </template>
+      </el-table-column>
+<!--      <el-table-column label="物流状态" align="center" prop="deliveryStatus" width="100px">-->
+<!--        <template slot-scope="scope">-->
+<!--          <el-tag v-for="(item, index) in deliveryStatusOptions" :key="index" v-if="scope.row.deliveryStatus == item.dictValue" size="mini">-->
+<!--            {{ item.dictLabel }}-->
+<!--          </el-tag>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
+      <el-table-column label="运单号" align="center" prop="deliveryId" width="150px" />
+      <el-table-column label="商品ID" align="center" prop="productId" width="120px" />
+      <el-table-column label="商品名称" align="center" prop="productName" width="150px" />
+      <el-table-column label="条码" align="center" prop="barCode" width="120px" />
+      <el-table-column label="分类" align="center" prop="cateName" width="120px" />
+      <el-table-column label="运费" align="center" prop="payDelivery" width="100px">
+        <template slot-scope="scope">
+          <span v-if="scope.row.payDelivery != null">{{ scope.row.payDelivery }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="成本价" align="center" prop="cost" width="100px">
+        <template slot-scope="scope">
+          <span v-if="scope.row.cost != null">{{ scope.row.cost }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" fixed="right" width="80px" align="center">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            @click="handleDetails(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="getList"
+    />
+
+    <!-- 订单详情侧边栏 -->
+    <el-drawer
+      :title="drawerTitle"
+      :visible.sync="drawerVisible"
+      direction="rtl"
+      size="75%"
+      :before-close="closeDrawer"
+    >
+      <div v-loading="drawerLoading" style="min-height: 200px;">
+        <!-- 直播订单详情 -->
+        <liveDetail
+          v-if="currentOrderType === 3 && drawerVisible"
+          ref="liveDetailRef"
+          :order-id="currentOrderId"
+        />
+        <!-- 销售订单详情 -->
+        <userDetail
+          v-if="currentOrderType === 1 && drawerVisible"
+          ref="userDetailRef"
+          :order-id="currentOrderId"
+        />
+        <!-- 商城订单详情 -->
+        <storeDetail
+          v-if="currentOrderType === 2 && drawerVisible"
+          ref="storeDetailRef"
+          :order-id="currentOrderId"
+        />
+      </div>
+    </el-drawer>
+
+    <!-- 批量导入物流单号对话框 -->
+    <el-dialog
+      :before-close="cancelResetDeliveryNote"
+      :visible.sync="deliveryNoteOpen"
+      center
+      title="批量发货"
+      width="35%"
+    >
+      <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
+        <el-form-item label="小程序:" prop="miniAppId">
+          <el-select
+            v-model="ruleForm.miniAppId"
+            clearable
+            placeholder="请选择发货小程序"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in miniAppList"
+              :key="item.appId"
+              :label="item.appName"
+              :value="item.appId"
+            />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <el-upload ref="upload" :action="uploadUrl" :auto-upload="false" :disabled="orderUpload.isUploading" :headers="orderUpload.headers"
+                 :limit="1" :on-progress="handleFileUploadProgress"
+                 :on-success="handleFileSuccess" accept=".xlsx, .xls" drag
+      >
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div slot="tip" class="el-upload__tip">
+          <el-link style="font-size:12px" type="info" @click="importDeliveryNoteTemplate">下载模板</el-link>
+        </div>
+        <div slot="tip" class="el-upload__tip" style="color:red">提示:仅允许导入"xls"或"xlsx"格式文件!</div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="cancelResetDeliveryNote">取 消</el-button>
+        <el-button type="primary" @click="submitDeliveryNote('ruleForm')">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 导入银行回单对话框 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" append-to-body width="400px">
+      <el-upload ref="upload" :action="upload.url + '?updateSupport=' + upload.updateSupport" :auto-upload="false" :disabled="upload.isUploading"
+                 :headers="upload.headers" :limit="1"
+                 :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" accept=".xlsx, .xls" drag
+      >
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">
+          将文件拖到此处,或
+          <em>点击上传</em>
+        </div>
+        <div slot="tip" class="el-upload__tip" style="color:red">提示:仅允许导入"xls"或"xlsx"格式文件!</div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listMergedOrder, exportMergedOrder, exportMergedShipping, exportMergedOrderItems, exportMergedOrderDetails, exportMergedOrderItemsDetails, importDeliveryNoteExpressTemplate } from '@/api/live/mergedOrder'
+import { getCompanyList } from '@/api/company/company'
+import { treeselect } from '@/api/company/companyDept'
+import Treeselect from '@riophae/vue-treeselect'
+import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+import liveDetail from './liveDetail.vue'
+import userDetail from './userDetail.vue'
+import storeDetail from './storeDetail.vue'
+import { getToken } from '@/utils/auth'
+import { getTcmScheduleList } from '@/api/company/schedule'
+import { list as getAppMallOptions } from '@/api/course/coursePlaySourceConfig'
+import { getErpAccount } from '@/api/hisStore/storeOrder'
+import { getConfigByKey } from '@/api/system/config'
+
+export default {
+  name: 'MergedOrder',
+  components: {Treeselect, liveDetail, userDetail, storeDetail },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      exportLoading: false,
+      exportShippingLoading: false,
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 订单表格数据
+      orderList: [],
+      // 公司列表
+      companys: [],
+      companyId: null,
+      // 部门树选项
+      deptOptions: [],
+      // 字典
+      payTypeOptions: [],
+      deliveryStatusOptions: [],
+      deliveryPayStatusOptions: [],
+      statusOptions: [],
+      orderTypeOptions: [],
+      appMallOptions: [],
+      scheduleOptions: [],
+      erpAccountQueryList: [],
+      SFDFopen: false,
+      // 订单号标签输入相关
+      maxOrderCodes: 50,
+      inputVisible: false,
+      currentInput: '',
+      // 弹出层标题
+      title: '',
+      // 是否显示弹出层
+      open: false,
+      createTimeRange: [],
+      payTimeRange: [],
+      deliverySendTimeRange: [],
+      deliveryImportTimeRange: [],
+      activeName: '00',
+      // 侧边栏相关
+      drawerVisible: false,
+      drawerTitle: '订单详情',
+      currentOrderType: null,
+      currentOrderId: null,
+      drawerLoading: false,
+      // 上传相关
+      upload: {
+        open: false,
+        title: '',
+        isUploading: false,
+        updateSupport: 0,
+        headers: { Authorization: 'Bearer ' + getToken() },
+        url: process.env.VUE_APP_BASE_API + '/order/importExpress'
+      },
+      orderUpload: {
+        open: false,
+        title: '',
+        isUploading: false,
+        updateSupport: 0,
+        headers: { Authorization: 'Bearer ' + getToken() }
+      },
+      deliveryNoteOpen: false,
+      miniAppList: [],
+      ruleForm: {
+        miniAppId: null
+      },
+      rules: {
+        miniAppId: [
+          { required: true, message: '发货小程序不能为空' }
+        ]
+      },
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        companyId: null,
+        deptId: null,
+        orderTypeFilter: null,
+        salesName: null,
+        orderCodes: [],
+        orderCode: null,
+        orderCodeList: null,
+        deliveryId: null,
+        userPhone: null,
+        realName: null,
+        productName: null,
+        bankTransactionId: null,
+        productSpec: null,
+        productId: null,
+        totalNum: null,
+        price: null,
+        userAddress: null,
+        cost: null,
+        supplierName: null,
+        companyUserNickName: null,
+        orderType: null,
+        isUpload: null,
+        payType: null,
+        deliveryStatus: null,
+        deliveryPayStatus: null,
+        appId: null,
+        scheduleId: null,
+        erpAccount: null,
+        erpPhoneNumber: null,
+        status: null,
+        createTimeStart: null,
+        createTimeEnd: null,
+        createTimeRange: null,
+        payTimeRange: null,
+        deliverySendTimeRange: null,
+        deliveryImportTimeRange: null
+      }
+    }
+  },
+  created() {
+    // 初始化公司列表
+    getCompanyList().then(response => {
+      this.companys = response.data || []
+      if (this.companys != null && this.companys.length > 0) {
+        this.companyId = this.companys[0].companyId
+        this.getTreeselect()
+      }
+    }).catch(error => {
+      console.error('获取公司列表失败:', error)
+      this.companys = []
+    })
+
+    // 初始化支付方式字典
+    this.getDicts('store_pay_type').then((response) => {
+      this.payTypeOptions = response.data || []
+    }).catch(error => {
+      console.error('获取支付方式字典失败:', error)
+      this.payTypeOptions = []
+    })
+
+    // 初始化订单状态字典
+    this.getDicts('store_order_status').then((response) => {
+      this.statusOptions = response.data || []
+    }).catch(error => {
+      console.error('获取订单状态字典失败:', error)
+      this.statusOptions = []
+    })
+
+    // 初始化物流状态字典
+    this.getDicts('store_order_delivery_status').then((response) => {
+      this.deliveryStatusOptions = response.data || []
+    }).catch(error => {
+      console.error('获取物流状态字典失败:', error)
+      this.deliveryStatusOptions = []
+    })
+
+    // 初始化物流结算状态字典
+    this.getDicts('store_delivery_pay_status').then((response) => {
+      this.deliveryPayStatusOptions = response.data || []
+    }).catch(error => {
+      console.error('获取物流结算状态字典失败:', error)
+      this.deliveryPayStatusOptions = []
+    })
+
+    // // 初始化订单类型字典
+    // this.getDicts('store_order_type').then((response) => {
+    //   this.orderTypeOptions = response.data || []
+    // }).catch(error => {
+    //   console.error('获取订单类型字典失败:', error)
+    //   this.orderTypeOptions = []
+    // })
+
+    // 初始化档期归属列表
+    getTcmScheduleList().then(response => {
+      this.scheduleOptions = response.data || []
+    }).catch(error => {
+      console.error('获取档期归属列表失败:', error)
+      this.scheduleOptions = []
+    })
+
+    // 初始化小程序列表
+    this.getAppMallOptions()
+
+    // 初始化ERP账户列表
+    this.getErpAccountList()
+
+    // 加载订单列表
+    this.getList()
+  },
+  computed: {
+    uploadUrl() {
+      return process.env.VUE_APP_BASE_API +
+        '/order/importDeliveryNoteExpress?miniAppId=' +
+        this.ruleForm.miniAppId
+    }
+  },
+  methods: {
+    /** 查询订单列表 */
+    getList() {
+      this.loading = true
+      if (this.queryParams.status == '00') {
+        this.queryParams.status = null
+      }
+
+      // 处理订单号数组
+      if (this.queryParams.orderCodes && this.queryParams.orderCodes.length > 0) {
+        this.queryParams.orderCodeList = this.queryParams.orderCodes.join(',')
+      } else {
+        this.queryParams.orderCodeList = null
+      }
+
+      // 处理时间范围
+      if (this.createTimeRange != null && this.createTimeRange.length === 2) {
+        this.queryParams.createTimeRange = this.createTimeRange[0] + '--' + this.createTimeRange[1]
+      } else {
+        this.queryParams.createTimeRange = null
+      }
+
+      if (this.payTimeRange != null && this.payTimeRange.length === 2) {
+        this.queryParams.payTimeRange = this.payTimeRange[0] + '--' + this.payTimeRange[1]
+      } else {
+        this.queryParams.payTimeRange = null
+      }
+
+      if (this.deliverySendTimeRange != null && this.deliverySendTimeRange.length === 2) {
+        this.queryParams.deliverySendTimeRange = this.deliverySendTimeRange[0] + '--' + this.deliverySendTimeRange[1]
+      } else {
+        this.queryParams.deliverySendTimeRange = null
+      }
+
+      if (this.deliveryImportTimeRange != null && this.deliveryImportTimeRange.length === 2) {
+        this.queryParams.deliveryImportTimeRange = this.deliveryImportTimeRange[0] + '--' + this.deliveryImportTimeRange[1]
+      } else {
+        this.queryParams.deliveryImportTimeRange = null
+      }
+
+      listMergedOrder(this.queryParams).then(response => {
+        this.orderList = response.rows
+        this.total = response.total
+        this.loading = false
+        if (response.msg == 'knt') {
+          this.SFDFopen = true
+        } else {
+          this.SFDFopen = false
+        }
+      }).catch(error => {
+        console.error('查询失败:', error)
+        this.loading = false
+        this.$message.error('查询数据失败')
+      })
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.createTimeRange = []
+      this.payTimeRange = []
+      this.deliverySendTimeRange = []
+      this.deliveryImportTimeRange = []
+      this.queryParams.orderCodes = []
+      this.currentInput = ''
+      this.inputVisible = false
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id || item.orderId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    handleClick(tab, event) {
+      this.activeName = tab.name
+      this.queryParams.status = tab.name
+      this.getList()
+    },
+    handleOrderTimeChange(value) {
+      if (value && value.length === 2) {
+        this.queryParams.createTimeStart = value[0]
+        this.queryParams.createTimeEnd = value[1]
+      } else {
+        this.queryParams.createTimeStart = null
+        this.queryParams.createTimeEnd = null
+      }
+    },
+    /** 导出订单 */
+    handleExport() {
+      this.prepareExportParams()
+      this.$confirm('是否确认导出所有订单数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.exportLoading = true
+        return exportMergedOrder(this.queryParams)
+      }).then(response => {
+        this.download(response.msg)
+        this.exportLoading = false
+      }).catch(() => {
+        this.exportLoading = false
+      })
+    },
+    /** 导出发货单 */
+    handleExportShippingOrder() {
+      this.prepareExportParams()
+      this.$confirm('是否确认导出所有订单明细数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.exportShippingLoading = true
+        return exportMergedShipping(this.queryParams)
+      }).then(response => {
+        this.download(response.msg)
+        this.exportShippingLoading = false
+      }).catch(() => {
+        this.exportShippingLoading = false
+      })
+    },
+    handleDetails(row) {
+      // 根据订单类型打开对应的侧边栏详情
+      this.currentOrderType = row.orderType
+
+      if (row.orderType === 1) {
+        // 销售订单
+        this.currentOrderId = row.id
+        this.drawerTitle = '销售订单详情'
+      } else if (row.orderType === 2) {
+        // 商城订单
+        this.currentOrderId = row.id
+        this.drawerTitle = '商城订单详情'
+      } else if (row.orderType === 3) {
+        // 直播订单
+        this.currentOrderId = row.orderId
+        this.drawerTitle = '直播订单详情'
+      }
+
+      this.drawerVisible = true
+    },
+    closeDrawer() {
+      this.drawerVisible = false
+      this.currentOrderType = null
+      this.currentOrderId = null
+    },
+    getOrderTypeTagType(orderType) {
+      const typeMap = {
+        1: 'success', // 销售订单
+        2: 'warning', // 商城订单
+        3: 'info' // 直播订单
+      }
+      return typeMap[orderType] || ''
+    },
+    /** 查询部门下拉树结构 */
+    getTreeselect() {
+      var param = { companyId: this.companyId }
+      treeselect(param).then((response) => {
+        this.deptOptions = response.data
+      })
+    },
+    companyChange(val) {
+      this.companyId = val
+      this.getTreeselect()
+    },
+    // 订单号标签输入相关方法
+    handleKeyDown(event) {
+      const { key, target } = event
+      if (key === 'Backspace' && !target.value && this.queryParams.orderCodes.length > 0) {
+        event.preventDefault()
+        this.removeOrderCode(this.queryParams.orderCodes.length - 1)
+      }
+      if ([',', ',', ' ', 'Enter'].includes(key)) {
+        event.preventDefault()
+        this.handleInputConfirm()
+      }
+    },
+    handleKeyUp(event) {
+      const value = event.target.value
+      if (/[,,\s]/.test(value)) {
+        this.handleInputConfirm()
+      }
+    },
+    handleInputConfirm() {
+      const inputValue = this.currentInput.trim()
+      if (inputValue) {
+        const codes = inputValue.split(/[,,\s]+/).filter(code => code.trim())
+        codes.forEach(code => {
+          this.addOrderCode(code.trim())
+        })
+      }
+      this.currentInput = ''
+    },
+    addOrderCode(code) {
+      if (!code) return
+      if (this.maxOrderCodes > 0 && this.queryParams.orderCodes.length >= this.maxOrderCodes) {
+        this.$message.warning(`最多只能添加 ${this.maxOrderCodes} 个订单号`)
+        return
+      }
+      if (this.queryParams.orderCodes.includes(code)) {
+        this.$message.warning(`订单号 "${code}" 已存在`)
+        return
+      }
+      this.queryParams.orderCodes.push(code)
+    },
+    removeOrderCode(index) {
+      this.queryParams.orderCodes.splice(index, 1)
+    },
+    showInput() {
+      this.inputVisible = true
+      this.$nextTick(() => {
+        if (this.$refs.tagInput) {
+          this.$refs.tagInput.focus()
+        }
+      })
+    },
+    focusInput() {
+      if (!this.inputVisible) {
+        this.showInput()
+      }
+    },
+    // 导出相关方法
+    handleExportItems() {
+      this.prepareExportParams()
+      this.$confirm('是否确认导出所有订单明细数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return exportMergedOrderItems(this.queryParams)
+      }).then(response => {
+        this.download(response.msg)
+      }).catch(() => {})
+    },
+    handleExportDetails() {
+      this.prepareExportParams()
+      this.$confirm('是否确认导出所有订单数据项(明文)?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return exportMergedOrderDetails(this.queryParams)
+      }).then(response => {
+        this.download(response.msg)
+      }).catch(() => {})
+    },
+    handleExportItemsDetails() {
+      this.prepareExportParams()
+      this.$confirm('是否确认导出所有订单明细数据项(明文)?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return exportMergedOrderItemsDetails(this.queryParams)
+      }).then(response => {
+        this.download(response.msg)
+      }).catch(() => {})
+    },
+    prepareExportParams() {
+      if (this.queryParams.status == '00') {
+        this.queryParams.status = null
+      }
+      if (this.createTimeRange != null && this.createTimeRange.length === 2) {
+        this.queryParams.createTimeRange = this.createTimeRange[0] + '--' + this.createTimeRange[1]
+      } else {
+        this.queryParams.createTimeRange = null
+      }
+      if (this.payTimeRange != null && this.payTimeRange.length === 2) {
+        this.queryParams.payTimeRange = this.payTimeRange[0] + '--' + this.payTimeRange[1]
+      } else {
+        this.queryParams.payTimeRange = null
+      }
+      if (this.deliverySendTimeRange != null && this.deliverySendTimeRange.length === 2) {
+        this.queryParams.deliverySendTimeRange = this.deliverySendTimeRange[0] + '--' + this.deliverySendTimeRange[1]
+      } else {
+        this.queryParams.deliverySendTimeRange = null
+      }
+      if (this.deliveryImportTimeRange != null && this.deliveryImportTimeRange.length === 2) {
+        this.queryParams.deliveryImportTimeRange = this.deliveryImportTimeRange[0] + '--' + this.deliveryImportTimeRange[1]
+      } else {
+        this.queryParams.deliveryImportTimeRange = null
+      }
+      if (this.queryParams.orderCodes && this.queryParams.orderCodes.length > 0) {
+        this.queryParams.orderCodeList = this.queryParams.orderCodes.join(',')
+      } else {
+        this.queryParams.orderCodeList = null
+      }
+    },
+    // 导入相关方法
+    handleImport() {
+      this.upload.title = '导入银行回单'
+      this.upload.open = true
+    },
+    submitFileForm() {
+      this.$refs.upload.submit()
+    },
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true
+    },
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false
+      this.upload.isUploading = false
+      if (this.$refs.upload) {
+        this.$refs.upload.clearFiles()
+      }
+      this.$message.success('导入成功')
+      this.getList()
+    },
+    // 批量导入物流单号
+    openDeliveryNote() {
+      this.deliveryNoteOpen = true
+      this.getAppList()
+    },
+    getAppList() {
+      this.miniAppList = []
+      const key = 'courseMa.config'
+      getConfigByKey(key).then(response => {
+        const { code, data } = response
+        if (code === 200) {
+          let value = data?.configValue
+          if (value) {
+            try {
+              const appList = JSON.parse(value)
+              this.miniAppList = appList.filter(v => v.type === '1').map(v => {
+                return { appId: v.appid, appName: v.name }
+              })
+            } catch (parseError) {
+              console.error('解析小程序配置失败:', parseError)
+              this.miniAppList = []
+            }
+          }
+        }
+      }).catch(error => {
+        console.error('获取小程序配置失败:', error)
+        this.miniAppList = []
+      })
+    },
+    importDeliveryNoteTemplate() {
+      importDeliveryNoteExpressTemplate().then((response) => {
+        this.download(response.msg)
+      })
+    },
+    submitDeliveryNote(formName) {
+      this.$refs[formName].validate((valid) => {
+        if (valid) {
+          const uploadFiles = this.$refs.upload.uploadFiles
+          if (uploadFiles.length === 0) {
+            this.$message.error('请选择要上传的文件')
+            return
+          }
+          this.$refs.upload.submit()
+        } else {
+          return false
+        }
+      })
+    },
+    cancelResetDeliveryNote() {
+      this.deliveryNoteOpen = false
+      this.resetForm('ruleForm')
+    },
+    // 获取小程序选项列表
+    getAppMallOptions() {
+      getAppMallOptions({ pageNum: 1, pageSize: 100, isMall: 1 }).then(response => {
+        this.appMallOptions = response.rows || []
+      }).catch(error => {
+        console.error('获取小程序选项列表失败:', error)
+        this.appMallOptions = []
+      })
+    },
+    // 获取ERP账户列表
+    async getErpAccountList() {
+      try {
+        const response = await getErpAccount()
+        if (response.code === 200) {
+          const list = response.data || []
+          this.erpAccountQueryList = [...list, '未分拣']
+        } else {
+          this.erpAccountQueryList = []
+        }
+      } catch (error) {
+        console.error('获取ERP账户列表失败:', error)
+        this.erpAccountQueryList = []
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.tag-input-container {
+  min-width: 445px;
+}
+
+.tags-wrapper {
+  min-height: 32px;
+  padding: 4px 8px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  cursor: text;
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 4px;
+  transition: border-color 0.2s;
+}
+
+.tags-wrapper:hover {
+  border-color: #c0c4cc;
+}
+
+.tags-wrapper:focus-within {
+  border-color: #409eff;
+  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+}
+
+.order-tag {
+  margin: 2px;
+  flex-shrink: 0;
+}
+
+.tag-input {
+  border: none;
+  outline: none;
+  flex: 1;
+  min-width: 120px;
+}
+
+.tag-input >>> .el-input__inner {
+  border: none;
+  padding: 0;
+  height: 24px;
+  line-height: 24px;
+}
+
+.button-new-tag {
+  height: 24px;
+  line-height: 22px;
+  padding: 0 8px;
+  margin: 2px;
+}
+
+.input-tips {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #909399;
+}
+
+.tip-text {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+</style>
+

+ 1118 - 0
src/views/live/order/liveDetail.vue

@@ -0,0 +1,1118 @@
+<template>
+  <div style="background-color: #f0f2f5; padding-bottom: 20px; min-height: 100%; " >
+    <div style="padding: 20px; background-color: #fff;">
+      订单详情
+    </div>
+    <div class="contentx" v-if="item!=null">
+      <div class="desct"></div>
+      <div class="order-status" v-if="item!=null" >
+        <el-steps  :active="item.status+1" align-center>
+          <el-step title="待支付"></el-step>
+          <el-step title="待发货"></el-step>
+          <el-step title="待收货"></el-step>
+          <el-step title="已完成"></el-step>
+        </el-steps>
+      </div>
+      <el-card shadow="never" style="margin-top: 15px">
+        <div class="operate-container"  v-if="item!=null">
+               <span style="margin-left: 20px" class="color-danger">订单状态:
+                  <el-tag prop="status" v-for="(ite, index) in orderOptions"    v-if="item.status==ite.dictValue">{{ite.dictLabel}}</el-tag>
+               </span>
+          <div class="operate-button-container" v-if="item.status==3">
+            <el-button size="mini" @click="finishOrder()" v-hasPermi="['his:liveOrder:good']">确认收货</el-button>
+          </div>
+          <div class="operate-button-container"  v-if="item.status==2&&item.orderType==1">
+            <el-button size="mini" @click="sendVisible=true" v-hasPermi="['his:liveOrder:sendGoods']">发货</el-button>
+          </div>
+          <div class="operate-button-container"  v-if="item.status==2&&item.orderType==2">
+            <el-button size="mini" @click="tuiOrder()" v-hasPermi="['his:liveOrder:sendHisGoods']" >推送订单</el-button>
+          </div>
+          <div class="operate-button-container"   v-if="item.deliverySn!=null" v-hasPermi="['his:liveOrder:express']">
+            <el-button size="mini" @click="showExpress()" >查看物流</el-button>
+          </div>
+          <div class="operate-button-container" >
+            <el-button size="mini" @click="editDelivery()"  v-hasPermi="['his:liveOrder:updateDelivery']" >修改物流单号</el-button>
+          </div>
+
+          <div class="operate-button-container" >
+            <el-button size="mini" @click="editOrder()"  v-hasPermi="['his:liveOrder:edit']" >修改订单</el-button>
+          </div>
+          <div class="operate-button-container" v-if="item.extendOrderId == null"  >
+            <el-button size="mini" @click="addErpOrder()" >创建ERP订单信息</el-button>
+          </div>
+          <div class="operate-button-container" v-if="item.extendOrderId!=null"  >
+            <el-button size="mini" @click="showErpOrder()" >ERP订单信息</el-button>
+          </div>
+          <div class="operate-button-container" v-if="item.status>1">
+            <el-button size="mini" @click="refund()" v-hasPermi="['his:liveOrder:refundOrderMoney']">退款</el-button>
+          </div>
+        </div>
+        <div class="desct">
+          基本信息
+        </div>
+        <el-descriptions :column="3" border  >
+          <el-descriptions-item label="订单编号"  ><span v-if="item!=null">{{item.orderCode}}</span></el-descriptions-item>
+          <el-descriptions-item label="会员"><span v-if="item.nickName!=null">{{item.nickName}}({{item.phone}})</span></el-descriptions-item>
+          <el-descriptions-item label="支付方式"  ><dict-tag :options="PayOptions" :value="item.payType"/></el-descriptions-item>
+          <el-descriptions-item label="收货人" >  <span v-if="item!=null">{{item.userName}}</span>  </el-descriptions-item>
+          <el-descriptions-item label="手机号码" > <span v-if="item!=null">{{item.userPhone}}</span>
+            <el-button icon="el-icon-search" size="mini" @click="handlePhone()" style="margin-left: 20px;" circle v-hasPermi="['his:liveOrder:queryPhone']"></el-button>
+          </el-descriptions-item>
+          <el-descriptions-item label="收货地址" > <span>{{item.userAddress}}</span>  </el-descriptions-item>
+          <el-descriptions-item label="所属公司"><span v-if="item!=null">{{item.companyName}}</span></el-descriptions-item>
+          <el-descriptions-item label="员工"><span v-if="item!=null">{{item.companyUserName}}</span></el-descriptions-item>
+          <el-descriptions-item label="推广佣金" ><span v-if="item.tuiMoney!=null">{{item.tuiMoney.toFixed(2)}}</span></el-descriptions-item>
+          <el-descriptions-item label="推广佣金状态" ><span v-if="item!=null"><dict-tag :options="tuiOptions" :value="item.tuiMoneyStatus"/> </span></el-descriptions-item>
+          <el-descriptions-item label="ERP编号" ><span v-if="item!=null">{{item.extendOrderId}}</span></el-descriptions-item>
+          <el-descriptions-item label="用户备注" ><span v-if="item!=null">{{item.remark}}</span></el-descriptions-item>
+          <el-descriptions-item label="档期归属" >
+            <el-tag prop="scheduleId" v-for="(schedule, index) in scheduleOptions"    v-if="item!=null&&item.scheduleId==schedule.id">{{schedule.name}}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="订单购买类型" ><span v-if="item!=null"><dict-tag :options="orderBuyTypeOptions" :value="item.orderBuyType"/></span></el-descriptions-item>
+          <el-descriptions-item label=" 公众号/渠道" ><span v-if="item!=null">{{item.channel}}</span></el-descriptions-item>
+          <el-descriptions-item label=" 渠道" ><span v-if="item!=null"><dict-tag :options="channelOptions" :value="item.orderChannel"/></span></el-descriptions-item>
+          <el-descriptions-item label=" 企微主体" ><span v-if="item!=null"><dict-tag :options="qwSubjectOptions" :value="item.qwSubject"/></span></el-descriptions-item>
+        </el-descriptions>
+      </el-card>
+    </div>
+<!--    <div class="contentx" v-if="item!=null" style="padding-bottom: 70px;">-->
+<!--      <div style="margin-top: 20px">-->
+<!--        <div class="desct">-->
+<!--          物流信息-->
+<!--        </div>-->
+<!--        &nbsp;-->
+<!--        <el-link  type="primary" @click="editDelivery(null)">添加物流信息</el-link>-->
+<!--      </div>-->
+<!--      <el-table-->
+<!--        border-->
+<!--        :data="deliverList"-->
+<!--        size="small"-->
+<!--        style="width: 100%;margin-top: 20px" >-->
+<!--        <el-table-column label="物流公司编码" width="150" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            <p>{{scope.row.deliverSn}}</p>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="物流公司名称" width="300" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            <p>{{scope.row.deliverName}}</p>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="物流单号" width="300" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            <p>{{scope.row.deliverId}}</p>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="物流状态" width="300" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--              <span>-->
+<!--              <el-tag v-for="(item, index) in deliveryStatusOptions"    v-if="scope.row!=null&&scope.row.status==item.dictValue">-->
+<!--              {{item.dictLabel}}-->
+<!--              </el-tag>-->
+<!--              </span>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="发货时间" width="240" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            {{scope.row.deliverySendTime}}-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--        <el-table-column label="操作" width="240" align="center">-->
+<!--          <template slot-scope="scope">-->
+<!--            <el-link  type="primary" @click="showExpress(scope)">查看物流跟踪</el-link>-->
+<!--            &nbsp;&nbsp;-->
+<!--            <el-link  type="primary" @click="editDelivery(scope)">修改物流</el-link>-->
+<!--          </template>-->
+<!--        </el-table-column>-->
+<!--      </el-table>-->
+<!--    </div>-->
+    <div class="contentx" v-if="item!=null" style="padding-bottom: 70px;">
+      <div class="desct">
+        商品信息
+      </div>
+      <el-tooltip class="item" effect="dark" :content="showList ? '显示全部' : '隐藏'" placement="top" style="float: right;">
+        <el-button size="mini" circle icon="el-icon-search" @click="showListD()" />
+      </el-tooltip>
+      <el-table border v-if="showProd!=null" :data="showProd" size="small" style="width: 100%;margin-top: 20px" >
+        <el-table-column label="商品图片" width="150" align="center">
+          <template slot-scope="scope">
+            <img :src="scope.row.imgUrl" style="height: 80px">
+          </template>
+        </el-table-column>
+        <el-table-column label="商品编码" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{scope.row.barCode}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="仓库代码" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{scope.row.warehouseCode}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品名称" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{scope.row.productName}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="单价" width="240" align="center">
+          <template slot-scope="scope">
+            <p>¥{{scope.row.price.toFixed(2)}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="市场价" width="240" align="center">
+          <template slot-scope="scope">
+            {{scope.row.otPrice}}
+          </template>
+        </el-table-column>
+      </el-table>
+      <div style="float: right;margin: 20px" v-if="item.totalPrice!=null">
+        合计:<span class="color-danger">¥{{item.totalPrice.toFixed(2)}}</span>
+      </div>
+    </div>
+    <div class="contentx" v-if="item!=null" style="padding-bottom: 70px;">
+      <div class="desct">
+        费用信息
+      </div>
+      <el-tooltip class="item" effect="dark" :content="showList ? '显示全部' : '隐藏'" placement="top" style="float: right;">
+        <el-button size="mini" circle icon="el-icon-search" @click="showListD()" />
+      </el-tooltip>
+      <el-descriptions :column="3" border  >
+        <el-descriptions-item label="商品合计"  ><span v-if="item!=null">{{item.totalPrice}}</span></el-descriptions-item>
+        <el-descriptions-item label="应付金额"><span v-if="item.totalPrice!=null">{{item.totalPrice}}</span></el-descriptions-item>
+        <el-descriptions-item label="运费"><span v-if="item.payDelivery!=null">{{item.payDelivery}}</span></el-descriptions-item>
+        <el-descriptions-item label="优惠券"  ><span v-if="item.discountMoney!=null"/>{{item.discountMoney}}</el-descriptions-item>
+        <el-descriptions-item label="积分抵扣" >  <span v-if="item!=null">{{item.payIntegral}}</span>  </el-descriptions-item>
+        <el-descriptions-item label="实付金额" >  <span v-if="item!=null">{{item.payMoney}}</span>  </el-descriptions-item>
+        <el-descriptions-item label="代收金额" >  <span v-if="item!=null">{{item.payRemain}}</span>  </el-descriptions-item>
+        <el-descriptions-item label="服务费" >  <span v-if="item!=null">{{0.00}}</span>  </el-descriptions-item>
+      </el-descriptions>
+      <div style="float: right;margin: 20px" v-if="item.totalPrice!=null">
+        合计:<span class="color-danger">¥{{item.totalPrice.toFixed(2)}}</span>
+      </div>
+    </div>
+
+    <div class="contentx" v-if="payments!=null" style="padding-bottom: 70px;">
+      <div class="desct">
+        支付信息
+      </div>
+    <el-table
+      border
+      :data="payments"
+      size="small"
+      style="width: 100%;margin-top: 20px" >
+      <el-table-column label="支付单号" align="center" prop="payCode" width="120px" />
+      <el-table-column label="支付金额" align="center" prop="payMoney" />
+      <el-table-column label="类型" align="center" prop="payTypeCode" />
+      <el-table-column label="交易单号" align="center" prop="bankTransactionId" />
+      <el-table-column label="银行单号" align="center" prop="bankSerialNo" />
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="支付时间" align="center" prop="payTime" />
+    </el-table>
+    </div>
+
+    <div class="contentx" v-if="logs!=null" style="padding-bottom: 70px;">
+
+      <div class="desct">
+        操作信息
+      </div>
+    <el-table style="margin-top: 20px;width: 100%"
+              ref="orderHistoryTable"
+              :data="logs" border>
+      <el-table-column label="操作时间"  width="160" align="center">
+        <template slot-scope="scope">
+          {{scope.row.changeTime}}
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center">
+        <template slot-scope="scope">
+          {{scope.row.changeMessage}}
+        </template>
+      </el-table-column>
+    </el-table>
+</div>
+
+    <div class="contentx" v-if="tuiMoneyLogs!=null" style="padding-bottom: 70px;">
+
+      <div class="desct">
+        分佣信息
+      </div>
+    <el-table
+      border
+      :data="tuiMoneyLogs"
+      size="small"
+      style="width: 100%;margin-top: 20px" >
+      <el-table-column label="公司名称" align="center" prop="companyName" width="120px" />
+      <el-table-column label="金额" align="center" prop="money" />
+      <el-table-column label="余额" align="center" prop="balance" />
+      <el-table-column label="创建时间" align="center" prop="createTime" />
+      <el-table-column label="备注" align="center" prop="remark" />
+    </el-table>
+    </div>
+
+    <el-dialog :title="edit.title" :visible.sync="edit.open" width="600px" append-to-body>
+      <el-form ref="editForm" :model="editForm" :rules="editRules" label-width="100px">
+
+        <el-form-item label="订单状态" prop="status" >
+          <el-select v-model="editForm.status" placeholder="请选择状态" clearable size="small" filterable>
+            <el-option
+              v-for="dict in orderOptions "
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="物流状态" prop="deliveryStatus" >
+          <el-select v-model="editForm.deliveryStatus" placeholder="请选择物流状态" clearable size="small" filterable>
+            <el-option
+              v-for="dict in deliveryStatusOptions "
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="物流跟踪状态" prop="deliveryType" >
+          <el-select v-model="editForm.deliveryType" placeholder="请选择状态" clearable size="small" filterable>
+            <el-option
+              v-for="dict in deliveryTypeOptions "
+              :key="dict.dictValue"
+              :label="dict.dictLabel"
+              :value="dict.dictValue"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="详情地址" prop="userAddress"  >
+          <el-input v-model="editForm.userAddress" placeholder="请输入" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark"  >
+          <el-input v-model="editForm.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitEditForm">确 定</el-button>
+      </div>
+    </el-dialog>
+
+
+    <el-dialog :title="erpDialog.title" :visible.sync="erpDialog.open" width="600px" append-to-body>
+      <div v-if="order!=null&&order.extendOrderId!=null&&order.status==1"  v-hasPermi="['store:storeOrder:updateExpress']"  >
+        <el-button size="mini" @click="updateExpress()" >同步物流发货</el-button>
+      </div>
+      <div class="table-layout"  v-if="erpOrder!=null">
+        <el-row>
+          <el-col :span="6" class="table-cell-title">订单编号</el-col>
+          <el-col :span="6" class="table-cell-title">是否代收</el-col>
+          <el-col :span="6" class="table-cell-title">快递编号</el-col>
+          <el-col :span="6" class="table-cell-title">快递名称</el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.code}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.cod?'是':'否'}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.express_code}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.express_name}}
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell-title">收货人</el-col>
+          <el-col :span="6" class="table-cell-title">电话</el-col>
+          <el-col :span="6" class="table-cell-title">地址</el-col>
+          <el-col :span="6" class="table-cell-title">运单号</el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_name}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_mobile}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_address}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.deliverys!=null&&erpOrder.deliverys.length>0?erpOrder.deliverys[0].mail_no:''}}
+          </el-col>
+
+        </el-row>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="editDy.title" :visible.sync="editDy.open" width="600px" append-to-body>
+      <el-form ref="editDyForm" :model="editDyForm" :rules="editDyRules" label-width="100px">
+        <div  v-hasPermi="['his:liveOrder:updateErpOrder']"  style="margin-bottom: 20px;" >
+          <el-button size="mini" @click="updateErpOrder" >同步物流单号信息</el-button>
+        </div>
+        <el-form-item label="物流公司" prop="deliveryCode" >
+          <el-select style="width:220px" v-model="editDyForm.deliveryCode" placeholder="请选择" clearable size="small">
+            <el-option key="SF"  label="顺丰" value="SF" />
+            <el-option key="EMS"  label="邮政" value="EMS" />
+            <el-option key="ZTO"  label="中通" value="ZTO" />
+            <el-option key="STO"  label="申通" value="STO" />
+            <el-option key="JD"  label="京东" value="JD" />
+            <el-option key="DBL"  label="德邦" value="DBL" />
+            <el-option key="JTSD"  label="极兔" value="JTSD" />
+            <el-option key="YD"  label="韵达" value="YD" />
+            <el-option key="YTO"  label="圆通" value="YTO" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="物流单号" prop="deliverySn"  >
+          <el-input v-model="editDyForm.deliverySn" placeholder="请输入物流单号" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitEditDyForm">确 定</el-button>
+        <el-button @click="editDy.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="expressDialog.title" :visible.sync="expressDialog.open" width="600px" append-to-body>
+      <div  v-hasPermi="['his:storeOrder:syncExpress']"  >
+        <el-button size="mini" @click="syncExpress()" >同步快递鸟物流状态</el-button>
+      </div>
+      <el-table style="margin-top: 20px;width: 100%"
+                ref="orderHistoryTable"
+                :data="traces" border>
+        <el-table-column label="操作时间"  width="160" align="center">
+          <template slot-scope="scope">
+            {{scope.row.AcceptTime}}
+          </template>
+        </el-table-column>
+        <el-table-column label="位置" align="center">
+          <template slot-scope="scope">
+            {{scope.row.Location}}
+          </template>
+        </el-table-column>
+        <el-table-column label="描述" align="center">
+          <template slot-scope="scope">
+            {{scope.row.AcceptStation}}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+
+
+  </div>
+</template>
+
+
+<script>
+import {
+  getLiveOrder,
+  syncExpress,
+  updateLiveOrder,
+  updateErp,
+  getExpress,
+  listOrderitem,
+  tuiOrder,
+  refundOrderMoney,
+  editDeliveryId,
+  finishOrder,
+  getOrderExpress,
+  getOrderPayments,
+  getOrderLog,
+  tuiMoneyLogs,
+  updateExpress,
+  createErpOrder, getEroOrder
+} from "@/api/live/liveOrder";
+import {getStoreOrderLive} from "@/api/store/storeOrder";
+
+
+export default {
+  props:{
+    data: [Object, Array, String, Number, Boolean],
+    orderId: {
+      type: [String, Number],
+      default: null
+    }
+  },
+  watch: {
+    orderId: {
+      immediate: true,
+      handler(val) {
+        if (val) {
+          this.getDetails(val, null, null)
+        }
+      }
+    }
+  },
+  data() {
+    return {
+      expressDialog:{
+        title:"物流信息",
+        open:false,
+      },
+      editDy:{
+        title:"修改物流单号",
+        open:false,
+      },
+      sourceOptions:[],
+      payments: [],
+      followDialogVisible:false,
+      dialogVisible:false,
+      total: 0,
+      msgForm:{
+        pageNum: 1,
+        pageSize: 10,
+        userId:null,
+        followDoctorId:null,
+      },
+      editDyForm:{
+        orderId:null,
+        deliveryId:null,
+        deliveryCode:null,
+      },
+      showList:true,
+      edit:{
+        title:"修改订单",
+        open:false,
+      },
+      erpDialog:{
+        title:"ERP订单信息",
+        open:false,
+      },
+      show:{
+        title:"问诊详情",
+        open:false,
+      },
+      pack:{
+        title:"套餐包",
+        open:false,
+      },
+      followList:[],
+      money:null,
+      moneyVisible:false,
+      rules:{},
+      sendVisible:false,
+      logs:[],
+      pay:[],
+      nickName:null,
+      storeName:null,
+      PayOptions:[],
+      orderOptions:[],
+      payStatusOptions:[],
+      express:null,
+      traces:[],
+      msg:[],
+      deliverList: [],
+      sexOptions:[],
+      refundOptions:[],
+      channelOptions:[],
+      qwSubjectOptions:[],
+      tuiOptions:[],
+      orOptions:[],
+      storeOPtions:[],
+      deliveryStatusOptions:[],
+      deliveryPayStatusOptions:[],
+      deliveryTypeOptions:[],
+      orderTypeOptions:[],
+      orderBuyTypeOptions:[],
+      scheduleOptions:[],
+      item:null,
+      tuiMoneyLogs:[],
+      erpOrder:null,
+      prod:null,
+      showProd:null,
+      editForm:{
+        orderId:null,
+        status:null,
+        userAddress:null,
+        deliveryStatus:null,
+        deliveryType:null,
+        remark:"",
+      },
+      editDyRules:{
+        deliverySn: [
+          { required: true, message: "物流单号不能为空", trigger: "blur" }
+        ],
+        deliveryCode: [
+          { required: true, message: "物流公司不能为空", trigger: "blur" }
+        ],
+      },
+      editRules:{
+        userAddress: [
+          { required: true, message: "收货地址不能为空", trigger: "blur" }
+        ],
+      },
+      mrules:{
+      },
+      form: {
+        deliveryCode: null,
+        deliveryName:null,
+        deliverySn:null,
+        orderId:null,
+      }
+    }
+  },
+
+  created() {
+    this.getDicts("sys_store_pay_type").then(response => {
+      this.PayOptions = response.data;
+    });
+    this.getDicts("sys_live_order_status").then(response => {
+      this.orderOptions = response.data;
+    });
+    this.getDicts("sys_order_pay").then(response => {
+      this.payStatusOptions = response.data;
+    });
+    this.getDicts("sys_order_source").then(response => {
+      this.sourceOptions = response.data;
+    });
+    this.getDicts("sys_store_order_type").then(response => {
+      this.orderTypeOptions = response.data;
+    });
+    this.getDicts("sys_store_order_buy_type").then(response => {
+      this.orderBuyTypeOptions = response.data;
+    });
+    this.getDicts("sys_refund_status").then(response => {
+      this.refundOptions = response.data;
+    });
+    this.getDicts("sys_store_channel").then(response => {
+      this.channelOptions = response.data;
+    });
+    this.getDicts("sys_store_qw_subject").then(response => {
+      this.qwSubjectOptions = response.data;
+    });
+    this.getDicts("sys_tui_money_status").then(response => {
+      this.tuiOptions = response.data;
+    });
+    this.getDicts("sys_company_or").then(response => {
+      this.orOptions = response.data;
+    });
+    this.getDicts("sys_patient_sex").then(response => {
+      this.sexOptions = response.data;
+    });
+    this.getDicts("sys_store_delivery_pay_status").then(response => {
+      this.deliveryPayStatusOptions = response.data;
+    });
+    this.getDicts("store_order_delivery_status").then(response => {
+      this.deliveryStatusOptions = response.data;
+    });
+    this.getDicts("logistics_status").then(response => {
+      this.deliveryTypeOptions = response.data;
+    });
+
+  },
+  methods: {
+
+    getOrderLive(orderId){
+      getLiveOrder(orderId).then(response => {
+        this.item=response.data
+      });
+      this.getlistOrderitem(orderId);
+    },
+    followMsg(row){
+      const userId = this.item.userId;
+      const followDoctorId =this.item.followDoctorId;
+      const doctorName = this.item.doctorName;
+      const patientName = this.item.patientName;
+
+      setTimeout(() => {
+        this.$refs.msgDetails.getDetails(userId,followDoctorId,doctorName,patientName);
+      }, 500);
+      this.dialogVisible = true;
+    },
+    msgDialogClose(){
+      this.dialogVisible = false;
+    },
+    handlePhone(){
+      const orderId = this.item.orderId;
+      getOrderUserPhone(orderId).then(response =>{
+        this.item.userPhone = response.userPhone;
+      })
+    },
+    editDelivery(){
+      this.editDy.open = true;
+      this.editDyForm.orderId = this.item.orderId;
+
+    },
+    showListD(){
+      if(this.showList){
+        this.showProd=this.prod
+      }else{
+        this.showProd=[this.prod[0]]
+      }
+      this.showList=this.showList?false:true;
+    },
+
+    showExpress(){
+      this.expressDialog.open=true;
+      getExpress({
+        orderId: this.item.orderId,
+        deliverId: this.item.deliverySn,
+        deliverSn: this.item.deliveryCode,
+      }).then(response => {
+        this.express = response.data;
+        if(this.express!=null&&this.express.Traces!=null){
+          this.traces=this.express.Traces
+        }
+      });
+    },
+    updateErpOrder(){
+      var that=this;
+      this.$confirm('确定同步物流单号信息吗', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data={orderId:that.item.orderId}
+        return updateErp(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item=response.data
+          that.getlogList(this.item.orderId);
+          that.$parent.$parent.getList();
+        });
+        this.editDy.open = false
+      }).catch(function() {});
+
+    },
+    sendFollowMsg(){
+      var that=this;
+      this.$confirm('是否确认发送消息?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return sendMsg(that.item.orderId);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+      }).catch(function() {});
+    },
+    handleClose1(){
+      this.dialogVisible=false;
+    },
+    handleClose2(){
+      this.followDialogVisible=false;
+    },
+    follow(row){
+      getMsgFollow(row).then(response => {
+        if(response.data.formJson!=null&&response.data.formJson!=''&&response.data.writeStatus==1){
+          this.messageFollowList=JSON.parse(response.data.formJson );
+          this.followDialogVisible=true;
+        }else{
+          this.$message({
+            message: '未填写随访单',
+            type: 'info'
+          });
+          return
+        }
+      });
+    },
+
+    //修改订单状态
+    submitEditForm(){
+      this.$refs["editForm"].validate(valid => {
+        if (valid) {
+          updateLiveOrder(this.editForm).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("操作成功");
+              this.edit.open = false;
+              getLiveOrder(this.item.orderId).then(response => {
+                this.item=response.data
+                that.getlogList(this.item.orderId);
+                that.$parent.$parent.getList();
+              });
+            }
+          });
+        }
+      });
+    },
+    editOrder(){
+      this.edit.open=true;
+      this.editForm.orderId=this.item.orderId;
+      this.editForm.remark=this.item.remark;
+      this.editForm.userAddress = this.item.userAddress == null ? '' : this.item.userAddress.toString();
+      this.editForm.status = this.item.status == null ? '' : this.item.status.toString();
+      this.editForm.deliveryType = this.item.deliveryType == null ? '' : this.item.deliveryType.toString();
+      this.editForm.deliveryStatus = this.item.deliveryStatus == null ? '' : this.item.deliveryStatus.toString();
+
+    },
+    updateExpress(){
+      var that=this;
+      this.$confirm('确定同步物流信息吗,同步后将自动发货?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data=that.item.orderId
+        return updateExpress(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item=response.data
+          that.getlogList(this.item.orderId);
+          that.$parent.$parent.getList();
+        });
+      }).catch(function() {});
+    },
+    addErpOrder(){
+      var that=this;
+      this.$confirm('是否确认推送管易?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data=that.item.orderCode
+        return createErpOrder({
+          orderCode: data
+        });
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item=response.data
+          that.getlogList(this.item.orderId);
+          that.$parent.$parent.getList();
+        });
+      }).catch(function() {});
+    },
+    showErpOrder(){
+      this.erpDialog.open=true;
+      var data=this.item.extendOrderId;
+      getEroOrder({
+        extendOrderId: data
+      }).then(response => {
+        this.erp = response.data;
+        if(response.data.orders!=null&&response.data.orders.length==1){
+          this.erpOrder=response.data.orders[0]
+        }
+      });
+    },
+
+    editTuiMoney1(){
+      var that=this;
+      this.$confirm('是否解冻此订单推广佣金吗?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data=that.item.orderId;
+        return editTuiMoney(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item=response.data
+          that.getlogList(this.item.orderId);
+          that.$parent.$parent.getList();
+        });
+      }).catch(function() {});
+    },
+    editTuiMoney2(){
+      var that=this;
+      this.$confirm('是否冻结此订单推广佣金吗?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data=that.item.orderId;
+        return editTuiMoney(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item=response.data
+          that.getlogList(this.item.orderId);
+          that.$parent.$parent.getList();
+        });
+      }).catch(function() {});
+    },
+    returnCost(){
+      var that=this;
+      this.$confirm('是否退还此订单成本吗?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data=that.item.orderId;
+        return returnCost(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        this.getDetails(this.item.orderId,null,null)
+      }).catch(function() {});
+    },
+    moneyCancel(){
+      this.money=null;
+      this.moneyVisible=false;
+    },
+    refund(){
+      var that=this;
+      this.$confirm('是否确认申请退款?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data={
+          orderId:that.item.orderId
+        }
+        return refundOrderMoney(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item = response.data;
+          // this.getlogList(this.item.orderId);
+          this.$parent.$parent.getList();
+        });
+      }).catch(function() {});
+    },
+    finishOrder(){
+      var that=this;
+      this.$confirm('是否确认客户已收货?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data={
+          orderId:that.item.orderId
+        }
+        return finishOrder(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item = response.data;
+          this.getlistOrderitem(this.item.orderId);
+          this.$parent.$parent.getList();
+        });
+      }).catch(function() {});
+    },
+
+
+    syncExpress(){
+      var that=this;
+      this.$confirm('确定同步物流状态吗', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data=that.item.orderId
+        return syncExpress(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        this.expressDialog.open=false
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item=response.data
+          that.getlogList(this.item.orderId);
+          that.$parent.$parent.getList();
+        });
+      }).catch(function() {});
+    },
+    getInquiryOrder(){
+
+      this.show.open=true;
+      setTimeout(() => {
+        this.$refs.Details.getDetails(this.item.inquiryOrderId);
+      }, 1);
+    },
+    getPackageOrder(){
+      this.pack.open=true;
+      console.log(this.item.packageOrderId)
+      setTimeout(() => {
+        this.$refs.packDetails.getDetails(this.item.packageOrderId);
+      }, 1);
+    },
+    tuiOrder(){
+      var that=this;
+      this.$confirm('是否确认推送订单?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        var data={
+          orderId:that.item.orderId
+        }
+        return tuiOrder(data);
+      }).then(() => {
+        this.msgSuccess("操作成功");
+        getLiveOrder(this.item.orderId).then(response => {
+          this.item = response.data;
+          this.getlogList(this.item.orderId);
+          this.$parent.$parent.getList();
+        });
+      }).catch(function() {});
+    },
+    getDetails(orderId,nickName,storeName) {
+      this.nickName=nickName;
+      this.storeName=storeName;
+      this.item=null;
+      this.tuiMoneyLogs=null;
+      getLiveOrder(orderId).then(response => {
+        this.item = response.data;
+        console.log(this.tuiMoneyLogs)
+        this.tuiMoneyLogs = response.tuiMoneyLogs;
+        this.msgForm.userId=response.data.userId;
+        this.msgForm.followDoctorId=response.data.followDoctorId;
+        this.getlistOrderitem(this.item.orderId);
+        getOrderExpress(orderId).then(response => {
+          this.deliverList = response.data;
+          console.log(this.deliverList)
+        });
+
+        getOrderPayments(this.item.orderId).then(response => {
+          this.payments = response.payments;
+        });
+
+        getOrderLog(this.item.orderId).then(response => {
+          this.logs = response.logs
+        });
+
+        tuiMoneyLogs(this.item.orderId).then(response => {
+          this.tuiMoneyLogs = response.data
+        })
+      });
+
+    },
+    getOrder(){
+      getLiveOrder(this.item.orderId).then(response => {
+        this.item = response.data;
+        this.getlistOrderitem(this.item.orderId);
+      });
+    },
+    submitEditDyForm(){
+      this.$refs["editDyForm"].validate(valid => {
+        if (valid) {
+          editDeliveryId({orderId: this.editDyForm.orderId,
+            deliverSn: this.editDyForm.deliveryCode,
+            deliverId: this.editDyForm.deliverySn,type: 1}).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("操作成功");
+              this.editDy.open = false;
+              getLiveOrder(this.item.orderId).then(response => {
+                this.item = response.data;
+                this.$parent.$parent.getList();
+              });
+            }
+          })
+        }
+      });
+    },
+    getlistOrderitem(orderId){
+      this.prod = null
+      this.showProd=[]
+      this.showList = true
+      listOrderitem(orderId).then(response => {
+        this.prod = response.rows;
+        if (this.prod.length > 0) {
+          this.showProd=[this.prod[0]];
+        }
+      });
+    },
+    // getlogList(orderId){
+    //   logList(orderId).then(response => {
+    //     this.logs = response.rows;
+    //   });
+    // },
+    // getPayment(orderId){
+    //   payment(orderId).then(response => {
+    //     console.log(response)
+    //     this.pay = response.data;
+    //   });
+    // }
+  }
+}
+
+</script>
+
+
+<style scoped>
+.content{
+  height: 100%;
+  background-color: #fff;
+  padding: 0px 20px;
+
+}
+.contentx{
+  height: 100%;
+  background-color: #fff;
+  padding: 0px 20px 20px;
+
+
+  margin: 20px;
+}
+.el-descriptions-item__label.is-bordered-label{
+  font-weight: normal;
+}
+.el-descriptions-item__content {
+  max-width: 150px;
+  min-width: 100px;
+}
+.desct{
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #524b4a;
+  font-weight: bold;
+}
+
+.order-content{
+  margin: 10px;
+
+}
+
+.operate-container {
+  background: #F2F6FC;
+  height: 60px;
+  margin: -20px -20px 0;
+  line-height: 60px;
+}
+
+.operate-button-container {
+  float: right;
+  margin-right: 20px
+}
+</style>
+
+<style scoped>
+.chat-records {
+
+  overflow-y: auto;
+}
+.timestamp {
+  font-size: 12px;
+  color: #A9A9A9;
+}
+.chat-record {
+  margin: 10px;
+  flex-direction: column;
+  align-items: flex-start;
+}
+.sent {
+  background-color: #fbfdff;
+  color: #000839;
+}
+.sent .timestamp {
+  float: right;
+}
+.right{
+  float: right;
+}
+.received {
+  background-color: #fbfdff;
+  color: #000000;
+}
+
+.el-descriptions-item__content {
+  max-width: 150px;
+  min-width: 100px;
+}
+.order-content{
+  margin: 10px;
+
+}
+
+.operate-container {
+  background: #F2F6FC;
+  height: 60px;
+  margin: -20px -20px 0;
+  line-height: 60px;
+}
+
+.operate-button-container {
+  float: right;
+  margin-right: 20px
+}
+</style>

+ 880 - 0
src/views/live/order/storeDetail.vue

@@ -0,0 +1,880 @@
+<template>
+  <div class="order-content">
+      <div class="order-status" v-if="order!=null" >
+          <el-steps  :active="order.status==3?order.status+1:order.status" align-center>
+            <el-step title="待支付"></el-step>
+            <el-step title="待发货"></el-step>
+            <el-step title="待收货"></el-step>
+            <el-step title="交易完成"></el-step>
+          </el-steps>
+      </div>
+      <div>
+      <el-card shadow="never" style="margin-top: 15px">
+      <div class="operate-container"  v-if="order!=null">
+        <span style="margin-left: 20px" class="color-danger">订单状态:
+           <el-tag prop="status" v-for="(item, index) in statusOptions"   v-if="order.status==item.dictValue">{{item.dictLabel}}</el-tag>
+        </span>
+        <div class="operate-button-container" >
+          <el-button size="mini" @click="editOrder()"  v-hasPermi="['store:storeOrder:edit']" >修改订单</el-button>
+        </div>
+        <div class="operate-button-container"  v-hasPermi="['store:storeOrder:finishOrder']" v-if="order.status===2">
+          <el-button size="mini" @click="finishOrder()" >确认收货</el-button>
+        </div>
+        <div class="operate-button-container" v-if="order.status==1||order.status==2"  v-hasPermi="['store:storeOrder:refundOrderMoney']"  >
+          <el-button size="mini" @click="refundOrderMoney()" >退款</el-button>
+        </div>
+        <div class="operate-button-container"  v-hasPermi="['store:storeOrder:express']"  >
+          <el-button size="mini" @click="showExpress()">查看物流</el-button>
+        </div>
+        <div class="operate-button-container" v-if="order.tuiMoneyStatus==0"  v-hasPermi="['store:storeOrder:editTuiMoney']"  >
+          <el-button size="mini" @click="editTuiMoney1()" >解冻</el-button>
+        </div>
+        <div class="operate-button-container" v-if="order.tuiMoneyStatus==1"  v-hasPermi="['store:storeOrder:editTuiMoney']"  >
+          <el-button size="mini" @click="editTuiMoney2()" >冻结</el-button>
+        </div>
+
+        <div class="operate-button-container" v-if="order.status==3&&order.tuiMoneyStatus==null"  v-hasPermi="['store:storeOrder:addTuiMoney']"  >
+          <el-button size="mini" @click="addTuiMoney()" >分佣</el-button>
+        </div>
+
+<!--        <div class="operate-button-container" v-if="order.extendOrderId!=null"  v-hasPermi="['store:storeOrder:getEroOrder']"  >
+          <el-button size="mini" @click="showErpOrder()" >查看ERP订单信息</el-button>
+        </div>-->
+        <div class="operate-button-container" v-if="order.isPayRemain!=null&&order.isPayRemain==1"  v-hasPermi="['store:storeOrder:auditPayRemain']"  >
+          <el-button size="mini" @click="auditPayRemain()" >尾款审核</el-button>
+        </div>
+        <div class="operate-button-container" >
+          <el-button size="mini" @click="editDelivery()"  v-hasPermi="['store:storeOrder:editDeliveryId']" >修改物流</el-button>
+        </div>
+        <div class="operate-button-container" v-if="order.status >=1 && order.extendOrderId == null && order.deliveryId == null"  v-hasPermi="['store:storeOrder:createErpOrder']"  >
+          <el-button size="mini" @click="addErpOrder()" >创建ERP订单信息</el-button>
+        </div>
+        <!-- <div class="operate-button-container" v-show="order.status===2">
+          <el-button size="mini"  >去发货</el-button>
+          <el-button size="mini" >备注订单</el-button>
+        </div>
+        <div class="operate-button-container" v-show="order.status===4">
+          <el-button size="mini"  >订单跟踪</el-button>
+          <el-button size="mini"  disabled>备注订单</el-button>
+        </div> -->
+      </div>
+      <div style="margin: 20px 0px">
+        <span class="font-small">基本信息</span>
+      </div>
+      <el-descriptions :column="4" border  >
+            <el-descriptions-item label="订单编号"  >
+                <span v-if="order!=null">
+                  {{order.orderCode}}
+                </span>
+                <el-tag  v-for="(item, index) in createTypeOptions"    v-if="order!=null&&order.orderCreateType==item.dictValue">{{item.dictLabel}}
+                </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="会员"  >
+                <span v-if="user!=null">
+                  {{user.nickname}}({{user.phone}})
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="支付方式"  >
+              <el-tag prop="payType" v-for="(item, index) in payTypeOptions"    v-if="order!=null&&order.payType==item.dictValue">{{item.dictLabel}}
+              </el-tag>
+            </el-descriptions-item>
+<!--            <el-descriptions-item label="订单类型"  >
+              <el-tag prop="orderType" v-for="(item, index) in orderTypeOptions"    v-if="order!=null&&order.orderType==item.dictValue">{{item.dictLabel}}</el-tag>
+            </el-descriptions-item>-->
+            <el-descriptions-item label="物流公司编号"  >
+                <span v-if="order!=null">
+                  {{order.deliverySn}}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流公司名称"  >
+                <span v-if="order!=null">
+                  {{order.deliveryName}}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流单号"  >
+                <span v-if="order!=null">
+                  {{order.deliveryId}}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流状态"  >
+                <span v-if="order!=null">
+                  <el-tag prop="deliveryId" v-for="(item, index) in deliveryStatusOptions"    v-if="order!=null&&order.deliveryStatus==item.dictValue">{{item.dictLabel}}</el-tag>
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流跟踪状态"  >
+                <span v-if="order!=null">
+                  <el-tag prop="deliveryId" v-for="(item, index) in deliveryTypeOptions"    v-if="order!=null&&order.deliveryType==item.dictValue">{{item.dictLabel}}</el-tag>
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流结算费用"  >
+                <span v-if="order!=null&&order.deliveryPayMoney!=null ">
+                  {{order.deliveryPayMoney.toFixed(2) }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="快递帐单日期"  >
+                <span v-if="order!=null ">
+                  {{order.deliveryTime }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="快递结算日期"  >
+                <span v-if="order!=null ">
+                  {{order.deliveryPayTime }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="推广佣金"  >
+                <span v-if="order!=null ">
+                  {{order.tuiMoney }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="收货人"  >
+                <span v-if="order!=null ">
+                  {{order.realName }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="手机号码"  >
+                <span v-if="order!=null ">
+                  {{order.userPhone }}
+                </span>
+                <el-button icon="el-icon-search" size="mini" @click="handlePhone()" style="margin-left: 20px;" circle v-hasPermi="['store:storeOrder:queryPhone']"></el-button>
+            </el-descriptions-item>
+            <el-descriptions-item label="收货地址"  >
+              <el-popover
+                v-if="order!=null"
+                placement="top-start"
+                title="收货地址"
+                width="300"
+                trigger="hover"
+                :content="order.userAddress">
+                <span slot="reference">{{order.userAddress}}</span>
+                <el-button icon="el-icon-search" size="mini" @click="handleAddress()" style="margin-left: 20px;" circle v-hasPermi="['store:storeOrder:queryAddress']"></el-button>
+              </el-popover>
+            </el-descriptions-item>
+            <el-descriptions-item label="档期归属"  >
+              <el-tag prop="scheduleId" v-for="(item, index) in scheduleOptions"    v-if="order!=null&&order.scheduleId==item.id">{{item.name}}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="用户备注"  >
+                <span v-if="order!=null">
+                  {{order.mark}}
+                </span>
+            </el-descriptions-item>
+      </el-descriptions>
+      <div style="margin: 20px 0px"  v-if="order!=null">
+        <span class="font-small">
+          凭证信息
+        </span>
+      </div>
+      <el-image
+          v-if="certificates != null"
+          :src="certificates"
+          :preview-src-list="[certificates]"
+          :style="{ width: '100px', height: '100px' }"
+          @click.native="showImageDialog"
+        ></el-image>
+        <el-dialog :visible.sync="dialogVisibleImage" width="10%">
+          <img :src="certificates" style="width: 100%" alt="">
+        </el-dialog>
+      <div style="margin-top: 20px">
+        <span class="font-small">商品信息</span>
+      </div>
+      <el-table
+        border
+        v-if="items!=null"
+        :data="items"
+        size="small"
+        style="width: 100%;margin-top: 20px" >
+        <el-table-column label="商品图片" width="150" align="center">
+          <template slot-scope="scope">
+            <img :src="JSON.parse(scope.row.jsonInfo).image" style="height: 80px">
+          </template>
+        </el-table-column>
+        <el-table-column label="商品编码" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{JSON.parse(scope.row.jsonInfo).barCode}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品组合编码" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{JSON.parse(scope.row.jsonInfo).groupBarCode}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品名称" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{JSON.parse(scope.row.jsonInfo).productName}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="单价" width="240" align="center">
+          <template slot-scope="scope">
+            <p>¥{{JSON.parse(scope.row.jsonInfo).price.toFixed(2)}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="规格" width="240" align="center">
+          <template slot-scope="scope">
+            {{JSON.parse(scope.row.jsonInfo).sku}}
+          </template>
+        </el-table-column>
+        <el-table-column label="数量" width="180" align="center">
+          <template slot-scope="scope">
+            {{scope.row.num}}
+          </template>
+        </el-table-column>
+        <el-table-column label="处方药" width="240" align="center">
+          <template slot-scope="scope">
+            {{scope.row.isPrescribe!=null&&scope.row.isPrescribe==1?'是':'否'}}
+          </template>
+        </el-table-column>
+        <el-table-column label="小计"  align="center">
+          <template slot-scope="scope" >
+            ¥{{scope.row.num*JSON.parse(scope.row.jsonInfo).price.toFixed(2)}}
+          </template>
+        </el-table-column>
+      </el-table>
+      <div style="float: right;margin: 20px" v-if="order!=null">
+        合计:<span class="color-danger">¥{{order.totalPrice.toFixed(2)}}</span>
+      </div>
+      <div style="margin: 60px 0px 20px 0px">
+        <span class="font-small">费用信息</span>
+      </div>
+      <el-descriptions   :column="4" border  >
+          <el-descriptions-item label="商品合计"  >
+              <span v-if="order!=null">
+                ¥{{order.totalPrice.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="应付金额"  >
+              <span v-if="order!=null">
+                ¥{{order.payPrice.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="运费"  >
+              <span v-if="order!=null">
+                ¥{{order.payPostage.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="优惠券"  >
+              <span v-if="order!=null">
+                ¥{{order.couponPrice.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="积分抵扣"  >
+              <span v-if="order!=null">
+                ¥{{order.deductionPrice.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="实付金额"  >
+              <span v-if="order!=null">
+                ¥{{order.payMoney.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="代收金额"  >
+              <span v-if="order!=null">
+                ¥{{order.payDelivery.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+      </el-descriptions>
+
+      <div style="margin-top: 20px">
+        <span class="font-small">支付信息</span>
+      </div>
+      <el-table
+        border
+        :data="payments"
+        size="small"
+        style="width: 100%;margin-top: 20px" >
+          <el-table-column label="支付单号" align="center" prop="payCode" width="120px" />
+          <el-table-column label="支付金额" align="center" prop="payMoney" />
+          <el-table-column label="类型" align="center" prop="payTypeCode" />
+          <el-table-column label="交易单号" align="center" prop="bankTransactionId" />
+          <el-table-column label="银行单号" align="center" prop="bankSerialNo" />
+          <el-table-column label="创建时间" align="center" prop="createTime" />
+          <el-table-column label="支付时间" align="center" prop="payTime" />
+      </el-table>
+
+
+      <div style="margin-top: 20px">
+        <span class="font-small">操作信息</span>
+      </div>
+      <el-table style="margin-top: 20px;width: 100%"
+                ref="orderHistoryTable"
+                :data="logs" border>
+        <el-table-column label="操作时间"  width="160" align="center">
+          <template slot-scope="scope">
+            {{scope.row.changeTime}}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center">
+          <template slot-scope="scope">
+            {{scope.row.changeMessage}}
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div style="margin-top: 20px">
+        <!-- <svg-icon icon-class="marker" style="color: #606266"></svg-icon> -->
+        <span class="font-small">分佣信息</span>
+      </div>
+      <el-table
+        border
+        :data="tuiMoneyLogs"
+        size="small"
+                style="width: 100%;margin-top: 20px" >
+          <el-table-column label="公司名称" align="center" prop="companyName" width="120px" />
+          <el-table-column label="金额" align="center" prop="money" />
+          <el-table-column label="余额" align="center" prop="balance" />
+          <el-table-column label="创建时间" align="center" prop="createTime" />
+          <el-table-column label="备注" align="center" prop="remark" />
+      </el-table>
+
+        <div style="margin-top: 20px">
+          <span class="font-small">审批信息</span>
+        </div>
+        <el-table style="margin-top: 20px;width: 100%"
+                  :data="auditLogs" border>
+          <el-table-column label="操作时间"  width="160" align="center">
+            <template slot-scope="scope">
+              {{scope.row.createTime}}
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" align="center">
+            <template slot-scope="scope">
+              {{scope.row.content}}
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-card>
+    </div>
+
+    <el-dialog :title="edit.title" :visible.sync="edit.open" width="600px" append-to-body>
+      <el-form ref="editForm" :model="editForm" :rules="editRules" label-width="100px">
+<!--        <el-form-item label="订单类型" prop="orderType"  >
+            <el-select style="width: 200px" v-model="editForm.orderType" placeholder="请选择订单类型" clearable size="small" >
+              <el-option
+                      v-for="item in orderTypeOptions"
+                      :key="item.dictValue"
+                      :label="item.dictLabel"
+                      :value="item.dictValue"
+                    />
+            </el-select>
+        </el-form-item>-->
+       <el-form-item label="状态" prop="status" >
+           <el-select style="width:220px" v-model="editForm.status" placeholder="请选择状态" clearable size="small">
+            <el-option key="0"  label="待支付" value="0" />
+            <el-option key="1"  label="待发货" value="1" />
+             <el-option key="2"  label="待收货" value="2" />
+             <el-option key="3"  label="交易成功" value="3" />
+             <el-option key="-1"  label="退款中" value="-1" />
+             <el-option key="-2"  label="已退款" value="-2" />
+             <el-option key="-3"  label="已取消" value="-3" />
+           </el-select>
+         </el-form-item>
+<!--         <el-form-item label="档期归属" prop="scheduleId"  >
+            <el-select filterable style="width: 200px" v-model="editForm.scheduleId" placeholder="请选择档期" clearable size="small" >
+              <el-option
+                      v-for="item in scheduleOptions"
+                      :key="item.id"
+                      :label="item.name"
+                      :value="item.id"
+                    />
+            </el-select>
+        </el-form-item>-->
+         <el-form-item label="详情地址" prop="userAddress"  >
+          <el-input v-model="editForm.userAddress" placeholder="请输入" />
+        </el-form-item>
+        <!-- <el-form-item label="erp单号" prop="extendOrderId"  >
+          <el-input v-model="editForm.extendOrderId" placeholder="请输入" />
+        </el-form-item> -->
+        <el-form-item label="备注" prop="mark"  >
+          <el-input v-model="editForm.mark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitEditForm">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="editDy.title" :visible.sync="editDy.open" width="600px" append-to-body>
+      <el-form ref="editDyForm" :model="editDyForm" :rules="editDyRules" label-width="120px">
+<!--      <div  v-hasPermi="['store:storeOrder:updateErpOrder']"  style="margin-bottom: 20px;" >
+        <el-button size="mini" @click="updateErpOrder" >同步物流单号信息</el-button>
+      </div>-->
+        <el-form-item label="物流公司" prop="deliverySn" >
+           <el-select style="width:220px" v-model="editDyForm.deliverySn" placeholder="请选择" clearable size="small">
+            <el-option key="SF"  label="顺丰" value="SF" />
+            <el-option key="EMS"  label="邮政" value="EMS" />
+             <el-option key="ZTO"  label="中通" value="ZTO" />
+             <el-option key="STO"  label="申通" value="STO" />
+             <el-option key="JD"  label="京东" value="JD" />
+             <el-option key="DBL"  label="德邦" value="DBL" />
+             <el-option key="JTSD"  label="极兔" value="JTSD" />
+             <el-option key="YD"  label="韵达" value="YD" />
+             <el-option key="YTO"  label="圆通" value="YTO" />
+           </el-select>
+         </el-form-item>
+        <el-form-item label="物流单号" prop="deliveryId"  >
+          <el-input v-model="editDyForm.deliveryId" placeholder="请输入物流单号" />
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitEditDyForm">确 定</el-button>
+        <el-button @click="editDy.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="expressDialog.title" :visible.sync="expressDialog.open" width="600px" append-to-body>
+<!--      <div  v-hasPermi="['store:storeOrder:syncExpress']"  >
+        <el-button size="mini" @click="syncExpress()" >同步快递鸟物流状态</el-button>
+      </div>-->
+      <el-table style="margin-top: 20px;width: 100%"
+                ref="orderHistoryTable"
+                :data="traces" border>
+        <el-table-column label="操作时间"  width="160" align="center">
+          <template slot-scope="scope">
+            {{scope.row.AcceptTime}}
+          </template>
+        </el-table-column>
+         <el-table-column label="位置" align="center">
+          <template slot-scope="scope">
+            {{scope.row.Location}}
+          </template>
+        </el-table-column>
+        <el-table-column label="描述" align="center">
+          <template slot-scope="scope">
+            {{scope.row.AcceptStation}}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+
+    <el-dialog :title="erpDialog.title" :visible.sync="erpDialog.open" width="600px" append-to-body>
+      <div v-if="order!=null&&order.extendOrderId!=null&&order.status==1"  v-hasPermi="['store:storeOrder:updateExpress']"  >
+        <el-button size="mini" @click="updateExpress()" >同步物流发货</el-button>
+      </div>
+      <div class="table-layout"  v-if="erpOrder!=null">
+        <el-row>
+          <el-col :span="6" class="table-cell-title">订单编号</el-col>
+          <el-col :span="6" class="table-cell-title">是否代收</el-col>
+          <el-col :span="6" class="table-cell-title">快递编号</el-col>
+          <el-col :span="6" class="table-cell-title">快递名称</el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.code}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.cod?'是':'否'}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.express_code}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.express_name}}
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell-title">收货人</el-col>
+          <el-col :span="6" class="table-cell-title">电话</el-col>
+          <el-col :span="6" class="table-cell-title">地址</el-col>
+          <el-col :span="6" class="table-cell-title">运单号</el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_name}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_mobile}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_address}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.deliverys!=null&&erpOrder.deliverys.length>0?erpOrder.deliverys[0].mail_no:''}}
+          </el-col>
+
+        </el-row>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {auditPayRemain,addTuiMoney,syncExpress,updateExpress,getEroOrder,refundOrderMoney, editTuiMoney,getExpress,finishOrder,listStoreOrder, getStoreOrder, delStoreOrder, addStoreOrder, updateStoreOrder, exportStoreOrder,updateDeliveryId, createErpOrder,updateErp,getStoreOrderAddress,getStoreOrderPhone} from "@/api/hisStore/storeOrder";
+import { getTcmScheduleList } from "@/api/company/schedule";
+export default {
+  name: "StoreDetail",
+  props: {
+    orderId: {
+      type: [String, Number],
+      default: null
+    }
+  },
+  watch: {
+    orderId: {
+      immediate: true,
+      handler(val) {
+        if (val) {
+          this.getOrder(val);
+        }
+      }
+    }
+  },
+  data() {
+    return {
+      dialogVisibleImage: false,
+      createTypeOptions:[],
+      deliveryStatusOptions:[],
+      deliveryTypeOptions:[],
+      scheduleOptions:[],
+      schedules:[],
+      orderId:null,
+      erpDialog:{
+        title:"ERP订单信息",
+        open:false,
+      },
+      expressDialog:{
+        title:"物流信息",
+        open:false,
+      },
+      edit:{
+        title:"",
+        open:false,
+      },
+      editDy:{
+        title:"修改物流单号",
+        open:false,
+      },
+      editDyForm:{
+        deliverySn:null,
+        deliveryId:null,
+      },
+      editForm:{
+        orderType:null,
+        status:null,
+        userAddress:null,
+        // extendOrderId:null,
+        scheduleId:null,
+        mark:"",
+      },
+
+      editDyRules:{
+        deliverySn: [
+          { required: true, message: "物流公司不能为空", trigger: "blur" }
+        ],
+        deliveryId: [
+          { required: true, message: "物流单号不能为空", trigger: "blur" }
+        ],
+      },
+      editRules:{
+        userAddress: [
+          { required: true, message: "收货地址不能为空", trigger: "blur" }
+        ],
+
+      },
+      orderTypeOptions:[],
+      payTypeOptions:[],
+      statusOptions:[],
+      certificates:null,
+      orderStatus:null,
+      order:null,
+      user:{},
+      logs:[],
+      items:[],
+      express:null,
+      traces:[],
+      payments:[],
+      tuiMoneyLogs:[],
+      erpOrder:null,
+      auditLogs: [],
+    };
+  },
+  created() {
+    this.getDicts("store_order_delivery_status").then((response) => {
+      this.deliveryStatusOptions = response.data;
+    });
+    this.getDicts("store_order_delivery_type").then((response) => {
+      this.deliveryTypeOptions = response.data;
+    });
+
+    this.getDicts("store_order_type").then((response) => {
+      this.orderTypeOptions = response.data;
+    });
+    this.getDicts("store_order_status").then((response) => {
+      this.statusOptions = response.data;
+    });
+    this.getDicts("store_pay_type").then((response) => {
+      this.payTypeOptions = response.data;
+    });
+    this.getDicts("store_order_create_type").then((response) => {
+      this.createTypeOptions = response.data;
+    });
+    getTcmScheduleList().then(response => {
+      this.scheduleOptions = response.data;
+    });
+  },
+  methods: {
+    handleAddress(){
+        const id = this.order.id;
+        getStoreOrderAddress(id).then(response =>{
+            this.order.userAddress = response.address;
+        })
+      },
+      handlePhone(){
+        const id = this.order.id;
+        getStoreOrderPhone(id).then(response =>{
+            this.order.userPhone = response.userPhone;
+        })
+      },
+    showImageDialog() {
+      this.dialogVisible = true;
+    },
+    syncExpress(){
+      var that=this;
+        this.$confirm('确定同步物流状态吗', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return syncExpress(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    updateErpOrder(){
+      var that=this;
+        this.$confirm('确定同步物流单号信息吗', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return updateErp(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+          this.editDy.open = false
+        }).catch(function() {});
+    },
+    updateExpress(){
+      var that=this;
+        this.$confirm('确定同步物流信息吗,同步后将自动发货', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return updateExpress(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    showErpOrder(){
+      this.erpDialog.open=true;
+      var data={extendOrderId:this.order.extendOrderId};
+      getEroOrder(data).then(response => {
+          this.erp = response.data;
+          if(response.data.orders!=null&&response.data.orders.length==1){
+            this.erpOrder=response.data.orders[0]
+          }
+          console.log(this.erpOrder)
+
+      });
+    },
+    showExpress(){
+      this.expressDialog.open=true;
+      getExpress(this.orderId).then(response => {
+          this.express = response.data;
+          if(this.express!=null&&this.express.Traces!=null){
+              this.traces=this.express.Traces
+          }
+
+      });
+
+    },
+    submitEditForm(){
+        this.$refs["editForm"].validate(valid => {
+        if (valid) {
+          updateStoreOrder(this.editForm).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("操作成功");
+              this.edit.open = false;
+              this.getOrder(this.order.id);
+            }
+          });
+
+        }
+      });
+    },
+    submitEditDyForm(){
+        this.$refs["editDyForm"].validate(valid => {
+        if (valid) {
+          updateDeliveryId(this.editDyForm).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("操作成功");
+              this.editDy.open = false;
+              this.getOrder(this.order.id);
+            }
+          });
+
+        }
+      });
+    },
+    editTuiMoney1(){
+      var that=this;
+        this.$confirm('是否冻结此订单推广佣金吗?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return editTuiMoney(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    editTuiMoney2(){
+      var that=this;
+        this.$confirm('是否解冻此订单推广佣金吗?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return editTuiMoney(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    addTuiMoney(){
+      var that=this;
+        this.$confirm('确定手动分佣金吗?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return addTuiMoney(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    refundOrderMoney(){
+        var that=this;
+        this.$confirm('确认退款吗', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return refundOrderMoney(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    finishOrder(){
+        var that=this;
+        this.$confirm('是否确认客户已收货?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return finishOrder(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    auditPayRemain(){
+        var that=this;
+        this.$confirm('是否确认已收尾款?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return auditPayRemain(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    editOrder(){
+        this.edit.open=true;
+        this.editForm.id=this.order.id;
+        this.editForm.mark=this.order.mark
+        this.editForm.orderType=this.order.orderType.toString();
+        this.editForm.status = this.order.status.toString();
+        this.editForm.userAddress = this.order.userAddress.toString();
+        this.editForm.scheduleId = this.order.scheduleId;
+        // this.editForm.extendOrderId = this.order.extendOrderId.toString();
+
+    },
+    //推送管易按钮
+    addErpOrder(){
+        var that=this;
+        this.$confirm('是否确认推送erp?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderCode:that.order.orderCode}
+          return createErpOrder(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    editDelivery(){
+      this.editDy.open = true;
+      this.editDyForm.id = this.order.id;
+      this.editDyForm.deliveryId = this.order.deliveryId;
+      this.editDyForm.deliverySn = this.order.deliverySn;
+    },
+    getOrder(orderId){
+        this.orderId=orderId;
+        this.certificates = null;
+        getStoreOrder(orderId).then(response => {
+            this.order = response.order;
+            if(response.order.certificates != null){
+              this.certificates = response.order.certificates;
+            }
+            if(response.order.status != null){
+              this.orderStatus = response.order.status;
+            }
+            this.user = response.user;
+            this.logs = response.logs;
+            this.items = response.items;
+            this.payments=response.payments;
+            this.tuiMoneyLogs=response.tuiMoneyLogs;
+            this.auditLogs = response.auditLogs;
+        });
+     }
+  }
+};
+</script>
+<style scoped>
+.order-content{
+  margin: 10px;
+
+}
+
+.operate-container {
+  background: #F2F6FC;
+  height: 60px;
+  margin: -20px -20px 0;
+  line-height: 60px;
+}
+
+.operate-button-container {
+  float: right;
+  margin-right: 20px
+}
+</style>

+ 880 - 0
src/views/live/order/userDetail.vue

@@ -0,0 +1,880 @@
+<template>
+  <div class="order-content">
+      <div class="order-status" v-if="order!=null" >
+          <el-steps  :active="order.status==3?order.status+1:order.status" align-center>
+            <el-step title="待支付"></el-step>
+            <el-step title="待发货"></el-step>
+            <el-step title="待收货"></el-step>
+            <el-step title="交易完成"></el-step>
+          </el-steps>
+      </div>
+      <div>
+      <el-card shadow="never" style="margin-top: 15px">
+      <div class="operate-container"  v-if="order!=null">
+        <span style="margin-left: 20px" class="color-danger">订单状态:
+           <el-tag prop="status" v-for="(item, index) in statusOptions"   v-if="order.status==item.dictValue">{{item.dictLabel}}</el-tag>
+        </span>
+        <div class="operate-button-container" >
+          <el-button size="mini" @click="editOrder()"  v-hasPermi="['store:storeOrder:edit']" >修改订单</el-button>
+        </div>
+        <div class="operate-button-container"  v-hasPermi="['store:storeOrder:finishOrder']" v-if="order.status===2">
+          <el-button size="mini" @click="finishOrder()" >确认收货</el-button>
+        </div>
+        <div class="operate-button-container" v-if="order.status==1||order.status==2"  v-hasPermi="['store:storeOrder:refundOrderMoney']"  >
+          <el-button size="mini" @click="refundOrderMoney()" >退款</el-button>
+        </div>
+        <div class="operate-button-container"  v-hasPermi="['store:storeOrder:express']"  >
+          <el-button size="mini" @click="showExpress()">查看物流</el-button>
+        </div>
+        <div class="operate-button-container" v-if="order.tuiMoneyStatus==0"  v-hasPermi="['store:storeOrder:editTuiMoney']"  >
+          <el-button size="mini" @click="editTuiMoney1()" >解冻</el-button>
+        </div>
+        <div class="operate-button-container" v-if="order.tuiMoneyStatus==1"  v-hasPermi="['store:storeOrder:editTuiMoney']"  >
+          <el-button size="mini" @click="editTuiMoney2()" >冻结</el-button>
+        </div>
+
+        <div class="operate-button-container" v-if="order.status==3&&order.tuiMoneyStatus==null"  v-hasPermi="['store:storeOrder:addTuiMoney']"  >
+          <el-button size="mini" @click="addTuiMoney()" >分佣</el-button>
+        </div>
+
+<!--        <div class="operate-button-container" v-if="order.extendOrderId!=null"  v-hasPermi="['store:storeOrder:getEroOrder']"  >
+          <el-button size="mini" @click="showErpOrder()" >查看ERP订单信息</el-button>
+        </div>-->
+        <div class="operate-button-container" v-if="order.isPayRemain!=null&&order.isPayRemain==1"  v-hasPermi="['store:storeOrder:auditPayRemain']"  >
+          <el-button size="mini" @click="auditPayRemain()" >尾款审核</el-button>
+        </div>
+        <div class="operate-button-container" >
+          <el-button size="mini" @click="editDelivery()"  v-hasPermi="['store:storeOrder:editDeliveryId']" >修改物流</el-button>
+        </div>
+        <div class="operate-button-container" v-if="order.status >=1 && order.extendOrderId == null && order.deliveryId == null"  v-hasPermi="['store:storeOrder:createErpOrder']"  >
+          <el-button size="mini" @click="addErpOrder()" >创建ERP订单信息</el-button>
+        </div>
+        <!-- <div class="operate-button-container" v-show="order.status===2">
+          <el-button size="mini"  >去发货</el-button>
+          <el-button size="mini" >备注订单</el-button>
+        </div>
+        <div class="operate-button-container" v-show="order.status===4">
+          <el-button size="mini"  >订单跟踪</el-button>
+          <el-button size="mini"  disabled>备注订单</el-button>
+        </div> -->
+      </div>
+      <div style="margin: 20px 0px">
+        <span class="font-small">基本信息</span>
+      </div>
+      <el-descriptions :column="4" border  >
+            <el-descriptions-item label="订单编号"  >
+                <span v-if="order!=null">
+                  {{order.orderCode}}
+                </span>
+                <el-tag  v-for="(item, index) in createTypeOptions"    v-if="order!=null&&order.orderCreateType==item.dictValue">{{item.dictLabel}}
+                </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="会员"  >
+                <span v-if="user!=null">
+                  {{user.nickname}}({{user.phone}})
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="支付方式"  >
+              <el-tag prop="payType" v-for="(item, index) in payTypeOptions"    v-if="order!=null&&order.payType==item.dictValue">{{item.dictLabel}}
+              </el-tag>
+            </el-descriptions-item>
+<!--            <el-descriptions-item label="订单类型"  >
+              <el-tag prop="orderType" v-for="(item, index) in orderTypeOptions"    v-if="order!=null&&order.orderType==item.dictValue">{{item.dictLabel}}</el-tag>
+            </el-descriptions-item>-->
+            <el-descriptions-item label="物流公司编号"  >
+                <span v-if="order!=null">
+                  {{order.deliverySn}}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流公司名称"  >
+                <span v-if="order!=null">
+                  {{order.deliveryName}}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流单号"  >
+                <span v-if="order!=null">
+                  {{order.deliveryId}}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流状态"  >
+                <span v-if="order!=null">
+                  <el-tag prop="deliveryId" v-for="(item, index) in deliveryStatusOptions"    v-if="order!=null&&order.deliveryStatus==item.dictValue">{{item.dictLabel}}</el-tag>
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流跟踪状态"  >
+                <span v-if="order!=null">
+                  <el-tag prop="deliveryId" v-for="(item, index) in deliveryTypeOptions"    v-if="order!=null&&order.deliveryType==item.dictValue">{{item.dictLabel}}</el-tag>
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="物流结算费用"  >
+                <span v-if="order!=null&&order.deliveryPayMoney!=null ">
+                  {{order.deliveryPayMoney.toFixed(2) }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="快递帐单日期"  >
+                <span v-if="order!=null ">
+                  {{order.deliveryTime }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="快递结算日期"  >
+                <span v-if="order!=null ">
+                  {{order.deliveryPayTime }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="推广佣金"  >
+                <span v-if="order!=null ">
+                  {{order.tuiMoney }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="收货人"  >
+                <span v-if="order!=null ">
+                  {{order.realName }}
+                </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="手机号码"  >
+                <span v-if="order!=null ">
+                  {{order.userPhone }}
+                </span>
+                <el-button icon="el-icon-search" size="mini" @click="handlePhone()" style="margin-left: 20px;" circle v-hasPermi="['store:storeOrder:queryPhone']"></el-button>
+            </el-descriptions-item>
+            <el-descriptions-item label="收货地址"  >
+              <el-popover
+                v-if="order!=null"
+                placement="top-start"
+                title="收货地址"
+                width="300"
+                trigger="hover"
+                :content="order.userAddress">
+                <span slot="reference">{{order.userAddress}}</span>
+                <el-button icon="el-icon-search" size="mini" @click="handleAddress()" style="margin-left: 20px;" circle v-hasPermi="['store:storeOrder:queryAddress']"></el-button>
+              </el-popover>
+            </el-descriptions-item>
+            <el-descriptions-item label="档期归属"  >
+              <el-tag prop="scheduleId" v-for="(item, index) in scheduleOptions"    v-if="order!=null&&order.scheduleId==item.id">{{item.name}}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="用户备注"  >
+                <span v-if="order!=null">
+                  {{order.mark}}
+                </span>
+            </el-descriptions-item>
+      </el-descriptions>
+      <div style="margin: 20px 0px"  v-if="order!=null">
+        <span class="font-small">
+          凭证信息
+        </span>
+      </div>
+      <el-image
+          v-if="certificates != null"
+          :src="certificates"
+          :preview-src-list="[certificates]"
+          :style="{ width: '100px', height: '100px' }"
+          @click.native="showImageDialog"
+        ></el-image>
+        <el-dialog :visible.sync="dialogVisibleImage" width="10%">
+          <img :src="certificates" style="width: 100%" alt="">
+        </el-dialog>
+      <div style="margin-top: 20px">
+        <span class="font-small">商品信息</span>
+      </div>
+      <el-table
+        border
+        v-if="items!=null"
+        :data="items"
+        size="small"
+        style="width: 100%;margin-top: 20px" >
+        <el-table-column label="商品图片" width="150" align="center">
+          <template slot-scope="scope">
+            <img :src="JSON.parse(scope.row.jsonInfo).image" style="height: 80px">
+          </template>
+        </el-table-column>
+        <el-table-column label="商品编码" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{JSON.parse(scope.row.jsonInfo).barCode}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品组合编码" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{JSON.parse(scope.row.jsonInfo).groupBarCode}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品名称" width="300" align="center">
+          <template slot-scope="scope">
+            <p>{{JSON.parse(scope.row.jsonInfo).productName}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="单价" width="240" align="center">
+          <template slot-scope="scope">
+            <p>¥{{JSON.parse(scope.row.jsonInfo).price.toFixed(2)}}</p>
+          </template>
+        </el-table-column>
+        <el-table-column label="规格" width="240" align="center">
+          <template slot-scope="scope">
+            {{JSON.parse(scope.row.jsonInfo).sku}}
+          </template>
+        </el-table-column>
+        <el-table-column label="数量" width="180" align="center">
+          <template slot-scope="scope">
+            {{scope.row.num}}
+          </template>
+        </el-table-column>
+        <el-table-column label="处方药" width="240" align="center">
+          <template slot-scope="scope">
+            {{scope.row.isPrescribe!=null&&scope.row.isPrescribe==1?'是':'否'}}
+          </template>
+        </el-table-column>
+        <el-table-column label="小计"  align="center">
+          <template slot-scope="scope" >
+            ¥{{scope.row.num*JSON.parse(scope.row.jsonInfo).price.toFixed(2)}}
+          </template>
+        </el-table-column>
+      </el-table>
+      <div style="float: right;margin: 20px" v-if="order!=null">
+        合计:<span class="color-danger">¥{{order.totalPrice.toFixed(2)}}</span>
+      </div>
+      <div style="margin: 60px 0px 20px 0px">
+        <span class="font-small">费用信息</span>
+      </div>
+      <el-descriptions   :column="4" border  >
+          <el-descriptions-item label="商品合计"  >
+              <span v-if="order!=null">
+                ¥{{order.totalPrice.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="应付金额"  >
+              <span v-if="order!=null">
+                ¥{{order.payPrice.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="运费"  >
+              <span v-if="order!=null">
+                ¥{{order.payPostage.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="优惠券"  >
+              <span v-if="order!=null">
+                ¥{{order.couponPrice.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="积分抵扣"  >
+              <span v-if="order!=null">
+                ¥{{order.deductionPrice.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="实付金额"  >
+              <span v-if="order!=null">
+                ¥{{order.payMoney.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+          <el-descriptions-item label="代收金额"  >
+              <span v-if="order!=null">
+                ¥{{order.payDelivery.toFixed(2)}}
+              </span>
+          </el-descriptions-item>
+      </el-descriptions>
+
+      <div style="margin-top: 20px">
+        <span class="font-small">支付信息</span>
+      </div>
+      <el-table
+        border
+        :data="payments"
+        size="small"
+        style="width: 100%;margin-top: 20px" >
+          <el-table-column label="支付单号" align="center" prop="payCode" width="120px" />
+          <el-table-column label="支付金额" align="center" prop="payMoney" />
+          <el-table-column label="类型" align="center" prop="payTypeCode" />
+          <el-table-column label="交易单号" align="center" prop="bankTransactionId" />
+          <el-table-column label="银行单号" align="center" prop="bankSerialNo" />
+          <el-table-column label="创建时间" align="center" prop="createTime" />
+          <el-table-column label="支付时间" align="center" prop="payTime" />
+      </el-table>
+
+
+      <div style="margin-top: 20px">
+        <span class="font-small">操作信息</span>
+      </div>
+      <el-table style="margin-top: 20px;width: 100%"
+                ref="orderHistoryTable"
+                :data="logs" border>
+        <el-table-column label="操作时间"  width="160" align="center">
+          <template slot-scope="scope">
+            {{scope.row.changeTime}}
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center">
+          <template slot-scope="scope">
+            {{scope.row.changeMessage}}
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div style="margin-top: 20px">
+        <!-- <svg-icon icon-class="marker" style="color: #606266"></svg-icon> -->
+        <span class="font-small">分佣信息</span>
+      </div>
+      <el-table
+        border
+        :data="tuiMoneyLogs"
+        size="small"
+                style="width: 100%;margin-top: 20px" >
+          <el-table-column label="公司名称" align="center" prop="companyName" width="120px" />
+          <el-table-column label="金额" align="center" prop="money" />
+          <el-table-column label="余额" align="center" prop="balance" />
+          <el-table-column label="创建时间" align="center" prop="createTime" />
+          <el-table-column label="备注" align="center" prop="remark" />
+      </el-table>
+
+        <div style="margin-top: 20px">
+          <span class="font-small">审批信息</span>
+        </div>
+        <el-table style="margin-top: 20px;width: 100%"
+                  :data="auditLogs" border>
+          <el-table-column label="操作时间"  width="160" align="center">
+            <template slot-scope="scope">
+              {{scope.row.createTime}}
+            </template>
+          </el-table-column>
+          <el-table-column label="备注" align="center">
+            <template slot-scope="scope">
+              {{scope.row.content}}
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-card>
+    </div>
+
+    <el-dialog :title="edit.title" :visible.sync="edit.open" width="600px" append-to-body>
+      <el-form ref="editForm" :model="editForm" :rules="editRules" label-width="100px">
+<!--        <el-form-item label="订单类型" prop="orderType"  >
+            <el-select style="width: 200px" v-model="editForm.orderType" placeholder="请选择订单类型" clearable size="small" >
+              <el-option
+                      v-for="item in orderTypeOptions"
+                      :key="item.dictValue"
+                      :label="item.dictLabel"
+                      :value="item.dictValue"
+                    />
+            </el-select>
+        </el-form-item>-->
+       <el-form-item label="状态" prop="status" >
+           <el-select style="width:220px" v-model="editForm.status" placeholder="请选择状态" clearable size="small">
+            <el-option key="0"  label="待支付" value="0" />
+            <el-option key="1"  label="待发货" value="1" />
+             <el-option key="2"  label="待收货" value="2" />
+             <el-option key="3"  label="交易成功" value="3" />
+             <el-option key="-1"  label="退款中" value="-1" />
+             <el-option key="-2"  label="已退款" value="-2" />
+             <el-option key="-3"  label="已取消" value="-3" />
+           </el-select>
+         </el-form-item>
+<!--         <el-form-item label="档期归属" prop="scheduleId"  >
+            <el-select filterable style="width: 200px" v-model="editForm.scheduleId" placeholder="请选择档期" clearable size="small" >
+              <el-option
+                      v-for="item in scheduleOptions"
+                      :key="item.id"
+                      :label="item.name"
+                      :value="item.id"
+                    />
+            </el-select>
+        </el-form-item>-->
+         <el-form-item label="详情地址" prop="userAddress"  >
+          <el-input v-model="editForm.userAddress" placeholder="请输入" />
+        </el-form-item>
+        <!-- <el-form-item label="erp单号" prop="extendOrderId"  >
+          <el-input v-model="editForm.extendOrderId" placeholder="请输入" />
+        </el-form-item> -->
+        <el-form-item label="备注" prop="mark"  >
+          <el-input v-model="editForm.mark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitEditForm">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="editDy.title" :visible.sync="editDy.open" width="600px" append-to-body>
+      <el-form ref="editDyForm" :model="editDyForm" :rules="editDyRules" label-width="120px">
+<!--      <div  v-hasPermi="['store:storeOrder:updateErpOrder']"  style="margin-bottom: 20px;" >
+        <el-button size="mini" @click="updateErpOrder" >同步物流单号信息</el-button>
+      </div>-->
+        <el-form-item label="物流公司" prop="deliverySn" >
+           <el-select style="width:220px" v-model="editDyForm.deliverySn" placeholder="请选择" clearable size="small">
+            <el-option key="SF"  label="顺丰" value="SF" />
+            <el-option key="EMS"  label="邮政" value="EMS" />
+             <el-option key="ZTO"  label="中通" value="ZTO" />
+             <el-option key="STO"  label="申通" value="STO" />
+             <el-option key="JD"  label="京东" value="JD" />
+             <el-option key="DBL"  label="德邦" value="DBL" />
+             <el-option key="JTSD"  label="极兔" value="JTSD" />
+             <el-option key="YD"  label="韵达" value="YD" />
+             <el-option key="YTO"  label="圆通" value="YTO" />
+           </el-select>
+         </el-form-item>
+        <el-form-item label="物流单号" prop="deliveryId"  >
+          <el-input v-model="editDyForm.deliveryId" placeholder="请输入物流单号" />
+        </el-form-item>
+
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitEditDyForm">确 定</el-button>
+        <el-button @click="editDy.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="expressDialog.title" :visible.sync="expressDialog.open" width="600px" append-to-body>
+<!--      <div  v-hasPermi="['store:storeOrder:syncExpress']"  >
+        <el-button size="mini" @click="syncExpress()" >同步快递鸟物流状态</el-button>
+      </div>-->
+      <el-table style="margin-top: 20px;width: 100%"
+                ref="orderHistoryTable"
+                :data="traces" border>
+        <el-table-column label="操作时间"  width="160" align="center">
+          <template slot-scope="scope">
+            {{scope.row.AcceptTime}}
+          </template>
+        </el-table-column>
+         <el-table-column label="位置" align="center">
+          <template slot-scope="scope">
+            {{scope.row.Location}}
+          </template>
+        </el-table-column>
+        <el-table-column label="描述" align="center">
+          <template slot-scope="scope">
+            {{scope.row.AcceptStation}}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+
+    <el-dialog :title="erpDialog.title" :visible.sync="erpDialog.open" width="600px" append-to-body>
+      <div v-if="order!=null&&order.extendOrderId!=null&&order.status==1"  v-hasPermi="['store:storeOrder:updateExpress']"  >
+        <el-button size="mini" @click="updateExpress()" >同步物流发货</el-button>
+      </div>
+      <div class="table-layout"  v-if="erpOrder!=null">
+        <el-row>
+          <el-col :span="6" class="table-cell-title">订单编号</el-col>
+          <el-col :span="6" class="table-cell-title">是否代收</el-col>
+          <el-col :span="6" class="table-cell-title">快递编号</el-col>
+          <el-col :span="6" class="table-cell-title">快递名称</el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.code}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.cod?'是':'否'}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.express_code}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.express_name}}
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell-title">收货人</el-col>
+          <el-col :span="6" class="table-cell-title">电话</el-col>
+          <el-col :span="6" class="table-cell-title">地址</el-col>
+          <el-col :span="6" class="table-cell-title">运单号</el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_name}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_mobile}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.receiver_address}}
+          </el-col>
+          <el-col :span="6" class="table-cell">
+            {{erpOrder.deliverys!=null&&erpOrder.deliverys.length>0?erpOrder.deliverys[0].mail_no:''}}
+          </el-col>
+
+        </el-row>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {auditPayRemain,addTuiMoney,syncExpress,updateExpress,getEroOrder,refundOrderMoney, editTuiMoney,getExpress,finishOrder,listStoreOrder, getStoreOrder, delStoreOrder, addStoreOrder, updateStoreOrder, exportStoreOrder,updateDeliveryId, createErpOrder,updateErp,getStoreOrderAddress,getStoreOrderPhone} from "@/api/hisStore/storeOrder";
+import { getTcmScheduleList } from "@/api/company/schedule";
+export default {
+  name: "UserDetail",
+  props: {
+    orderId: {
+      type: [String, Number],
+      default: null
+    }
+  },
+  watch: {
+    orderId: {
+      immediate: true,
+      handler(val) {
+        if (val) {
+          this.getOrder(val);
+        }
+      }
+    }
+  },
+  data() {
+    return {
+      dialogVisibleImage: false,
+      createTypeOptions:[],
+      deliveryStatusOptions:[],
+      deliveryTypeOptions:[],
+      scheduleOptions:[],
+      schedules:[],
+      orderId:null,
+      erpDialog:{
+        title:"ERP订单信息",
+        open:false,
+      },
+      expressDialog:{
+        title:"物流信息",
+        open:false,
+      },
+      edit:{
+        title:"",
+        open:false,
+      },
+      editDy:{
+        title:"修改物流单号",
+        open:false,
+      },
+      editDyForm:{
+        deliverySn:null,
+        deliveryId:null,
+      },
+      editForm:{
+        orderType:null,
+        status:null,
+        userAddress:null,
+        // extendOrderId:null,
+        scheduleId:null,
+        mark:"",
+      },
+
+      editDyRules:{
+        deliverySn: [
+          { required: true, message: "物流公司不能为空", trigger: "blur" }
+        ],
+        deliveryId: [
+          { required: true, message: "物流单号不能为空", trigger: "blur" }
+        ],
+      },
+      editRules:{
+        userAddress: [
+          { required: true, message: "收货地址不能为空", trigger: "blur" }
+        ],
+
+      },
+      orderTypeOptions:[],
+      payTypeOptions:[],
+      statusOptions:[],
+      certificates:null,
+      orderStatus:null,
+      order:null,
+      user:{},
+      logs:[],
+      items:[],
+      express:null,
+      traces:[],
+      payments:[],
+      tuiMoneyLogs:[],
+      erpOrder:null,
+      auditLogs: [],
+    };
+  },
+  created() {
+    this.getDicts("store_order_delivery_status").then((response) => {
+      this.deliveryStatusOptions = response.data;
+    });
+    this.getDicts("store_order_delivery_type").then((response) => {
+      this.deliveryTypeOptions = response.data;
+    });
+
+    this.getDicts("store_order_type").then((response) => {
+      this.orderTypeOptions = response.data;
+    });
+    this.getDicts("store_order_status").then((response) => {
+      this.statusOptions = response.data;
+    });
+    this.getDicts("store_pay_type").then((response) => {
+      this.payTypeOptions = response.data;
+    });
+    this.getDicts("store_order_create_type").then((response) => {
+      this.createTypeOptions = response.data;
+    });
+    getTcmScheduleList().then(response => {
+      this.scheduleOptions = response.data;
+    });
+  },
+  methods: {
+    handleAddress(){
+        const id = this.order.id;
+        getStoreOrderAddress(id).then(response =>{
+            this.order.userAddress = response.address;
+        })
+      },
+      handlePhone(){
+        const id = this.order.id;
+        getStoreOrderPhone(id).then(response =>{
+            this.order.userPhone = response.userPhone;
+        })
+      },
+    showImageDialog() {
+      this.dialogVisible = true;
+    },
+    syncExpress(){
+      var that=this;
+        this.$confirm('确定同步物流状态吗', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return syncExpress(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    updateErpOrder(){
+      var that=this;
+        this.$confirm('确定同步物流单号信息吗', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return updateErp(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+          this.editDy.open = false
+        }).catch(function() {});
+    },
+    updateExpress(){
+      var that=this;
+        this.$confirm('确定同步物流信息吗,同步后将自动发货', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return updateExpress(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    showErpOrder(){
+      this.erpDialog.open=true;
+      var data={extendOrderId:this.order.extendOrderId};
+      getEroOrder(data).then(response => {
+          this.erp = response.data;
+          if(response.data.orders!=null&&response.data.orders.length==1){
+            this.erpOrder=response.data.orders[0]
+          }
+          console.log(this.erpOrder)
+
+      });
+    },
+    showExpress(){
+      this.expressDialog.open=true;
+      getExpress(this.orderId).then(response => {
+          this.express = response.data;
+          if(this.express!=null&&this.express.Traces!=null){
+              this.traces=this.express.Traces
+          }
+
+      });
+
+    },
+    submitEditForm(){
+        this.$refs["editForm"].validate(valid => {
+        if (valid) {
+          updateStoreOrder(this.editForm).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("操作成功");
+              this.edit.open = false;
+              this.getOrder(this.order.id);
+            }
+          });
+
+        }
+      });
+    },
+    submitEditDyForm(){
+        this.$refs["editDyForm"].validate(valid => {
+        if (valid) {
+          updateDeliveryId(this.editDyForm).then(response => {
+            if (response.code === 200) {
+              this.msgSuccess("操作成功");
+              this.editDy.open = false;
+              this.getOrder(this.order.id);
+            }
+          });
+
+        }
+      });
+    },
+    editTuiMoney1(){
+      var that=this;
+        this.$confirm('是否冻结此订单推广佣金吗?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return editTuiMoney(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    editTuiMoney2(){
+      var that=this;
+        this.$confirm('是否解冻此订单推广佣金吗?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return editTuiMoney(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    addTuiMoney(){
+      var that=this;
+        this.$confirm('确定手动分佣金吗?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return addTuiMoney(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    refundOrderMoney(){
+        var that=this;
+        this.$confirm('确认退款吗', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return refundOrderMoney(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    finishOrder(){
+        var that=this;
+        this.$confirm('是否确认客户已收货?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return finishOrder(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    auditPayRemain(){
+        var that=this;
+        this.$confirm('是否确认已收尾款?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderId:that.order.id}
+          return auditPayRemain(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    editOrder(){
+        this.edit.open=true;
+        this.editForm.id=this.order.id;
+        this.editForm.mark=this.order.mark
+        this.editForm.orderType=this.order.orderType.toString();
+        this.editForm.status = this.order.status.toString();
+        this.editForm.userAddress = this.order.userAddress.toString();
+        this.editForm.scheduleId = this.order.scheduleId;
+        // this.editForm.extendOrderId = this.order.extendOrderId.toString();
+
+    },
+    //推送管易按钮
+    addErpOrder(){
+        var that=this;
+        this.$confirm('是否确认推送erp?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          var data={orderCode:that.order.orderCode}
+          return createErpOrder(data);
+        }).then(() => {
+          this.msgSuccess("操作成功");
+          this.getOrder(this.order.id);
+        }).catch(function() {});
+    },
+    editDelivery(){
+      this.editDy.open = true;
+      this.editDyForm.id = this.order.id;
+      this.editDyForm.deliveryId = this.order.deliveryId;
+      this.editDyForm.deliverySn = this.order.deliverySn;
+    },
+    getOrder(orderId){
+        this.orderId=orderId;
+        this.certificates = null;
+        getStoreOrder(orderId).then(response => {
+            this.order = response.order;
+            if(response.order.certificates != null){
+              this.certificates = response.order.certificates;
+            }
+            if(response.order.status != null){
+              this.orderStatus = response.order.status;
+            }
+            this.user = response.user;
+            this.logs = response.logs;
+            this.items = response.items;
+            this.payments=response.payments;
+            this.tuiMoneyLogs=response.tuiMoneyLogs;
+            this.auditLogs = response.auditLogs;
+        });
+     }
+  }
+};
+</script>
+<style scoped>
+.order-content{
+  margin: 10px;
+
+}
+
+.operate-container {
+  background: #F2F6FC;
+  height: 60px;
+  margin: -20px -20px 0;
+  line-height: 60px;
+}
+
+.operate-button-container {
+  float: right;
+  margin-right: 20px
+}
+</style>

Some files were not shown because too many files changed in this diff