瀏覽代碼

直播数据

yuhongqi 4 天之前
父節點
當前提交
717765aff1
共有 4 個文件被更改,包括 1042 次插入789 次删除
  1. 51 0
      src/api/live/liveData.js
  2. 982 727
      src/views/live/liveData/index.vue
  3. 1 0
      src/views/live/liveOrder/index.vue
  4. 8 62
      src/views/live/liveVideo/index.vue

+ 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'
+  })
+}

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

@@ -1,760 +1,1015 @@
 <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";
+
+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>
-

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

@@ -183,6 +183,7 @@
       <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="totalNum" />
       <el-table-column label="物流编号" align="center" prop="deliveryCode" />
       <el-table-column label="物流名称" align="center" prop="deliveryName" />
       <el-table-column label="物流单号" align="center" prop="deliverySn" />

+ 8 - 62
src/views/live/liveVideo/index.vue

@@ -11,20 +11,12 @@
         />
       </el-form-item>
       <el-form-item label="分类" prop="category">
-        <el-select
+        <el-input
           v-model="queryParams.category"
-          placeholder="请选择分类"
+          placeholder="请输入分类"
           clearable
-          filterable
-          style="width: 200px"
-        >
-          <el-option
-            v-for="item in categoryOptions"
-            :key="item.dictValue"
-            :label="item.dictLabel"
-            :value="item.dictValue"
-          />
-        </el-select>
+          @keyup.enter.native="handleQuery"
+        />
       </el-form-item>
       <el-form-item label="备注" prop="remark">
         <el-input
@@ -95,7 +87,7 @@
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="编号" align="center" prop="videoId" />
       <el-table-column label="视频名称" align="center" prop="videoName" />
-      <el-table-column label="分类" align="center" prop="category" :formatter="categoryFormatter" />
+      <el-table-column label="分类" align="center" prop="category" />
       <el-table-column label="排序" align="center" prop="sort" />
       <el-table-column label="备注" align="center" prop="remark" />
       <el-table-column label="视频地址" align="center" prop="videoUrl">
@@ -167,19 +159,7 @@
           <el-input v-model="form.videoName" placeholder="请输入视频名称" />
         </el-form-item>
         <el-form-item label="分类" prop="category">
-          <el-select
-            v-model="form.category"
-            placeholder="请选择分类"
-            filterable
-            style="width: 100%"
-          >
-            <el-option
-              v-for="item in categoryOptions"
-              :key="item.dictValue"
-              :label="item.dictLabel"
-              :value="item.dictValue"
-            />
-          </el-select>
+          <el-input v-model="form.category" placeholder="请输入分类" />
         </el-form-item>
         <el-form-item label="排序" prop="sort">
           <el-input-number v-model="form.sort" :min="0" placeholder="请输入排序号" />
@@ -227,19 +207,7 @@
           <el-input v-model="editVideoInfoForm.videoName" placeholder="请输入视频名称" />
         </el-form-item>
         <el-form-item label="分类" prop="category">
-          <el-select
-            v-model="editVideoInfoForm.category"
-            placeholder="请选择分类"
-            filterable
-            style="width: 100%"
-          >
-            <el-option
-              v-for="item in categoryOptions"
-              :key="item.dictValue"
-              :label="item.dictLabel"
-              :value="item.dictValue"
-            />
-          </el-select>
+          <el-input v-model="editVideoInfoForm.category" placeholder="请输入分类" />
         </el-form-item>
         <el-form-item label="排序" prop="sort">
           <el-input-number v-model="editVideoInfoForm.sort" :min="0" placeholder="请输入排序号" />
@@ -297,19 +265,7 @@
     <el-dialog title="批量修改分类" :visible.sync="batchUpdateCategoryDialog" width="500px" append-to-body>
       <el-form ref="batchUpdateCategoryForm" :model="batchUpdateCategoryForm" label-width="100px">
         <el-form-item label="分类" prop="category">
-          <el-select
-            v-model="batchUpdateCategoryForm.category"
-            placeholder="请选择分类"
-            filterable
-            style="width: 100%"
-          >
-            <el-option
-              v-for="item in categoryOptions"
-              :key="item.dictValue"
-              :label="item.dictLabel"
-              :value="item.dictValue"
-            />
-          </el-select>
+          <el-input v-model="batchUpdateCategoryForm.category" placeholder="请输入分类" />
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
@@ -332,7 +288,6 @@ export default {
     return {
       //字典
       videoTypeOptions: [],
-      categoryOptions: [],
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -375,9 +330,6 @@ export default {
       },
       // 表单校验
       rules: {
-        category: [
-          { required: true, message: "分类不能为空", trigger: "change" }
-        ]
       },
       isPrivate:null,
       // 是否显示修改视频信息对话框
@@ -413,9 +365,6 @@ export default {
   created() {
     this.getList();
     this.getAllCompanyOptions();
-    this.getDicts("sys_live_video_category").then(response => {
-      this.categoryOptions = response.data;
-    });
   },
   mounted() {
     this.getAllCompanyOptions();
@@ -458,9 +407,6 @@ export default {
     videoTypeFormatter(row, column) {
       return this.selectDictLabel(this.videoTypeOptions, row.status);
     },
-    categoryFormatter(row, column) {
-      return this.selectDictLabel(this.categoryOptions, row.category);
-    },
     // 取消按钮
     cancel() {
       this.open = false;