message-dashboard.component.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
  2. import { CommonModule, DatePipe } from '@angular/common';
  3. import { HttpClient, HttpParams } from '@angular/common/http';
  4. import { Router } from '@angular/router';
  5. import { Observable, forkJoin } from 'rxjs';
  6. import { finalize, catchError, map } from 'rxjs/operators';
  7. import { of } from 'rxjs';
  8. // 导入国家代码转换库
  9. import * as countries from 'i18n-iso-countries';
  10. // 导入中文语言包
  11. import * as countriesZh from 'i18n-iso-countries/langs/zh.json';
  12. // 导入英文语言包(可选)
  13. import * as countriesEn from 'i18n-iso-countries/langs/en.json';
  14. // 注册语言包
  15. countries.registerLocale(countriesZh as any);
  16. countries.registerLocale(countriesEn as any);
  17. // NG-ZORRO 组件
  18. import { NzCardModule } from 'ng-zorro-antd/card';
  19. import { NzGridModule } from 'ng-zorro-antd/grid';
  20. import { NzStatisticModule } from 'ng-zorro-antd/statistic';
  21. import { NzTableModule } from 'ng-zorro-antd/table';
  22. import { NzTabsModule } from 'ng-zorro-antd/tabs';
  23. import { NzTagModule } from 'ng-zorro-antd/tag';
  24. import { NzSpinModule } from 'ng-zorro-antd/spin';
  25. import { NzMessageService } from 'ng-zorro-antd/message';
  26. import { NzDividerModule } from 'ng-zorro-antd/divider';
  27. import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
  28. import { FormsModule } from '@angular/forms';
  29. import { NzSelectModule } from 'ng-zorro-antd/select';
  30. import { NzButtonModule } from 'ng-zorro-antd/button';
  31. import { NzIconModule } from 'ng-zorro-antd/icon';
  32. import { NzEmptyModule } from 'ng-zorro-antd/empty';
  33. import { NzTooltipModule } from 'ng-zorro-antd/tooltip';
  34. import { NzProgressModule } from 'ng-zorro-antd/progress';
  35. import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions';
  36. import { NzImageModule } from 'ng-zorro-antd/image';
  37. // Chart.js
  38. import { BaseChartDirective } from 'ng2-charts';
  39. import {
  40. Chart,
  41. ChartConfiguration,
  42. ChartOptions,
  43. registerables,
  44. } from 'chart.js';
  45. @Component({
  46. selector: 'app-message-dashboard',
  47. standalone: true,
  48. imports: [
  49. CommonModule,
  50. NzCardModule,
  51. NzGridModule,
  52. NzStatisticModule,
  53. NzTableModule,
  54. NzTabsModule,
  55. NzTagModule,
  56. NzSpinModule,
  57. NzDividerModule,
  58. NzDatePickerModule,
  59. FormsModule,
  60. NzSelectModule,
  61. NzButtonModule,
  62. NzIconModule,
  63. NzEmptyModule,
  64. NzTooltipModule,
  65. NzProgressModule,
  66. NzImageModule,
  67. NzDescriptionsModule,
  68. DatePipe,
  69. BaseChartDirective,
  70. ],
  71. templateUrl: './message-dashboard.component.html',
  72. styleUrls: ['./message-dashboard.component.css'],
  73. })
  74. export class MessageDashboardComponent implements OnInit {
  75. isLoading = false;
  76. overallStats: any = null;
  77. strategyStats: any[] = [];
  78. templateStats: any[] = [];
  79. ccStats: any[] = [];
  80. imageStats: any[] = [];
  81. dailyTrends: any[] = [];
  82. avgDeliveryTime: any = null;
  83. activeTab: number = 0;
  84. // 排序相关属性
  85. strategySortField: string | null = null;
  86. strategySortDirection: 'ascend' | 'descend' | null = null;
  87. templateSortField: string | null = null;
  88. templateSortDirection: 'ascend' | 'descend' | null = null;
  89. ccSortField: string | null = null;
  90. ccSortDirection: 'ascend' | 'descend' | null = null;
  91. imageSortField: string | null = null;
  92. imageSortDirection: 'ascend' | 'descend' | null = null;
  93. // 日期范围
  94. dateRange: Date[] = [];
  95. strategies: string[] = []; // 存储所有策略名称
  96. selectedStrategy: string = ''; // 当前选中的策略
  97. // 组合图表配置
  98. public combinedChartData: ChartConfiguration<'bar' | 'line'>['data'] = {
  99. labels: [],
  100. datasets: [
  101. {
  102. label: '总发送量',
  103. data: [],
  104. backgroundColor: '#1890ff',
  105. yAxisID: 'y',
  106. },
  107. {
  108. label: '成功发送',
  109. data: [],
  110. backgroundColor: '#52c41a',
  111. yAxisID: 'y',
  112. },
  113. {
  114. label: '已送达',
  115. data: [],
  116. backgroundColor: '#13c2c2',
  117. yAxisID: 'y',
  118. },
  119. {
  120. label: '展示数',
  121. data: [],
  122. backgroundColor: '#faad14',
  123. yAxisID: 'y',
  124. },
  125. {
  126. label: '展示用户',
  127. data: [],
  128. backgroundColor: '#482880',
  129. yAxisID: 'y',
  130. },
  131. {
  132. label: '点击数',
  133. data: [],
  134. backgroundColor: '#722ed1',
  135. yAxisID: 'y',
  136. },
  137. {
  138. label: '点击用户',
  139. data: [],
  140. backgroundColor: '#0066CC',
  141. yAxisID: 'y',
  142. },
  143. // 折线图数据集
  144. {
  145. label: '送达率',
  146. data: [],
  147. borderColor: '#13c2c2',
  148. backgroundColor: 'transparent',
  149. yAxisID: 'y1',
  150. type: 'line',
  151. tension: 0.3,
  152. borderWidth: 2,
  153. pointRadius: 4,
  154. pointHoverRadius: 6,
  155. },
  156. {
  157. label: '展示率',
  158. data: [],
  159. borderColor: '#faad14',
  160. backgroundColor: 'transparent',
  161. yAxisID: 'y1',
  162. type: 'line',
  163. tension: 0.3,
  164. borderWidth: 2,
  165. pointRadius: 4,
  166. pointHoverRadius: 6,
  167. },
  168. {
  169. label: '点击率',
  170. data: [],
  171. borderColor: '#722ed1',
  172. backgroundColor: 'transparent',
  173. yAxisID: 'y1',
  174. type: 'line',
  175. tension: 0.3,
  176. borderWidth: 2,
  177. pointRadius: 4,
  178. pointHoverRadius: 6,
  179. },
  180. {
  181. label: '用户点击率',
  182. data: [],
  183. borderColor: '#fb56fb',
  184. backgroundColor: 'transparent',
  185. yAxisID: 'y1',
  186. type: 'line',
  187. tension: 0.3,
  188. borderWidth: 2,
  189. pointRadius: 4,
  190. pointHoverRadius: 6,
  191. },
  192. ],
  193. };
  194. public combinedChartOptions: ChartOptions<'bar' | 'line'> = {
  195. responsive: true,
  196. maintainAspectRatio: false,
  197. plugins: {
  198. tooltip: {
  199. mode: 'index',
  200. intersect: false,
  201. callbacks: {
  202. label: (context) => {
  203. let label = context.dataset.label || '';
  204. if (label) {
  205. label += ': ';
  206. }
  207. // 如果是折线图(转化率),格式化显示两位小数并添加百分号
  208. if (context.datasetIndex >= 7) {
  209. // 假设6-8是折线图数据集
  210. const value = typeof context.raw === 'number' ? context.raw : 0;
  211. label += value.toFixed(2) + '%';
  212. } else {
  213. // 柱状图数据保持不变
  214. label += context.raw;
  215. }
  216. return label;
  217. },
  218. },
  219. },
  220. legend: {
  221. position: 'top',
  222. },
  223. },
  224. scales: {
  225. y: {
  226. type: 'linear',
  227. display: true,
  228. position: 'left',
  229. title: {
  230. display: true,
  231. text: '消息数量',
  232. },
  233. beginAtZero: true,
  234. },
  235. y1: {
  236. type: 'linear',
  237. display: true,
  238. position: 'right',
  239. title: {
  240. display: true,
  241. text: '百分比(%)',
  242. },
  243. min: 0,
  244. max: 100,
  245. grid: {
  246. drawOnChartArea: false,
  247. },
  248. ticks: {
  249. callback: (value) => {
  250. // 确保刻度值显示两位小数
  251. return typeof value === 'number' ? value.toFixed(2) + '%' : value;
  252. },
  253. },
  254. },
  255. },
  256. };
  257. constructor(
  258. private http: HttpClient,
  259. private message: NzMessageService,
  260. private router: Router,
  261. private cd: ChangeDetectorRef
  262. ) {
  263. Chart.register(...registerables);
  264. }
  265. ngOnInit(): void {
  266. this.loadAllStatistics();
  267. this.loadStrategies();
  268. }
  269. // 格式化国家显示:国家名称(国家代码)
  270. formatCountry(cc: string): string {
  271. if (!cc) return '-';
  272. const code = cc.toUpperCase();
  273. const countryName =
  274. countries.getName(code, 'zh') ||
  275. countries.getName(code, 'en') ||
  276. '未知国家';
  277. return `${countryName}(${code})`;
  278. }
  279. // 获取策略列表
  280. private loadStrategies(): void {
  281. this.http
  282. .get('/api/message-strategies')
  283. .pipe(
  284. map((res: any) => res || []),
  285. catchError((err) => {
  286. console.error('Failed to load strategies:', err);
  287. this.message.error('加载策略列表失败');
  288. return of([]);
  289. })
  290. )
  291. .subscribe((data) => {
  292. this.strategies = data.map((item: any) => item.name).filter(Boolean);
  293. });
  294. }
  295. loadAllStatistics(): void {
  296. if (!this.isDateRangeValid()) {
  297. return;
  298. }
  299. this.isLoading = true;
  300. console.log(this.dateRange[0], this.dateRange[1]); // 打印: Mon Sep 15 2025 02:22:55 GMT+0800 (中国标准时间) Mon Sep 15 2025 02:22:55 GMT+0800 (中国标准时间)
  301. this.dateRange[0]
  302. ? console.log(this.formatDateForApi(this.dateRange[0]))
  303. : ''; // 打印 2025-09-14
  304. this.dateRange[1]
  305. ? console.log(this.formatDateForApi(this.dateRange[1]))
  306. : ''; // 打印 2025-09-14 为什么
  307. const params = new HttpParams()
  308. .set(
  309. 'startDate',
  310. this.dateRange[0] ? this.formatDateForApi(this.dateRange[0]) : ''
  311. )
  312. .set(
  313. 'endDate',
  314. this.dateRange[1] ? this.formatDateForApi(this.dateRange[1]) : ''
  315. )
  316. .set('strategyName', this.selectedStrategy || '');
  317. forkJoin({
  318. overall: this.http
  319. .get(`/api/message/statistics/overall`, { params })
  320. .pipe(
  321. map((res: any) => res?.data || null),
  322. catchError((err) => {
  323. console.error('Failed to load overall statistics:', err);
  324. this.message.error('加载整体统计失败');
  325. return of(null);
  326. })
  327. ),
  328. strategies: this.http
  329. .get(`/api/message/statistics/by-strategy`, { params })
  330. .pipe(
  331. map((res: any) => res?.data || []),
  332. catchError((err) => {
  333. console.error('Failed to load strategy statistics:', err);
  334. this.message.error('加载策略统计失败');
  335. return of([]);
  336. })
  337. ),
  338. templates: this.http
  339. .get(`/api/message/statistics/by-template`, { params })
  340. .pipe(
  341. map((res: any) => res?.data || []),
  342. catchError((err) => {
  343. console.error('Failed to load template statistics:', err);
  344. this.message.error('加载模板统计失败');
  345. return of([]);
  346. })
  347. ),
  348. ccs: this.http.get(`/api/message/statistics/by-cc`, { params }).pipe(
  349. map((res: any) => res?.data || []),
  350. catchError((err) => {
  351. console.error('Failed to load cc statistics:', err);
  352. this.message.error('加载国家统计失败');
  353. return of([]);
  354. })
  355. ),
  356. images: this.http
  357. .get(`/api/message/statistics/by-image`, { params })
  358. .pipe(
  359. map((res: any) => res?.data || []),
  360. catchError((err) => {
  361. console.error('Failed to load image statistics:', err);
  362. this.message.error('加载图片统计失败');
  363. return of([]);
  364. })
  365. ),
  366. dailyTrends: this.http
  367. .get(`/api/message/statistics/daily-trends`, { params })
  368. .pipe(
  369. map((res: any) => res?.data || []),
  370. catchError((err) => {
  371. console.error('Failed to load daily trends:', err);
  372. this.message.error('加载每日趋势失败');
  373. return of([]);
  374. })
  375. ),
  376. })
  377. .pipe(
  378. finalize(() => {
  379. this.isLoading = false;
  380. this.cd.detectChanges();
  381. })
  382. )
  383. .subscribe((results: any) => {
  384. this.overallStats = results.overall;
  385. this.strategyStats = results.strategies.map((item: any) => ({
  386. ...item,
  387. expanded: false,
  388. dailyData: null,
  389. loading: false,
  390. }));
  391. this.templateStats = results.templates.map((item: any) => ({
  392. ...item,
  393. expanded: false,
  394. dailyData: null,
  395. loading: false,
  396. }));
  397. this.ccStats = results.ccs.map((item: any) => ({
  398. ...item,
  399. expanded: false,
  400. dailyData: null,
  401. loading: false,
  402. }));
  403. this.imageStats = results.images.map((item: any) => ({
  404. ...item,
  405. expanded: false,
  406. dailyData: null,
  407. loading: false,
  408. }));
  409. this.dailyTrends = results.dailyTrends;
  410. this.updateChartData();
  411. });
  412. }
  413. // 验证日期范围是否有效
  414. private isDateRangeValid(): boolean {
  415. // 如果只选择了一个日期或未选择日期,视为有效
  416. if (
  417. !this.dateRange ||
  418. this.dateRange.length < 2 ||
  419. !this.dateRange[0] ||
  420. !this.dateRange[1]
  421. ) {
  422. return true;
  423. }
  424. // 比较开始日期和结束日期
  425. const startDate = new Date(this.dateRange[0]);
  426. const endDate = new Date(this.dateRange[1]);
  427. // 清除时间部分,只比较日期
  428. startDate.setHours(0, 0, 0, 0);
  429. endDate.setHours(0, 0, 0, 0);
  430. if (endDate < startDate) {
  431. this.message.error('结束日期不能早于开始日期,请重新选择');
  432. return false;
  433. }
  434. return true;
  435. }
  436. // 处理日期范围变化
  437. onDateRangeChange(dateRange: Date[]): void {
  438. this.dateRange = dateRange;
  439. // 当日期范围变化时自动验证并刷新数据
  440. if (this.isDateRangeValid()) {
  441. this.loadAllStatistics();
  442. }
  443. }
  444. // 为API格式化日期为ISO字符串(YYYY-MM-DD)
  445. private formatDateForApi(date: Date): string {
  446. return date.toISOString().split('T')[0];
  447. }
  448. // private formatDateForApi(date: Date): string {
  449. // // 手动拼接年月日,确保使用本地日期
  450. // const year = date.getFullYear();
  451. // const month = String(date.getMonth() + 1).padStart(2, '0');
  452. // const day = String(date.getDate()).padStart(2, '0');
  453. // return `${year}-${month}-${day}`;
  454. // }
  455. // 排序方法
  456. sortData(
  457. data: any[],
  458. field: string,
  459. currentSortField: string | null,
  460. currentSortDirection: 'ascend' | 'descend' | null
  461. ): {
  462. sortedData: any[];
  463. newSortField: string;
  464. newSortDirection: 'ascend' | 'descend' | null;
  465. } {
  466. // 复制数据以避免直接修改原始数组
  467. const sortedData = [...data];
  468. // 确定新的排序方向
  469. let newSortDirection: 'ascend' | 'descend' | null = 'ascend';
  470. if (currentSortField === field) {
  471. if (currentSortDirection === 'ascend') {
  472. newSortDirection = 'descend';
  473. } else if (currentSortDirection === 'descend') {
  474. newSortDirection = null;
  475. return { sortedData: data, newSortField: field, newSortDirection };
  476. }
  477. }
  478. // 执行排序
  479. sortedData.sort((a, b) => {
  480. const valueA = a[field] ?? 0;
  481. const valueB = b[field] ?? 0;
  482. // 处理数字比较
  483. if (typeof valueA === 'number' && typeof valueB === 'number') {
  484. return newSortDirection === 'ascend'
  485. ? valueA - valueB
  486. : valueB - valueA;
  487. }
  488. // 处理字符串比较
  489. const strA = String(valueA).toLowerCase();
  490. const strB = String(valueB).toLowerCase();
  491. return newSortDirection === 'ascend'
  492. ? strA.localeCompare(strB)
  493. : strB.localeCompare(strA);
  494. });
  495. return { sortedData, newSortField: field, newSortDirection };
  496. }
  497. // 策略表格排序
  498. sortStrategyTable(field: string): void {
  499. const result = this.sortData(
  500. this.strategyStats,
  501. field,
  502. this.strategySortField,
  503. this.strategySortDirection
  504. );
  505. this.strategyStats = result.sortedData;
  506. this.strategySortField = result.newSortField;
  507. this.strategySortDirection = result.newSortDirection;
  508. }
  509. // 模板表格排序
  510. sortTemplateTable(field: string): void {
  511. const result = this.sortData(
  512. this.templateStats,
  513. field,
  514. this.templateSortField,
  515. this.templateSortDirection
  516. );
  517. this.templateStats = result.sortedData;
  518. this.templateSortField = result.newSortField;
  519. this.templateSortDirection = result.newSortDirection;
  520. }
  521. // 国家表格排序
  522. sortCcTable(field: string): void {
  523. const result = this.sortData(
  524. this.ccStats,
  525. field,
  526. this.ccSortField,
  527. this.ccSortDirection
  528. );
  529. this.ccStats = result.sortedData;
  530. this.ccSortField = result.newSortField;
  531. this.ccSortDirection = result.newSortDirection;
  532. }
  533. // 图片表格排序
  534. sortImageTable(field: string): void {
  535. const result = this.sortData(
  536. this.imageStats,
  537. field,
  538. this.imageSortField,
  539. this.imageSortDirection
  540. );
  541. this.imageStats = result.sortedData;
  542. this.imageSortField = result.newSortField;
  543. this.imageSortDirection = result.newSortDirection;
  544. }
  545. private updateChartData(): void {
  546. this.combinedChartData = {
  547. labels: this.dailyTrends.map((t) => this.formatDate(t.date)),
  548. datasets: [
  549. {
  550. ...this.combinedChartData.datasets[0],
  551. data: this.dailyTrends.map((t) => t.totalRecords || 0),
  552. },
  553. {
  554. ...this.combinedChartData.datasets[1],
  555. data: this.dailyTrends.map((t) => t.sent || 0),
  556. },
  557. {
  558. ...this.combinedChartData.datasets[2],
  559. data: this.dailyTrends.map((t) => t.delivered || 0),
  560. },
  561. {
  562. ...this.combinedChartData.datasets[3],
  563. data: this.dailyTrends.map((t) => t.displayCount || 0),
  564. },
  565. {
  566. ...this.combinedChartData.datasets[4],
  567. data: this.dailyTrends.map((t) => t.displayedUsers || 0),
  568. },
  569. {
  570. ...this.combinedChartData.datasets[5],
  571. data: this.dailyTrends.map((t) => t.opened || 0),
  572. },
  573. {
  574. ...this.combinedChartData.datasets[6],
  575. data: this.dailyTrends.map((t) => t.openedUsers || 0),
  576. },
  577. // 折线图数据
  578. {
  579. ...this.combinedChartData.datasets[7],
  580. data: this.dailyTrends.map((t) =>
  581. this.preciseRound((t.deliveredRate || 0) * 100, 2)
  582. ),
  583. },
  584. {
  585. ...this.combinedChartData.datasets[8],
  586. data: this.dailyTrends.map((t) =>
  587. this.preciseRound((t.displayRate || 0) * 100, 2)
  588. ),
  589. },
  590. {
  591. ...this.combinedChartData.datasets[9],
  592. data: this.dailyTrends.map((t) =>
  593. this.preciseRound((t.clickThroughRate || 0) * 100, 2)
  594. ),
  595. },
  596. {
  597. ...this.combinedChartData.datasets[10],
  598. data: this.dailyTrends.map((t) =>
  599. this.preciseRound((t.actualClickThroughRate || 0) * 100, 2)
  600. ),
  601. },
  602. ],
  603. };
  604. }
  605. refreshData(): void {
  606. this.loadAllStatistics();
  607. }
  608. navigateToStrategy(strategyName: string): void {
  609. this.router.navigate(['/message-strategy'], {
  610. queryParams: { strategyName: strategyName },
  611. });
  612. }
  613. navigateToTemplate(templateName: string): void {
  614. this.router.navigate(['/message-template'], {
  615. queryParams: { templateName: templateName },
  616. });
  617. }
  618. formatPercentage(value: number): string {
  619. return (value * 100).toFixed(2) + '%';
  620. }
  621. formatPercentageToNumber(value: number): number {
  622. return Number((value * 100).toFixed(2));
  623. }
  624. formatSeconds(seconds: number): string {
  625. if (seconds < 60) {
  626. return seconds.toFixed(2) + '秒';
  627. } else {
  628. return (seconds / 60).toFixed(2) + '分钟';
  629. }
  630. }
  631. preciseRound(num: number, decimalPlaces: number): number {
  632. if (decimalPlaces === 0) return Math.round(num);
  633. const multiplier = Math.pow(10, decimalPlaces);
  634. return Number(Math.round(num * multiplier) / multiplier);
  635. }
  636. public formatDate(date: string | null): string {
  637. if (!date) return '未知日期';
  638. return new Date(date).toLocaleDateString();
  639. }
  640. // 展开/折叠行并加载数据
  641. toggleExpand(
  642. element: any,
  643. type: 'strategy' | 'template' | 'cc' | 'image'
  644. ): void {
  645. element.expanded = !element.expanded;
  646. // 如果展开且没有加载过数据,则加载
  647. if (element.expanded && !element.dailyData && !element.loading) {
  648. this.loadDailyData(element, type);
  649. }
  650. }
  651. // 加载每日数据
  652. private loadDailyData(element: any, type: string): void {
  653. element.loading = true;
  654. let url = '';
  655. const params = new HttpParams()
  656. .set(
  657. 'startDate',
  658. this.dateRange[0] ? this.formatDateForApi(this.dateRange[0]) : ''
  659. )
  660. .set(
  661. 'endDate',
  662. this.dateRange[1] ? this.formatDateForApi(this.dateRange[1]) : ''
  663. )
  664. .set('strategyName', this.selectedStrategy || '');
  665. // 根据类型构建请求URL
  666. switch (type) {
  667. case 'strategy':
  668. url = `/api/message/daily/trends/by-strategy/${encodeURIComponent(
  669. element.strategyName
  670. )}`;
  671. break;
  672. case 'template':
  673. url = `/api/message/daily/trends/by-template/${encodeURIComponent(
  674. element.templateName
  675. )}`;
  676. break;
  677. case 'cc':
  678. url = `/api/message/daily/trends/by-cc/${encodeURIComponent(
  679. element.cc
  680. )}`;
  681. break;
  682. case 'image':
  683. url = `/api/message/daily/trends/by-image/${encodeURIComponent(
  684. element.image
  685. )}`;
  686. break;
  687. default:
  688. element.loading = false;
  689. return;
  690. }
  691. this.http
  692. .get(url, { params })
  693. .pipe(
  694. map((res: any) => res?.data || []),
  695. catchError((err) => {
  696. console.error(`Error loading daily data for ${type}:`, err);
  697. this.message.error(`加载每日数据失败`);
  698. return of([]);
  699. }),
  700. finalize(() => {
  701. element.loading = false;
  702. })
  703. )
  704. .subscribe((data) => {
  705. element.dailyData = data;
  706. });
  707. }
  708. }