|
@@ -1,60 +1,57 @@
|
|
|
-import { Component, AfterViewInit, OnDestroy } from '@angular/core';
|
|
|
+import { Component, AfterViewInit, OnDestroy, OnInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
|
|
|
import * as echarts from 'echarts';
|
|
|
-import { CommonModule } from '@angular/common';
|
|
|
+import { CommonModule, DatePipe, DecimalPipe } from '@angular/common';
|
|
|
import { RouterModule } from '@angular/router';
|
|
|
+import { FormsModule } from '@angular/forms';
|
|
|
+import Parse from 'parse';
|
|
|
+import { VibrationEvent } from '../../../../shared/interfaces/event.interface';
|
|
|
|
|
|
@Component({
|
|
|
selector: 'app-page-vibration-history',
|
|
|
standalone: true,
|
|
|
- imports: [CommonModule, RouterModule],
|
|
|
+ imports: [CommonModule, RouterModule, FormsModule, DatePipe, DecimalPipe],
|
|
|
templateUrl: './page-vibration-history.html',
|
|
|
styleUrls: ['./page-vibration-history.scss']
|
|
|
})
|
|
|
-export class PageVibrationHistory implements AfterViewInit, OnDestroy {
|
|
|
- showCustomRange = false;
|
|
|
+export class PageVibrationHistory implements AfterViewInit, OnDestroy, OnInit {
|
|
|
+ // 分页相关
|
|
|
currentPage = 1;
|
|
|
- totalPages = 5;
|
|
|
+ totalPages = 1;
|
|
|
+ pageSize = 10;
|
|
|
|
|
|
- events = [
|
|
|
- {
|
|
|
- time: '2025-06-15 14:23:45',
|
|
|
- type: '振动超标',
|
|
|
- device: 'CNC-01',
|
|
|
- value: 2.8,
|
|
|
- duration: '12秒'
|
|
|
- },
|
|
|
- {
|
|
|
- time: '2025-06-15 10:12:33',
|
|
|
- type: '刀具磨损',
|
|
|
- device: 'CNC-02',
|
|
|
- value: 1.2,
|
|
|
- duration: '持续'
|
|
|
- },
|
|
|
- {
|
|
|
- time: '2025-06-14 18:45:21',
|
|
|
- type: '正常停机',
|
|
|
- device: 'CNC-01',
|
|
|
- value: 0.5,
|
|
|
- duration: '3分12秒'
|
|
|
- },
|
|
|
- {
|
|
|
- time: '2025-06-14 09:30:15',
|
|
|
- type: '严重故障',
|
|
|
- device: 'CNC-03',
|
|
|
- value: 4.2,
|
|
|
- duration: '2分45秒'
|
|
|
- },
|
|
|
- {
|
|
|
- time: '2025-06-13 16:20:08',
|
|
|
- type: '振动超标',
|
|
|
- device: 'CNC-02',
|
|
|
- value: 2.1,
|
|
|
- duration: '8秒'
|
|
|
- }
|
|
|
- ];
|
|
|
-
|
|
|
+ // 时间范围筛选
|
|
|
+ timeRange = '7days';
|
|
|
+ showCustomRange = false;
|
|
|
+ startDate = '';
|
|
|
+ endDate = '';
|
|
|
+
|
|
|
+ // 搜索相关
|
|
|
+ searchQuery = '';
|
|
|
+
|
|
|
+ // 批量操作相关
|
|
|
+ showBatchActions = false;
|
|
|
+ batchAction = '';
|
|
|
+ selectedEvents: string[] = [];
|
|
|
+
|
|
|
+ // 数据加载状态
|
|
|
+ isLoading = true;
|
|
|
+
|
|
|
+ // 事件数据
|
|
|
+ events: VibrationEvent[] = [];
|
|
|
+
|
|
|
+ // 图表实例
|
|
|
private trendChart!: echarts.ECharts;
|
|
|
private eventChart!: echarts.ECharts;
|
|
|
+ private chartsInitialized = false;
|
|
|
+
|
|
|
+ @ViewChild('trendChart') trendChartEl!: ElementRef;
|
|
|
+ @ViewChild('eventChart') eventChartEl!: ElementRef;
|
|
|
+
|
|
|
+ constructor(private cdr: ChangeDetectorRef) {}
|
|
|
+
|
|
|
+ ngOnInit() {
|
|
|
+ this.loadMockEvents();
|
|
|
+ }
|
|
|
|
|
|
ngAfterViewInit() {
|
|
|
this.initCharts();
|
|
@@ -72,152 +69,134 @@ export class PageVibrationHistory implements AfterViewInit, OnDestroy {
|
|
|
this.eventChart?.resize();
|
|
|
};
|
|
|
|
|
|
- private initCharts() {
|
|
|
- this.trendChart = echarts.init(document.getElementById('trend-chart') as HTMLElement);
|
|
|
+ private updateCharts() {
|
|
|
this.trendChart.setOption(this.getTrendChartOption());
|
|
|
-
|
|
|
- this.eventChart = echarts.init(document.getElementById('event-chart') as HTMLElement);
|
|
|
this.eventChart.setOption(this.getEventChartOption());
|
|
|
}
|
|
|
|
|
|
+private initCharts() {
|
|
|
+ setTimeout(() => { // 添加延迟确保DOM就绪
|
|
|
+ try {
|
|
|
+ if (!this.trendChartEl?.nativeElement || !this.eventChartEl?.nativeElement) {
|
|
|
+ console.error('图表容器未找到:', {
|
|
|
+ trend: this.trendChartEl,
|
|
|
+ event: this.eventChartEl
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.trendChart = echarts.init(this.trendChartEl.nativeElement);
|
|
|
+ this.eventChart = echarts.init(this.eventChartEl.nativeElement);
|
|
|
+
|
|
|
+ // 强制设置容器大小
|
|
|
+ this.trendChart.resize();
|
|
|
+ this.eventChart.resize();
|
|
|
+
|
|
|
+ this.chartsInitialized = true;
|
|
|
+ this.updateCharts(); // 立即更新一次
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('图表初始化错误:', error);
|
|
|
+ }
|
|
|
+ }, 100);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 添加加载模拟数据的方法
|
|
|
+ loadMockEvents() {
|
|
|
+ this.isLoading = true;
|
|
|
+ this.cdr.detectChanges(); // 手动触发变更检测
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ try {
|
|
|
+ this.events = this.generateMockEvents();
|
|
|
+ this.totalPages = Math.ceil(this.events.length / this.pageSize);
|
|
|
+
|
|
|
+ if (this.chartsInitialized) {
|
|
|
+ this.updateCharts();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载模拟数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ this.isLoading = false;
|
|
|
+ this.cdr.detectChanges();
|
|
|
+ }
|
|
|
+ }, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 获取趋势图配置
|
|
|
private getTrendChartOption(): echarts.EChartsOption {
|
|
|
const hours = Array.from({length: 24}, (_, i) => `${i}:00`);
|
|
|
- const data = hours.map((_, i) => {
|
|
|
- return 0.5 + Math.sin(i / 3) * 0.8 + Math.random() * 0.3;
|
|
|
- });
|
|
|
+ const data = hours.map((_, i) => 0.5 + Math.sin(i / 3) * 0.8 + Math.random() * 0.3);
|
|
|
|
|
|
return {
|
|
|
backgroundColor: 'transparent',
|
|
|
- grid: {
|
|
|
- top: 30,
|
|
|
- right: 30,
|
|
|
- bottom: 40,
|
|
|
- left: 50
|
|
|
- },
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis'
|
|
|
- },
|
|
|
+ grid: { top: 30, right: 30, bottom: 40, left: 50 },
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
data: hours,
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#666'
|
|
|
- }
|
|
|
- },
|
|
|
- axisLabel: {
|
|
|
- color: '#999',
|
|
|
- rotate: 45
|
|
|
- }
|
|
|
+ axisLine: { lineStyle: { color: '#666' } },
|
|
|
+ axisLabel: { color: '#999', rotate: 45 }
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
name: '振动值 (g)',
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#666'
|
|
|
- }
|
|
|
- },
|
|
|
- axisLabel: {
|
|
|
- color: '#999'
|
|
|
- },
|
|
|
- splitLine: {
|
|
|
- lineStyle: {
|
|
|
- color: 'rgba(255, 255, 255, 0.05)'
|
|
|
- }
|
|
|
- }
|
|
|
+ axisLine: { lineStyle: { color: '#666' } },
|
|
|
+ axisLabel: { color: '#999' },
|
|
|
+ splitLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.05)' } }
|
|
|
},
|
|
|
- series: [
|
|
|
- {
|
|
|
- name: '振动值',
|
|
|
- type: 'line',
|
|
|
- smooth: true,
|
|
|
- data: data,
|
|
|
- lineStyle: {
|
|
|
- width: 2,
|
|
|
- color: '#00b6c1'
|
|
|
- },
|
|
|
- itemStyle: {
|
|
|
- color: '#00b6c1'
|
|
|
- },
|
|
|
- areaStyle: {
|
|
|
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: 'rgba(0, 182, 193, 0.7)' },
|
|
|
- { offset: 1, color: 'rgba(0, 182, 193, 0.1)' }
|
|
|
- ])
|
|
|
- },
|
|
|
- markLine: {
|
|
|
- silent: true,
|
|
|
- lineStyle: {
|
|
|
- color: '#ff9800',
|
|
|
- width: 1,
|
|
|
- type: 'dashed'
|
|
|
- },
|
|
|
- data: [
|
|
|
- {
|
|
|
- yAxis: 1.5,
|
|
|
- label: {
|
|
|
- formatter: '警告阈值',
|
|
|
- position: 'start'
|
|
|
- }
|
|
|
- },
|
|
|
- {
|
|
|
- yAxis: 2.0,
|
|
|
- label: {
|
|
|
- formatter: '危险阈值',
|
|
|
- position: 'start'
|
|
|
- },
|
|
|
- lineStyle: {
|
|
|
- color: '#f44336'
|
|
|
- }
|
|
|
- }
|
|
|
- ]
|
|
|
- }
|
|
|
+ series: [{
|
|
|
+ name: '振动值',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: data,
|
|
|
+ lineStyle: { width: 2, color: '#00b6c1' },
|
|
|
+ itemStyle: { color: '#00b6c1' },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(0, 182, 193, 0.7)' },
|
|
|
+ { offset: 1, color: 'rgba(0, 182, 193, 0.1)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ markLine: {
|
|
|
+ silent: true,
|
|
|
+ lineStyle: { color: '#ff9800', width: 1, type: 'dashed' },
|
|
|
+ data: [
|
|
|
+ { yAxis: 1.5, label: { formatter: '警告阈值', position: 'start' } },
|
|
|
+ { yAxis: 2.0, label: { formatter: '危险阈值', position: 'start' }, lineStyle: { color: '#f44336' } }
|
|
|
+ ]
|
|
|
}
|
|
|
- ]
|
|
|
+ }]
|
|
|
};
|
|
|
}
|
|
|
|
|
|
private getEventChartOption(): echarts.EChartsOption {
|
|
|
+ const eventTypes = ['振动超标', '刀具磨损', '正常停机', '严重故障'];
|
|
|
+ const warningData = eventTypes.map(type =>
|
|
|
+ this.events.filter(e => e.eventType === type && ['振动超标', '刀具磨损'].includes(e.eventType)).length
|
|
|
+ );
|
|
|
+ const dangerData = eventTypes.map(type =>
|
|
|
+ this.events.filter(e => e.eventType === type && e.eventType === '严重故障').length
|
|
|
+ );
|
|
|
+
|
|
|
return {
|
|
|
backgroundColor: 'transparent',
|
|
|
- grid: {
|
|
|
- top: 30,
|
|
|
- right: 30,
|
|
|
- bottom: 40,
|
|
|
- left: 50
|
|
|
- },
|
|
|
- tooltip: {
|
|
|
- trigger: 'axis'
|
|
|
- },
|
|
|
+ grid: { top: 30, right: 30, bottom: 40, left: 50 },
|
|
|
+ tooltip: { trigger: 'axis' },
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
- data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#666'
|
|
|
- }
|
|
|
- },
|
|
|
- axisLabel: {
|
|
|
- color: '#999'
|
|
|
- }
|
|
|
+ data: eventTypes,
|
|
|
+ axisLine: { lineStyle: { color: '#666' } },
|
|
|
+ axisLabel: { color: '#999' }
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
name: '事件次数',
|
|
|
- axisLine: {
|
|
|
- lineStyle: {
|
|
|
- color: '#666'
|
|
|
- }
|
|
|
- },
|
|
|
- axisLabel: {
|
|
|
- color: '#999'
|
|
|
- },
|
|
|
- splitLine: {
|
|
|
- lineStyle: {
|
|
|
- color: 'rgba(255, 255, 255, 0.05)'
|
|
|
- }
|
|
|
- }
|
|
|
+ axisLine: { lineStyle: { color: '#666' } },
|
|
|
+ axisLabel: { color: '#999' },
|
|
|
+ splitLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.05)' } }
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
@@ -225,44 +204,329 @@ export class PageVibrationHistory implements AfterViewInit, OnDestroy {
|
|
|
type: 'bar',
|
|
|
barGap: 0,
|
|
|
barWidth: 15,
|
|
|
- data: [2, 1, 3, 2, 1, 0, 1],
|
|
|
- itemStyle: {
|
|
|
- color: '#ff9800'
|
|
|
- }
|
|
|
+ data: warningData,
|
|
|
+ itemStyle: { color: '#ff9800' }
|
|
|
},
|
|
|
{
|
|
|
name: '严重事件',
|
|
|
type: 'bar',
|
|
|
barWidth: 15,
|
|
|
- data: [0, 1, 0, 1, 0, 0, 0],
|
|
|
- itemStyle: {
|
|
|
- color: '#f44336'
|
|
|
- }
|
|
|
+ data: dangerData,
|
|
|
+ itemStyle: { color: '#f44336' }
|
|
|
}
|
|
|
],
|
|
|
legend: {
|
|
|
data: ['警告事件', '严重事件'],
|
|
|
- textStyle: {
|
|
|
- color: '#ccc'
|
|
|
- },
|
|
|
+ textStyle: { color: '#ccc' },
|
|
|
top: 0
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- toggleCustomRange(show: boolean) {
|
|
|
- this.showCustomRange = show;
|
|
|
+ private generateMockEvents(): VibrationEvent[] {
|
|
|
+ const mockEvents: VibrationEvent[] = [];
|
|
|
+ const eventTypes: ('振动超标' | '刀具磨损' | '正常停机' | '严重故障')[] =
|
|
|
+ ['振动超标', '刀具磨损', '正常停机', '严重故障'];
|
|
|
+ const deviceIds = ['CNC-001', 'CNC-002', 'CNC-003', 'MILL-001', 'LATHE-001'];
|
|
|
+
|
|
|
+ const now = new Date();
|
|
|
+ const oneDay = 24 * 60 * 60 * 1000;
|
|
|
+
|
|
|
+ for (let i = 0; i < 50; i++) {
|
|
|
+ const daysAgo = Math.floor(Math.random() * 90);
|
|
|
+ const hoursAgo = Math.floor(Math.random() * 24);
|
|
|
+ const minutesAgo = Math.floor(Math.random() * 60);
|
|
|
+
|
|
|
+ const eventTime = new Date(now.getTime() - (daysAgo * oneDay) - (hoursAgo * 60 * 60 * 1000) - (minutesAgo * 60 * 1000));
|
|
|
+ const eventType = eventTypes[Math.floor(Math.random() * eventTypes.length)];
|
|
|
+ const deviceId = deviceIds[Math.floor(Math.random() * deviceIds.length)];
|
|
|
+
|
|
|
+ let vibrationValue: number;
|
|
|
+ switch(eventType) {
|
|
|
+ case '振动超标': vibrationValue = 1.6 + Math.random() * 0.8; break;
|
|
|
+ case '刀具磨损': vibrationValue = 1.2 + Math.random() * 0.6; break;
|
|
|
+ case '严重故障': vibrationValue = 2.5 + Math.random() * 1.5; break;
|
|
|
+ default: vibrationValue = 0.5 + Math.random() * 0.5;
|
|
|
+ }
|
|
|
+
|
|
|
+ const durationMinutes = Math.floor(Math.random() * 30);
|
|
|
+ const durationSeconds = Math.floor(Math.random() * 60);
|
|
|
+ const duration = `${durationMinutes}:${durationSeconds.toString().padStart(2, '0')}`;
|
|
|
+
|
|
|
+ mockEvents.push({
|
|
|
+ id: `mock-${i}`,
|
|
|
+ eventTime,
|
|
|
+ eventType,
|
|
|
+ deviceId,
|
|
|
+ vibrationValue,
|
|
|
+ duration
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return mockEvents.sort((a, b) => b.eventTime.getTime() - a.eventTime.getTime());
|
|
|
+ }
|
|
|
+
|
|
|
+ initializeParse() {
|
|
|
+ Parse.initialize('dev', 'devmk');
|
|
|
+ (Parse as any).serverURL = 'http://dev.fmode.cn:1337/parse';
|
|
|
}
|
|
|
|
|
|
- prevPage() {
|
|
|
+ async loadEvents() {
|
|
|
+ this.isLoading = true;
|
|
|
+ this.selectedEvents = [];
|
|
|
+
|
|
|
+ const Event = Parse.Object.extend('VibrationEvent');
|
|
|
+ const query = new Parse.Query(Event);
|
|
|
+
|
|
|
+ this.applyTimeFilter(query);
|
|
|
+
|
|
|
+ if (this.searchQuery) {
|
|
|
+ query.contains('deviceId', this.searchQuery);
|
|
|
+ query.contains('eventType', this.searchQuery);
|
|
|
+ }
|
|
|
+
|
|
|
+ query.limit(this.pageSize);
|
|
|
+ query.skip((this.currentPage - 1) * this.pageSize);
|
|
|
+ query.descending('eventTime');
|
|
|
+
|
|
|
+ try {
|
|
|
+ const results = await query.find();
|
|
|
+ this.events = results.map(item => ({
|
|
|
+ id: item.id,
|
|
|
+ eventTime: item.get('eventTime') as Date,
|
|
|
+ eventType: item.get('eventType') as '振动超标' | '刀具磨损' | '正常停机' | '严重故障',
|
|
|
+ deviceId: item.get('deviceId') as string,
|
|
|
+ vibrationValue: item.get('vibrationValue') as number,
|
|
|
+ duration: item.get('duration') as string,
|
|
|
+ rawData: item
|
|
|
+ }));
|
|
|
+
|
|
|
+ const count = await query.count();
|
|
|
+ this.totalPages = Math.ceil(count / this.pageSize);
|
|
|
+ this.updateCharts();
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error loading events:', error);
|
|
|
+ } finally {
|
|
|
+ this.isLoading = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private applyTimeFilter(query: Parse.Query) {
|
|
|
+ const now = new Date();
|
|
|
+
|
|
|
+ switch (this.timeRange) {
|
|
|
+ case 'today':
|
|
|
+ const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
|
+ query.greaterThanOrEqualTo('eventTime', todayStart);
|
|
|
+ break;
|
|
|
+ case '7days':
|
|
|
+ const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
|
+ query.greaterThanOrEqualTo('eventTime', sevenDaysAgo);
|
|
|
+ break;
|
|
|
+ case '30days':
|
|
|
+ const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
|
+ query.greaterThanOrEqualTo('eventTime', thirtyDaysAgo);
|
|
|
+ break;
|
|
|
+ case '90days':
|
|
|
+ const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
|
|
|
+ query.greaterThanOrEqualTo('eventTime', ninetyDaysAgo);
|
|
|
+ break;
|
|
|
+ case 'custom':
|
|
|
+ if (this.startDate && this.endDate) {
|
|
|
+ query.greaterThanOrEqualTo('eventTime', new Date(this.startDate));
|
|
|
+ query.lessThanOrEqualTo('eventTime', new Date(this.endDate));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onTimeRangeChange() {
|
|
|
+ this.showCustomRange = this.timeRange === 'custom';
|
|
|
+ if (this.timeRange !== 'custom') {
|
|
|
+ this.loadEvents();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ applyCustomRange() {
|
|
|
+ if (this.startDate && this.endDate) {
|
|
|
+ this.loadEvents();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ searchEvents() {
|
|
|
+ this.currentPage = 1;
|
|
|
+ this.loadEvents();
|
|
|
+ }
|
|
|
+
|
|
|
+ resetSearch() {
|
|
|
+ this.searchQuery = '';
|
|
|
+ this.currentPage = 1;
|
|
|
+ this.loadEvents();
|
|
|
+ }
|
|
|
+
|
|
|
+ async prevPage() {
|
|
|
if (this.currentPage > 1) {
|
|
|
this.currentPage--;
|
|
|
+ await this.loadEvents();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- nextPage() {
|
|
|
+ async nextPage() {
|
|
|
if (this.currentPage < this.totalPages) {
|
|
|
this.currentPage++;
|
|
|
+ await this.loadEvents();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onPageSizeChange() {
|
|
|
+ this.currentPage = 1;
|
|
|
+ this.loadEvents();
|
|
|
+ }
|
|
|
+
|
|
|
+ exportCSV() {
|
|
|
+ try {
|
|
|
+ const headers = ['时间', '事件类型', '设备', '振动值(g)', '持续时间'];
|
|
|
+ const rows = this.events.map(event => [
|
|
|
+ event.eventTime.toLocaleString(),
|
|
|
+ event.eventType,
|
|
|
+ event.deviceId,
|
|
|
+ event.vibrationValue,
|
|
|
+ event.duration
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const csvContent = [headers, ...rows].map(row => row.join(',')).join('\n');
|
|
|
+ this.downloadCSV(csvContent, '振动事件记录.csv');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('导出CSV失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private downloadCSV(content: string, filename: string) {
|
|
|
+ const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });
|
|
|
+ const link = document.createElement('a');
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
+
|
|
|
+ link.setAttribute('href', url);
|
|
|
+ link.setAttribute('download', filename);
|
|
|
+ link.style.visibility = 'hidden';
|
|
|
+
|
|
|
+ document.body.appendChild(link);
|
|
|
+ link.click();
|
|
|
+ document.body.removeChild(link);
|
|
|
+ }
|
|
|
+
|
|
|
+ toggleSelectEvent(eventId: string | undefined) {
|
|
|
+ if (!eventId) return;
|
|
|
+
|
|
|
+ const index = this.selectedEvents.indexOf(eventId);
|
|
|
+ if (index > -1) {
|
|
|
+ this.selectedEvents.splice(index, 1);
|
|
|
+ } else {
|
|
|
+ this.selectedEvents.push(eventId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ isSelected(eventId: string | undefined): boolean {
|
|
|
+ if (!eventId) return false;
|
|
|
+ return this.selectedEvents.includes(eventId);
|
|
|
+ }
|
|
|
+
|
|
|
+ showDetails(event: VibrationEvent) {
|
|
|
+ console.log('事件详情:', event);
|
|
|
+ alert(`事件详情:
|
|
|
+时间: ${event.eventTime}
|
|
|
+类型: ${event.eventType}
|
|
|
+设备: ${event.deviceId}
|
|
|
+振动值: ${event.vibrationValue}g
|
|
|
+持续时间: ${event.duration}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增的方法 - 执行批量操作
|
|
|
+ executeBatchAction() {
|
|
|
+ if (!this.batchAction || this.selectedEvents.length === 0) {
|
|
|
+ alert('请选择要执行的操作和至少一个事件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据不同的批量操作类型执行不同的逻辑
|
|
|
+ switch (this.batchAction) {
|
|
|
+ case 'export':
|
|
|
+ this.exportSelectedEvents();
|
|
|
+ break;
|
|
|
+ case 'delete':
|
|
|
+ this.deleteSelectedEvents();
|
|
|
+ break;
|
|
|
+ case 'mark':
|
|
|
+ this.markSelectedEvents();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ console.warn('未知的批量操作类型:', this.batchAction);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private exportSelectedEvents() {
|
|
|
+ const selectedEvents = this.events.filter(event =>
|
|
|
+ event.id && this.selectedEvents.includes(event.id)
|
|
|
+ );
|
|
|
+
|
|
|
+ if (selectedEvents.length === 0) {
|
|
|
+ alert('没有选中的事件可导出');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const headers = ['时间', '事件类型', '设备', '振动值(g)', '持续时间'];
|
|
|
+ const rows = selectedEvents.map(event => [
|
|
|
+ event.eventTime.toLocaleString(),
|
|
|
+ event.eventType,
|
|
|
+ event.deviceId,
|
|
|
+ event.vibrationValue,
|
|
|
+ event.duration
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const csvContent = [headers, ...rows].map(row => row.join(',')).join('\n');
|
|
|
+ this.downloadCSV(csvContent, '选中振动事件.csv');
|
|
|
+ }
|
|
|
+
|
|
|
+ private async deleteSelectedEvents() {
|
|
|
+ if (!confirm(`确定要删除选中的 ${this.selectedEvents.length} 个事件吗?`)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const Event = Parse.Object.extend('VibrationEvent');
|
|
|
+ const objectsToDelete = this.selectedEvents.map(id => {
|
|
|
+ const obj = new Event();
|
|
|
+ obj.id = id;
|
|
|
+ return obj;
|
|
|
+ });
|
|
|
+
|
|
|
+ await Parse.Object.destroyAll(objectsToDelete);
|
|
|
+ alert('删除成功');
|
|
|
+ this.loadEvents(); // 刷新列表
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除事件失败:', error);
|
|
|
+ alert('删除失败,请查看控制台日志');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private markSelectedEvents() {
|
|
|
+ // 这里可以实现标记选中事件的逻辑
|
|
|
+ alert(`已标记 ${this.selectedEvents.length} 个事件`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 新增的方法 - 全选/取消全选
|
|
|
+ toggleSelectAll(event: Event) {
|
|
|
+ const isChecked = (event.target as HTMLInputElement).checked;
|
|
|
+
|
|
|
+ if (isChecked) {
|
|
|
+ // 全选当前页的所有事件
|
|
|
+ this.selectedEvents = this.events
|
|
|
+ .filter(event => !!event.id)
|
|
|
+ .map(event => event.id as string);
|
|
|
+ } else {
|
|
|
+ // 取消全选
|
|
|
+ this.selectedEvents = [];
|
|
|
}
|
|
|
}
|
|
|
}
|