|
@@ -1,20 +1,16 @@
|
|
|
import { Component, OnInit } from '@angular/core';
|
|
import { Component, OnInit } from '@angular/core';
|
|
|
-import { CommonModule, NgFor, DatePipe } from '@angular/common';
|
|
|
|
|
-import { debounceTime } from 'rxjs/operators';
|
|
|
|
|
-import { DashboardService } from '../services/dashboard.service';
|
|
|
|
|
|
|
+import { CommonModule, NgFor, NgIf, DatePipe } from '@angular/common';
|
|
|
|
|
+import { DashboardService, NewArtworkTab } from '../services/dashboard.service';
|
|
|
|
|
|
|
|
-// NG-ZORRO 组件
|
|
|
|
|
import { NzCardModule } from 'ng-zorro-antd/card';
|
|
import { NzCardModule } from 'ng-zorro-antd/card';
|
|
|
import { NzIconModule } from 'ng-zorro-antd/icon';
|
|
import { NzIconModule } from 'ng-zorro-antd/icon';
|
|
|
import { NzGridModule } from 'ng-zorro-antd/grid';
|
|
import { NzGridModule } from 'ng-zorro-antd/grid';
|
|
|
import { NzStatisticModule } from 'ng-zorro-antd/statistic';
|
|
import { NzStatisticModule } from 'ng-zorro-antd/statistic';
|
|
|
import { NzSpinModule } from 'ng-zorro-antd/spin';
|
|
import { NzSpinModule } from 'ng-zorro-antd/spin';
|
|
|
import { NzTableModule } from 'ng-zorro-antd/table';
|
|
import { NzTableModule } from 'ng-zorro-antd/table';
|
|
|
-import { NzDividerModule } from 'ng-zorro-antd/divider';
|
|
|
|
|
import { NzProgressModule } from 'ng-zorro-antd/progress';
|
|
import { NzProgressModule } from 'ng-zorro-antd/progress';
|
|
|
import { NzPageHeaderModule } from 'ng-zorro-antd/page-header';
|
|
import { NzPageHeaderModule } from 'ng-zorro-antd/page-header';
|
|
|
-import { NzTagModule } from 'ng-zorro-antd/tag';
|
|
|
|
|
-import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
|
|
|
|
|
|
|
+import { NzTabsModule } from 'ng-zorro-antd/tabs';
|
|
|
import { NzModalService } from 'ng-zorro-antd/modal';
|
|
import { NzModalService } from 'ng-zorro-antd/modal';
|
|
|
import { NzMessageService } from 'ng-zorro-antd/message';
|
|
import { NzMessageService } from 'ng-zorro-antd/message';
|
|
|
|
|
|
|
@@ -24,19 +20,17 @@ import { NzMessageService } from 'ng-zorro-antd/message';
|
|
|
imports: [
|
|
imports: [
|
|
|
CommonModule,
|
|
CommonModule,
|
|
|
NgFor,
|
|
NgFor,
|
|
|
|
|
+ NgIf,
|
|
|
DatePipe,
|
|
DatePipe,
|
|
|
- // NG-ZORRO 模块
|
|
|
|
|
NzCardModule,
|
|
NzCardModule,
|
|
|
NzIconModule,
|
|
NzIconModule,
|
|
|
NzGridModule,
|
|
NzGridModule,
|
|
|
NzStatisticModule,
|
|
NzStatisticModule,
|
|
|
NzSpinModule,
|
|
NzSpinModule,
|
|
|
NzTableModule,
|
|
NzTableModule,
|
|
|
- NzDividerModule,
|
|
|
|
|
NzProgressModule,
|
|
NzProgressModule,
|
|
|
NzPageHeaderModule,
|
|
NzPageHeaderModule,
|
|
|
- NzTagModule,
|
|
|
|
|
- NzToolTipModule,
|
|
|
|
|
|
|
+ NzTabsModule,
|
|
|
],
|
|
],
|
|
|
providers: [NzModalService, NzMessageService],
|
|
providers: [NzModalService, NzMessageService],
|
|
|
template: `
|
|
template: `
|
|
@@ -44,215 +38,122 @@ import { NzMessageService } from 'ng-zorro-antd/message';
|
|
|
<nz-page-header [nzGhost]="false">
|
|
<nz-page-header [nzGhost]="false">
|
|
|
<nz-page-header-title>数据看板</nz-page-header-title>
|
|
<nz-page-header-title>数据看板</nz-page-header-title>
|
|
|
<nz-page-header-extra>
|
|
<nz-page-header-extra>
|
|
|
- <span
|
|
|
|
|
- nz-icon
|
|
|
|
|
- nzType="sync"
|
|
|
|
|
- nzTheme="outline"
|
|
|
|
|
- (click)="refreshData()"
|
|
|
|
|
- ></span>
|
|
|
|
|
|
|
+ <span nz-icon nzType="sync" nzTheme="outline" (click)="refreshData()"></span>
|
|
|
</nz-page-header-extra>
|
|
</nz-page-header-extra>
|
|
|
<nz-page-header-content>
|
|
<nz-page-header-content>
|
|
|
- <p>
|
|
|
|
|
- 最后更新时间:{{ lastUpdateTime | date : 'yyyy-MM-dd HH:mm:ss' }}
|
|
|
|
|
- </p>
|
|
|
|
|
|
|
+ <p>最后更新时间:{{ lastUpdateTime | date : 'yyyy-MM-dd HH:mm:ss' }}</p>
|
|
|
</nz-page-header-content>
|
|
</nz-page-header-content>
|
|
|
</nz-page-header>
|
|
</nz-page-header>
|
|
|
|
|
|
|
|
<nz-spin [nzSpinning]="isLoading" nzTip="数据加载中...">
|
|
<nz-spin [nzSpinning]="isLoading" nzTip="数据加载中...">
|
|
|
- <!-- 核心指标卡片 -->
|
|
|
|
|
- <div class="metrics-container">
|
|
|
|
|
- <nz-row [nzGutter]="16">
|
|
|
|
|
- <!-- 日活用户数 -->
|
|
|
|
|
- <nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6">
|
|
|
|
|
- <nz-card nzHoverable>
|
|
|
|
|
- <nz-statistic
|
|
|
|
|
- [nzTitle]="'日活用户(DAU)'"
|
|
|
|
|
- [nzValue]="activeUsersToday"
|
|
|
|
|
- [nzPrefix]="userIcon"
|
|
|
|
|
- [nzSuffix]="'人'"
|
|
|
|
|
- [nzValueStyle]="{ color: '#1890ff' }"
|
|
|
|
|
- ></nz-statistic>
|
|
|
|
|
- <ng-template #userIcon>
|
|
|
|
|
- <span nz-icon nzType="user" nzTheme="outline"></span>
|
|
|
|
|
- </ng-template>
|
|
|
|
|
- <div class="compare">
|
|
|
|
|
- <span>实时数据自动更新</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </nz-card>
|
|
|
|
|
- </nz-col>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 日均广告收益 -->
|
|
|
|
|
- <nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6">
|
|
|
|
|
- <nz-card nzHoverable>
|
|
|
|
|
- <nz-statistic
|
|
|
|
|
- [nzTitle]="'日广告收益'"
|
|
|
|
|
- [nzValue]="dailyRevenue"
|
|
|
|
|
- [nzPrefix]="revenueIcon"
|
|
|
|
|
- [nzSuffix]="'元'"
|
|
|
|
|
- [nzValueStyle]="{ color: '#faad14' }"
|
|
|
|
|
- ></nz-statistic>
|
|
|
|
|
- <ng-template #revenueIcon>
|
|
|
|
|
- <span nz-icon nzType="dollar" nzTheme="outline"></span>
|
|
|
|
|
- </ng-template>
|
|
|
|
|
- <div class="compare">
|
|
|
|
|
- <span>来自广告平台</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </nz-card>
|
|
|
|
|
- </nz-col>
|
|
|
|
|
-
|
|
|
|
|
- <!-- DAU 趋势占位 -->
|
|
|
|
|
- <nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6">
|
|
|
|
|
- <nz-card nzHoverable>
|
|
|
|
|
- <nz-statistic
|
|
|
|
|
- [nzTitle]="'7日平均DAU'"
|
|
|
|
|
- [nzValue]="avgDau7d"
|
|
|
|
|
- [nzPrefix]="trendIcon"
|
|
|
|
|
- [nzSuffix]="'人'"
|
|
|
|
|
- [nzValueStyle]="{ color: '#13c2c2' }"
|
|
|
|
|
- ></nz-statistic>
|
|
|
|
|
- <ng-template #trendIcon>
|
|
|
|
|
- <span nz-icon nzType="line-chart" nzTheme="outline"></span>
|
|
|
|
|
- </ng-template>
|
|
|
|
|
- <div class="compare">
|
|
|
|
|
- <span>近 7 天平均值</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </nz-card>
|
|
|
|
|
- </nz-col>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 收益趋势占位 -->
|
|
|
|
|
- <nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6">
|
|
|
|
|
- <nz-card nzHoverable>
|
|
|
|
|
- <nz-statistic
|
|
|
|
|
- [nzTitle]="'7日收益总额'"
|
|
|
|
|
- [nzValue]="totalRevenue7d"
|
|
|
|
|
- [nzPrefix]="totalRevenueIcon"
|
|
|
|
|
- [nzSuffix]="'元'"
|
|
|
|
|
- [nzValueStyle]="{ color: '#eb2f96' }"
|
|
|
|
|
- ></nz-statistic>
|
|
|
|
|
- <ng-template #totalRevenueIcon>
|
|
|
|
|
- <span nz-icon nzType="bar-chart" nzTheme="outline"></span>
|
|
|
|
|
- </ng-template>
|
|
|
|
|
- <div class="compare">
|
|
|
|
|
- <span>近 7 天累计</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </nz-card>
|
|
|
|
|
- </nz-col>
|
|
|
|
|
- </nz-row>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 广告收益部分 -->
|
|
|
|
|
- <div class="section">
|
|
|
|
|
- <h3 class="section-title">今日广告收益</h3>
|
|
|
|
|
- <nz-row [nzGutter]="16">
|
|
|
|
|
- <nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8">
|
|
|
|
|
- <nz-card nzHoverable>
|
|
|
|
|
- <nz-statistic
|
|
|
|
|
- [nzTitle]="'Banner广告收益'"
|
|
|
|
|
- [nzValue]="bannerRevenue"
|
|
|
|
|
- [nzPrefix]="bannerIcon"
|
|
|
|
|
- [nzSuffix]="'元'"
|
|
|
|
|
- [nzValueStyle]="{ color: '#fa8c16' }"
|
|
|
|
|
- ></nz-statistic>
|
|
|
|
|
- <ng-template #bannerIcon>
|
|
|
|
|
- <span nz-icon nzType="picture" nzTheme="outline"></span>
|
|
|
|
|
- </ng-template>
|
|
|
|
|
- <div class="compare">
|
|
|
|
|
- <span>展示量 {{ bannerImpressions }} 次</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </nz-card>
|
|
|
|
|
- </nz-col>
|
|
|
|
|
-
|
|
|
|
|
- <nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8">
|
|
|
|
|
- <nz-card nzHoverable>
|
|
|
|
|
- <nz-statistic
|
|
|
|
|
- [nzTitle]="'插屏广告收益'"
|
|
|
|
|
- [nzValue]="interstitialRevenue"
|
|
|
|
|
- [nzPrefix]="interstitialIcon"
|
|
|
|
|
- [nzSuffix]="'元'"
|
|
|
|
|
- [nzValueStyle]="{ color: '#fa541c' }"
|
|
|
|
|
- ></nz-statistic>
|
|
|
|
|
- <ng-template #interstitialIcon>
|
|
|
|
|
- <span nz-icon nzType="switcher" nzTheme="outline"></span>
|
|
|
|
|
- </ng-template>
|
|
|
|
|
- <div class="compare">
|
|
|
|
|
- <span>展示量 {{ interstitialImpressions }} 次</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </nz-card>
|
|
|
|
|
- </nz-col>
|
|
|
|
|
-
|
|
|
|
|
- <nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8">
|
|
|
|
|
- <nz-card nzHoverable>
|
|
|
|
|
- <nz-statistic
|
|
|
|
|
- [nzTitle]="'激励广告收益'"
|
|
|
|
|
- [nzValue]="rewardedRevenue"
|
|
|
|
|
- [nzPrefix]="rewardedIcon"
|
|
|
|
|
- [nzSuffix]="'元'"
|
|
|
|
|
- [nzValueStyle]="{ color: '#eb2f96' }"
|
|
|
|
|
- ></nz-statistic>
|
|
|
|
|
- <ng-template #rewardedIcon>
|
|
|
|
|
- <span nz-icon nzType="gift" nzTheme="outline"></span>
|
|
|
|
|
- </ng-template>
|
|
|
|
|
- <div class="compare">
|
|
|
|
|
- <span>展示量 {{ rewardedImpressions }} 次</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </nz-card>
|
|
|
|
|
- </nz-col>
|
|
|
|
|
- </nz-row>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 今日上新作品 -->
|
|
|
|
|
- <div class="section">
|
|
|
|
|
- <h3 class="section-title">今日上新作品</h3>
|
|
|
|
|
- <nz-table
|
|
|
|
|
- #worksTable
|
|
|
|
|
- [nzData]="newWorksToday"
|
|
|
|
|
- [nzLoading]="isLoading"
|
|
|
|
|
- [nzFrontPagination]="false"
|
|
|
|
|
- [nzBordered]="true"
|
|
|
|
|
- [nzSize]="'small'"
|
|
|
|
|
- >
|
|
|
|
|
- <thead>
|
|
|
|
|
- <tr>
|
|
|
|
|
- <th>作品</th>
|
|
|
|
|
- <th>名称</th>
|
|
|
|
|
- <th>填色开始数</th>
|
|
|
|
|
- <th>填色完成数</th>
|
|
|
|
|
- <th>完成率</th>
|
|
|
|
|
- <th>操作</th>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </thead>
|
|
|
|
|
- <tbody>
|
|
|
|
|
- <tr *ngFor="let work of worksTable.data">
|
|
|
|
|
- <td>
|
|
|
|
|
- <div
|
|
|
|
|
- class="work-thumbnail"
|
|
|
|
|
- [style.backgroundImage]="'url(' + work.thumbnail + ')'"
|
|
|
|
|
- ></div>
|
|
|
|
|
- </td>
|
|
|
|
|
- <td>{{ work.name }}</td>
|
|
|
|
|
- <td>{{ work.startedCount }}</td>
|
|
|
|
|
- <td>{{ work.completedCount }}</td>
|
|
|
|
|
- <td>
|
|
|
|
|
- <nz-progress
|
|
|
|
|
- [nzPercent]="work.completionRate"
|
|
|
|
|
- [nzStrokeColor]="
|
|
|
|
|
- work.completionRate > 70
|
|
|
|
|
- ? '#52c41a'
|
|
|
|
|
- : work.completionRate > 30
|
|
|
|
|
- ? '#faad14'
|
|
|
|
|
- : '#f5222d'
|
|
|
|
|
- "
|
|
|
|
|
- [nzShowInfo]="true"
|
|
|
|
|
- [nzStrokeWidth]="5"
|
|
|
|
|
- ></nz-progress>
|
|
|
|
|
- </td>
|
|
|
|
|
- <td>
|
|
|
|
|
- <a (click)="showWorkDetails(work)">详情</a>
|
|
|
|
|
- </td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </nz-table>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <nz-row [nzGutter]="16">
|
|
|
|
|
+ <nz-col [nzXs]="24" [nzLg]="10">
|
|
|
|
|
+ <nz-card nzTitle="用户活跃" nzHoverable>
|
|
|
|
|
+ <nz-statistic
|
|
|
|
|
+ [nzTitle]="'当日日活(DAU)'"
|
|
|
|
|
+ [nzValue]="activeUsersToday"
|
|
|
|
|
+ [nzPrefix]="userIcon"
|
|
|
|
|
+ [nzSuffix]="'人'"
|
|
|
|
|
+ [nzValueStyle]="{ color: '#1677ff' }"
|
|
|
|
|
+ ></nz-statistic>
|
|
|
|
|
+ <ng-template #userIcon>
|
|
|
|
|
+ <span nz-icon nzType="user" nzTheme="outline"></span>
|
|
|
|
|
+ </ng-template>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="range-buttons">
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="range-btn"
|
|
|
|
|
+ [class.active]="selectedDauRange === 7"
|
|
|
|
|
+ (click)="changeDauRange(7)"
|
|
|
|
|
+ >7天</button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="range-btn"
|
|
|
|
|
+ [class.active]="selectedDauRange === 14"
|
|
|
|
|
+ (click)="changeDauRange(14)"
|
|
|
|
|
+ >14天</button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="range-btn"
|
|
|
|
|
+ [class.active]="selectedDauRange === 30"
|
|
|
|
|
+ (click)="changeDauRange(30)"
|
|
|
|
|
+ >30天</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="chart-wrapper">
|
|
|
|
|
+ <svg viewBox="0 0 600 180" class="line-chart" preserveAspectRatio="none">
|
|
|
|
|
+ <polyline
|
|
|
|
|
+ *ngIf="dauChartPoints"
|
|
|
|
|
+ [attr.points]="dauChartPoints"
|
|
|
|
|
+ fill="none"
|
|
|
|
|
+ stroke="#1677ff"
|
|
|
|
|
+ stroke-width="3"
|
|
|
|
|
+ stroke-linecap="round"
|
|
|
|
|
+ stroke-linejoin="round"
|
|
|
|
|
+ ></polyline>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="chart-meta">
|
|
|
|
|
+ <span>{{ dauTrendStartDate }}</span>
|
|
|
|
|
+ <span>Max: {{ dauMax }}</span>
|
|
|
|
|
+ <span>Min: {{ dauMin }}</span>
|
|
|
|
|
+ <span>{{ dauTrendEndDate }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </nz-card>
|
|
|
|
|
+ </nz-col>
|
|
|
|
|
+
|
|
|
|
|
+ <nz-col [nzXs]="24" [nzLg]="14">
|
|
|
|
|
+ <nz-card nzTitle="最近7天上新作品表现" nzHoverable>
|
|
|
|
|
+ <nz-tabset [nzSelectedIndex]="activeArtworkTabIndex" (nzSelectedIndexChange)="activeArtworkTabIndex = $event">
|
|
|
|
|
+ <nz-tab *ngFor="let tab of artworkTabs" [nzTitle]="tab.label">
|
|
|
|
|
+ <div class="tab-subtitle">{{ tab.date }} · 当日DAU {{ tab.dau }}</div>
|
|
|
|
|
+
|
|
|
|
|
+ <nz-table
|
|
|
|
|
+ #worksTable
|
|
|
|
|
+ [nzData]="tab.artworks"
|
|
|
|
|
+ [nzFrontPagination]="false"
|
|
|
|
|
+ [nzBordered]="true"
|
|
|
|
|
+ [nzSize]="'small'"
|
|
|
|
|
+ >
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>作品</th>
|
|
|
|
|
+ <th>名称</th>
|
|
|
|
|
+ <th>点击率</th>
|
|
|
|
|
+ <th>完成率</th>
|
|
|
|
|
+ <th>点击数</th>
|
|
|
|
|
+ <th>完成数</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr *ngFor="let work of worksTable.data">
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="work-thumbnail" [style.backgroundImage]="'url(' + work.thumbnail + ')'" ></div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>{{ work.name }}</td>
|
|
|
|
|
+ <td>{{ (work.clickRate * 100) | number : '1.1-2' }}%</td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <nz-progress
|
|
|
|
|
+ [nzPercent]="(work.completionRate * 100)"
|
|
|
|
|
+ [nzShowInfo]="true"
|
|
|
|
|
+ [nzStrokeWidth]="6"
|
|
|
|
|
+ [nzStrokeColor]="work.completionRate >= 0.6 ? '#52c41a' : work.completionRate >= 0.35 ? '#faad14' : '#ff4d4f'"
|
|
|
|
|
+ ></nz-progress>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>{{ work.clickCount }}</td>
|
|
|
|
|
+ <td>{{ work.completionCount }}</td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </nz-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="empty-tip" *ngIf="tab.artworks.length === 0">当日无上新作品</div>
|
|
|
|
|
+ </nz-tab>
|
|
|
|
|
+ </nz-tabset>
|
|
|
|
|
+ </nz-card>
|
|
|
|
|
+ </nz-col>
|
|
|
|
|
+ </nz-row>
|
|
|
</nz-spin>
|
|
</nz-spin>
|
|
|
</div>
|
|
</div>
|
|
|
`,
|
|
`,
|
|
@@ -260,69 +161,69 @@ import { NzMessageService } from 'ng-zorro-antd/message';
|
|
|
`
|
|
`
|
|
|
.dashboard-container {
|
|
.dashboard-container {
|
|
|
padding: 24px;
|
|
padding: 24px;
|
|
|
- background: #f0f2f5;
|
|
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
min-height: 100%;
|
|
min-height: 100%;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .metrics-container {
|
|
|
|
|
- margin-bottom: 24px;
|
|
|
|
|
|
|
+ .range-buttons {
|
|
|
|
|
+ margin: 12px 0 8px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 8px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .section {
|
|
|
|
|
|
|
+ .range-btn {
|
|
|
|
|
+ border: 1px solid #d9d9d9;
|
|
|
background: #fff;
|
|
background: #fff;
|
|
|
- padding: 16px 24px;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- margin-bottom: 24px;
|
|
|
|
|
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
|
|
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ padding: 4px 10px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .section-title {
|
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
- font-size: 18px;
|
|
|
|
|
- color: rgba(0, 0, 0, 0.85);
|
|
|
|
|
|
|
+ .range-btn.active {
|
|
|
|
|
+ border-color: #1677ff;
|
|
|
|
|
+ color: #1677ff;
|
|
|
|
|
+ background: #e6f4ff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- nz-card {
|
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
- transition: all 0.3s;
|
|
|
|
|
|
|
+ .chart-wrapper {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #fcfdff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- nz-card:hover {
|
|
|
|
|
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
- transform: translateY(-4px);
|
|
|
|
|
|
|
+ .line-chart {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 180px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .compare {
|
|
|
|
|
|
|
+ .chart-meta {
|
|
|
margin-top: 8px;
|
|
margin-top: 8px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ color: #8c8c8c;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tab-subtitle {
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ color: #595959;
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
- color: rgba(0, 0, 0, 0.45);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.work-thumbnail {
|
|
.work-thumbnail {
|
|
|
- width: 60px;
|
|
|
|
|
- height: 60px;
|
|
|
|
|
|
|
+ width: 52px;
|
|
|
|
|
+ height: 52px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
background-size: cover;
|
|
background-size: cover;
|
|
|
background-position: center;
|
|
background-position: center;
|
|
|
- border-radius: 4px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .ant-statistic-content {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .ant-statistic-title {
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: rgba(0, 0, 0, 0.65);
|
|
|
|
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .ant-statistic-content-value {
|
|
|
|
|
- font-size: 28px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .ant-statistic-content-suffix {
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
|
|
+ .empty-tip {
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+ color: #8c8c8c;
|
|
|
|
|
+ font-size: 12px;
|
|
|
}
|
|
}
|
|
|
`,
|
|
`,
|
|
|
],
|
|
],
|
|
@@ -330,32 +231,20 @@ import { NzMessageService } from 'ng-zorro-antd/message';
|
|
|
export class DashboardComponent implements OnInit {
|
|
export class DashboardComponent implements OnInit {
|
|
|
isLoading = true;
|
|
isLoading = true;
|
|
|
lastUpdateTime = new Date();
|
|
lastUpdateTime = new Date();
|
|
|
- Math = Math; // 暴露 Math 对象给模板使antml
|
|
|
|
|
|
|
|
|
|
- // 核心指标数据(来自 KPI 接口)
|
|
|
|
|
- activeUsersToday = 0; // 日活用户数
|
|
|
|
|
- dailyRevenue = 0; // 当日广告收益
|
|
|
|
|
-
|
|
|
|
|
- // DAU 趋势数据
|
|
|
|
|
- dauTrendLabels: string[] = [];
|
|
|
|
|
|
|
+ activeUsersToday = 0;
|
|
|
|
|
+ selectedDauRange = 30;
|
|
|
|
|
+
|
|
|
dauTrendData: number[] = [];
|
|
dauTrendData: number[] = [];
|
|
|
- avgDau7d = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 收益趋势数据
|
|
|
|
|
- revenueTrendLabels: string[] = [];
|
|
|
|
|
- revenueTrendData: number[] = [];
|
|
|
|
|
- totalRevenue7d = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 广告收益数据(暂时保留,后续可删除)
|
|
|
|
|
- bannerRevenue = 0;
|
|
|
|
|
- interstitialRevenue = 0;
|
|
|
|
|
- rewardedRevenue = 0;
|
|
|
|
|
- bannerImpressions = 0;
|
|
|
|
|
- interstitialImpressions = 0;
|
|
|
|
|
- rewardedImpressions = 0;
|
|
|
|
|
-
|
|
|
|
|
- // 今日上新作品
|
|
|
|
|
- newWorksToday: any[] = [];
|
|
|
|
|
|
|
+ dauTrendLabels: string[] = [];
|
|
|
|
|
+ dauChartPoints = '';
|
|
|
|
|
+ dauMin = 0;
|
|
|
|
|
+ dauMax = 0;
|
|
|
|
|
+ dauTrendStartDate = '';
|
|
|
|
|
+ dauTrendEndDate = '';
|
|
|
|
|
+
|
|
|
|
|
+ artworkTabs: NewArtworkTab[] = [];
|
|
|
|
|
+ activeArtworkTabIndex = 0;
|
|
|
|
|
|
|
|
constructor(
|
|
constructor(
|
|
|
private modalService: NzModalService,
|
|
private modalService: NzModalService,
|
|
@@ -368,102 +257,117 @@ export class DashboardComponent implements OnInit {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
refreshData(): void {
|
|
refreshData(): void {
|
|
|
- this.isLoading = true;
|
|
|
|
|
this.loadDashboardData();
|
|
this.loadDashboardData();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- loadDashboardData(): void {
|
|
|
|
|
- this.dashboardService.getKpi(7).subscribe({
|
|
|
|
|
|
|
+ changeDauRange(days: number): void {
|
|
|
|
|
+ if (this.selectedDauRange === days) return;
|
|
|
|
|
+ this.selectedDauRange = days;
|
|
|
|
|
+ this.loadUserCardData();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private buildDauChartPoints(values: number[]): string {
|
|
|
|
|
+ if (!values.length) return '';
|
|
|
|
|
+
|
|
|
|
|
+ const width = 600;
|
|
|
|
|
+ const height = 180;
|
|
|
|
|
+ const padding = 16;
|
|
|
|
|
+ const minVal = Math.min(...values);
|
|
|
|
|
+ const maxVal = Math.max(...values);
|
|
|
|
|
+ const span = Math.max(1, maxVal - minVal);
|
|
|
|
|
+ const stepX = values.length > 1 ? (width - padding * 2) / (values.length - 1) : 0;
|
|
|
|
|
+
|
|
|
|
|
+ return values
|
|
|
|
|
+ .map((value, idx) => {
|
|
|
|
|
+ const x = padding + idx * stepX;
|
|
|
|
|
+ const y = height - padding - ((value - minVal) / span) * (height - padding * 2);
|
|
|
|
|
+ return `${x},${y}`;
|
|
|
|
|
+ })
|
|
|
|
|
+ .join(' ');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private loadUserCardData(): void {
|
|
|
|
|
+ this.isLoading = true;
|
|
|
|
|
+ this.dashboardService.getKpi(this.selectedDauRange).subscribe({
|
|
|
next: (response) => {
|
|
next: (response) => {
|
|
|
- if (response.success) {
|
|
|
|
|
- const { dau, revenue } = response.data;
|
|
|
|
|
-
|
|
|
|
|
- // 更新 KPI 数据
|
|
|
|
|
- this.activeUsersToday = dau.today;
|
|
|
|
|
- this.dailyRevenue = revenue.today;
|
|
|
|
|
-
|
|
|
|
|
- // 更新 DAU 趋势
|
|
|
|
|
- this.dauTrendLabels = dau.trend.map((item) => item.date);
|
|
|
|
|
- this.dauTrendData = dau.trend.map((item) => item.dau);
|
|
|
|
|
- const dauSum = this.dauTrendData.reduce((sum, value) => sum + value, 0);
|
|
|
|
|
- this.avgDau7d = this.dauTrendData.length > 0 ? Math.round(dauSum / this.dauTrendData.length) : 0;
|
|
|
|
|
-
|
|
|
|
|
- // 更新收益趋势
|
|
|
|
|
- this.revenueTrendLabels = revenue.trend.map((item) => item.date);
|
|
|
|
|
- this.revenueTrendData = revenue.trend.map((item) => item.revenue);
|
|
|
|
|
- const revenueSum = this.revenueTrendData.reduce((sum, value) => sum + value, 0);
|
|
|
|
|
- this.totalRevenue7d = Math.round(revenueSum);
|
|
|
|
|
-
|
|
|
|
|
- // 模拟广告收益细分(后续可从其他接口获取)
|
|
|
|
|
- this.bannerRevenue = revenue.today * 0.35;
|
|
|
|
|
- this.interstitialRevenue = revenue.today * 0.40;
|
|
|
|
|
- this.rewardedRevenue = revenue.today * 0.25;
|
|
|
|
|
- this.bannerImpressions = Math.floor(Math.random() * 30000);
|
|
|
|
|
- this.interstitialImpressions = Math.floor(Math.random() * 10000);
|
|
|
|
|
- this.rewardedImpressions = Math.floor(Math.random() * 8000);
|
|
|
|
|
-
|
|
|
|
|
- // 模拟上新作品数据(后续可从接口获取)
|
|
|
|
|
- this.newWorksToday = [
|
|
|
|
|
- {
|
|
|
|
|
- id: 1,
|
|
|
|
|
- name: '作品 #1',
|
|
|
|
|
- thumbnail: 'https://via.placeholder.com/60',
|
|
|
|
|
- startedCount: Math.floor(Math.random() * 2000),
|
|
|
|
|
- completedCount: Math.floor(Math.random() * 1000),
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 2,
|
|
|
|
|
- name: '作品 #2',
|
|
|
|
|
- thumbnail: 'https://via.placeholder.com/60',
|
|
|
|
|
- startedCount: Math.floor(Math.random() * 2000),
|
|
|
|
|
- completedCount: Math.floor(Math.random() * 1000),
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 3,
|
|
|
|
|
- name: '作品 #3',
|
|
|
|
|
- thumbnail: 'https://via.placeholder.com/60',
|
|
|
|
|
- startedCount: Math.floor(Math.random() * 2000),
|
|
|
|
|
- completedCount: Math.floor(Math.random() * 1000),
|
|
|
|
|
- },
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- this.newWorksToday = this.newWorksToday.map((work) => ({
|
|
|
|
|
- ...work,
|
|
|
|
|
- completionRate: work.startedCount > 0
|
|
|
|
|
- ? Math.round((work.completedCount / work.startedCount) * 100)
|
|
|
|
|
- : 0,
|
|
|
|
|
- }));
|
|
|
|
|
-
|
|
|
|
|
- this.lastUpdateTime = new Date();
|
|
|
|
|
- this.isLoading = false;
|
|
|
|
|
- } else {
|
|
|
|
|
- this.message.error('获取 KPI 数据失败');
|
|
|
|
|
|
|
+ if (!response.success) {
|
|
|
|
|
+ this.message.error('获取日活数据失败');
|
|
|
this.isLoading = false;
|
|
this.isLoading = false;
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ const { dau } = response.data;
|
|
|
|
|
+ this.activeUsersToday = dau.today;
|
|
|
|
|
+ this.dauTrendLabels = dau.trend.map((item) => item.date);
|
|
|
|
|
+ this.dauTrendData = dau.trend.map((item) => item.dau);
|
|
|
|
|
+ this.dauMin = this.dauTrendData.length ? Math.min(...this.dauTrendData) : 0;
|
|
|
|
|
+ this.dauMax = this.dauTrendData.length ? Math.max(...this.dauTrendData) : 0;
|
|
|
|
|
+ this.dauTrendStartDate = this.dauTrendLabels[0] || '';
|
|
|
|
|
+ this.dauTrendEndDate = this.dauTrendLabels[this.dauTrendLabels.length - 1] || '';
|
|
|
|
|
+ this.dauChartPoints = this.buildDauChartPoints(this.dauTrendData);
|
|
|
|
|
+
|
|
|
|
|
+ this.lastUpdateTime = new Date();
|
|
|
|
|
+ this.isLoading = false;
|
|
|
},
|
|
},
|
|
|
error: (error) => {
|
|
error: (error) => {
|
|
|
- console.error('Failed to load KPI data:', error);
|
|
|
|
|
- this.message.error('加载数据出错,请重试');
|
|
|
|
|
|
|
+ console.error('Failed to load DAU data:', error);
|
|
|
|
|
+ this.message.error('加载日活数据出错');
|
|
|
this.isLoading = false;
|
|
this.isLoading = false;
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- showWorkDetails(work: any): void {
|
|
|
|
|
- this.modalService.create({
|
|
|
|
|
- nzTitle: '作品详情',
|
|
|
|
|
- nzContent: `
|
|
|
|
|
- <div style="padding: 16px;">
|
|
|
|
|
- <div style="text-align: center; margin-bottom: 16px;">
|
|
|
|
|
- <img src="${work.thumbnail}" style="max-width: 100%; max-height: 300px; border-radius: 4px;">
|
|
|
|
|
- </div>
|
|
|
|
|
- <p><strong>作品名称:</strong> ${work.name}</p>
|
|
|
|
|
- <p><strong>填色开始数:</strong> ${work.startedCount}</p>
|
|
|
|
|
- <p><strong>填色完成数:</strong> ${work.completedCount}</p>
|
|
|
|
|
- <p><strong>完成率:</strong> ${work.completionRate}%</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- `,
|
|
|
|
|
- nzFooter: null,
|
|
|
|
|
|
|
+ private loadArtworkTabsData(): Promise<void> {
|
|
|
|
|
+ return new Promise((resolve) => {
|
|
|
|
|
+ this.dashboardService.getNewArtworkTabs(7, 20).subscribe({
|
|
|
|
|
+ next: (response) => {
|
|
|
|
|
+ if (response.success) {
|
|
|
|
|
+ this.artworkTabs = response.data.tabs || [];
|
|
|
|
|
+ this.activeArtworkTabIndex = 0;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.message.error('获取上新作品数据失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ },
|
|
|
|
|
+ error: (error) => {
|
|
|
|
|
+ console.error('Failed to load artwork tabs:', error);
|
|
|
|
|
+ this.message.error('加载上新作品数据出错');
|
|
|
|
|
+ resolve();
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ loadDashboardData(): void {
|
|
|
|
|
+ this.isLoading = true;
|
|
|
|
|
+ this.dashboardService.getKpi(this.selectedDauRange).subscribe({
|
|
|
|
|
+ next: async (kpiResponse) => {
|
|
|
|
|
+ if (!kpiResponse.success) {
|
|
|
|
|
+ this.message.error('获取日活数据失败');
|
|
|
|
|
+ this.isLoading = false;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const { dau } = kpiResponse.data;
|
|
|
|
|
+ this.activeUsersToday = dau.today;
|
|
|
|
|
+ this.dauTrendLabels = dau.trend.map((item) => item.date);
|
|
|
|
|
+ this.dauTrendData = dau.trend.map((item) => item.dau);
|
|
|
|
|
+ this.dauMin = this.dauTrendData.length ? Math.min(...this.dauTrendData) : 0;
|
|
|
|
|
+ this.dauMax = this.dauTrendData.length ? Math.max(...this.dauTrendData) : 0;
|
|
|
|
|
+ this.dauTrendStartDate = this.dauTrendLabels[0] || '';
|
|
|
|
|
+ this.dauTrendEndDate = this.dauTrendLabels[this.dauTrendLabels.length - 1] || '';
|
|
|
|
|
+ this.dauChartPoints = this.buildDauChartPoints(this.dauTrendData);
|
|
|
|
|
+
|
|
|
|
|
+ await this.loadArtworkTabsData();
|
|
|
|
|
+
|
|
|
|
|
+ this.lastUpdateTime = new Date();
|
|
|
|
|
+ this.isLoading = false;
|
|
|
|
|
+ },
|
|
|
|
|
+ error: (error) => {
|
|
|
|
|
+ console.error('Failed to load dashboard data:', error);
|
|
|
|
|
+ this.message.error('加载看板数据出错');
|
|
|
|
|
+ this.isLoading = false;
|
|
|
|
|
+ },
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|