| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884 |
- import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { HttpClient, HttpParams } from '@angular/common/http';
- import { Router } from '@angular/router';
- import { Observable, forkJoin } from 'rxjs';
- import { finalize, catchError, map } from 'rxjs/operators';
- import { of } from 'rxjs';
- // 导入国家代码转换库
- import * as countries from 'i18n-iso-countries';
- // 导入中文语言包
- import * as countriesZh from 'i18n-iso-countries/langs/zh.json';
- // 导入英文语言包(可选)
- import * as countriesEn from 'i18n-iso-countries/langs/en.json';
- // 注册语言包
- countries.registerLocale(countriesZh as any);
- countries.registerLocale(countriesEn as any);
- // NG-ZORRO 组件
- import { NzCardModule } from 'ng-zorro-antd/card';
- import { NzGridModule } from 'ng-zorro-antd/grid';
- import { NzStatisticModule } from 'ng-zorro-antd/statistic';
- import { NzTableModule } from 'ng-zorro-antd/table';
- import { NzTabsModule } from 'ng-zorro-antd/tabs';
- import { NzTagModule } from 'ng-zorro-antd/tag';
- import { NzSpinModule } from 'ng-zorro-antd/spin';
- import { NzMessageService } from 'ng-zorro-antd/message';
- import { NzDividerModule } from 'ng-zorro-antd/divider';
- import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
- import { FormsModule } from '@angular/forms';
- import { NzSelectModule } from 'ng-zorro-antd/select';
- import { NzButtonModule } from 'ng-zorro-antd/button';
- import { NzIconModule } from 'ng-zorro-antd/icon';
- import { NzEmptyModule } from 'ng-zorro-antd/empty';
- import { NzTooltipModule } from 'ng-zorro-antd/tooltip';
- import { NzProgressModule } from 'ng-zorro-antd/progress';
- import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions';
- import { NzImageModule } from 'ng-zorro-antd/image';
- // Chart.js
- import { BaseChartDirective } from 'ng2-charts';
- import {
- Chart,
- ChartConfiguration,
- ChartOptions,
- registerables,
- } from 'chart.js';
- @Component({
- selector: 'app-message-dashboard',
- standalone: true,
- imports: [
- CommonModule,
- NzCardModule,
- NzGridModule,
- NzStatisticModule,
- NzTableModule,
- NzTabsModule,
- NzTagModule,
- NzSpinModule,
- NzDividerModule,
- NzDatePickerModule,
- FormsModule,
- NzSelectModule,
- NzButtonModule,
- NzIconModule,
- NzEmptyModule,
- NzTooltipModule,
- NzProgressModule,
- NzImageModule,
- NzDescriptionsModule,
- BaseChartDirective,
- ],
- templateUrl: './message-dashboard.component.html',
- styleUrls: ['./message-dashboard.component.css'],
- })
- export class MessageDashboardComponent implements OnInit {
- overallLoading = false;
- strategyLoading = false;
- templateLoading = false;
- ccLoading = false;
- imageLoading = false;
- dailyTrendsLoading = false;
- chartLoading = false;
- overallStats: any = null;
- strategyStats: any[] = [];
- templateStats: any[] = [];
- ccStats: any[] = [];
- imageStats: any[] = [];
- dailyTrends: any[] = [];
- avgDeliveryTime: any = null;
- activeTab: number = 0;
- // 排序相关属性
- strategySortField: string | null = null;
- strategySortDirection: 'ascend' | 'descend' | null = null;
- templateSortField: string | null = null;
- templateSortDirection: 'ascend' | 'descend' | null = null;
- ccSortField: string | null = null;
- ccSortDirection: 'ascend' | 'descend' | null = null;
- imageSortField: string | null = null;
- imageSortDirection: 'ascend' | 'descend' | null = null;
- // 日期范围
- dateRange: Date[] = [];
- strategies: string[] = []; // 存储所有策略名称
- selectedStrategy: string = ''; // 当前选中的策略
- private summaryRequestInFlight = false;
- private lastSummaryRequestKey = '';
- // 组合图表配置
- public combinedChartData: ChartConfiguration<'bar' | 'line'>['data'] = {
- labels: [],
- datasets: [
- {
- label: '总发送量',
- data: [],
- backgroundColor: '#1890ff',
- yAxisID: 'y',
- },
- {
- label: '成功发送',
- data: [],
- backgroundColor: '#52c41a',
- yAxisID: 'y',
- },
- {
- label: '已送达',
- data: [],
- backgroundColor: '#13c2c2',
- yAxisID: 'y',
- },
- {
- label: '展示数',
- data: [],
- backgroundColor: '#faad14',
- yAxisID: 'y',
- },
- {
- label: '展示用户',
- data: [],
- backgroundColor: '#482880',
- yAxisID: 'y',
- },
- {
- label: '点击数',
- data: [],
- backgroundColor: '#722ed1',
- yAxisID: 'y',
- },
- {
- label: '点击用户',
- data: [],
- backgroundColor: '#0066CC',
- yAxisID: 'y',
- },
- // 折线图数据集
- {
- label: '送达率',
- data: [],
- borderColor: '#13c2c2',
- backgroundColor: 'transparent',
- yAxisID: 'y1',
- type: 'line',
- tension: 0.3,
- borderWidth: 2,
- pointRadius: 4,
- pointHoverRadius: 6,
- },
- {
- label: '展示率',
- data: [],
- borderColor: '#faad14',
- backgroundColor: 'transparent',
- yAxisID: 'y1',
- type: 'line',
- tension: 0.3,
- borderWidth: 2,
- pointRadius: 4,
- pointHoverRadius: 6,
- },
- {
- label: '点击率',
- data: [],
- borderColor: '#722ed1',
- backgroundColor: 'transparent',
- yAxisID: 'y1',
- type: 'line',
- tension: 0.3,
- borderWidth: 2,
- pointRadius: 4,
- pointHoverRadius: 6,
- },
- {
- label: '用户点击率',
- data: [],
- borderColor: '#fb56fb',
- backgroundColor: 'transparent',
- yAxisID: 'y1',
- type: 'line',
- tension: 0.3,
- borderWidth: 2,
- pointRadius: 4,
- pointHoverRadius: 6,
- },
- ],
- };
- public combinedChartOptions: ChartOptions<'bar' | 'line'> = {
- responsive: true,
- maintainAspectRatio: false,
- plugins: {
- tooltip: {
- mode: 'index',
- intersect: false,
- callbacks: {
- label: (context) => {
- let label = context.dataset.label || '';
- if (label) {
- label += ': ';
- }
- // 如果是折线图(转化率),格式化显示两位小数并添加百分号
- if (context.datasetIndex >= 7) {
- // 假设6-8是折线图数据集
- const value = typeof context.raw === 'number' ? context.raw : 0;
- label += value.toFixed(2) + '%';
- } else {
- // 柱状图数据保持不变
- label += context.raw;
- }
- return label;
- },
- },
- },
- legend: {
- position: 'top',
- },
- },
- scales: {
- y: {
- type: 'linear',
- display: true,
- position: 'left',
- title: {
- display: true,
- text: '消息数量',
- },
- beginAtZero: true,
- },
- y1: {
- type: 'linear',
- display: true,
- position: 'right',
- title: {
- display: true,
- text: '百分比(%)',
- },
- min: 0,
- max: 100,
- grid: {
- drawOnChartArea: false,
- },
- ticks: {
- callback: (value) => {
- // 确保刻度值显示两位小数
- return typeof value === 'number' ? value.toFixed(2) + '%' : value;
- },
- },
- },
- },
- };
- constructor(
- private http: HttpClient,
- private message: NzMessageService,
- private router: Router,
- private cd: ChangeDetectorRef,
- ) {
- Chart.register(...registerables);
- }
- ngOnInit(): void {
- if (!this.dateRange || this.dateRange.length < 2) {
- const end = new Date();
- end.setHours(23, 59, 59, 999);
- const start = new Date(end);
- start.setDate(end.getDate() - 6);
- start.setHours(0, 0, 0, 0);
- this.dateRange = [start, end];
- }
- this.loadAllStatistics();
- this.loadStrategies();
- }
- // 格式化国家显示:国家名称(国家代码)
- formatCountry(cc: string): string {
- if (!cc) return '-';
- const code = cc.toUpperCase();
- const countryName =
- countries.getName(code, 'zh') ||
- countries.getName(code, 'en') ||
- '未知国家';
- return `${countryName}(${code})`;
- }
- // 获取策略列表
- private loadStrategies(): void {
- this.http
- .get('/api/message-strategies')
- .pipe(
- map((res: any) => res || []),
- catchError((err) => {
- console.error('Failed to load strategies:', err);
- this.message.error('加载策略列表失败');
- return of([]);
- }),
- )
- .subscribe((data) => {
- this.strategies = data.map((item: any) => item.name).filter(Boolean);
- });
- }
- loadAllStatistics(): void {
- if (!this.isDateRangeValid()) {
- return;
- }
- // 仅重置当前激活标签页的数据
- if (this.activeTab === 0) this.strategyStats = [];
- if (this.activeTab === 1) this.templateStats = [];
- if (this.activeTab === 2) this.ccStats = [];
- if (this.activeTab === 3) this.imageStats = [];
- const params = new HttpParams()
- .set(
- 'startDate',
- this.dateRange[0] ? this.formatDateForApi(this.dateRange[0]) : '',
- )
- .set(
- 'endDate',
- this.dateRange[1] ? this.formatDateForApi(this.dateRange[1]) : '',
- )
- .set('strategyName', this.selectedStrategy || '')
- .set('page', '1')
- .set('limit', '50');
- const summaryRequestKey = params.toString();
- if (
- this.summaryRequestInFlight &&
- this.lastSummaryRequestKey === summaryRequestKey
- ) {
- return;
- }
- // 首屏汇总数据:overall + dailyTrends + strategy(默认第一页)
- this.overallLoading = true;
- this.chartLoading = true;
- this.dailyTrendsLoading = true;
- this.strategyLoading = this.activeTab === 0;
- this.summaryRequestInFlight = true;
- this.lastSummaryRequestKey = summaryRequestKey;
- this.http
- .get(`/api/message/statistics/summary`, { params })
- .pipe(
- map((res: any) => res?.data || null),
- catchError((err) => {
- console.error('Failed to load summary statistics:', err);
- this.message.error('加载汇总统计失败');
- return of(null);
- }),
- finalize(() => {
- this.overallLoading = false;
- this.chartLoading = false;
- this.dailyTrendsLoading = false;
- this.strategyLoading = false;
- this.summaryRequestInFlight = false;
- }),
- )
- .subscribe((summary) => {
- if (!summary) {
- this.overallStats = null;
- this.dailyTrends = [];
- this.strategyStats = [];
- this.updateChartData();
- return;
- }
- this.overallStats = summary.overall || null;
- this.dailyTrends = summary.dailyTrends || [];
- this.updateChartData();
- if (this.activeTab === 0) {
- const strategyData = Array.isArray(summary.strategyStats)
- ? summary.strategyStats
- : [];
- this.strategyStats = strategyData.map((item: any) => ({
- ...item,
- expanded: false,
- dailyData: null,
- loading: false,
- }));
- }
- });
- // 非策略页签在首次加载时再单独拉取对应数据
- if (this.activeTab !== 0) {
- this.loadActiveTabData(params);
- }
- }
- // 加载当前激活标签页的数据
- loadActiveTabData(params: HttpParams): void {
- switch (this.activeTab) {
- case 0: // 策略统计
- this.loadStrategyData(params);
- break;
- case 1: // 模板统计
- this.loadTemplateData(params);
- break;
- case 2: // 国家统计
- this.loadCcData(params);
- break;
- case 3: // 图片统计
- this.loadImageData(params);
- break;
- }
- }
- // 拆分各标签页数据加载方法
- private loadStrategyData(params: HttpParams): void {
- this.strategyLoading = true;
- this.http
- .get(`/api/message/statistics/by-strategy`, { params })
- .pipe(
- map((res: any) => res?.data || []),
- catchError((err) => {
- console.error('Failed to load strategy statistics:', err);
- this.message.error('加载策略统计失败');
- return of([]);
- }),
- finalize(() => (this.strategyLoading = false)),
- )
- .subscribe((data) => {
- this.strategyStats = data.map((item: any) => ({
- ...item,
- expanded: false,
- dailyData: null,
- loading: false,
- }));
- });
- }
- private loadTemplateData(params: HttpParams): void {
- this.templateLoading = true;
- this.http
- .get(`/api/message/statistics/by-template`, { params })
- .pipe(
- map((res: any) => res?.data || []),
- catchError((err) => {
- console.error('Failed to load template statistics:', err);
- this.message.error('加载模板统计失败');
- return of([]);
- }),
- finalize(() => (this.templateLoading = false)),
- )
- .subscribe((data) => {
- this.templateStats = data.map((item: any) => ({
- ...item,
- expanded: false,
- dailyData: null,
- loading: false,
- }));
- });
- }
- private loadCcData(params: HttpParams): void {
- this.ccLoading = true;
- this.http
- .get(`/api/message/statistics/by-cc`, { params })
- .pipe(
- map((res: any) => res?.data || []),
- catchError((err) => {
- console.error('Failed to load cc statistics:', err);
- this.message.error('加载国家统计失败');
- return of([]);
- }),
- finalize(() => (this.ccLoading = false)),
- )
- .subscribe((data) => {
- this.ccStats = data.map((item: any) => ({
- ...item,
- expanded: false,
- dailyData: null,
- loading: false,
- }));
- });
- }
- private loadImageData(params: HttpParams): void {
- this.imageLoading = true;
- this.http
- .get(`/api/message/statistics/by-image`, { params })
- .pipe(
- map((res: any) => res?.data || []),
- catchError((err) => {
- console.error('Failed to load image statistics:', err);
- this.message.error('加载图片统计失败');
- return of([]);
- }),
- finalize(() => (this.imageLoading = false)),
- )
- .subscribe((data) => {
- this.imageStats = data.map((item: any) => ({
- ...item,
- expanded: false,
- dailyData: null,
- loading: false,
- }));
- });
- }
- // 添加标签页切换事件处理
- onTabChange(index: number): void {
- this.activeTab = index;
- // 当切换到新标签页时加载对应数据
- const params = new HttpParams()
- .set(
- 'startDate',
- this.dateRange[0] ? this.formatDateForApi(this.dateRange[0]) : '',
- )
- .set(
- 'endDate',
- this.dateRange[1] ? this.formatDateForApi(this.dateRange[1]) : '',
- )
- .set('strategyName', this.selectedStrategy || '');
- this.loadActiveTabData(params);
- }
- // 验证日期范围是否有效
- private isDateRangeValid(): boolean {
- // 如果只选择了一个日期或未选择日期,视为有效
- if (
- !this.dateRange ||
- this.dateRange.length < 2 ||
- !this.dateRange[0] ||
- !this.dateRange[1]
- ) {
- return true;
- }
- // 比较开始日期和结束日期
- const startDate = new Date(this.dateRange[0]);
- const endDate = new Date(this.dateRange[1]);
- // 清除时间部分,只比较日期
- startDate.setHours(0, 0, 0, 0);
- endDate.setHours(0, 0, 0, 0);
- if (endDate < startDate) {
- this.message.error('结束日期不能早于开始日期,请重新选择');
- return false;
- }
- return true;
- }
- // 处理日期范围变化
- onDateRangeChange(dateRange: Date[]): void {
- this.dateRange = dateRange;
- // 当日期范围变化时自动验证并刷新数据
- if (this.isDateRangeValid()) {
- this.loadAllStatistics();
- }
- }
- // 为API格式化日期为ISO字符串(YYYY-MM-DD)
- private formatDateForApi(date: Date): string {
- return date.toISOString().split('T')[0];
- }
- // private formatDateForApi(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}`;
- // }
- // 排序方法
- sortData(
- data: any[],
- field: string,
- currentSortField: string | null,
- currentSortDirection: 'ascend' | 'descend' | null,
- ): {
- sortedData: any[];
- newSortField: string;
- newSortDirection: 'ascend' | 'descend' | null;
- } {
- // 复制数据以避免直接修改原始数组
- const sortedData = [...data];
- // 确定新的排序方向
- let newSortDirection: 'ascend' | 'descend' | null = 'ascend';
- if (currentSortField === field) {
- if (currentSortDirection === 'ascend') {
- newSortDirection = 'descend';
- } else if (currentSortDirection === 'descend') {
- newSortDirection = null;
- return { sortedData: data, newSortField: field, newSortDirection };
- }
- }
- // 执行排序
- sortedData.sort((a, b) => {
- const valueA = a[field] ?? 0;
- const valueB = b[field] ?? 0;
- // 处理数字比较
- if (typeof valueA === 'number' && typeof valueB === 'number') {
- return newSortDirection === 'ascend'
- ? valueA - valueB
- : valueB - valueA;
- }
- // 处理字符串比较
- const strA = String(valueA).toLowerCase();
- const strB = String(valueB).toLowerCase();
- return newSortDirection === 'ascend'
- ? strA.localeCompare(strB)
- : strB.localeCompare(strA);
- });
- return { sortedData, newSortField: field, newSortDirection };
- }
- // 策略表格排序
- sortStrategyTable(field: string): void {
- const result = this.sortData(
- this.strategyStats,
- field,
- this.strategySortField,
- this.strategySortDirection,
- );
- this.strategyStats = result.sortedData;
- this.strategySortField = result.newSortField;
- this.strategySortDirection = result.newSortDirection;
- }
- // 模板表格排序
- sortTemplateTable(field: string): void {
- const result = this.sortData(
- this.templateStats,
- field,
- this.templateSortField,
- this.templateSortDirection,
- );
- this.templateStats = result.sortedData;
- this.templateSortField = result.newSortField;
- this.templateSortDirection = result.newSortDirection;
- }
- // 国家表格排序
- sortCcTable(field: string): void {
- const result = this.sortData(
- this.ccStats,
- field,
- this.ccSortField,
- this.ccSortDirection,
- );
- this.ccStats = result.sortedData;
- this.ccSortField = result.newSortField;
- this.ccSortDirection = result.newSortDirection;
- }
- // 图片表格排序
- sortImageTable(field: string): void {
- const result = this.sortData(
- this.imageStats,
- field,
- this.imageSortField,
- this.imageSortDirection,
- );
- this.imageStats = result.sortedData;
- this.imageSortField = result.newSortField;
- this.imageSortDirection = result.newSortDirection;
- }
- private updateChartData(): void {
- this.combinedChartData = {
- labels: this.dailyTrends.map((t) => this.formatDate(t.date)),
- datasets: [
- {
- ...this.combinedChartData.datasets[0],
- data: this.dailyTrends.map((t) => t.totalRecords || 0),
- },
- {
- ...this.combinedChartData.datasets[1],
- data: this.dailyTrends.map((t) => t.sent || 0),
- },
- {
- ...this.combinedChartData.datasets[2],
- data: this.dailyTrends.map((t) => t.delivered || 0),
- },
- {
- ...this.combinedChartData.datasets[3],
- data: this.dailyTrends.map((t) => t.displayCount || 0),
- },
- {
- ...this.combinedChartData.datasets[4],
- data: this.dailyTrends.map((t) => t.displayedUsers || 0),
- },
- {
- ...this.combinedChartData.datasets[5],
- data: this.dailyTrends.map((t) => t.opened || 0),
- },
- {
- ...this.combinedChartData.datasets[6],
- data: this.dailyTrends.map((t) => t.openedUsers || 0),
- },
- // 折线图数据
- {
- ...this.combinedChartData.datasets[7],
- data: this.dailyTrends.map((t) =>
- this.preciseRound((t.deliveredRate || 0) * 100, 2),
- ),
- },
- {
- ...this.combinedChartData.datasets[8],
- data: this.dailyTrends.map((t) =>
- this.preciseRound((t.displayRate || 0) * 100, 2),
- ),
- },
- {
- ...this.combinedChartData.datasets[9],
- data: this.dailyTrends.map((t) =>
- this.preciseRound((t.clickThroughRate || 0) * 100, 2),
- ),
- },
- {
- ...this.combinedChartData.datasets[10],
- data: this.dailyTrends.map((t) =>
- this.preciseRound((t.actualClickThroughRate || 0) * 100, 2),
- ),
- },
- ],
- };
- }
- refreshData(): void {
- this.loadAllStatistics();
- }
- navigateToStrategy(strategyName: string): void {
- this.router.navigate(['/message-strategy'], {
- queryParams: { strategyName: strategyName },
- });
- }
- navigateToTemplate(templateName: string): void {
- this.router.navigate(['/message-template'], {
- queryParams: { templateName: templateName },
- });
- }
- formatPercentage(value: number): string {
- return (value * 100).toFixed(2) + '%';
- }
- formatPercentageToNumber(value: number): number {
- return Number((value * 100).toFixed(2));
- }
- formatSeconds(seconds: number): string {
- if (seconds < 60) {
- return seconds.toFixed(2) + '秒';
- } else {
- return (seconds / 60).toFixed(2) + '分钟';
- }
- }
- preciseRound(num: number, decimalPlaces: number): number {
- if (decimalPlaces === 0) return Math.round(num);
- const multiplier = Math.pow(10, decimalPlaces);
- return Number(Math.round(num * multiplier) / multiplier);
- }
- public formatDate(date: string | null): string {
- if (!date) return '未知日期';
- return new Date(date).toLocaleDateString();
- }
- // 展开/折叠行并加载数据
- toggleExpand(
- element: any,
- type: 'strategy' | 'template' | 'cc' | 'image',
- ): void {
- element.expanded = !element.expanded;
- // 如果展开且没有加载过数据,则加载
- if (element.expanded && !element.dailyData && !element.loading) {
- this.loadDailyData(element, type);
- }
- }
- // 加载每日数据
- private loadDailyData(element: any, type: string): void {
- // 如果已有请求,先取消
- if (element.subscription) {
- element.subscription.unsubscribe();
- }
- element.loading = true;
- let url = '';
- const params = new HttpParams()
- .set(
- 'startDate',
- this.dateRange[0] ? this.formatDateForApi(this.dateRange[0]) : '',
- )
- .set(
- 'endDate',
- this.dateRange[1] ? this.formatDateForApi(this.dateRange[1]) : '',
- )
- .set('strategyName', this.selectedStrategy || '');
- // 根据类型构建请求URL
- switch (type) {
- case 'strategy':
- url = `/api/message/daily/trends/by-strategy/${encodeURIComponent(
- element.strategyName,
- )}`;
- break;
- case 'template':
- url = `/api/message/daily/trends/by-template/${encodeURIComponent(
- element.templateName,
- )}`;
- break;
- case 'cc':
- url = `/api/message/daily/trends/by-cc/${encodeURIComponent(
- element.cc,
- )}`;
- break;
- case 'image':
- url = `/api/message/daily/trends/by-image/${encodeURIComponent(
- element.image,
- )}`;
- break;
- default:
- element.loading = false;
- return;
- }
- // 保存订阅以便取消
- element.subscription = this.http
- .get(url, { params })
- .pipe(
- map((res: any) => res?.data || []),
- catchError((err) => {
- console.error(`Error loading daily data for ${type}:`, err);
- this.message.error(`加载每日数据失败`);
- return of([]);
- }),
- finalize(() => {
- element.loading = false;
- element.subscription = null;
- }),
- )
- .subscribe((data) => {
- element.dailyData = data;
- });
- }
- }
|