Pārlūkot izejas kodu

feat: new tok pipe & device item & deviceList

未来全栈 4 dienas atpakaļ
vecāks
revīzija
aeba8d7596
19 mainītis faili ar 1596 papildinājumiem un 74 dzēšanām
  1. 32 0
      industry-monitor-web/package-lock.json
  2. 1 0
      industry-monitor-web/package.json
  3. 4 0
      industry-monitor-web/src/app/app.routes.ts
  4. 40 0
      industry-monitor-web/src/app/componets/star-rating/README.md
  5. 59 0
      industry-monitor-web/src/app/componets/star-rating/comp-star-rating.component.ts
  6. 53 0
      industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.html
  7. 61 0
      industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.scss
  8. 23 0
      industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.spec.ts
  9. 16 0
      industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.ts
  10. 4 71
      industry-monitor-web/src/app/pages/device-management/device-management.component.html
  11. 55 1
      industry-monitor-web/src/app/pages/device-management/device-management.component.ts
  12. 8 0
      industry-monitor-web/src/app/pages/factory-overview/factory-overview.component.html
  13. 25 2
      industry-monitor-web/src/app/pages/factory-overview/factory-overview.component.ts
  14. 86 0
      industry-monitor-web/src/app/pipes/tok-pipe/README.md
  15. 61 0
      industry-monitor-web/src/app/pipes/tok-pipe/tok.pipe.ts
  16. 112 0
      industry-monitor-web/src/modules/industry/machine/page-vibration-monitor/page-vibration-monitor.html
  17. 341 0
      industry-monitor-web/src/modules/industry/machine/page-vibration-monitor/page-vibration-monitor.scss
  18. 23 0
      industry-monitor-web/src/modules/industry/machine/page-vibration-monitor/page-vibration-monitor.spec.ts
  19. 592 0
      industry-monitor-web/src/modules/industry/machine/page-vibration-monitor/page-vibration-monitor.ts

+ 32 - 0
industry-monitor-web/package-lock.json

@@ -14,6 +14,7 @@
         "@angular/forms": "^20.0.0",
         "@angular/platform-browser": "^20.0.0",
         "@angular/router": "^20.0.0",
+        "echarts": "^5.6.0",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
         "zone.js": "~0.15.0"
@@ -4334,6 +4335,22 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/echarts": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.6.0.tgz",
+      "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "2.3.0",
+        "zrender": "5.6.1"
+      }
+    },
+    "node_modules/echarts/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
+    },
     "node_modules/ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
@@ -8902,6 +8919,21 @@
       "resolved": "https://registry.npmmirror.com/zone.js/-/zone.js-0.15.1.tgz",
       "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
       "license": "MIT"
+    },
+    "node_modules/zrender": {
+      "version": "5.6.1",
+      "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
+      "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "tslib": "2.3.0"
+      }
+    },
+    "node_modules/zrender/node_modules/tslib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
+      "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+      "license": "0BSD"
     }
   }
 }

+ 1 - 0
industry-monitor-web/package.json

@@ -26,6 +26,7 @@
     "@angular/forms": "^20.0.0",
     "@angular/platform-browser": "^20.0.0",
     "@angular/router": "^20.0.0",
+    "echarts": "^5.6.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.15.0"

+ 4 - 0
industry-monitor-web/src/app/app.routes.ts

@@ -19,6 +19,10 @@ export const routes: Routes = [
       { path: 'devices', component: DeviceManagementComponent },
       { path: 'history', component: HistoricalDataComponent },
       { path: 'settings', component: SystemSettingsComponent },
+      { 
+        path: 'device', 
+        loadComponent: () => import('../modules/industry/machine/page-vibration-monitor/page-vibration-monitor').then(m => m.PageVibrationMonitorComponent)
+       },
     ]
   },
   { path: '**', redirectTo: 'overview' }

+ 40 - 0
industry-monitor-web/src/app/componets/star-rating/README.md

@@ -0,0 +1,40 @@
+# 使用示例:星星打分组件
+
+## 组件特性
+
+1. **独立组件**:使用 `standalone: true` 声明
+2. **输入参数**:通过 `@Input() star` 接收初始评分
+3. **输出事件**:通过 `@Output() onStarChange` 发射评分变化
+4. **交互逻辑**:
+   - 点击星星点亮到当前星星
+   - 再次点击相同星星则取消评分
+5. **样式**:
+   - 默认灰色星星
+   - 点亮星星为金色
+   - 悬停时为橙色
+
+你可以根据需要调整星星的大小、颜色或其他样式属性。
+
+```ts
+import { CompStarRatingComponent } from './comp-star-rating.component';
+
+// 在需要使用的地方
+@Component({
+  standalone: true,
+  imports: [CompStarRatingComponent],
+  template: `
+    <comp-star-rating 
+      [star]="userRating" 
+      (onStarChange)="handleRatingChange($event)"
+    ></comp-star-rating>
+    <p>当前评分: {{userRating}}</p>
+  `
+})
+export class SomeComponent {
+  userRating = 3;
+
+  handleRatingChange(rating: number) {
+    this.userRating = rating;
+  }
+}
+```

+ 59 - 0
industry-monitor-web/src/app/componets/star-rating/comp-star-rating.component.ts

@@ -0,0 +1,59 @@
+import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+@Component({
+  selector: 'comp-star-rating',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  template: `
+    <div class="star-rating">
+      <span 
+        *ngFor="let star of stars; let i = index" 
+        (click)="rate(i + 1)"
+        [class.active]="(i + 1) <= currentRating"
+        class="star"
+      >
+        ★
+      </span>
+    </div>
+  `,
+  styles: [`
+    .star-rating {
+      font-size: 24px;
+      cursor: pointer;
+    }
+    .star {
+      font-size: 2rem;
+      color: #ccc;
+    }
+    .star.active {
+      color: gold;
+    }
+    .star:hover {
+      color: orange;
+    }
+  `]
+})
+export class CompStarRatingComponent implements OnInit {
+  @Input() star: number = 0;
+  @Output() onStarChange = new EventEmitter<number>();
+
+  stars = [1, 2, 3, 4, 5];
+  currentRating = 0;
+
+  ngOnInit(): void {
+    if (this.star > 0) {
+      this.currentRating = this.star;
+    }
+  }
+
+  rate(rating: number): void {
+    if (rating === this.currentRating) {
+      this.currentRating = 0;
+    } else {
+      this.currentRating = rating;
+    }
+    this.onStarChange.emit(this.currentRating);
+  }
+}

+ 53 - 0
industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.html

@@ -0,0 +1,53 @@
+@if(type=="list"){
+    <tr>
+        <td>{{device.did}}</td>
+        <td>{{device.name}}</td>
+        <td>{{device.type}}</td>
+        <td>{{device.location}}</td>
+        <td><span class="status-badge running">{{device.status}}</span></td>
+        <td><div class="health-meter"><div class="health-fill" style="width: {{device.health}}%;"></div><span>{{device.health}}%</span></div></td>
+        <td>{{device.lastMaintenance | date:'yyyy-MM-dd'}}</td>
+        <td>
+            <button class="icon-btn"><i class="fa fa-eye" routerLink="/device"></i></button>
+            <button class="icon-btn"><i class="fa fa-edit"></i></button>
+            <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
+        </td>
+    </tr>
+}
+
+@if(type=="card"){
+
+  <div class="device-card">
+    <div class="card-header">
+      <h2>{{device.did}}</h2>
+      <span class="status-badge running">{{device.status}}</span>
+    </div>
+    <div class="card-body">
+      <div class="card-row">
+        <span class="label">名称</span>
+        <span class="value">{{device.name}}</span>
+      </div>
+      <div class="card-row">
+        <span class="label">类型</span>
+        <span class="value">{{device.type}}</span>
+      </div>
+      <div class="card-row">
+        <span class="label">位置</span>
+        <span class="value">{{device.location}}</span>
+      </div>
+      <div class="card-row">
+        <span class="label">健康度</span>
+        <div class="health-meter"><div class="health-fill" [style.width]="device.health + '%'"></div><span>{{device.health}}%</span></div>
+      </div> 
+      <div class="card-row">
+        <span class="label">最后维护</span>
+        <span class="value">{{device.lastMaintenance | date:'yyyy-MM-dd'}}</span>
+      </div>
+    </div>
+    <div class="card-actions">
+      <button class="icon-btn"><i class="fa fa-eye" routerLink="/device"></i></button>
+      <button class="icon-btn"><i class="fa fa-edit"></i></button>
+      <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
+    </div>
+  </div>
+}

+ 61 - 0
industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.scss

@@ -0,0 +1,61 @@
+
+.device-container {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 20px;
+  padding: 20px;
+}
+
+.device-card {
+    float: left;
+    width: 30%;
+    margin-left: 3%;
+  border: 1px solid #ddd;
+  border-radius: 8px;
+  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+  overflow: hidden;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px;
+  background-color: #f5f5f5;
+  border-bottom: 1px solid #ddd;
+}
+
+.card-header h2 {
+  margin: 0;
+  font-size: 2em;
+}
+
+.card-body {
+  padding: 15px;
+}
+
+.card-row {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 10px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #eee;
+}
+
+.card-row:last-child {
+  margin-bottom: 0;
+  border-bottom: none;
+}
+
+.label {
+  font-weight: bold;
+  color: #666;
+}
+
+.card-actions {
+  display: flex;
+  justify-content: flex-end;
+  padding: 10px 15px;
+  background-color: #f9f9f9;
+  border-top: 1px solid #ddd;
+}

+ 23 - 0
industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CompDeviceItem } from './comp-device-item';
+
+describe('CompDeviceItem', () => {
+  let component: CompDeviceItem;
+  let fixture: ComponentFixture<CompDeviceItem>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [CompDeviceItem]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(CompDeviceItem);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 16 - 0
industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.ts

@@ -0,0 +1,16 @@
+import { CommonModule, DatePipe } from '@angular/common';
+import { Component, Input } from '@angular/core';
+import { RouterModule } from '@angular/router';
+
+@Component({
+  selector: 'app-comp-device-item',
+  imports: [CommonModule,RouterModule],
+  templateUrl: './comp-device-item.html',
+  styleUrl: './comp-device-item.scss'
+})
+export class CompDeviceItem {
+  @Input()
+  device:any
+  @Input()
+  type:string = "list" // list card
+}

+ 4 - 71
industry-monitor-web/src/app/pages/device-management/device-management.component.html

@@ -45,7 +45,7 @@
   </div>
 
   <div class="device-table-card">
-    <h3><i class="fa fa-list"></i> 设备列表</h3>
+    <h3><i class="fa fa-list"></i> 设备列表 <button (click)="listType='list'">列表</button> <button (click)="listType='card'">卡片</button></h3>
     <table class="device-table">
       <thead>
         <tr>
@@ -60,76 +60,9 @@
         </tr>
       </thead>
       <tbody>
-        <tr>
-          <td>DEV-2023-001</td>
-          <td>CNC 铣床 #01</td>
-          <td>CNC加工中心</td>
-          <td>车间A/产线3</td>
-          <td><span class="status-badge running">运行中</span></td>
-          <td><div class="health-meter"><div class="health-fill" style="width: 89%;"></div><span>89%</span></div></td>
-          <td>2023-07-15</td>
-          <td>
-            <button class="icon-btn"><i class="fa fa-eye"></i></button>
-            <button class="icon-btn"><i class="fa fa-edit"></i></button>
-            <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
-          </td>
-        </tr>
-        <tr>
-          <td>DEV-2023-002</td>
-          <td>注塑机 #05</td>
-          <td>注塑机</td>
-          <td>车间B/产线1</td>
-          <td><span class="status-badge idle">待机</span></td>
-          <td><div class="health-meter"><div class="health-fill" style="width: 76%;"></div><span>76%</span></div></td>
-          <td>2023-07-10</td>
-          <td>
-            <button class="icon-btn"><i class="fa fa-eye"></i></button>
-            <button class="icon-btn"><i class="fa fa-edit"></i></button>
-            <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
-          </td>
-        </tr>
-        <tr>
-          <td>DEV-2023-003</td>
-          <td>装配机器人 #03</td>
-          <td>装配机器人</td>
-          <td>车间C/产线2</td>
-          <td><span class="status-badge maintenance">维护中</span></td>
-          <td><div class="health-meter"><div class="health-fill" style="width: 65%;"></div><span>65%</span></div></td>
-          <td>2023-07-12</td>
-          <td>
-            <button class="icon-btn"><i class="fa fa-eye"></i></button>
-            <button class="icon-btn"><i class="fa fa-edit"></i></button>
-            <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
-          </td>
-        </tr>
-        <tr>
-          <td>DEV-2023-004</td>
-          <td>质检仪 #02</td>
-          <td>检测设备</td>
-          <td>车间A/产线1</td>
-          <td><span class="status-badge running">运行中</span></td>
-          <td><div class="health-meter"><div class="health-fill" style="width: 92%;"></div><span>92%</span></div></td>
-          <td>2023-07-14</td>
-          <td>
-            <button class="icon-btn"><i class="fa fa-eye"></i></button>
-            <button class="icon-btn"><i class="fa fa-edit"></i></button>
-            <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
-          </td>
-        </tr>
-        <tr>
-          <td>DEV-2023-005</td>
-          <td>包装机 #07</td>
-          <td>包装设备</td>
-          <td>车间C/产线3</td>
-          <td><span class="status-badge stopped">已停止</span></td>
-          <td><div class="health-meter"><div class="health-fill" style="width: 58%;"></div><span>58%</span></div></td>
-          <td>2023-07-05</td>
-          <td>
-            <button class="icon-btn"><i class="fa fa-eye"></i></button>
-            <button class="icon-btn"><i class="fa fa-edit"></i></button>
-            <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
-          </td>
-        </tr>
+        @for(device of deviceList;track device){
+          <app-comp-device-item [type]="listType" [device]="device"></app-comp-device-item>
+        }
       </tbody>
     </table>
   </div>

+ 55 - 1
industry-monitor-web/src/app/pages/device-management/device-management.component.ts

@@ -1,15 +1,69 @@
 import { Component, ViewEncapsulation } from '@angular/core';
 import { DatePipe } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { CompDeviceItem } from './comp-device-item/comp-device-item';
 
 @Component({
   standalone: true,
   selector: 'app-device-management',
   styleUrls: ['./device-management.css'],
-  imports: [DatePipe],
+  imports: [
+    DatePipe,
+    RouterModule,
+    CompDeviceItem
+  ],
   templateUrl: './device-management.component.html',
   encapsulation: ViewEncapsulation.None
 })
 export class DeviceManagementComponent {
+  listType:string = "list"
+  deviceList:Array<any> = [
+  {
+    "did": "DEV-2023-001",
+    "name": "CNC 铣床 #01",
+    "type": "CNC加工中心",
+    "location": "车间A/产线3",
+    "status": "运行中",
+    "health": 89,
+    "lastMaintenance": new Date()
+    },
+  {
+    "did": "DEV-2023-002",
+    "name": "注塑机 #05",
+    "type": "注塑机",
+    "location": "车间B/产线1",
+    "status": "待机",
+    "health": 76,
+    "lastMaintenance": new Date()
+    },
+  {
+    "did": "DEV-2023-003",
+    "name": "装配机器人 #03",
+    "type": "装配机器人",
+    "location": "车间C/产线2",
+    "status": "维护中",
+    "health": 65,
+    "lastMaintenance": new Date()
+    },
+  {
+    "did": "DEV-2023-004",
+    "name": "质检仪 #02",
+    "type": "检测设备",
+    "location": "车间A/产线1",
+    "status": "运行中",
+    "health": 92,
+    "lastMaintenance": new Date()
+    },
+  {
+    "did": "DEV-2023-005",
+    "name": "包装机 #07",
+    "type": "包装设备",
+    "location": "车间C/产线3",
+    "status": "已停止",
+    "health": 58,
+    "lastMaintenance": new Date()
+    }
+]
 currentTime: Date = new Date();
    ngOnInit(): void {
     // 更新时间,每秒更新一次

+ 8 - 0
industry-monitor-web/src/app/pages/factory-overview/factory-overview.component.html

@@ -79,6 +79,11 @@
           <div class="alert-content">
             <strong>设备 #A23 温度过高</strong>
             <span>车间B - 产线2 | 15:23:45</span>
+            <comp-star-rating 
+              [star]="rating" 
+              (onStarChange)="ratingChange($event)"
+            ></comp-star-rating>
+            <p>当前评分: {{rating}}</p>
           </div>
         </div>
         <div class="alert-item warning">
@@ -86,6 +91,9 @@
           <div class="alert-content">
             <strong>设备 #B07 振动异常</strong>
             <span>车间A - 产线4 | 15:20:12</span>
+            <p>{{ 125006 | tok}}</p>
+            <p>{{ 100 | tok}}</p>
+            <p>{{ 99987565 | tok}}</p>
           </div>
         </div>
         <div class="alert-item info">

+ 25 - 2
industry-monitor-web/src/app/pages/factory-overview/factory-overview.component.ts

@@ -1,6 +1,8 @@
 
 import { DatePipe } from '@angular/common';
 import { Component, ViewEncapsulation } from '@angular/core';
+import { CompStarRatingComponent } from '../../componets/star-rating/comp-star-rating.component';
+import { TokPipe } from '../../pipes/tok-pipe/tok.pipe';
 
 @Component({
   standalone: true,
@@ -8,10 +10,17 @@ import { Component, ViewEncapsulation } from '@angular/core';
   styleUrls: ['./factory-overview.css'],
   templateUrl: './factory-overview.component.html',
   encapsulation: ViewEncapsulation.None,
-  imports: [DatePipe]
+  imports: [
+    DatePipe,TokPipe,
+    CompStarRatingComponent
+  ]
   
 })
 export class FactoryOverviewComponent {
+  rating:number = 0
+  ratingChange(rating: number) {
+    this.rating = rating;
+  }
   currentTime: Date = new Date();
    ngOnInit(): void {
     // 更新时间,每秒更新一次
@@ -19,4 +28,18 @@ export class FactoryOverviewComponent {
       this.currentTime = new Date();
     }, 1000);
 
-   }}
+  }
+
+    tok(value:number){
+      if(value<=1000){
+        return value
+      }
+      if(value<=999999){
+        return (value/1000).toFixed(0) + 'k'
+      }
+      if(value<=999999999){
+        return (value/10000).toFixed(0) + '万'
+      }
+      return value
+    }
+}

+ 86 - 0
industry-monitor-web/src/app/pipes/tok-pipe/README.md

@@ -0,0 +1,86 @@
+# Angular 数字缩写管道 (TokPipe)
+
+## 1. 使用管道
+
+### 基本使用
+
+```html
+<!-- 在模板中使用 -->
+<p>{{ 1234 | tok }}</p>          <!-- 输出: 1.2k -->
+<p>{{ 12345678 | tok }}</p>      <!-- 输出: 12.3m -->
+<p>{{ 1234 | tok:{system: 'eastern'} }}</p>  <!-- 输出: 0.1万 -->
+```
+
+### 带参数使用
+
+```html
+<p>{{ 1500 | tok:{decimal: 0} }}</p>         <!-- 输出: 2k -->
+<p>{{ 1500000 | tok:{system: 'eastern'} }}</p>  <!-- 输出: 150万 -->
+<p>{{ 1500000 | tok:{decimal: 2, suffix: '元'} }}</p>  <!-- 输出: 1.50m元 -->
+```
+
+### 在组件类中使用
+
+```typescript
+import { TokPipe } from './tok.pipe';
+
+@Component({
+  standalone: true,
+  imports: [TokPipe],
+  // ...
+})
+export class MyComponent {
+  private tokPipe = new TokPipe();
+  
+  formatNumber(num: number): string {
+    return this.tokPipe.transform(num, { system: 'eastern' });
+  }
+}
+```
+
+## 2. 注册为独立管道
+
+由于我们设置了 `standalone: true`,这个管道可以直接在任何独立组件中导入使用:
+
+```typescript
+import { TokPipe } from './tok.pipe';
+
+@Component({
+  standalone: true,
+  imports: [TokPipe],
+  template: `
+    <p>{{ largeNumber | tok }}</p>
+  `
+})
+export class MyStandaloneComponent {
+  largeNumber = 1234567;
+}
+```
+
+## 3. 可选:添加到共享模块
+
+如果你想在非独立组件中使用,可以将其添加到共享模块:
+
+```typescript
+import { NgModule } from '@angular/core';
+import { TokPipe } from './tok.pipe';
+
+@NgModule({
+  declarations: [TokPipe],
+  exports: [TokPipe]
+})
+export class SharedModule {}
+```
+
+然后在需要的模块中导入 `SharedModule` 即可。
+
+## 特点
+
+1. 支持两种数字缩写系统:西方(k/m/b)和东方(万/亿)
+2. 可配置小数位数
+3. 可添加后缀
+4. 自动处理负数和字符串输入
+5. 作为独立管道,可以直接导入使用
+6. 移除不必要的.0后缀
+
+你可以根据需要进一步扩展或修改这个管道。

+ 61 - 0
industry-monitor-web/src/app/pipes/tok-pipe/tok.pipe.ts

@@ -0,0 +1,61 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+  name: 'tok',
+  standalone: true  // 设置为独立管道,可以直接在组件中导入使用
+})
+export class TokPipe implements PipeTransform {
+  transform(value: number | string, args?: {
+    decimal?: number,
+    system?: 'western' | 'eastern',
+    suffix?: string
+  }): string {
+    // 默认参数
+    const options = {
+      decimal: 1,
+      system: 'western', // 'western' (k,m,b) 或 'eastern' (万,亿)
+      suffix: '',
+      ...args
+    };
+
+    // 转换输入为数字
+    let num = typeof value === 'string' ? parseFloat(value) : value;
+    if (isNaN(num)) return String(value);
+
+    // 处理负数
+    const isNegative = num < 0;
+    num = Math.abs(num);
+
+    let result: string;
+
+    if (options.system === 'eastern') {
+      // 中文单位系统 (万, 亿)
+      if (num >= 100000000) {
+        result = (num / 100000000).toFixed(options.decimal) + '亿';
+      } else if (num >= 10000) {
+        result = (num / 10000).toFixed(options.decimal) + '万';
+      } else {
+        result = num.toString();
+      }
+    } else {
+      // 西方单位系统 (k, m, b)
+      if (num >= 1000000000) {
+        result = (num / 1000000000).toFixed(options.decimal) + 'b';
+      } else if (num >= 1000000) {
+        result = (num / 1000000).toFixed(options.decimal) + 'm';
+      } else if (num >= 1000) {
+        result = (num / 1000).toFixed(options.decimal) + 'k';
+      } else {
+        result = num.toString();
+      }
+    }
+
+    // 移除不必要的 .0
+    if (options.decimal > 0) {
+      result = result.replace(/\.0+$/, '');
+    }
+
+    // 添加后缀和负号
+    return (isNegative ? '-' : '') + result + options.suffix;
+  }
+}

+ 112 - 0
industry-monitor-web/src/modules/industry/machine/page-vibration-monitor/page-vibration-monitor.html

@@ -0,0 +1,112 @@
+<div class="container">
+  <header>
+    <div class="logo">
+      <div>
+        <h1>数控车床振幅监测系统</h1>
+        <div class="tagline">工业监测领域的"歼-20"方案</div>
+      </div>
+    </div>
+    
+    <div class="system-stats">
+      <div class="stat-card">
+        <h3>当前状态</h3>
+        <div class="value">{{ systemStatus }}</div>
+        <div class="highlight">{{ uptime }}</div>
+      </div>
+      
+      <div class="stat-card">
+        <h3>检测响应速度</h3>
+        <div class="value">{{ responseTime }}ms</div>
+        <div class="highlight">↑德国方案150倍</div>
+      </div>
+      
+      <div class="stat-card">
+        <h3>误报率</h3>
+        <div class="value">{{ errorRate }}%</div>
+        <div class="highlight">↓德国方案86%</div>
+      </div>
+    </div>
+  </header>
+  
+  <div class="dashboard">
+    <div class="card">
+      <div class="card-header">
+        <h2 class="card-title">实时振动波形</h2>
+        <div class="alert-indicator" id="vibration-alert"></div>
+      </div>
+      <div class="chart-container" id="vibration-chart"></div>
+      <div class="status-bar">
+        <div class="status-indicator" id="status-indicator"></div>
+        <div class="status-text" id="status-text">系统运行正常</div>
+      </div>
+    </div>
+    
+    <div class="card">
+      <div class="card-header">
+        <h2 class="card-title">成本控制与性能对比</h2>
+      </div>
+      <div class="chart-container" id="cost-chart"></div>
+      
+      <div class="cost-comparison">
+        <div class="cost-card">
+          <h4>单通道成本 (德国方案)</h4>
+          <div class="cost-value import-value">¥10,284</div>
+        </div>
+        <div class="cost-card">
+          <h4>单通道成本 (本系统)</h4>
+          <div class="cost-value our-value">¥1,920</div>
+        </div>
+      </div>
+      
+      <div class="key-metrics">
+        <div class="metric-card">
+          <h4>成本节约</h4>
+          <div class="metric-value" style="color: var(--secondary-color);">92%</div>
+        </div>
+        <div class="metric-card">
+          <h4>响应提升</h4>
+          <div class="metric-value" style="color: var(--secondary-color);">150倍</div>
+        </div>
+        <div class="metric-card">
+          <h4>误报率降低</h4>
+          <div class="metric-value" style="color: var(--secondary-color);">86%</div>
+        </div>
+      </div>
+    </div>
+  </div>
+  
+  <div class="dashboard-row">
+    <div class="card">
+      <div class="card-header">
+        <h2 class="card-title">刀具寿命预测</h2>
+        <div class="alert-indicator" id="tool-alert"></div>
+      </div>
+      <div class="chart-container" id="tool-chart"></div>
+      <div class="controls">
+        <button class="btn">导出报告</button>
+        <button class="btn secondary">维护计划</button>
+        <button class="btn warning">更换刀具</button>
+      </div>
+    </div>
+    
+    <div class="card">
+      <div class="card-header">
+        <h2 class="card-title">异常事件分析</h2>
+      </div>
+      <div class="chart-container" id="event-chart"></div>
+    </div>
+  </div>
+  
+  <footer>
+    <p>国产化高精度振动监测系统 © 2025 | 技术指标:微秒级采样(100kHz) | AI动态阈值算法(误报率0.8%) | 响应延迟&lt;10ms</p>
+  </footer>
+</div>
+
+<div class="alert-notification" id="alert-notification" style="display: none;">
+  <div class="alert-icon">⚠️</div>
+  <div class="alert-content">
+    <h3>振动异常报警!</h3>
+    <p>检测到异常振动幅度超过阈值,请立即检查设备!</p>
+  </div>
+  <button class="close-alert" (click)="closeAlert()">×</button>
+</div>

+ 341 - 0
industry-monitor-web/src/modules/industry/machine/page-vibration-monitor/page-vibration-monitor.scss

@@ -0,0 +1,341 @@
+:host {
+  --primary-color: #0066cc;
+  --secondary-color: #00b6c1;
+  --warning-color: #ff9800;
+  --danger-color: #f44336;
+  --success-color: #4caf50;
+  --dark-bg: #1a2a3a;
+  --card-bg: rgba(255, 255, 255, 0.1);
+}
+
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
+}
+
+.container {
+  background: linear-gradient(135deg, var(--dark-bg) 0%, #0d1721 100%);
+  color: #fff;
+  min-height: 100vh;
+  padding: 20px;
+  max-width: 1800px;
+  margin: 0 auto;
+}
+
+header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 0;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+  margin-bottom: 30px;
+}
+
+.logo {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+}
+
+.logo h1 {
+  font-size: 28px;
+  font-weight: 700;
+  background: linear-gradient(90deg, var(--secondary-color), #6ec1e4);
+  -webkit-background-clip: text;
+  background-clip: text;
+  -webkit-text-fill-color: transparent;
+}
+
+.logo .tagline {
+  font-size: 18px;
+  color: #aaa;
+  margin-top: 5px;
+}
+
+.system-stats {
+  display: flex;
+  gap: 25px;
+}
+
+.stat-card {
+  background: var(--card-bg);
+  border-radius: 12px;
+  padding: 15px 25px;
+  min-width: 200px;
+  text-align: center;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+  border: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.stat-card h3 {
+  font-size: 14px;
+  font-weight: 500;
+  margin-bottom: 10px;
+  color: #ccc;
+}
+
+.stat-card .value {
+  font-size: 26px;
+  font-weight: 700;
+  margin-bottom: 5px;
+}
+
+.stat-card .highlight {
+  color: var(--secondary-color);
+  font-weight: 700;
+}
+
+.alert-indicator {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  background-color: var(--success-color);
+  box-shadow: 0 0 10px var(--success-color);
+  transition: all 0.3s ease;
+}
+
+.alert-indicator.warning {
+  background-color: var(--warning-color);
+  box-shadow: 0 0 10px var(--warning-color);
+}
+
+.alert-indicator.danger {
+  background-color: var(--danger-color);
+  box-shadow: 0 0 10px var(--danger-color);
+  animation: pulse 1.5s infinite;
+}
+
+@keyframes pulse {
+  0% { opacity: 1; }
+  50% { opacity: 0.4; }
+  100% { opacity: 1; }
+}
+
+.dashboard {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 25px;
+  margin-bottom: 30px;
+}
+
+.card {
+  background: var(--card-bg);
+  border-radius: 15px;
+  padding: 25px;
+  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
+  border: 1px solid rgba(255, 255, 255, 0.05);
+  position: relative;
+  overflow: hidden;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.card-title {
+  font-size: 20px;
+  font-weight: 600;
+  color: #fff;
+}
+
+.chart-container {
+  height: 400px;
+  width: 100%;
+}
+
+.dashboard-row {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 25px;
+  margin-bottom: 30px;
+}
+
+.cost-comparison {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 20px;
+  margin-top: 20px;
+}
+
+.cost-card {
+  background: rgba(0, 0, 0, 0.3);
+  border-radius: 10px;
+  padding: 15px;
+  text-align: center;
+}
+
+.cost-card h4 {
+  font-size: 16px;
+  margin-bottom: 10px;
+  color: #ccc;
+}
+
+.cost-value {
+  font-size: 24px;
+  font-weight: 700;
+}
+
+.import-value {
+  color: #ff6b6b;
+}
+
+.our-value {
+  color: var(--secondary-color);
+}
+
+.key-metrics {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 15px;
+  margin-top: 20px;
+}
+
+.metric-card {
+  background: rgba(0, 0, 0, 0.3);
+  border-radius: 10px;
+  padding: 15px;
+  text-align: center;
+}
+
+.metric-card h4 {
+  font-size: 14px;
+  margin-bottom: 10px;
+  color: #ccc;
+}
+
+.metric-value {
+  font-size: 22px;
+  font-weight: 700;
+}
+
+.controls {
+  display: flex;
+  gap: 15px;
+  margin-top: 20px;
+}
+
+.btn {
+  background: var(--primary-color);
+  color: white;
+  border: none;
+  padding: 10px 20px;
+  border-radius: 6px;
+  cursor: pointer;
+  font-size: 16px;
+  font-weight: 500;
+  transition: all 0.3s ease;
+  flex: 1;
+}
+
+.btn:hover {
+  background: #004d99;
+  transform: translateY(-2px);
+  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
+}
+
+.btn.secondary {
+  background: rgba(255, 255, 255, 0.1);
+}
+
+.btn.secondary:hover {
+  background: rgba(255, 255, 255, 0.2);
+}
+
+.btn.warning {
+  background: var(--warning-color);
+}
+
+.btn.warning:hover {
+  background: #e68a00;
+}
+
+.status-bar {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-top: 20px;
+  padding: 10px 15px;
+  background: rgba(0, 0, 0, 0.3);
+  border-radius: 8px;
+}
+
+.status-indicator {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  background: var(--success-color);
+}
+
+.status-text {
+  font-size: 16px;
+}
+
+.status-indicator.warning {
+  background: var(--warning-color);
+}
+
+.status-indicator.danger {
+  background: var(--danger-color);
+}
+
+footer {
+  text-align: center;
+  padding: 30px 0;
+  color: #777;
+  font-size: 14px;
+  border-top: 1px solid rgba(255, 255, 255, 0.1);
+  margin-top: 40px;
+}
+
+.alert-notification {
+  position: fixed;
+  top: 20px;
+  right: 20px;
+  background: var(--danger-color);
+  color: white;
+  padding: 20px 30px;
+  border-radius: 10px;
+  box-shadow: 0 10px 30px rgba(244, 67, 54, 0.3);
+  animation: slideIn 0.5s ease;
+  z-index: 1000;
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  max-width: 400px;
+}
+
+@keyframes slideIn {
+  from { transform: translateX(100%); opacity: 0; }
+  to { transform: translateX(0); opacity: 1; }
+}
+
+.alert-icon {
+  font-size: 28px;
+}
+
+.alert-content h3 {
+  font-size: 18px;
+  margin-bottom: 5px;
+}
+
+.alert-content p {
+  font-size: 14px;
+  opacity: 0.9;
+}
+
+.close-alert {
+  background: none;
+  border: none;
+  color: white;
+  font-size: 20px;
+  cursor: pointer;
+  margin-left: 10px;
+}

+ 23 - 0
industry-monitor-web/src/modules/industry/machine/page-vibration-monitor/page-vibration-monitor.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PageVibrationMonitor } from './page-vibration-monitor';
+
+describe('PageVibrationMonitor', () => {
+  let component: PageVibrationMonitor;
+  let fixture: ComponentFixture<PageVibrationMonitor>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [PageVibrationMonitor]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(PageVibrationMonitor);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 592 - 0
industry-monitor-web/src/modules/industry/machine/page-vibration-monitor/page-vibration-monitor.ts

@@ -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]
+        }
+      ]
+    };
+  }
+}