| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775 |
- <template>
- <div class="play-dashboard-root">
- <div class="play-dashboard-page">
- <el-container class="play-dashboard-container" style="height: 100vh">
- <el-header style="height: auto; padding: 20px;">
- <el-card shadow="hover" class="header-card">
- <el-row justify="end" align="middle">
- <el-col :span="19" style="text-align: right">
- <el-radio-group v-model="timeRadio" size="small" @change="handleDateRangeChange"
- class="time-radio-group">
- <el-radio-button label="近7天" value="week"/>
- <el-radio-button label="近30天" value="month"/>
- <el-radio-button label="自定义" value="diy"/>
- </el-radio-group>
- </el-col>
- <el-col :span="5" style="text-align: right">
- <el-date-picker v-model="dateRange" type="daterange" @change="handleDateRangeChange"
- range-separator="-"
- start-placeholder="开始日期"
- :disabled="diyFlag" :clearable="false" end-placeholder="结束日期"
- style="margin-left: 10px; margin-right: 30px"/>
- </el-col>
- </el-row>
- </el-card>
- </el-header>
- <el-main style="padding: 0 20px;">
- <el-row :gutter="20">
- <el-col :span="12">
- <el-card shadow="never" class="stat-card-large">
- <el-row :gutter="20">
- <el-col :span="8">
- <div class="stat-content">
- <p class="stat-label">总内容量</p>
- <h2 class="stat-number primary">{{ totalNum }}</h2>
- </div>
- </el-col>
- <el-col :span="16">
- <div ref="createLine" class="chart-small"></div>
- </el-col>
- </el-row>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card shadow="never" class="stat-card">
- <p class="stat-label">图片素材</p>
- <h2 class="stat-number warning">{{ imageNum }}</h2>
- </el-card>
- </el-col>
- <el-col :span="6">
- <el-card shadow="never" class="stat-card">
- <p class="stat-label">视频素材</p>
- <h2 class="stat-number success">{{ videoNum }}</h2>
- </el-card>
- </el-col>
- </el-row>
- <el-row :gutter="20" class="margin-top-20">
- <el-col :span="18">
- <el-row :gutter="20">
- <el-col :span="8">
- <el-card shadow="never">
- <h3 class="chart-title">内容占比</h3>
- <div ref="typePie" class="chart-placeholder"></div>
- </el-card>
- </el-col>
- <el-col :span="8">
- <el-card shadow="never">
- <h3 class="chart-title">类型占比</h3>
- <div ref="tagPie" class="chart-placeholder"></div>
- </el-card>
- </el-col>
- <el-col :span="8">
- <el-card shadow="never">
- <h3 class="chart-title">素材统计</h3>
- <div ref="typeAndTag" class="chart-placeholder"></div>
- </el-card>
- </el-col>
- </el-row>
- <el-row :gutter="20" class="margin-top-20">
- <el-col :span="12">
- <el-card shadow="never" class="disk-card">
- <h3 class="chart-title">空间使用情况</h3>
- <div class="disk-info">
- <p>已用空间</p>
- <h2>{{ diskUsed }} GB / {{ diskTotal }} GB</h2>
- </div>
- <div class="disk-progress">
- <el-row align="middle">
- <el-col :span="3">
- <h3 class="progress-label">图片:</h3>
- </el-col>
- <el-col :span="21">
- <el-progress :text-inside="true" :stroke-width="22" :percentage="diskImage"
- color="#E6A23C" />
- </el-col>
- </el-row>
- </div>
- <div class="disk-progress">
- <el-row align="middle">
- <el-col :span="3">
- <h3 class="progress-label">视频:</h3>
- </el-col>
- <el-col :span="21">
- <el-progress :text-inside="true" :stroke-width="22" :percentage="diskVideo"
- color="#67C23A" />
- </el-col>
- </el-row>
- </div>
- </el-card>
- </el-col>
- <el-col :span="12">
- <el-card shadow="never">
- <h3 class="chart-title">播放时长统计</h3>
- <div ref="onlineTimeLine" class="chart-placeholder"></div>
- </el-card>
- </el-col>
- </el-row>
- </el-col>
- <el-col :span="6">
- <el-card shadow="never" class="top5-card">
- <div>
- <el-row :gutter="20" align="middle" class="top5-header">
- <el-col :span="12">
- <h3 class="chart-title">内容 TOP 5</h3>
- </el-col>
- <el-col :span="12">
- <el-select v-model="topType" @change="getPlayTop" placeholder="Select" style="margin-right: 10px">
- <el-option label="播放次数" value="1" />
- <el-option label="播放时长" value="2" />
- </el-select>
- </el-col>
- </el-row>
- </div>
- <div class="top-stats">
- <el-row :gutter="20" align="bottom">
- <el-col :span="12">
- <h4>图片</h4>
- </el-col>
- <el-col :span="12" class="text-right">
- <h3 class="top-stat-number">{{ imageTopNum }}</h3>
- </el-col>
- </el-row>
- </div>
- <div ref="playImageTop" class="chart-placeholder"></div>
- <div class="top-stats">
- <el-row :gutter="20" align="bottom">
- <el-col :span="12">
- <h4>视频</h4>
- </el-col>
- <el-col :span="12" class="text-right">
- <h3 class="top-stat-number">{{ videoTopNum }}</h3>
- </el-col>
- </el-row>
- </div>
- <div ref="playVideoTop" class="chart-placeholder"></div>
- </el-card>
- </el-col>
- </el-row>
- </el-main>
- </el-container>
- </div>
- </div>
- </template>
- <style>
- /* Global styles to ensure full height and remove default margins */
- html,
- body,
- #app {
- height: 100%;
- margin: 0;
- padding: 0;
- background-color: #f0f2f5;
- /* Set a background for the whole page */
- }
- .play-dashboard-root {
- height: 100vh;
- overflow-y: auto;
- }
- .play-dashboard-page {
- height: 100%;
- }
- /* Overriding Element Plus container styles for proper layout */
- .play-dashboard-container {
- height: 100%;
- flex-direction: column !important;
- display: flex !important;
- }
- .el-main.scrollable-main {
- height: 100%;
- overflow-y: auto;
- padding: 20px;
- }
- /* Overrides for nested layout components if they exist */
- .play-dashboard-page .navbar-fixed-full {
- position: static !important;
- }
- .play-dashboard-page .app-wrapper,
- .play-dashboard-page .layout-body,
- .play-dashboard-page .main-container {
- height: auto !important;
- min-height: auto !important;
- overflow: visible !important;
- display: block !important;
- margin-top: 0 !important;
- }
- </style>
- <style scoped>
- /* Main Content Area */
- .main-content {
- padding: 20px;
- background-color: #f0f2f5;
- }
- .play-dashboard-header {
- margin-bottom: 20px;
- height: 50px;
- }
- /* Header Card */
- .header-card {
- border-radius: 8px;
- border: none;
- }
- .text-right {
- text-align: right;
- }
- .date-picker {
- margin-left: 10px;
- }
- /* General Card Styling */
- .el-card {
- border-radius: 12px;
- border: none;
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
- height: 100%;
- }
- /* Statistic Cards */
- .stat-card,
- .stat-card-large {
- text-align: center;
- display: flex;
- flex-direction: column;
- justify-content: center;
- padding: 20px;
- }
- .stat-card-large {
- text-align: left;
- }
- .stat-content {
- text-align: center;
- }
- .stat-label {
- font-size: 14px;
- color: #606266;
- margin: 0 0 10px 0;
- font-weight: bold;
- }
- .stat-number {
- font-size: 36px;
- font-weight: bold;
- margin: 0;
- line-height: 1.2;
- }
- .primary {
- color: #409EFF;
- }
- .success {
- color: #67C23A;
- }
- .warning {
- color: #E6A23C;
- }
- .danger {
- color: #F56C6C;
- }
- /* Chart Styling */
- .chart-title {
- font-size: 16px;
- font-weight: 600;
- color: #303133;
- margin-bottom: 20px;
- padding-left: 10px;
- }
- .chart-small {
- height: 120px;
- }
- .chart-placeholder {
- height: 230px;
- }
- .margin-top-20 {
- margin-top: 20px;
- }
- /* Disk Usage Card */
- .disk-card {
- height: 300px;
- }
- .disk-info {
- text-align: center;
- margin: 10px 0;
- }
- .disk-info p {
- color: #606266;
- margin: 0;
- }
- .disk-info h2 {
- font-size: 28px;
- font-weight: bold;
- color: #303133;
- margin-top: 8px;
- }
- .disk-progress {
- margin-top: 25px;
- padding: 0 15px;
- }
- .progress-label {
- font-size: 14px;
- font-weight: normal;
- color: #606266;
- }
- :deep(.el-progress-bar__inner) {
- border-radius: 100px;
- }
- :deep(.el-progress-bar__outer) {
- border-radius: 100px;
- }
- /* Top 5 Card */
- .top5-card {
- height: 605px; /* Allow height to adjust to content */
- }
- .top5-header {
- margin-bottom: 20px;
- }
- .top-stats {
- margin-top: 15px;
- padding: 0 10px;
- }
- .top-stats h4 {
- font-size: 16px;
- font-weight: 500;
- color: #606266;
- margin: 0;
- }
- .top-stat-number {
- color: #303133;
- font-weight: bold;
- margin: 0;
- }
- </style>
- <script setup lang="ts">
- import * as echarts from 'echarts';
- import {onMounted, onUnmounted, ref} from 'vue';
- import {
- diskUse,
- fileStatistics,
- fileStatisticsByTag,
- numLine,
- statisticsByTypeAndTag
- } from '@/api/smsb/source/minioData';
- import {playTopStatistics, sumOnlineTimeLine} from '@/api/smsb/source/play_record';
- // ECharts instances
- let onlineTimeLineInstance: echarts.ECharts | null = null;
- let playImageTopInstance: echarts.ECharts | null = null;
- let playVideoTopInstance: echarts.ECharts | null = null;
- let typeAndTagInstance: echarts.ECharts | null = null;
- let tagPieInstance: echarts.ECharts | null = null;
- let createLineInstance: echarts.ECharts | null = null;
- let typePieInstance: echarts.ECharts | null = null;
- // ECharts Color Palette
- const colorPalette = ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'];
- onUnmounted(() => {
- // Dispose all echarts instances on component unmount
- onlineTimeLineInstance?.dispose();
- playImageTopInstance?.dispose();
- playVideoTopInstance?.dispose();
- typeAndTagInstance?.dispose();
- tagPieInstance?.dispose();
- createLineInstance?.dispose();
- typePieInstance?.dispose();
- });
- // Reactive state variables
- const timeRadio = ref('week');
- const dateRange = ref<[Date, Date]>([new Date(), new Date()]);
- const diyFlag = ref(true);
- const totalNum = ref(0);
- const imageNum = ref(0);
- const videoNum = ref(0);
- const imageTopNum = ref(0);
- const videoTopNum = ref(0);
- const diskUsed = ref('0');
- const diskTotal = ref('0');
- const diskImage = ref(0);
- const diskVideo = ref(0);
- const topType = ref('1');
- // Template refs for chart containers
- const createLine = ref<HTMLElement>();
- const typePie = ref<HTMLElement>();
- const tagPie = ref<HTMLElement>();
- const typeAndTag = ref<HTMLElement>();
- const playImageTop = ref<HTMLElement>();
- const playVideoTop = ref<HTMLElement>();
- const onlineTimeLine = ref<HTMLElement>();
- // Helper function to format date
- const formatDate = (date: Date): string => {
- 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}`;
- };
- // Set initial date range to the last 7 days
- const setInitialDateRange = () => {
- const endDate = new Date();
- const startDate = new Date();
- startDate.setDate(endDate.getDate() - 6); // Inclusive of the start day
- dateRange.value = [startDate, endDate];
- };
- const getDiskUse = async () => {
- try {
- const res = await diskUse();
- diskTotal.value = (res.data.total / 1024 / 1024).toFixed(2);
- diskUsed.value = (res.data.used / 1024 / 1024).toFixed(2); // Keep consistent decimal places
- diskImage.value = parseFloat(((res.data.imageUse / res.data.total) * 100).toFixed(2));
- diskVideo.value = parseFloat(((res.data.videoUse / res.data.total) * 100).toFixed(2));
- } catch (error) {
- console.error('Error fetching disk usage:', error);
- }
- };
- const getOnlineTimeLine = async () => {
- if (!onlineTimeLine.value) return;
- try {
- const params = {
- startTime: formatDate(dateRange.value[0]),
- endTime: formatDate(dateRange.value[1])
- };
- const res = await sumOnlineTimeLine(params);
- onlineTimeLineInstance = echarts.init(onlineTimeLine.value, 'macaroons');
- onlineTimeLineInstance.setOption({
- color: ['#80FFA5', '#00DDFF'],
- tooltip: { trigger: 'axis' },
- legend: { data: ['图片', '视频'], textStyle: { color: '#606266' } },
- grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
- xAxis: {
- type: 'category',
- boundaryGap: false,
- data: res.data.timeList,
- axisLine: { lineStyle: { color: '#DCDFE6' } },
- axisLabel: { color: '#909399' }
- },
- yAxis: {
- type: 'value',
- axisLine: { show: true, lineStyle: { color: '#DCDFE6' } },
- axisLabel: { color: '#909399' },
- splitLine: { lineStyle: { color: '#E4E7ED', type: 'dashed' } }
- },
- series: [
- {
- name: '图片',
- type: 'line',
- smooth: true,
- data: res.data.imageNumberList,
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(128, 255, 165, 0.5)' }, { offset: 1, color: 'rgba(128, 255, 165, 0)' }])
- }
- },
- {
- name: '视频',
- type: 'line',
- smooth: true,
- data: res.data.videoNumberList,
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(0, 221, 255, 0.5)' }, { offset: 1, color: 'rgba(0, 221, 255, 0)' }])
- }
- }
- ]
- });
- } catch (error) {
- console.error('Error fetching online time line:', error);
- }
- };
- const getPlayTop = async () => {
- if (!playImageTop.value || !playVideoTop.value) return;
- try {
- const params = {
- startTime: formatDate(dateRange.value[0]),
- endTime: formatDate(dateRange.value[1]),
- type: topType.value
- };
- const res = await playTopStatistics(params);
- imageTopNum.value = res.data.imageNum;
- videoTopNum.value = res.data.videoNum;
- // 文本截取函数
- const truncateText = (text: string, maxLength: number = 15): string => {
- if (!text) return '';
- return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
- };
- // Image Top Chart
- playImageTopInstance = echarts.init(playImageTop.value, 'macaroons');
- playImageTopInstance.setOption({
- tooltip: {
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
- formatter: (params) => {
- const param = params[0];
- return `${param.name}<br/>${param.seriesName}: ${param.value}`;
- }
- },
- grid: { left: '3%', right: '10%', bottom: '3%', containLabel: true },
- xAxis: { type: 'value', boundaryGap: [0, 0.01], show: false },
- yAxis: {
- type: 'category',
- data: res.data.imageList,
- axisLabel: {
- color: '#606266',
- formatter: (value) => truncateText(value, 5) // 限制y轴标签最多显示5个字符
- }
- },
- series: [{
- type: 'bar',
- data: res.data.imageNumberList,
- itemStyle: { color: '#E6A23C', borderRadius: [0, 5, 5, 0] },
- label: {
- show: false // 如果需要在柱子上显示数值可以设为true
- }
- }]
- });
- // Video Top Chart
- playVideoTopInstance = echarts.init(playVideoTop.value, 'macaroons');
- playVideoTopInstance.setOption({
- tooltip: {
- trigger: 'axis',
- axisPointer: { type: 'shadow' },
- formatter: (params) => {
- const param = params[0];
- return `${param.name}<br/>${param.seriesName}: ${param.value}`;
- }
- },
- grid: { left: '3%', right: '10%', bottom: '3%', containLabel: true },
- xAxis: { type: 'value', boundaryGap: [0, 0.01], show: false },
- yAxis: {
- type: 'category',
- data: res.data.videoList,
- axisLabel: {
- color: '#606266',
- formatter: (value) => truncateText(value, 5) // 限制y轴标签最多显示5个字符
- }
- },
- series: [{
- type: 'bar',
- data: res.data.videoNumberList,
- itemStyle: { color: '#67C23A', borderRadius: [0, 5, 5, 0] },
- label: {
- show: false // 如果需要在柱子上显示数值可以设为true
- }
- }]
- });
- } catch (error) {
- console.error('Error fetching play top statistics:', error);
- }
- };
- const getNumByTypeAndTag = async () => {
- if (!typeAndTag.value) return;
- try {
- const res = await statisticsByTypeAndTag();
- typeAndTagInstance = echarts.init(typeAndTag.value, 'macaroons');
- typeAndTagInstance.setOption({
- color: colorPalette,
- legend: { textStyle: { color: '#606266' } },
- tooltip: {},
- dataset: {
- source: [['product', '广告', '公益', '宣传'], ['图片'].concat(res.data.imageTagList), ['视频'].concat(res.data.videoTagList)]
- },
- xAxis: { type: 'category', axisLabel: { color: '#909399' } },
- yAxis: { axisLabel: { color: '#909399' }, splitLine: { lineStyle: { color: '#E4E7ED', type: 'dashed' } } },
- series: [{ type: 'bar' }, { type: 'bar' }, { type: 'bar' }]
- });
- } catch (error) {
- console.error('Error fetching statistics by type and tag:', error);
- }
- };
- const getNumByTag = async () => {
- if (!tagPie.value) return;
- try {
- const res = await fileStatisticsByTag();
- tagPieInstance = echarts.init(tagPie.value, 'macaroons');
- tagPieInstance.setOption({
- color: ['#fac858', '#91cc75', '#73c0de'],
- tooltip: { trigger: 'item', formatter: '{b} : {c} ({d}%)' },
- legend: { orient: 'vertical', left: 'left', textStyle: { color: '#606266' } },
- series: [
- {
- name: '类型占比',
- type: 'pie',
- radius: ['40%', '65%'],
- avoidLabelOverlap: false,
- label: { show: false, position: 'center' },
- emphasis: { label: { show: true, fontSize: '20', fontWeight: 'bold' } },
- data: [
- { value: res.data.ggNum, name: '广告' },
- { value: res.data.gyNum, name: '公益' },
- { value: res.data.xcNum, name: '宣传' }
- ]
- }
- ]
- });
- } catch (error) {
- console.error('Error fetching statistics by tag:', error);
- }
- };
- const getNumAndLine = async () => {
- try {
- const params = {
- startTime: formatDate(dateRange.value[0]),
- endTime: formatDate(dateRange.value[1])
- };
- const res = await fileStatistics();
- totalNum.value = res.data.totalNum;
- imageNum.value = res.data.imageNum;
- videoNum.value = res.data.videoNum;
- if (createLine.value) {
- const lineRes = await numLine(params);
- createLineInstance = echarts.init(createLine.value, 'macaroons');
- createLineInstance.setOption({
- tooltip: { trigger: 'axis' },
- grid: { left: '0', right: '0', bottom: '0', top: '10%', containLabel: false },
- xAxis: { type: 'category', boundaryGap: false, data: lineRes.data.timeList, show: false },
- yAxis: { type: 'value', show: false },
- series: [{
- name: '内容量',
- type: 'line',
- smooth: true,
- symbol: 'none',
- lineStyle: { color: '#409EFF', width: 2 },
- areaStyle: {
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' }, { offset: 1, color: 'rgba(64, 158, 255, 0)' }])
- },
- data: lineRes.data.numberList
- }]
- });
- }
- if (typePie.value) {
- typePieInstance = echarts.init(typePie.value, 'macaroons');
- typePieInstance.setOption({
- color: ['#E6A23C', '#67C23A'],
- tooltip: { trigger: 'item', formatter: '{b} : {c} ({d}%)' },
- legend: { orient: 'vertical', left: 'left', textStyle: { color: '#606266' } },
- series: [{
- name: '内容占比',
- type: 'pie',
- radius: ['40%', '65%'],
- avoidLabelOverlap: false,
- label: { show: false, position: 'center' },
- emphasis: { label: { show: true, fontSize: '20', fontWeight: 'bold' } },
- data: [
- { value: res.data.imageNum, name: '图片' },
- { value: res.data.videoNum, name: '视频' }
- ],
- }]
- });
- }
- } catch (error) {
- console.error('Error fetching number and line data:', error);
- }
- };
- const handleDateRangeChange = () => {
- const rangeType = timeRadio.value;
- const endDate = new Date();
- const startDate = new Date();
- if (rangeType === 'week') {
- startDate.setDate(endDate.getDate() - 6);
- dateRange.value = [startDate, endDate];
- diyFlag.value = true;
- } else if (rangeType === 'month') {
- startDate.setMonth(endDate.getMonth() - 1);
- dateRange.value = [startDate, endDate];
- diyFlag.value = true;
- } else if (rangeType === 'diy') {
- diyFlag.value = false;
- return; // Don't refetch until user selects a new DIY range
- }
- // Refetch data for the new date range
- fetchAllData();
- };
- const fetchAllData = () => {
- getNumAndLine();
- getPlayTop();
- getOnlineTimeLine();
- }
- onMounted(() => {
- setInitialDateRange();
- // Initial data fetch
- getNumByTag();
- getNumByTypeAndTag();
- getDiskUse();
- fetchAllData();
- // Resize charts on window resize
- const resizeHandler = () => {
- onlineTimeLineInstance?.resize();
- playImageTopInstance?.resize();
- playVideoTopInstance?.resize();
- typeAndTagInstance?.resize();
- tagPieInstance?.resize();
- createLineInstance?.resize();
- typePieInstance?.resize();
- };
- window.addEventListener('resize', resizeHandler);
- onUnmounted(() => {
- window.removeEventListener('resize', resizeHandler);
- });
- });
- </script>
|