import { Component, OnInit, OnDestroy } from '@angular/core'; import { CommonModule, DatePipe } from '@angular/common'; import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators, } from '@angular/forms'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { Subscription } from 'rxjs'; import { Router, ActivatedRoute } from '@angular/router'; // NG-ZORRO 组件 import { NzTableModule } from 'ng-zorro-antd/table'; import { NzDividerModule } from 'ng-zorro-antd/divider'; import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzFormModule } from 'ng-zorro-antd/form'; import { NzInputModule } from 'ng-zorro-antd/input'; import { NzSelectModule } from 'ng-zorro-antd/select'; import { NzDatePickerModule } from 'ng-zorro-antd/date-picker'; import { NzPageHeaderModule } from 'ng-zorro-antd/page-header'; import { NzCardModule } from 'ng-zorro-antd/card'; import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm'; import { NzTagModule } from 'ng-zorro-antd/tag'; import { NzMessageService } from 'ng-zorro-antd/message'; import { NzSpinModule } from 'ng-zorro-antd/spin'; import { NzEmptyModule } from 'ng-zorro-antd/empty'; import { NzPaginationModule } from 'ng-zorro-antd/pagination'; import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal'; import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions'; import { MessageService, IMessageRecord } from '../services/message.service'; import { MessageRecordDetailComponent } from './message-record-detail.component'; import { UserDetailModalComponent } from './user-detail-modal.component'; @Component({ selector: 'app-message-record', standalone: true, imports: [ CommonModule, FormsModule, ReactiveFormsModule, NzTableModule, NzDividerModule, NzButtonModule, NzIconModule, NzFormModule, NzInputModule, NzSelectModule, NzDatePickerModule, NzPageHeaderModule, NzCardModule, NzPopconfirmModule, NzTagModule, NzSpinModule, NzEmptyModule, NzPaginationModule, NzDescriptionsModule, NzModalModule, DatePipe, ], template: ` 消息推送记录 查看和管理所有消息推送记录
用户ID CC 消息 来源 状态 计划发送时间 实际发送时间 送达时间 打开时间 创建时间 操作 {{ record.uid }} {{ record.cc }}
{{ record.title }}
{{ record.content }}
@if(record.activityName) {
活动: {{ record.activityName }}
} @if(record.strategyName) {
策略: {{ record.strategyName }}
} @if(record.templateName) {
模板: {{ record.templateName }}
} {{ getStatusName(record.status) }} {{ record.plannedSendAt | date : 'yyyy-MM-dd HH:mm' }} {{ record.actualSendAt | date : 'yyyy-MM-dd HH:mm' }} {{ record.deliveredAt | date : 'yyyy-MM-dd HH:mm' }} {{ record.openedAt | date : 'yyyy-MM-dd HH:mm' }} {{ record.createdAt | date : 'yyyy-MM-dd HH:mm' }} 详情
共 {{ total }} 条记录
`, styles: [ ` nz-select { width: 100%; } .filter-form { margin-bottom: 16px; nz-range-picker { width: 100%; } } .button-col { display: flex; gap: 8px; justify-content: flex-end; } nz-table { margin-top: 16px; } .pagination-container { display: flex; justify-content: center; margin-top: 16px; } nz-tag { margin-right: 0; } /* 详情按钮样式 */ a[nz-button] { padding: 0 8px; } @media (max-width: 768px) { nz-col { margin-bottom: 8px; } .button-col { justify-content: flex-start; } } /* 消息内容单元格样式 */ .message-content-cell { max-width: 300px; min-width: 200px; } /* 消息标题样式 */ .message-title { font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 4px; } /* 消息内容样式 */ .message-content { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; line-height: 1.4; color: #666; } /* 来源单元格样式 */ .source-cell { min-width: 200px; max-width: 300px; } .source-item { display: flex; margin-bottom: 4px; line-height: 1.5; } .source-label { font-weight: 500; color: #666; min-width: 40px; margin-right: 8px; } .source-value { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* 响应式调整 */ @media (max-width: 1200px) { .source-cell { min-width: 150px; max-width: 200px; } } `, ], }) export class MessageRecordComponent implements OnInit, OnDestroy { records: IMessageRecord[] = []; isLoading = false; private queryParamsSubscription!: Subscription; // 使用 FormGroup 管理所有筛选字段 filterForm: FormGroup; // 分页参数 pagination = { page: 1, limit: 30, total: 0, }; constructor( private messageService: MessageService, private message: NzMessageService, private modalService: NzModalService, private fb: FormBuilder, private router: Router, private route: ActivatedRoute ) { this.filterForm = this.fb.group({ uid: [null], activityName: [null], templateName: [null], strategyName: [null], status: [null], plannedSendAt: [null], actualSendAt: [null], deliveredAt: [null], openedAt: [null], }); } ngOnInit(): void { // 订阅 URL 查询参数的变化 this.queryParamsSubscription = this.route.queryParams.subscribe( (params) => { // 更新分页状态 this.pagination.page = params['page'] ? parseInt(params['page'], 10) : 1; this.pagination.limit = params['limit'] ? parseInt(params['limit'], 10) : 30; // 更新表单控件的值,日期需要特殊处理 const formValue: any = {}; for (const key in this.filterForm.controls) { if (params[key]) { // 日期范围参数格式: "2023-01-01T00:00:00Z,2023-01-31T23:59:59Z" if (key.includes('At')) { const dateRange = params[key].split(','); if (dateRange.length === 2) { formValue[key] = [ new Date(dateRange[0]), new Date(dateRange[1]), ]; } } else { formValue[key] = params[key]; } } } // 使用 patchValue 来避免未提供的控件报错 this.filterForm.patchValue(formValue, { emitEvent: false }); this.loadRecords(); } ); // 监听表单变化,自动触发查询 this.filterForm.valueChanges .pipe( debounceTime(500), distinctUntilChanged( (prev, curr) => JSON.stringify(prev) === JSON.stringify(curr) ) ) .subscribe(() => { this.onFilter(); }); } ngOnDestroy(): void { if (this.queryParamsSubscription) { this.queryParamsSubscription.unsubscribe(); } } loadRecords(): void { this.isLoading = true; // 准备查询参数 const params: any = this.prepareFilters(); params['page'] = this.pagination.page; params['limit'] = this.pagination.limit; this.messageService.getPaginatedRecords(params).subscribe({ next: (response) => { this.records = response.data; this.pagination.total = response.pagination.total; this.isLoading = false; }, error: (err) => { this.message.error('加载记录失败: ' + (err.message || '未知错误')); this.isLoading = false; }, }); } private prepareFilters(): any { const formValue = this.filterForm.value; const filters: any = {}; for (const key in formValue) { if ( formValue.hasOwnProperty(key) && formValue[key] !== null && formValue[key] !== '' ) { if (Array.isArray(formValue[key])) { if (formValue[key].length === 2) { const start = formValue[key][0].toISOString(); const end = formValue[key][1].toISOString(); filters[key] = `${start},${end}`; } } else { filters[key] = formValue[key]; } } } return filters; } // 重构为私有方法,用于导航到新 URL private navigateToUrl(): void { const filters = this.prepareFilters(); const queryParams = { page: this.pagination.page, limit: this.pagination.limit, ...filters, }; // FIX: Removed queryParamsHandling: 'merge' to allow empty filters to be removed from the URL this.router.navigate([], { relativeTo: this.route, queryParams, }); } onFilter(): void { this.pagination.page = 1; // 筛选时重置到第一页 this.navigateToUrl(); } resetFilters(): void { this.filterForm.reset(); this.onFilter(); } // 分页变更处理 onPageChange(page: number): void { this.pagination.page = page; this.navigateToUrl(); } onPageSizeChange(size: number): void { this.pagination.limit = size; this.pagination.page = 1; this.navigateToUrl(); } // 显示详情模态框 showDetail(record: IMessageRecord): void { this.modalService.create({ nzTitle: `消息记录详情 - ${record.uid}`, nzWidth: '800px', nzContent: MessageRecordDetailComponent, nzData: { record }, nzFooter: null, }); } // 获取状态名称 getStatusName(status: number): string { const statuses: Record = { 0: '未发送', 1: '发送成功', 2: '已送达', 3: '已打开', [-1]: '发送失败', }; return statuses[status] || '未知状态'; } // 获取状态颜色 getStatusColor(status: number): string { const colors: Record = { 0: 'default', 1: 'processing', 2: 'success', 3: 'green', [-1]: 'error', }; return colors[status] || 'default'; } showUserDetail(uid: string): void { this.modalService.create({ nzTitle: `用户详情 - ${uid}`, nzContent: UserDetailModalComponent, nzWidth: '800px', nzData: { uid }, // 传递用户UID nzFooter: null, nzBodyStyle: { 'max-height': '70vh', 'overflow-y': 'auto', }, }); } }