| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- import { CommonModule } from '@angular/common';
- import {
- ChangeDetectionStrategy,
- ChangeDetectorRef,
- Component,
- DestroyRef,
- EventEmitter,
- Input,
- OnChanges,
- OnInit,
- Output,
- SimpleChanges,
- inject,
- } from '@angular/core';
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
- import { FormsModule } from '@angular/forms';
- import {
- Activity,
- AlertCircle,
- BrainCircuit,
- CheckCircle2,
- ChevronRight,
- Clock3,
- Eye,
- LoaderCircle,
- RefreshCw,
- Search,
- LucideAngularModule,
- } from 'lucide-angular';
- import { RUNTIME_CONFIG } from '../../../app/core/config/runtime-config';
- import {
- SaasAnalysisRun,
- SaasPlatformService,
- } from '../../../app/core/services/saas-platform.service';
- import { AnalysisRunDetailComponent } from './analysis-run-detail.component';
- import {
- ANALYSIS_RUN_STATUS_OPTIONS,
- AnalysisRunStatusFilter,
- analysisModeLabel,
- analysisRunStatusLabel,
- analysisRunStatusTone,
- analysisRunTypeLabel,
- analysisTargetKindLabel,
- analysisTargetLabel,
- formatAnalysisDate,
- readAnalysisRunInput,
- readAnalysisRunResult,
- } from './analysis-run.models';
- @Component({
- selector: 'app-analysis-run-list',
- standalone: true,
- imports: [CommonModule, FormsModule, LucideAngularModule, AnalysisRunDetailComponent],
- templateUrl: './analysis-run-list.component.html',
- styleUrl: './analysis-run-list.component.scss',
- changeDetection: ChangeDetectionStrategy.OnPush,
- })
- export class AnalysisRunListComponent implements OnInit, OnChanges {
- private readonly saasService = inject(SaasPlatformService);
- private readonly cdr = inject(ChangeDetectorRef);
- private readonly destroyRef = inject(DestroyRef);
- private initialized = false;
- private requestVersion = 0;
- @Input() workspaceId = RUNTIME_CONFIG.domesticWorkspaceId;
- @Output() readonly runSelected = new EventEmitter<SaasAnalysisRun>();
- readonly icons = {
- activity: Activity,
- alert: AlertCircle,
- brain: BrainCircuit,
- check: CheckCircle2,
- chevron: ChevronRight,
- clock: Clock3,
- eye: Eye,
- loader: LoaderCircle,
- refresh: RefreshCw,
- search: Search,
- };
- readonly statusOptions = ANALYSIS_RUN_STATUS_OPTIONS;
- runs: SaasAnalysisRun[] = [];
- statusFilter: AnalysisRunStatusFilter = '';
- searchTerm = '';
- loading = true;
- loadingMore = false;
- error = '';
- nextCursor: string | null = null;
- selectedRun: SaasAnalysisRun | null = null;
- loadedAt: string | null = null;
- ngOnInit(): void {
- this.initialized = true;
- this.loadRuns(true);
- }
- ngOnChanges(changes: SimpleChanges): void {
- if (this.initialized && changes['workspaceId']) this.loadRuns(true);
- }
- get visibleRuns(): SaasAnalysisRun[] {
- const query = this.searchTerm.trim().toLocaleLowerCase('zh-CN');
- if (!query) return this.runs;
- return this.runs.filter((run) => [
- run.id,
- run.targetKey,
- run.requestedBy,
- run.errorSummary ?? '',
- analysisRunTypeLabel(run.analysisType),
- analysisTargetKindLabel(run.targetKind),
- ].some((value) => value.toLocaleLowerCase('zh-CN').includes(query)));
- }
- get processingCount(): number {
- return this.runs.filter((run) => run.status === 'pending' || run.status === 'processing').length;
- }
- get completedCount(): number {
- return this.runs.filter((run) => run.status === 'completed' || run.status === 'partial').length;
- }
- get failedCount(): number {
- return this.runs.filter((run) => run.status === 'failed').length;
- }
- applyStatusFilter(): void {
- this.loadRuns(true);
- }
- refresh(): void {
- this.loadRuns(true);
- }
- loadMore(): void {
- if (!this.nextCursor || this.loadingMore) return;
- this.loadRuns(false);
- }
- selectRun(run: SaasAnalysisRun): void {
- this.selectedRun = run;
- this.runSelected.emit(run);
- }
- closeDetail(): void {
- this.selectedRun = null;
- }
- trackRun(_: number, run: SaasAnalysisRun): string {
- return run.id;
- }
- statusLabel(run: SaasAnalysisRun): string {
- return analysisRunStatusLabel(run.status);
- }
- statusTone(run: SaasAnalysisRun): string {
- return analysisRunStatusTone(run.status);
- }
- typeLabel(run: SaasAnalysisRun): string {
- return analysisRunTypeLabel(run.analysisType);
- }
- targetKindLabel(run: SaasAnalysisRun): string {
- return analysisTargetKindLabel(run.targetKind);
- }
- targetLabel(run: SaasAnalysisRun): string {
- return analysisTargetLabel(run);
- }
- sampleCount(run: SaasAnalysisRun): string {
- const input = readAnalysisRunInput(run);
- const result = readAnalysisRunResult(run);
- const value = input.sampleCount ?? result?.sampleCount ?? null;
- return value === null ? '未记录' : value.toLocaleString('zh-CN');
- }
- modeLabel(run: SaasAnalysisRun): string {
- return analysisModeLabel(readAnalysisRunResult(run)?.mode ?? null);
- }
- formatDate(value: string | null): string {
- return formatAnalysisDate(value);
- }
- private loadRuns(reset: boolean): void {
- const requestVersion = ++this.requestVersion;
- if (!this.workspaceId.trim()) {
- this.runs = [];
- this.loading = false;
- this.loadingMore = false;
- this.error = '工作空间 ID 未记录,运行列表未请求。';
- this.cdr.markForCheck();
- return;
- }
- const cursor = reset ? '' : this.nextCursor ?? '';
- if (reset) {
- this.loading = true;
- this.error = '';
- this.nextCursor = null;
- } else {
- this.loadingMore = true;
- }
- this.saasService.analyses(this.workspaceId, this.statusFilter, cursor, 50)
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe({
- next: (page) => {
- if (requestVersion !== this.requestVersion) return;
- this.runs = reset ? page.items : this.mergeRuns(this.runs, page.items);
- this.nextCursor = page.nextCursor;
- this.loadedAt = new Date().toISOString();
- this.loading = false;
- this.loadingMore = false;
- this.error = '';
- if (this.selectedRun) {
- this.selectedRun = this.runs.find((run) => run.id === this.selectedRun?.id) ?? this.selectedRun;
- }
- this.cdr.markForCheck();
- },
- error: (error: unknown) => {
- if (requestVersion !== this.requestVersion) return;
- this.loading = false;
- this.loadingMore = false;
- this.error = this.errorMessage(error);
- this.cdr.markForCheck();
- },
- });
- }
- private mergeRuns(current: SaasAnalysisRun[], incoming: SaasAnalysisRun[]): SaasAnalysisRun[] {
- const byId = new Map(current.map((run) => [run.id, run]));
- incoming.forEach((run) => byId.set(run.id, run));
- return [...byId.values()];
- }
- private errorMessage(error: unknown): string {
- if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') {
- return `分析运行加载失败:${error.message}`;
- }
- return '分析运行加载失败,请检查数据服务后重试。';
- }
- }
|