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