|
|
@@ -1,81 +1,92 @@
|
|
|
<template>
|
|
|
- <el-container>
|
|
|
- <el-header style="margin-top: 10px">
|
|
|
+ <el-container class="play-info-container">
|
|
|
+ <el-header style="height: auto; padding: 20px;">
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
- <el-card shadow="hover" class="stat-card">
|
|
|
- <el-row :gutter="20">
|
|
|
- <el-col :span="8">
|
|
|
- <h2 style="color: green; font-size: 30px">{{ totalNum }}</h2>
|
|
|
- <p class="success">总发布量</p>
|
|
|
+ <el-card shadow="hover" class="stat-card overview-card">
|
|
|
+<!-- <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>发布趋势</span>
|
|
|
+ </div>
|
|
|
+ </template>-->
|
|
|
+ <el-row :gutter="20" align="middle">
|
|
|
+ <el-col :span="8" class="text-center">
|
|
|
+ <div class="stat-number total-num">{{ totalNum }}</div>
|
|
|
+ <div class="stat-label">总发布量</div>
|
|
|
</el-col>
|
|
|
<el-col :span="16">
|
|
|
- <div ref="pushLine" style="height: 150px"></div>
|
|
|
+ <div ref="pushLine" style="height: 120px"></div>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-card shadow="hover" class="stat-card">
|
|
|
- <h2 style="color: orange; font-size: 30px">{{ imageNum }}</h2>
|
|
|
- <p class="warning">图片发布</p>
|
|
|
+ <div class="stat-content">
|
|
|
+ <el-icon class="stat-icon icon-image"><Picture /></el-icon>
|
|
|
+ <div>
|
|
|
+ <div class="stat-number image-num">{{ imageNum }}</div>
|
|
|
+ <div class="stat-label">图片发布</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
<el-col :span="6">
|
|
|
<el-card shadow="hover" class="stat-card">
|
|
|
- <h2 style="color: green; font-size: 30px">{{ videoNum }}</h2>
|
|
|
- <p class="success">视频发布</p>
|
|
|
+ <div class="stat-content">
|
|
|
+ <el-icon class="stat-icon icon-video"><VideoCameraFilled /></el-icon>
|
|
|
+ <div>
|
|
|
+ <div class="stat-number video-num">{{ videoNum }}</div>
|
|
|
+ <div class="stat-label">视频发布</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
</el-header>
|
|
|
|
|
|
- <el-main style="margin-top: 90px">
|
|
|
- <el-card shadow="hover" style="margin-top: 10px; height: 60px">
|
|
|
- <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="40px">
|
|
|
- <el-form-item label="">
|
|
|
- <el-button type="warning" plain icon="Download" @click="handleExport">报表导出</el-button>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="" prop="sourceType">
|
|
|
- <el-input v-model="queryParams.sourceName" style="width: 150px" placeholder="请输入文件名称" @input="getRecordList"
|
|
|
- clearable></el-input>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="" prop="sourceType">
|
|
|
- <el-select v-model="queryParams.sourceType" style="width: 150px" placeholder="请选择类型" @change="getRecordList"
|
|
|
- clearable>
|
|
|
- <el-option v-for="dict in smsb_source_type" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="" prop="sourceTag">
|
|
|
- <el-select v-model="queryParams.sourceTag" style="width: 150px" placeholder="请选择分类" @change="getRecordList"
|
|
|
- clearable>
|
|
|
- <el-option v-for="dict in smsb_source_classify" :key="dict.value" :label="dict.label"
|
|
|
- :value="dict.value" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="" prop="dataRage" style="width: 500px">
|
|
|
- <el-col :span="12" style="text-align: right">
|
|
|
- <el-radio-group v-model="timeRadio" size="small" @change="handleDateRangeChange">
|
|
|
- <!-- <el-radio-button label="今日" value="today" />-->
|
|
|
- <el-radio-button label="近7天" value="week" />
|
|
|
- <el-radio-button label="近30天" value="month" />
|
|
|
- <el-radio-button label="自定义" value="diy" />
|
|
|
- </el-radio-group>
|
|
|
+ <el-main style="padding: 0 20px;">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
|
|
+ <el-row justify="space-between" align="middle" style="width: 100%">
|
|
|
+ <el-col :span="20">
|
|
|
+ <el-space wrap>
|
|
|
+ <el-form-item prop="sourceName">
|
|
|
+ <el-input v-model="queryParams.sourceName" placeholder="请输入文件名称" @input="getRecordList" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="sourceType">
|
|
|
+ <el-select v-model="queryParams.sourceType" placeholder="请选择类型" @change="getRecordList" clearable>
|
|
|
+ <el-option v-for="dict in smsb_source_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="sourceTag">
|
|
|
+ <el-select v-model="queryParams.sourceTag" placeholder="请选择分类" @change="getRecordList" clearable>
|
|
|
+ <el-option v-for="dict in smsb_source_classify" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="dataRage">
|
|
|
+ <el-radio-group v-model="timeRadio" @change="handleDateRangeChange" style="margin-right: 10px;">
|
|
|
+ <el-radio-button label="近7天" value="week" />
|
|
|
+ <el-radio-button label="近30天" value="month" />
|
|
|
+ <el-radio-button label="自定义" value="diy" />
|
|
|
+ </el-radio-group>
|
|
|
+ <el-date-picker v-model="dateRange" type="daterange" range-separator="-" start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期" :disabled="diyFlag" :clearable="false" @change="handleDateRangeChange" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-space>
|
|
|
</el-col>
|
|
|
- <el-col :span="12" style="text-align: right">
|
|
|
- <el-date-picker v-model="dateRange" type="daterange" range-separator="-"
|
|
|
- start-placeholder="开始日期" :disabled="diyFlag" :clearable="false" @change="handleDateRangeChange" end-placeholder="结束日期" style="margin-left: 10px; margin-right: 30px" />
|
|
|
+ <el-col :span="4" style="text-align: right;">
|
|
|
+ <el-button type="warning" plain icon="Download" style="margin-bottom: 15px" @click="handleExport">报表导出</el-button>
|
|
|
</el-col>
|
|
|
- </el-form-item>
|
|
|
+ </el-row>
|
|
|
</el-form>
|
|
|
- </el-card>
|
|
|
- <el-card style="margin-top: 10px">
|
|
|
- <div class="table-content">
|
|
|
- <el-table v-loading="loading" :data="playRecordList">
|
|
|
- <el-table-column label="资源ID" align="left" prop="sourceId" width="250" />
|
|
|
- <el-table-column label="资源名称" align="left" prop="fileName" :show-overflow-tooltip="true" />
|
|
|
- <el-table-column label="播放次数" align="center" prop="playTimes" width="200" />
|
|
|
- <el-table-column label="播放时长" align="center" prop="playDuration" width="200" />
|
|
|
+
|
|
|
+ <div class="table-content" style="margin-top: 10px;">
|
|
|
+ <el-table v-loading="loading" :data="playRecordList" row-key="sourceId" header-cell-class-name="table-header">
|
|
|
+ <el-table-column label="资源ID" prop="sourceId" width="250" />
|
|
|
+ <el-table-column label="资源名称" prop="fileName" :show-overflow-tooltip="true" />
|
|
|
+ <el-table-column label="播放次数" align="center" prop="playTimes" width="150" />
|
|
|
+ <el-table-column label="播放时长" align="center" prop="playDuration" width="150" />
|
|
|
<el-table-column label="分类" align="center" prop="fileTag" width="120">
|
|
|
<template #default="scope">
|
|
|
<dict-tag :options="smsb_source_classify" :value="scope.row.fileTag" />
|
|
|
@@ -88,58 +99,74 @@
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120">
|
|
|
<template #default="scope">
|
|
|
- <el-tooltip content="查看详情" placement="top">
|
|
|
- <el-button link type="primary" icon="View" @click="handleView(scope.row)"></el-button>
|
|
|
- </el-tooltip>
|
|
|
+ <el-button link type="primary" icon="View" @click="handleView(scope.row)">查看详情</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</div>
|
|
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
|
|
|
- v-model:limit="queryParams.pageSize" @pagination="getRecordList" />
|
|
|
+ v-model:limit="queryParams.pageSize" @pagination="getRecordList" />
|
|
|
</el-card>
|
|
|
</el-main>
|
|
|
</el-container>
|
|
|
- <el-dialog :title="dialog.title" v-model="dialog.visible" width="1200px" append-to-body :style="{ height: '850px' }">
|
|
|
- <div>
|
|
|
- <el-row :gutter="20">
|
|
|
- <el-col :span="16" style="text-align: right">
|
|
|
- <el-radio-group v-model="DTimeRadio" size="small" @change="DHandleDateRangeChange">
|
|
|
- <!-- <el-radio-button label="今日" value="today" />-->
|
|
|
+
|
|
|
+ <el-dialog :title="dialog.title" v-model="dialog.visible" width="70%" top="5vh" append-to-body
|
|
|
+ custom-class="details-dialog">
|
|
|
+ <el-container>
|
|
|
+ <el-header style="height: auto; padding-bottom: 20px;">
|
|
|
+ <el-row justify="end">
|
|
|
+ <el-radio-group v-model="DTimeRadio" size="small" @change="DHandleDateRangeChange" style="margin-right: 10px;">
|
|
|
<el-radio-button label="近7天" value="week" />
|
|
|
<el-radio-button label="近30天" value="month" />
|
|
|
</el-radio-group>
|
|
|
- </el-col>
|
|
|
- <el-col :span="8" style="text-align: right">
|
|
|
- <el-date-picker :disabled="true" v-model="DDateRange" type="daterange" range-separator="-"
|
|
|
- start-placeholder="开始日期" end-placeholder="结束日期" style="margin-left: 10px; margin-right: 30px" />
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
- <div ref="tadLine" class="chart-placeholder"></div>
|
|
|
- <div>
|
|
|
- <el-row :gutter="20">
|
|
|
- <el-col :span="12">
|
|
|
- <el-table v-loading="loading" :data="deviceRecordList">
|
|
|
- <el-table-column label="设备名称" align="center" prop="deviceName" />
|
|
|
- <el-table-column label="播放次数" align="center" prop="playTimes" />
|
|
|
- <el-table-column label="播放时长" align="center" prop="duration" />
|
|
|
- </el-table>
|
|
|
- </el-col>
|
|
|
- <el-col :span="12">
|
|
|
- <el-table v-loading="loading" :data="itemRecordList">
|
|
|
- <el-table-column label="组名称" align="center" prop="itemName" />
|
|
|
- <el-table-column label="类型" align="center" prop="itemType">
|
|
|
- <template #default="scope">
|
|
|
- <dict-tag :options="smsb_item_type" :value="scope.row.itemType" />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="播放次数" align="center" prop="playTimes" />
|
|
|
- <el-table-column label="播放时长" align="center" prop="duration" />
|
|
|
- </el-table>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
+<!-- <el-date-picker style="width: 280px" :disabled="true" v-model="DDateRange" type="daterange" range-separator="-"
|
|
|
+ start-placeholder="开始日期" end-placeholder="结束日期" size="small" />-->
|
|
|
+ </el-row>
|
|
|
+ </el-header>
|
|
|
+ <el-main style="padding: 0;">
|
|
|
+ <el-card shadow="never" style="margin-bottom: 20px;">
|
|
|
+ <div ref="tadLine" class="chart-placeholder"></div>
|
|
|
+ </el-card>
|
|
|
+ <div style="width: 100%">
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-card shadow="never">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>设备播放统计</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-table v-loading="loading" :data="deviceRecordList" height="250px">
|
|
|
+ <el-table-column label="设备名称" align="center" prop="deviceName" />
|
|
|
+ <el-table-column label="播放次数" align="center" prop="playTimes" />
|
|
|
+ <el-table-column label="播放时长" align="center" prop="duration" />
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-card shadow="never">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>节目播放统计</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-table v-loading="loading" :data="itemRecordList" height="250px">
|
|
|
+ <el-table-column label="组名称" align="center" prop="itemName" />
|
|
|
+ <el-table-column label="类型" align="center" prop="itemType">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="smsb_item_type" :value="scope.row.itemType" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="播放次数" align="center" prop="playTimes" />
|
|
|
+ <el-table-column label="播放时长" align="center" prop="duration" />
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </el-main>
|
|
|
+ </el-container>
|
|
|
</el-dialog>
|
|
|
</template>
|
|
|
|
|
|
@@ -154,6 +181,7 @@ import {
|
|
|
} from '@/api/smsb/source/play_record';
|
|
|
import {SourcePlayRecordForm, SourcePlayRecordQuery, SourcePlayRecordVO} from '@/api/smsb/source/play_record_type';
|
|
|
import {onUnmounted} from 'vue';
|
|
|
+import { Picture, VideoCameraFilled } from '@element-plus/icons-vue';
|
|
|
|
|
|
const {proxy} = getCurrentInstance() as ComponentInternalInstance;
|
|
|
const {smsb_source_classify, smsb_source_type, smsb_item_type} = toRefs<any>(
|
|
|
@@ -165,8 +193,8 @@ const deviceRecordList = ref<SourcePlayRecordVO[]>([]);
|
|
|
const itemRecordList = ref<SourcePlayRecordVO[]>([]);
|
|
|
const timeRadio = ref('week');
|
|
|
const DTimeRadio = ref('week');
|
|
|
-const dateRange = ref(['2025-01-01', '2025-01-01']);
|
|
|
-const DDateRange = ref(['2025-01-01', '2025-01-01']);
|
|
|
+const dateRange = ref<(string | Date)[]>([]);
|
|
|
+const DDateRange = ref<(string | Date)[]>([]);
|
|
|
const totalNum = ref(0);
|
|
|
const imageNum = ref(0);
|
|
|
const videoNum = ref(0);
|
|
|
@@ -194,6 +222,75 @@ const data = reactive<PageData<SourcePlayRecordForm, SourcePlayRecordQuery>>({
|
|
|
rules: {}
|
|
|
});
|
|
|
|
|
|
+// ★★★★★【美化核心】: 定义通用的 ECharts 美化配置 ★★★★★
|
|
|
+const getBaseChartOptions = () => ({
|
|
|
+ grid: {
|
|
|
+ left: '5%',
|
|
|
+ right: '5%',
|
|
|
+ bottom: '10%',
|
|
|
+ top: '15%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
|
+ borderColor: '#E5E5E5',
|
|
|
+ borderWidth: 1,
|
|
|
+ textStyle: {
|
|
|
+ color: '#333'
|
|
|
+ },
|
|
|
+ axisPointer: {
|
|
|
+ type: 'cross',
|
|
|
+ label: {
|
|
|
+ backgroundColor: '#6a7985'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ toolbox: {
|
|
|
+ show: false,
|
|
|
+ feature: {
|
|
|
+ saveAsImage: {
|
|
|
+ title: '保存图片',
|
|
|
+ pixelRatio: 2
|
|
|
+ }
|
|
|
+ },
|
|
|
+ right: '20px'
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ boundaryGap: false,
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#888'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ axisLabel: {
|
|
|
+ color: '#888'
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: '#E5E5E5'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: [],
|
|
|
+ right: 'center',
|
|
|
+ top: '5px',
|
|
|
+ textStyle: {
|
|
|
+ color: '#555'
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
const getTimesAndDurationLine = async () => {
|
|
|
const params = {
|
|
|
startTime: DDateRange.value[0],
|
|
|
@@ -201,64 +298,62 @@ const getTimesAndDurationLine = async () => {
|
|
|
sourceId: dialogSourceId.value
|
|
|
};
|
|
|
const res = await timesAndDurationLine(params);
|
|
|
- const playDurationList = res.data.playDurationList.map((seconds) => Math.round((seconds / 60) * 100) / 100);
|
|
|
- if (!tadLine.value) {
|
|
|
- // console.log('[tadLine] ref is null when initializing echarts');
|
|
|
- } else {
|
|
|
- // console.log('[tadLine] ref:', tadLine.value);
|
|
|
- }
|
|
|
+ const playDurationList = res.data.playDurationList.map((seconds: number) => Math.round((seconds / 60) * 100) / 100);
|
|
|
+
|
|
|
if (tadLine.value) {
|
|
|
echarts.dispose(tadLine.value);
|
|
|
- // console.log('[tadLine] echarts instance disposed before init');
|
|
|
}
|
|
|
const tadLineInstance = echarts.init(tadLine.value, 'macaroons');
|
|
|
- // console.log('[tadLine] echarts instance created:', tadLineInstance);
|
|
|
- tadLineInstance.setOption({
|
|
|
- title: {
|
|
|
- text: ''
|
|
|
- },
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis'
|
|
|
- },
|
|
|
+
|
|
|
+ // 合并通用配置和特定配置
|
|
|
+ const option = {
|
|
|
+ ...getBaseChartOptions(),
|
|
|
legend: {
|
|
|
+ ...getBaseChartOptions().legend,
|
|
|
data: ['播放次数', '播放时长(分钟)']
|
|
|
},
|
|
|
- grid: {
|
|
|
- left: '3%',
|
|
|
- right: '4%',
|
|
|
- bottom: '3%',
|
|
|
- containLabel: true
|
|
|
- },
|
|
|
- toolbox: {
|
|
|
- feature: {
|
|
|
- saveAsImage: {}
|
|
|
- }
|
|
|
- },
|
|
|
xAxis: {
|
|
|
- type: 'category',
|
|
|
- boundaryGap: false,
|
|
|
+ ...getBaseChartOptions().xAxis,
|
|
|
data: res.data.timeList
|
|
|
},
|
|
|
- yAxis: {
|
|
|
- type: 'value'
|
|
|
- },
|
|
|
series: [
|
|
|
{
|
|
|
name: '播放次数',
|
|
|
type: 'line',
|
|
|
- stack: 'Total',
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ itemStyle: { color: '#5470C6' },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(84, 112, 198, 0.4)' },
|
|
|
+ { offset: 1, color: 'rgba(84, 112, 198, 0)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
data: res.data.playTimesList
|
|
|
},
|
|
|
{
|
|
|
name: '播放时长(分钟)',
|
|
|
type: 'line',
|
|
|
- stack: 'Total',
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ itemStyle: { color: '#91CC75' },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(145, 204, 117, 0.4)' },
|
|
|
+ { offset: 1, color: 'rgba(145, 204, 117, 0)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
data: playDurationList
|
|
|
}
|
|
|
]
|
|
|
- });
|
|
|
+ };
|
|
|
+
|
|
|
+ tadLineInstance.setOption(option);
|
|
|
};
|
|
|
+
|
|
|
const { queryParams, form, rules } = toRefs(data);
|
|
|
+
|
|
|
+// ★★★★★【应用美化】: 修改 getPushNumber 函数 ★★★★★
|
|
|
const getPushNumber = async () => {
|
|
|
const res = await pushNumber();
|
|
|
totalNum.value = res.data.totalNum;
|
|
|
@@ -266,56 +361,56 @@ const getPushNumber = async () => {
|
|
|
videoNum.value = res.data.videoNum;
|
|
|
|
|
|
const lineRes = await getPushLine();
|
|
|
- if (!pushLine.value) {
|
|
|
- // console.log('[pushLine] ref is null when initializing echarts');
|
|
|
- } else {
|
|
|
- // console.log('[pushLine] ref:', pushLine.value);
|
|
|
- }
|
|
|
+
|
|
|
if (pushLine.value) {
|
|
|
echarts.dispose(pushLine.value);
|
|
|
- // console.log('[pushLine] echarts instance disposed before init');
|
|
|
}
|
|
|
const pushLineInstance = echarts.init(pushLine.value, 'macaroons');
|
|
|
- // console.log('[pushLine] echarts instance created:', pushLineInstance);
|
|
|
- pushLineInstance.setOption({
|
|
|
- title: {
|
|
|
- text: ''
|
|
|
- },
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis'
|
|
|
- },
|
|
|
- legend: {
|
|
|
- data: ['']
|
|
|
- },
|
|
|
- grid: {
|
|
|
+
|
|
|
+ // 合并通用配置和特定配置
|
|
|
+ const option = {
|
|
|
+ ...getBaseChartOptions(),
|
|
|
+ grid: { // 针对小图表微调 grid
|
|
|
left: '3%',
|
|
|
right: '4%',
|
|
|
- bottom: '3%',
|
|
|
+ bottom: '5%',
|
|
|
+ top: '10%',
|
|
|
containLabel: true
|
|
|
},
|
|
|
- toolbox: {
|
|
|
- feature: {
|
|
|
- saveAsImage: {}
|
|
|
- }
|
|
|
+ tooltip: { // 微调 tooltip
|
|
|
+ ...getBaseChartOptions().tooltip
|
|
|
},
|
|
|
+ legend: {}, // 这个图表不需要 legend
|
|
|
xAxis: {
|
|
|
- type: 'category',
|
|
|
- boundaryGap: false,
|
|
|
+ ...getBaseChartOptions().xAxis,
|
|
|
data: lineRes.data.timeList
|
|
|
},
|
|
|
yAxis: {
|
|
|
- type: 'value'
|
|
|
+ ...getBaseChartOptions().yAxis,
|
|
|
+ axisLabel: { show: false }, // 小图表可以隐藏y轴标签
|
|
|
+ splitLine: { show: false } // 隐藏分割线
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
- name: '',
|
|
|
+ name: '发布量',
|
|
|
type: 'line',
|
|
|
- stack: 'Total',
|
|
|
+ smooth: true,
|
|
|
+ showSymbol: false,
|
|
|
+ itemStyle: { color: '#34a853' }, // 与总发布量颜色一致
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(52, 168, 83, 0.4)' },
|
|
|
+ { offset: 1, color: 'rgba(52, 168, 83, 0)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
data: lineRes.data.numberList
|
|
|
}
|
|
|
]
|
|
|
- });
|
|
|
+ };
|
|
|
+
|
|
|
+ pushLineInstance.setOption(option);
|
|
|
};
|
|
|
+
|
|
|
/** 导出按钮操作 */
|
|
|
const handleExport = () => {
|
|
|
proxy?.download(
|
|
|
@@ -327,7 +422,7 @@ const handleExport = () => {
|
|
|
);
|
|
|
};
|
|
|
const handleView = async (row?: SourcePlayRecordVO) => {
|
|
|
- dialog.title = row.fileName;
|
|
|
+ dialog.title = "详情 - " + row.fileName;
|
|
|
dialog.visible = true;
|
|
|
dialogSourceId.value = row.sourceId;
|
|
|
DHandleDateRangeChange();
|
|
|
@@ -345,8 +440,8 @@ const getDeviceItemList = async () => {
|
|
|
/** 查询资源播放记录列表 */
|
|
|
const getRecordList = async () => {
|
|
|
loading.value = true;
|
|
|
- queryParams.value.startTime = dateRange.value[0];
|
|
|
- queryParams.value.endTime = dateRange.value[1];
|
|
|
+ queryParams.value.startTime = dateRange.value[0] as string;
|
|
|
+ queryParams.value.endTime = dateRange.value[1] as string;
|
|
|
const res = await listPlayRecordSummary(queryParams.value);
|
|
|
playRecordList.value = res.rows;
|
|
|
playRecordList.value.forEach((item) => {
|
|
|
@@ -356,21 +451,32 @@ const getRecordList = async () => {
|
|
|
loading.value = false;
|
|
|
};
|
|
|
// 格式化秒数为 HH:mm:ss
|
|
|
-const formatDuration = (seconds) => {
|
|
|
+const formatDuration = (seconds: number | null | undefined): string => {
|
|
|
+ if (seconds === null || seconds === undefined) return '00:00:00';
|
|
|
const hours = Math.floor(seconds / 3600);
|
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
const secs = seconds % 60;
|
|
|
|
|
|
const paddedHours = String(hours).padStart(2, '0');
|
|
|
const paddedMinutes = String(minutes).padStart(2, '0');
|
|
|
- const paddedSeconds = String(secs).padStart(2, '0');
|
|
|
+ const paddedSeconds = String(Math.round(secs)).padStart(2, '0');
|
|
|
|
|
|
return `${paddedHours}:${paddedMinutes}:${paddedSeconds}`;
|
|
|
};
|
|
|
+
|
|
|
+const formatDate = (date: Date | string): string => {
|
|
|
+ if (!date) return '';
|
|
|
+ const d = new Date(date);
|
|
|
+ const year = d.getFullYear();
|
|
|
+ const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(d.getDate()).padStart(2, '0');
|
|
|
+ return `${year}-${month}-${day}`;
|
|
|
+};
|
|
|
+
|
|
|
const handleDateRangeChange = () => {
|
|
|
const rangeType = timeRadio.value;
|
|
|
const today = new Date();
|
|
|
- const startDate = new Date();
|
|
|
+ let startDate = new Date();
|
|
|
const endDate = new Date();
|
|
|
switch (rangeType) {
|
|
|
case 'week':
|
|
|
@@ -385,11 +491,12 @@ const handleDateRangeChange = () => {
|
|
|
break;
|
|
|
case "diy" :
|
|
|
diyFlag.value = false;
|
|
|
- dateRange.value = [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])];
|
|
|
+ if (dateRange.value && dateRange.value.length === 2) {
|
|
|
+ dateRange.value = [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])];
|
|
|
+ }
|
|
|
break;
|
|
|
default:
|
|
|
break
|
|
|
- // throw new Error('Invalid range type');
|
|
|
}
|
|
|
getRecordList();
|
|
|
getPushNumber();
|
|
|
@@ -397,10 +504,11 @@ const handleDateRangeChange = () => {
|
|
|
const DHandleDateRangeChange = () => {
|
|
|
const rangeType = DTimeRadio.value;
|
|
|
const today = new Date();
|
|
|
- const startDate = new Date();
|
|
|
+ let startDate = new Date();
|
|
|
const endDate = new Date();
|
|
|
switch (rangeType) {
|
|
|
case 'today':
|
|
|
+ startDate = today;
|
|
|
break;
|
|
|
case 'week':
|
|
|
startDate.setDate(today.getDate() - 7);
|
|
|
@@ -415,61 +523,99 @@ const DHandleDateRangeChange = () => {
|
|
|
getTimesAndDurationLine();
|
|
|
getDeviceItemList();
|
|
|
};
|
|
|
-const formatDate = (date: 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}`;
|
|
|
-};
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
handleDateRangeChange();
|
|
|
- // getPushNumber();
|
|
|
- // getRecordList();
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
if (pushLine?.value) {
|
|
|
echarts.dispose(pushLine.value);
|
|
|
- // console.log('[play_info] pushLine disposed on unmount');
|
|
|
}
|
|
|
if (tadLine?.value) {
|
|
|
echarts.dispose(tadLine.value);
|
|
|
- // console.log('[play_info] tadLine disposed on unmount');
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
</script>
|
|
|
-<style scoped>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.play-info-container {
|
|
|
+ background-color: #f0f2f5;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
.stat-card {
|
|
|
- text-align: center;
|
|
|
+ border-radius: 12px;
|
|
|
+ border: none;
|
|
|
height: 170px;
|
|
|
-}
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
|
|
|
-.number {
|
|
|
- font-size: 24px;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
+ &.overview-card {
|
|
|
+ .card-header {
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ .text-center {
|
|
|
+ border-right: 1px solid #e0e0e0;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-.success {
|
|
|
- color: green;
|
|
|
+ .stat-content {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-icon {
|
|
|
+ font-size: 48px;
|
|
|
+ &.icon-image { color: #ff9900; }
|
|
|
+ &.icon-video { color: #34a853; }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-number {
|
|
|
+ font-size: 28px;
|
|
|
+ font-weight: bold;
|
|
|
+ line-height: 1.2;
|
|
|
+
|
|
|
+ &.total-num { color: #34a853; }
|
|
|
+ &.image-num { color: #ff9900; }
|
|
|
+ &.video-num { color: #34a853; }
|
|
|
+ }
|
|
|
+
|
|
|
+ .stat-label {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #888;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-.danger {
|
|
|
- color: red;
|
|
|
+.el-card {
|
|
|
+ border-radius: 12px;
|
|
|
}
|
|
|
|
|
|
-.warning {
|
|
|
- color: orange;
|
|
|
+.table-header {
|
|
|
+ background-color: #f5f7fa !important;
|
|
|
+ color: #333;
|
|
|
+ font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.chart-placeholder {
|
|
|
- height: 250px;
|
|
|
- /*background: #f5f5f5;*/
|
|
|
- border-radius: 8px;
|
|
|
+ height: 280px;
|
|
|
+ width: 100%;
|
|
|
}
|
|
|
|
|
|
-.disk-progress .el-progress--line {
|
|
|
- margin-bottom: 15px;
|
|
|
- max-width: 600px;
|
|
|
- margin-top: 50px;
|
|
|
+.details-dialog .el-dialog__body {
|
|
|
+ padding: 10px 20px 30px 20px;
|
|
|
+ max-height: calc(90vh - 120px);
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.card-header {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #333;
|
|
|
}
|
|
|
</style>
|