|
@@ -1,10 +1,10 @@
|
|
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
|
import { CommonModule } from '@angular/common';
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
import { FormsModule } from '@angular/forms';
|
|
|
-import { Router } from '@angular/router';
|
|
|
|
|
|
|
+import { Router, RouterModule } from '@angular/router';
|
|
|
import { Subject, forkJoin } from 'rxjs';
|
|
import { Subject, forkJoin } from 'rxjs';
|
|
|
import { takeUntil } from 'rxjs/operators';
|
|
import { takeUntil } from 'rxjs/operators';
|
|
|
-import { MonitoringDataService } from '../../../shared/services/monitoring-data.service';
|
|
|
|
|
|
|
+import { DomesticMonitoringViewService } from '../../../../app/core/services/domestic-monitoring-view.service';
|
|
|
import { RiskAlertItem, ScenarioComplaintCluster, SentimentSpikeEvent } from '../../../shared/interfaces/monitoring-interfaces';
|
|
import { RiskAlertItem, ScenarioComplaintCluster, SentimentSpikeEvent } from '../../../shared/interfaces/monitoring-interfaces';
|
|
|
import { MsqSearchBoxComponent } from '../../../shared/components/msq-search-box/msq-search-box.component';
|
|
import { MsqSearchBoxComponent } from '../../../shared/components/msq-search-box/msq-search-box.component';
|
|
|
import { FilterBarComponent } from '../../../shared/components/filter-bar/filter-bar.component';
|
|
import { FilterBarComponent } from '../../../shared/components/filter-bar/filter-bar.component';
|
|
@@ -19,7 +19,7 @@ type RiskAlertTabKey = 'products' | 'scenarios' | 'spikes';
|
|
|
@Component({
|
|
@Component({
|
|
|
selector: 'app-risk-alert',
|
|
selector: 'app-risk-alert',
|
|
|
standalone: true,
|
|
standalone: true,
|
|
|
- imports: [CommonModule, FormsModule, MsqSearchBoxComponent, FilterBarComponent, PageHeaderComponent, BoardTabsComponent, EmptyStateComponent, LoadingSpinnerComponent, ProductIdentityComponent],
|
|
|
|
|
|
|
+ imports: [CommonModule, FormsModule, RouterModule, MsqSearchBoxComponent, FilterBarComponent, PageHeaderComponent, BoardTabsComponent, EmptyStateComponent, LoadingSpinnerComponent, ProductIdentityComponent],
|
|
|
template: `
|
|
template: `
|
|
|
<div class="risk-alert-container">
|
|
<div class="risk-alert-container">
|
|
|
<app-page-header
|
|
<app-page-header
|
|
@@ -28,7 +28,7 @@ type RiskAlertTabKey = 'products' | 'scenarios' | 'spikes';
|
|
|
description="在系统性差评爆发前实现自动拦截与止损"
|
|
description="在系统性差评爆发前实现自动拦截与止损"
|
|
|
icon="🛡️">
|
|
icon="🛡️">
|
|
|
<div pageHeaderActions class="header-right">
|
|
<div pageHeaderActions class="header-right">
|
|
|
- <msq-search-box placeholder="搜索 ASIN..." [(ngModel)]="searchQuery"></msq-search-box>
|
|
|
|
|
|
|
+ <msq-search-box placeholder="搜索商品ID或名称" [(ngModel)]="searchQuery"></msq-search-box>
|
|
|
<button class="refresh-btn" (click)="refreshData()" [disabled]="loading">
|
|
<button class="refresh-btn" (click)="refreshData()" [disabled]="loading">
|
|
|
<span [class.spinning]="loading">🔄</span> 刷新
|
|
<span [class.spinning]="loading">🔄</span> 刷新
|
|
|
</button>
|
|
</button>
|
|
@@ -41,11 +41,10 @@ type RiskAlertTabKey = 'products' | 'scenarios' | 'spikes';
|
|
|
|
|
|
|
|
<app-filter-bar
|
|
<app-filter-bar
|
|
|
[filters]="currentFilters"
|
|
[filters]="currentFilters"
|
|
|
- [enableDesigner]="true"
|
|
|
|
|
- [enableOperator]="true"
|
|
|
|
|
- [enableDeveloper]="true"
|
|
|
|
|
- [enableListingStatus]="true"
|
|
|
|
|
- [enableFulfillmentType]="true"
|
|
|
|
|
|
|
+ [autoLoadOptions]="false"
|
|
|
|
|
+ [showSite]="false"
|
|
|
|
|
+ [showStore]="false"
|
|
|
|
|
+ [showActions]="false"
|
|
|
[showDatePicker]="false"
|
|
[showDatePicker]="false"
|
|
|
(filtersChange)="onFilterChange($event)">
|
|
(filtersChange)="onFilterChange($event)">
|
|
|
</app-filter-bar>
|
|
</app-filter-bar>
|
|
@@ -111,7 +110,7 @@ type RiskAlertTabKey = 'products' | 'scenarios' | 'spikes';
|
|
|
[clickable]="true"
|
|
[clickable]="true"
|
|
|
[externalHint]="true"
|
|
[externalHint]="true"
|
|
|
size="md"
|
|
size="md"
|
|
|
- (identityClick)="openAmazonPage(item.asin)">
|
|
|
|
|
|
|
+ (identityClick)="openProductDetail(item.asin)">
|
|
|
</app-product-identity>
|
|
</app-product-identity>
|
|
|
<span class="rp-severity-badge" [class]="item.severity">
|
|
<span class="rp-severity-badge" [class]="item.severity">
|
|
|
{{ item.severity === 'critical' ? '🔴 严重' : item.severity === 'warning' ? '🟡 警告' : '🟢 正常' }}
|
|
{{ item.severity === 'critical' ? '🔴 严重' : item.severity === 'warning' ? '🟡 警告' : '🟢 正常' }}
|
|
@@ -137,7 +136,7 @@ type RiskAlertTabKey = 'products' | 'scenarios' | 'spikes';
|
|
|
差评率
|
|
差评率
|
|
|
<b [style.color]="item.negativeRate > 30 ? '#dc2626' : item.negativeRate > 15 ? '#d97706' : '#16a34a'">{{ item.negativeRate }}%</b>
|
|
<b [style.color]="item.negativeRate > 30 ? '#dc2626' : item.negativeRate > 15 ? '#d97706' : '#16a34a'">{{ item.negativeRate }}%</b>
|
|
|
@if (item.negativeRateBasis === 'rating_estimate') {
|
|
@if (item.negativeRateBasis === 'rating_estimate') {
|
|
|
- <span class="rp-estimate-tag" title="基于 Amazon 评分占比估算,非可分析评论样本">估算</span>
|
|
|
|
|
|
|
+ <span class="rp-estimate-tag" title="基于平台评分占比估算,非可分析评论样本">估算</span>
|
|
|
}
|
|
}
|
|
|
<span class="neg-bar-mini">
|
|
<span class="neg-bar-mini">
|
|
|
<span class="neg-bar-fill" [style.width.%]="Math.min(item.negativeRate, 100)"
|
|
<span class="neg-bar-fill" [style.width.%]="Math.min(item.negativeRate, 100)"
|
|
@@ -149,7 +148,7 @@ type RiskAlertTabKey = 'products' | 'scenarios' | 'spikes';
|
|
|
@if (item.negativeRateBasis === 'reviews') {
|
|
@if (item.negativeRateBasis === 'reviews') {
|
|
|
<span>可分析评论 <b>{{ item.analyzableReviews | number }}</b></span>
|
|
<span>可分析评论 <b>{{ item.analyzableReviews | number }}</b></span>
|
|
|
} @else {
|
|
} @else {
|
|
|
- <span title="Amazon 评分计数,非可分析评论样本">Amazon评分 <b>{{ item.ratingsCount | number }}</b></span>
|
|
|
|
|
|
|
+ <span title="平台评分计数,非可分析评论样本">平台评分 <b>{{ item.ratingsCount | number }}</b></span>
|
|
|
}
|
|
}
|
|
|
</div>
|
|
</div>
|
|
|
@if (item.alerts.length > 0) {
|
|
@if (item.alerts.length > 0) {
|
|
@@ -169,7 +168,7 @@ type RiskAlertTabKey = 'products' | 'scenarios' | 'spikes';
|
|
|
<div class="rp-detail-actions">
|
|
<div class="rp-detail-actions">
|
|
|
<button class="action-btn primary" (click)="navigateToVocAnalysis(item.asin)">📊 VOC 深度分析</button>
|
|
<button class="action-btn primary" (click)="navigateToVocAnalysis(item.asin)">📊 VOC 深度分析</button>
|
|
|
<button class="action-btn" (click)="navigateToEmotionAnalysis(item.asin)">😊 情感分析</button>
|
|
<button class="action-btn" (click)="navigateToEmotionAnalysis(item.asin)">😊 情感分析</button>
|
|
|
- <button class="action-btn" (click)="openAmazonPage(item.asin)">🛒 Amazon 商品页</button>
|
|
|
|
|
|
|
+ <button class="action-btn" (click)="openProductDetail(item.asin)">商品经营详情</button>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="rp-detail-info">
|
|
<div class="rp-detail-info">
|
|
|
<span>预警数: <b>{{ item.alerts.length }}</b></span>
|
|
<span>预警数: <b>{{ item.alerts.length }}</b></span>
|
|
@@ -244,7 +243,7 @@ type RiskAlertTabKey = 'products' | 'scenarios' | 'spikes';
|
|
|
<div class="spike-asins">
|
|
<div class="spike-asins">
|
|
|
<span class="spike-asins-label">涉及商品:</span>
|
|
<span class="spike-asins-label">涉及商品:</span>
|
|
|
@for (asin of spike.affectedAsins.slice(0, expandedSpike === spike.date ? 20 : 3); track asin) {
|
|
@for (asin of spike.affectedAsins.slice(0, expandedSpike === spike.date ? 20 : 3); track asin) {
|
|
|
- <a class="spike-asin-tag" [href]="'https://www.amazon.com/dp/' + asin" target="_blank" (click)="$event.stopPropagation()">{{ asin }}</a>
|
|
|
|
|
|
|
+ <a class="spike-asin-tag" [routerLink]="['/domestic/product']" [queryParams]="{ productId: asin }" (click)="$event.stopPropagation()">{{ asin }}</a>
|
|
|
}
|
|
}
|
|
|
@if (spike.affectedAsins.length > 3 && expandedSpike !== spike.date) {
|
|
@if (spike.affectedAsins.length > 3 && expandedSpike !== spike.date) {
|
|
|
<span class="spike-more">+{{ spike.affectedAsins.length - 3 }} 更多</span>
|
|
<span class="spike-more">+{{ spike.affectedAsins.length - 3 }} 更多</span>
|
|
@@ -430,7 +429,7 @@ export class RiskAlertComponent implements OnInit, OnDestroy {
|
|
|
normalCount = 0;
|
|
normalCount = 0;
|
|
|
|
|
|
|
|
constructor(
|
|
constructor(
|
|
|
- private monitoringService: MonitoringDataService,
|
|
|
|
|
|
|
+ private monitoringService: DomesticMonitoringViewService,
|
|
|
private router: Router
|
|
private router: Router
|
|
|
) {}
|
|
) {}
|
|
|
|
|
|
|
@@ -514,15 +513,15 @@ export class RiskAlertComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
navigateToVocAnalysis(asin: string): void {
|
|
navigateToVocAnalysis(asin: string): void {
|
|
|
- this.router.navigate(['/voc-insight/scenario-dashboard'], { queryParams: { asin } });
|
|
|
|
|
|
|
+ this.router.navigate(['/voc-insight/voice'], { queryParams: { productId: asin } });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
navigateToEmotionAnalysis(asin: string): void {
|
|
navigateToEmotionAnalysis(asin: string): void {
|
|
|
- this.router.navigate(['/monitoring/voc-scene'], { queryParams: { asin } });
|
|
|
|
|
|
|
+ this.router.navigate(['/voc-insight/voice'], { queryParams: { productId: asin } });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- openAmazonPage(asin: string): void {
|
|
|
|
|
- window.open(`https://www.amazon.com/dp/${asin}`, '_blank');
|
|
|
|
|
|
|
+ openProductDetail(productId: string): void {
|
|
|
|
|
+ this.router.navigate(['/domestic/product'], { queryParams: { productId } });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
get sortedRiskItems(): RiskAlertItem[] {
|
|
get sortedRiskItems(): RiskAlertItem[] {
|