|
@@ -0,0 +1,592 @@
|
|
|
+import { Component, OnInit, OnDestroy, AfterViewInit } from '@angular/core';
|
|
|
+import { CommonModule } from '@angular/common';
|
|
|
+import * as echarts from 'echarts';
|
|
|
+
|
|
|
+@Component({
|
|
|
+ selector: 'app-page-vibration-monitor',
|
|
|
+ standalone: true,
|
|
|
+ imports: [CommonModule],
|
|
|
+ templateUrl: './page-vibration-monitor.html',
|
|
|
+ styleUrls: ['./page-vibration-monitor.scss']
|
|
|
+})
|
|
|
+export class PageVibrationMonitorComponent implements OnInit, AfterViewInit, OnDestroy {
|
|
|
+ // 系统状态变量
|
|
|
+ systemStatus = '运行中';
|
|
|
+ uptime = '正常运行: 0小时0分0秒';
|
|
|
+ responseTime = 8;
|
|
|
+ errorRate = 0.8;
|
|
|
+
|
|
|
+ // 图表实例
|
|
|
+ vibrationChart!: echarts.ECharts;
|
|
|
+ costChart!: echarts.ECharts;
|
|
|
+ toolChart!: echarts.ECharts;
|
|
|
+ eventChart!: echarts.ECharts;
|
|
|
+
|
|
|
+ // 数据变量
|
|
|
+ timeCounter = 0;
|
|
|
+ vibrationData: number[] = [];
|
|
|
+ thresholdData: number[] = [];
|
|
|
+ toolWearData: number[] = [];
|
|
|
+ isAlertActive = false;
|
|
|
+ uptimeSeconds = 0;
|
|
|
+
|
|
|
+ // 定时器
|
|
|
+ private dataInterval!: any;
|
|
|
+ private uptimeInterval!: any;
|
|
|
+
|
|
|
+ // 样式变量
|
|
|
+ colors = {
|
|
|
+ primary: '#0066cc',
|
|
|
+ secondary: '#00b6c1',
|
|
|
+ warning: '#ff9800',
|
|
|
+ danger: '#f44336',
|
|
|
+ success: '#4caf50',
|
|
|
+ darkBg: '#1a2a3a',
|
|
|
+ cardBg: 'rgba(255, 255, 255, 0.1)'
|
|
|
+ };
|
|
|
+
|
|
|
+ ngOnInit() {
|
|
|
+ // 初始化数据
|
|
|
+ this.startDataGeneration();
|
|
|
+ this.startUptimeCounter();
|
|
|
+ }
|
|
|
+
|
|
|
+ ngAfterViewInit() {
|
|
|
+ this.initCharts();
|
|
|
+ window.addEventListener('resize', this.onWindowResize);
|
|
|
+ }
|
|
|
+
|
|
|
+ ngOnDestroy() {
|
|
|
+ // 清理定时器和事件监听
|
|
|
+ clearInterval(this.dataInterval);
|
|
|
+ clearInterval(this.uptimeInterval);
|
|
|
+ window.removeEventListener('resize', this.onWindowResize);
|
|
|
+
|
|
|
+ // 销毁图表实例
|
|
|
+ this.vibrationChart?.dispose();
|
|
|
+ this.costChart?.dispose();
|
|
|
+ this.toolChart?.dispose();
|
|
|
+ this.eventChart?.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ private initCharts() {
|
|
|
+ // 初始化振动图表
|
|
|
+ this.vibrationChart = echarts.init(document.getElementById('vibration-chart') as HTMLElement);
|
|
|
+ this.vibrationChart.setOption(this.getVibrationChartOption());
|
|
|
+
|
|
|
+ // 初始化成本图表
|
|
|
+ this.costChart = echarts.init(document.getElementById('cost-chart') as HTMLElement);
|
|
|
+ this.costChart.setOption(this.getCostChartOption());
|
|
|
+
|
|
|
+ // 初始化刀具寿命图表
|
|
|
+ this.toolChart = echarts.init(document.getElementById('tool-chart') as HTMLElement);
|
|
|
+ this.toolChart.setOption(this.getToolChartOption());
|
|
|
+
|
|
|
+ // 初始化异常事件图表
|
|
|
+ this.eventChart = echarts.init(document.getElementById('event-chart') as HTMLElement);
|
|
|
+ this.eventChart.setOption(this.getEventChartOption());
|
|
|
+ }
|
|
|
+
|
|
|
+ private onWindowResize = () => {
|
|
|
+ this.vibrationChart?.resize();
|
|
|
+ this.costChart?.resize();
|
|
|
+ this.toolChart?.resize();
|
|
|
+ this.eventChart?.resize();
|
|
|
+ };
|
|
|
+
|
|
|
+ private startDataGeneration() {
|
|
|
+ this.dataInterval = setInterval(() => this.generateData(), 250);
|
|
|
+ }
|
|
|
+
|
|
|
+ private startUptimeCounter() {
|
|
|
+ this.uptimeInterval = setInterval(() => {
|
|
|
+ this.uptimeSeconds++;
|
|
|
+ const hours = Math.floor(this.uptimeSeconds / 3600);
|
|
|
+ const minutes = Math.floor((this.uptimeSeconds % 3600) / 60);
|
|
|
+ const seconds = this.uptimeSeconds % 60;
|
|
|
+ this.uptime = `正常运行: ${hours}小时${minutes}分${seconds}秒`;
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ private generateData() {
|
|
|
+ this.timeCounter++;
|
|
|
+
|
|
|
+ // 生成振动数据
|
|
|
+ const baseValue = Math.sin(this.timeCounter * 0.2) * 0.8;
|
|
|
+ const noise = (Math.random() - 0.5) * 0.3;
|
|
|
+ let amplitude = baseValue + noise;
|
|
|
+
|
|
|
+ // 0.1%概率产生异常
|
|
|
+ if (Math.random() < 0.001) {
|
|
|
+ amplitude = 2.5 + Math.random() * 1.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.vibrationData.push(amplitude);
|
|
|
+ if (this.vibrationData.length > 200) this.vibrationData.shift();
|
|
|
+
|
|
|
+ // 计算动态阈值
|
|
|
+ const threshold = this.calculateDynamicThreshold(this.vibrationData);
|
|
|
+ this.thresholdData.push(threshold);
|
|
|
+ if (this.thresholdData.length > 200) this.thresholdData.shift();
|
|
|
+
|
|
|
+ // 更新刀具磨损数据
|
|
|
+ const wear = Math.min(85, 20 + this.timeCounter * 0.05);
|
|
|
+ this.toolWearData.push(wear);
|
|
|
+ if (this.toolWearData.length > 20) this.toolWearData.shift();
|
|
|
+
|
|
|
+ // 更新图表
|
|
|
+ const xAxisData = Array.from({length: this.vibrationData.length}, (_, i) => i);
|
|
|
+ this.vibrationChart.setOption({
|
|
|
+ xAxis: {
|
|
|
+ data: xAxisData
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ { data: this.vibrationData },
|
|
|
+ { data: this.thresholdData }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新刀具寿命图表
|
|
|
+ const toolXAxisData = Array.from({length: this.toolWearData.length}, (_, i) => i);
|
|
|
+ this.toolChart.setOption({
|
|
|
+ xAxis: {
|
|
|
+ data: toolXAxisData
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ { data: this.toolWearData }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查报警状态
|
|
|
+ this.checkAlertStatus(amplitude, threshold);
|
|
|
+ }
|
|
|
+
|
|
|
+ private calculateDynamicThreshold(data: number[]): number {
|
|
|
+ if (data.length < 20) return 0.8;
|
|
|
+
|
|
|
+ const recentData = data.slice(-20);
|
|
|
+ const sum = recentData.reduce((a, b) => a + b, 0);
|
|
|
+ const mean = sum / recentData.length;
|
|
|
+
|
|
|
+ const squareDiffs = recentData.map(value => Math.pow(value - mean, 2));
|
|
|
+ const variance = squareDiffs.reduce((a, b) => a + b, 0) / recentData.length;
|
|
|
+ const stdDev = Math.sqrt(variance);
|
|
|
+
|
|
|
+ return mean + 4 * stdDev;
|
|
|
+ }
|
|
|
+
|
|
|
+ private checkAlertStatus(amplitude: number, threshold: number) {
|
|
|
+ const statusIndicator = document.getElementById('status-indicator');
|
|
|
+ const statusText = document.getElementById('status-text');
|
|
|
+ const alertIndicator = document.getElementById('vibration-alert');
|
|
|
+
|
|
|
+ if (!statusIndicator || !statusText || !alertIndicator) return;
|
|
|
+
|
|
|
+ if (amplitude > threshold) {
|
|
|
+ statusIndicator.className = 'status-indicator danger';
|
|
|
+ statusText.textContent = '检测到异常振动!';
|
|
|
+ alertIndicator.className = 'alert-indicator danger';
|
|
|
+
|
|
|
+ if (!this.isAlertActive) {
|
|
|
+ this.showAlert();
|
|
|
+ this.isAlertActive = true;
|
|
|
+ }
|
|
|
+ } else if (amplitude > threshold * 0.8) {
|
|
|
+ statusIndicator.className = 'status-indicator warning';
|
|
|
+ statusText.textContent = '振动接近阈值,请注意!';
|
|
|
+ alertIndicator.className = 'alert-indicator warning';
|
|
|
+ this.isAlertActive = false;
|
|
|
+ } else {
|
|
|
+ statusIndicator.className = 'status-indicator';
|
|
|
+ statusText.textContent = '系统运行正常';
|
|
|
+ alertIndicator.className = 'alert-indicator';
|
|
|
+ this.isAlertActive = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private showAlert() {
|
|
|
+ const alertNotification = document.getElementById('alert-notification');
|
|
|
+ if (alertNotification) {
|
|
|
+ alertNotification.style.display = 'flex';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 播放警报声
|
|
|
+ const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
|
|
+ const oscillator = audioContext.createOscillator();
|
|
|
+ const gainNode = audioContext.createGain();
|
|
|
+
|
|
|
+ oscillator.connect(gainNode);
|
|
|
+ gainNode.connect(audioContext.destination);
|
|
|
+
|
|
|
+ oscillator.type = 'sine';
|
|
|
+ oscillator.frequency.setValueAtTime(440, audioContext.currentTime);
|
|
|
+ oscillator.frequency.setValueAtTime(880, audioContext.currentTime + 0.1);
|
|
|
+
|
|
|
+ gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
|
|
+
|
|
|
+ oscillator.start();
|
|
|
+ oscillator.stop(audioContext.currentTime + 0.5);
|
|
|
+
|
|
|
+ // 背景闪烁效果
|
|
|
+ let flashCount = 0;
|
|
|
+ const flashInterval = setInterval(() => {
|
|
|
+ document.body.style.backgroundColor = flashCount % 2 === 0 ? 'rgba(244, 67, 54, 0.2)' : '';
|
|
|
+ flashCount++;
|
|
|
+
|
|
|
+ if (flashCount > 10) {
|
|
|
+ clearInterval(flashInterval);
|
|
|
+ document.body.style.backgroundColor = '';
|
|
|
+ }
|
|
|
+ }, 200);
|
|
|
+ }
|
|
|
+
|
|
|
+ closeAlert() {
|
|
|
+ const alertNotification = document.getElementById('alert-notification');
|
|
|
+ if (alertNotification) {
|
|
|
+ alertNotification.style.display = 'none';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private getVibrationChartOption(): echarts.EChartsOption {
|
|
|
+ return {
|
|
|
+ backgroundColor: 'transparent',
|
|
|
+ grid: {
|
|
|
+ top: 30,
|
|
|
+ right: 30,
|
|
|
+ bottom: 40,
|
|
|
+ left: 50
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['振动幅度', '动态阈值'],
|
|
|
+ textStyle: {
|
|
|
+ color: '#ccc'
|
|
|
+ },
|
|
|
+ top: 0
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: [],
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#666'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#999'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '振幅 (g)',
|
|
|
+ nameTextStyle: {
|
|
|
+ color: '#999'
|
|
|
+ },
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#666'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#999'
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.05)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '振动幅度',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: [],
|
|
|
+ lineStyle: {
|
|
|
+ width: 3,
|
|
|
+ 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)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ symbol: 'none'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '动态阈值',
|
|
|
+ type: 'line',
|
|
|
+ smooth: true,
|
|
|
+ data: [],
|
|
|
+ lineStyle: {
|
|
|
+ width: 2,
|
|
|
+ color: '#ff9800',
|
|
|
+ type: 'dashed'
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#ff9800'
|
|
|
+ },
|
|
|
+ symbol: 'none'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ animation: false
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private getCostChartOption(): echarts.EChartsOption {
|
|
|
+ return {
|
|
|
+ backgroundColor: 'transparent',
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ axisPointer: {
|
|
|
+ type: 'shadow'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['德国方案', '本系统'],
|
|
|
+ textStyle: {
|
|
|
+ color: '#ccc'
|
|
|
+ },
|
|
|
+ top: 0
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ top: 40,
|
|
|
+ right: 30,
|
|
|
+ bottom: 40,
|
|
|
+ left: 50
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: ['单通道成本', '误报率', '响应延迟'],
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#666'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#999',
|
|
|
+ interval: 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: [
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ name: '成本 (元)',
|
|
|
+ min: 0,
|
|
|
+ max: 12000,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#666'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#999',
|
|
|
+ formatter: '{value}'
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.05)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ name: '比率/毫秒',
|
|
|
+ min: 0,
|
|
|
+ max: 6,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#666'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#999',
|
|
|
+ formatter: (value: number) => {
|
|
|
+ if (value < 1) return value * 100 + '%';
|
|
|
+ return value + 'ms';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '德国方案',
|
|
|
+ type: 'bar',
|
|
|
+ barWidth: 30,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#ff6b6b'
|
|
|
+ },
|
|
|
+ data: [10284, 0.058, 1.2]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '本系统',
|
|
|
+ type: 'bar',
|
|
|
+ barWidth: 30,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#00b6c1'
|
|
|
+ },
|
|
|
+ data: [1920, 0.008, 0.008]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private getToolChartOption(): echarts.EChartsOption {
|
|
|
+ return {
|
|
|
+ backgroundColor: 'transparent',
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['刀具磨损度'],
|
|
|
+ textStyle: {
|
|
|
+ color: '#ccc'
|
|
|
+ },
|
|
|
+ top: 0
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ top: 30,
|
|
|
+ right: 30,
|
|
|
+ bottom: 40,
|
|
|
+ left: 50
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: [],
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#666'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#999'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ name: '磨损度 (%)',
|
|
|
+ min: 0,
|
|
|
+ max: 100,
|
|
|
+ axisLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: '#666'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ color: '#999'
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ color: 'rgba(255, 255, 255, 0.05)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '刀具磨损度',
|
|
|
+ type: 'line',
|
|
|
+ data: [],
|
|
|
+ smooth: true,
|
|
|
+ lineStyle: {
|
|
|
+ width: 3,
|
|
|
+ color: '#ff9800'
|
|
|
+ },
|
|
|
+ itemStyle: {
|
|
|
+ color: '#ff9800'
|
|
|
+ },
|
|
|
+ areaStyle: {
|
|
|
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
+ { offset: 0, color: 'rgba(255, 152, 0, 0.5)' },
|
|
|
+ { offset: 1, color: 'rgba(255, 152, 0, 0.1)' }
|
|
|
+ ])
|
|
|
+ },
|
|
|
+ markLine: {
|
|
|
+ silent: true,
|
|
|
+ lineStyle: {
|
|
|
+ color: '#f44336',
|
|
|
+ width: 2,
|
|
|
+ type: 'dashed'
|
|
|
+ },
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ yAxis: 85,
|
|
|
+ label: {
|
|
|
+ formatter: '更换阈值',
|
|
|
+ position: 'start'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ private getEventChartOption(): echarts.EChartsOption {
|
|
|
+ return {
|
|
|
+ backgroundColor: 'transparent',
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['异常事件'],
|
|
|
+ textStyle: {
|
|
|
+ color: '#ccc'
|
|
|
+ },
|
|
|
+ top: 0
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ top: 30,
|
|
|
+ right: 30,
|
|
|
+ bottom: 40,
|
|
|
+ left: 50
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
|
|
+ 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)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '异常事件',
|
|
|
+ type: 'bar',
|
|
|
+ barWidth: 30,
|
|
|
+ itemStyle: {
|
|
|
+ color: '#00b6c1'
|
|
|
+ },
|
|
|
+ data: [2, 0, 1, 3, 1, 0, 0]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|