group-detail.component.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { Component, OnInit, inject, signal } from '@angular/core';
  2. import { ActivatedRoute, Router } from '@angular/router';
  3. import { DatePipe } from '@angular/common';
  4. import { FaIconComponent } from '@fortawesome/angular-fontawesome';
  5. import { MockDataService } from '../../../core/services/mock-data.service';
  6. import { QiweApiService } from '../../../core/services/api/qiwe-api.service';
  7. import { environment } from '../../../../environments/environment';
  8. import type { Group, Document, RiskEvent } from '../../../core/models';
  9. import type { MessageDto } from '../../../core/services/api/qiwe-api.service';
  10. import { StatCardComponent } from '../../../shared/components/stat-card/stat-card.component';
  11. import { StatusBadgeComponent } from '../../../shared/components/status-badge/status-badge.component';
  12. import { MessageStripComponent } from '../../../shared/components/message-strip/message-strip.component';
  13. import { EmptyStateComponent } from '../../../shared/components/empty-state/empty-state.component';
  14. import { DataTableComponent } from '../../../shared/components/data-table/data-table.component';
  15. interface TabDef {
  16. key: string;
  17. label: string;
  18. }
  19. @Component({
  20. selector: 'app-group-detail',
  21. standalone: true,
  22. imports: [
  23. DatePipe, FaIconComponent,
  24. StatCardComponent, StatusBadgeComponent,
  25. MessageStripComponent, EmptyStateComponent, DataTableComponent,
  26. ],
  27. templateUrl: './group-detail.component.html',
  28. })
  29. export class GroupDetailComponent implements OnInit {
  30. private route = inject(ActivatedRoute);
  31. private router = inject(Router);
  32. private mockData = inject(MockDataService);
  33. private qiweApi = inject(QiweApiService);
  34. loading = true;
  35. dataSource = signal<'api' | 'mock'>('mock');
  36. group: Group | null = null;
  37. activeTab = 'info';
  38. documents: Document[] = [];
  39. riskEvents: RiskEvent[] = [];
  40. messages: MessageDto[] = [];
  41. messagesTotal = 0;
  42. chatMessagesTotal = 0;
  43. messagesLoading = false;
  44. messagesError = '';
  45. showSystemMessages = false;
  46. private static readonly MSG_TYPE_LABELS: Record<number, string> = {
  47. 0: '文本',
  48. 2: '文本',
  49. 13: '链接',
  50. 14: '图片',
  51. 31: '应用消息',
  52. 2063: '撤回',
  53. };
  54. readonly tabs: TabDef[] = [
  55. { key: 'info', label: '基本信息' },
  56. { key: 'messages', label: '群消息' },
  57. { key: 'members', label: '群成员' },
  58. { key: 'documents', label: '沟通记录' },
  59. { key: 'risk', label: '风控事件' },
  60. ];
  61. readonly docColumns = [
  62. { key: 'title', label: '文档标题' },
  63. { key: 'createdBy', label: '创建人' },
  64. { key: 'createdAt', label: '创建时间' },
  65. { key: 'complianceStatus', label: '状态', template: 'status' as const },
  66. ];
  67. get messageRows(): Array<MessageDto & { senderLabel: string; contentLabel: string; msgTypeLabel: string; timeLabel: string }> {
  68. return this.messages.map((m) => ({
  69. ...m,
  70. senderLabel: this.formatSenderLabel(m),
  71. contentLabel: this.formatMessageContent(m),
  72. msgTypeLabel: GroupDetailComponent.MSG_TYPE_LABELS[m.msgType] ?? `类型${m.msgType}`,
  73. timeLabel: new Date(m.timestamp).toLocaleString('zh-CN'),
  74. }));
  75. }
  76. private formatSenderLabel(m: MessageDto): string {
  77. if (m.senderName && m.senderName !== m.senderId) return m.senderName;
  78. if (!m.senderId) return '未知';
  79. return m.senderId;
  80. }
  81. private formatMessageContent(m: MessageDto): string {
  82. if (m.msgType === 2063) return '撤回了一条消息';
  83. let text = (m.content || '').trim();
  84. if (text.startsWith('{') || text.startsWith('[')) {
  85. try {
  86. const parsed = JSON.parse(text) as Record<string, unknown>;
  87. if (parsed['revokeMsgUniqueIdentifier']) return '撤回了一条消息';
  88. text = typeof parsed['content'] === 'string' ? parsed['content'].trim()
  89. : typeof parsed['title'] === 'string' ? parsed['title'].trim()
  90. : typeof parsed['linkUrl'] === 'string' ? parsed['linkUrl'].trim()
  91. : '';
  92. } catch {
  93. /* keep raw */
  94. }
  95. }
  96. if (text) return text;
  97. const labels: Record<number, string> = {
  98. 14: '[图片]', 16: '[语音]', 15: '[文件]', 22: '[视频]', 13: '[链接]',
  99. };
  100. return labels[m.msgType] ?? `(${GroupDetailComponent.MSG_TYPE_LABELS[m.msgType] ?? '无文本内容'})`;
  101. }
  102. readonly riskColumns = [
  103. { key: 'title', label: '事件名称' },
  104. { key: 'type', label: '类型' },
  105. { key: 'severity', label: '严重程度', template: 'status' as const },
  106. { key: 'status', label: '处理状态', template: 'status' as const },
  107. { key: 'createdAt', label: '发现时间' },
  108. ];
  109. ngOnInit(): void {
  110. this.route.paramMap.subscribe(params => {
  111. const id = params.get('id');
  112. if (id) void this.loadGroup(id);
  113. });
  114. }
  115. private async loadGroup(id: string): Promise<void> {
  116. this.loading = true;
  117. if (environment.useBackendApi) {
  118. const result = await this.qiweApi.getGroup(id);
  119. if (result.ok && result.data?.group) {
  120. this.group = this.qiweApi.mapToGroup(result.data.group);
  121. this.dataSource.set('api');
  122. this.documents = [];
  123. this.riskEvents = [];
  124. if (this.group.messageCountTotal > 0) {
  125. this.activeTab = 'messages';
  126. }
  127. await this.loadMessages(id);
  128. this.loading = false;
  129. return;
  130. }
  131. }
  132. this.group = this.mockData.getGroups().find(g => g.id === id) ?? null;
  133. if (this.group) {
  134. this.dataSource.set('mock');
  135. this.documents = this.mockData.getDocuments().filter(d => d.groupId === id);
  136. this.riskEvents = this.mockData.getRiskEvents().filter(e => e.groupId === id);
  137. }
  138. this.loading = false;
  139. }
  140. goBack(): void {
  141. this.router.navigate(['/groups']);
  142. }
  143. setActiveTab(key: string): void {
  144. this.activeTab = key;
  145. if (key === 'messages' && this.group && environment.useBackendApi && this.messages.length === 0) {
  146. void this.loadMessages(this.group.id);
  147. }
  148. }
  149. private async loadMessages(roomId: string): Promise<void> {
  150. if (!environment.useBackendApi) return;
  151. this.messagesLoading = true;
  152. this.messagesError = '';
  153. const result = await this.qiweApi.listMessages(roomId, 100, 0, !this.showSystemMessages);
  154. if (result.ok && result.data) {
  155. this.messages = result.data.messages;
  156. this.messagesTotal = result.data.total;
  157. this.chatMessagesTotal = result.data.chatTotal ?? result.data.messages.length;
  158. } else {
  159. this.messages = [];
  160. this.messagesTotal = 0;
  161. this.chatMessagesTotal = 0;
  162. this.messagesError = result.error ?? '加载消息失败';
  163. }
  164. this.messagesLoading = false;
  165. }
  166. toggleSystemMessages(): void {
  167. this.showSystemMessages = !this.showSystemMessages;
  168. if (this.group) void this.loadMessages(this.group.id);
  169. }
  170. }