|
|
@@ -1,760 +1,659 @@
|
|
|
<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>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 直播数据 -->
|
|
|
- <div class="stats">
|
|
|
- <div class="stat-item">
|
|
|
- <p class="label">直播间浏览量</p>
|
|
|
- <p class="value">{{ live.views }}</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 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">
|
|
|
+ 累计完课人数
|
|
|
+ <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>
|
|
|
- </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">直播观看人数</div>
|
|
|
+ <div class="statistics-value">{{ statisticsData.liveViewers || 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">
|
|
|
+ 直播完课人数
|
|
|
+ <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>
|
|
|
-
|
|
|
- <!-- 黑榜 -->
|
|
|
- <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-col>
|
|
|
+ <el-col :span="4">
|
|
|
+ <div class="statistics-item">
|
|
|
+ <div class="statistics-title">回放观看人数</div>
|
|
|
+ <div class="statistics-value">{{ statisticsData.playbackViewers || 0 }}</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- <el-table :data="top10List" border style="width: 100%">
|
|
|
- <el-table-column prop="rank" label="排名" width="80"></el-table-column>
|
|
|
- <el-table-column prop="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>
|
|
|
+ </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>
|
|
|
- </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 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 * 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 '变化';
|
|
|
- }
|
|
|
- },
|
|
|
- displayLives() {
|
|
|
- return this.lives.slice(0, 4); // 最多只显示前 4 条数据
|
|
|
+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
|
|
|
},
|
|
|
- /*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";
|
|
|
+ // 完课时长设置
|
|
|
+ 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
|
|
|
},
|
|
|
- valueFormat() {
|
|
|
- return this.selectedTimeRange === "day"
|
|
|
- ? "yyyy-MM-dd"
|
|
|
- : this.selectedTimeRange === "week"
|
|
|
- ? "yyyy-MM-dd"
|
|
|
- : "yyyy-MM";
|
|
|
+ // 打标签弹窗
|
|
|
+ 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;
|
|
|
+ });
|
|
|
},
|
|
|
- created() {
|
|
|
- //this.getList();
|
|
|
+ /** 搜索按钮操作 */
|
|
|
+ handleQuery() {
|
|
|
+ this.queryParams.pageNum = 1;
|
|
|
+ this.getList();
|
|
|
},
|
|
|
- mounted() {
|
|
|
- this.selectedMetric = this.trendData[0]; // 默认选中第一个
|
|
|
- this.initChart(); // 初始化折线图
|
|
|
+ /** 重置按钮操作 */
|
|
|
+ resetQuery() {
|
|
|
+ this.dateRange = [];
|
|
|
+ this.$refs.queryForm.resetFields();
|
|
|
+ this.handleQuery();
|
|
|
},
|
|
|
- methods: {
|
|
|
- changeDate(value) {
|
|
|
- if (this.selectedTimeRange === "week" && value) {
|
|
|
- /*console.log("🟢 监听到 selectedWeek 变化:", newVal);*/
|
|
|
- this.weekRange = this.getWeekRange(value);
|
|
|
- } else {
|
|
|
- this.weekRange = "";
|
|
|
- }
|
|
|
- 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 "日期错误";
|
|
|
+ /** 全选或取消全选 */
|
|
|
+ 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);
|
|
|
}
|
|
|
-
|
|
|
- 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));
|
|
|
-
|
|
|
- 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;
|
|
|
+ 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();
|
|
|
}
|
|
|
- },
|
|
|
- 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";
|
|
|
+ });
|
|
|
+ },
|
|
|
+ /** 自动备注 */
|
|
|
+ // 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 = '付费行为';
|
|
|
}
|
|
|
- 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);
|
|
|
+ if (this.remarkForm.remarkFormat === 'time') {
|
|
|
+ notes += (index > 0 ? ';' : '') + dateStr + '—' + ruleText;
|
|
|
+ } else {
|
|
|
+ notes += (index > 0 ? ';' : '') + ruleText;
|
|
|
}
|
|
|
- },
|
|
|
- // 切换日期时更新数据
|
|
|
- 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",
|
|
|
- },
|
|
|
- ],
|
|
|
- };
|
|
|
+ });
|
|
|
+
|
|
|
+ const data = {
|
|
|
+ userIds: userIds,
|
|
|
+ notes: notes,
|
|
|
+ type: this.remarkForm.position,
|
|
|
+ nameType: 3, // 不添加客户名称
|
|
|
+ filter: false
|
|
|
+ };
|
|
|
|
|
|
- this.chart.setOption(options);
|
|
|
- },
|
|
|
- changeMetric(metric) {
|
|
|
- this.selectedMetric = metric;
|
|
|
- this.updateChart();
|
|
|
- },
|
|
|
+ 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>
|
|
|
- .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;
|
|
|
+}
|
|
|
</style>
|
|
|
-
|