Browse Source

feat:emotions

0225304 1 day ago
parent
commit
ecc67fc5f3
25 changed files with 765 additions and 9 deletions
  1. 146 0
      myapp/src/app/services/audio.service.ts
  2. 0 0
      myapp/src/app/tab2/management/emotion-vent/emotion-vent-routing.module.ts
  3. 0 0
      myapp/src/app/tab2/management/emotion-vent/emotion-vent.module.ts
  4. 0 0
      myapp/src/app/tab2/management/emotion-vent/emotion-vent.page.html
  5. 0 0
      myapp/src/app/tab2/management/emotion-vent/emotion-vent.page.scss
  6. 0 0
      myapp/src/app/tab2/management/emotion-vent/emotion-vent.page.spec.ts
  7. 0 0
      myapp/src/app/tab2/management/emotion-vent/emotion-vent.page.ts
  8. 26 0
      myapp/src/app/tab2/management/management-routing.module.ts
  9. 20 0
      myapp/src/app/tab2/management/management.module.ts
  10. 41 0
      myapp/src/app/tab2/management/management.page.html
  11. 33 0
      myapp/src/app/tab2/management/management.page.scss
  12. 17 0
      myapp/src/app/tab2/management/management.page.spec.ts
  13. 27 0
      myapp/src/app/tab2/management/management.page.ts
  14. 17 0
      myapp/src/app/tab2/management/scream-room/scream-room-routing.module.ts
  15. 20 0
      myapp/src/app/tab2/management/scream-room/scream-room.module.ts
  16. 45 0
      myapp/src/app/tab2/management/scream-room/scream-room.page.html
  17. 69 0
      myapp/src/app/tab2/management/scream-room/scream-room.page.scss
  18. 17 0
      myapp/src/app/tab2/management/scream-room/scream-room.page.spec.ts
  19. 274 0
      myapp/src/app/tab2/management/scream-room/scream-room.page.ts
  20. 2 2
      myapp/src/app/tab2/tab2-routing.module.ts
  21. 1 1
      myapp/src/app/tab2/tab2.page.html
  22. 3 2
      myapp/src/app/tab2/tab2.page.ts
  23. 2 1
      myapp/src/app/tab4/tab4-routing.module.ts
  24. 2 2
      myapp/src/app/tab4/tab4.page.html
  25. 3 1
      myapp/src/app/tab4/tab4.page.ts

+ 146 - 0
myapp/src/app/services/audio.service.ts

@@ -0,0 +1,146 @@
+import { Injectable } from '@angular/core';
+import { Capacitor } from '@capacitor/core';
+import { Filesystem, Directory } from '@capacitor/filesystem';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AudioService {
+  private audioContext!: AudioContext;
+  private mediaRecorder!: MediaRecorder;
+  private audioChunks: Blob[] = [];
+  private audioAnalyser!: AnalyserNode;
+  private microphone!: MediaStreamAudioSourceNode;
+  private scriptProcessor!: ScriptProcessorNode;
+  private volumeCallback!: (volume: number) => void;
+  private isRecording = false;
+
+  constructor() {}
+
+  initAudioContext() {
+    if (!this.audioContext) {
+      this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
+    }
+  }
+
+  async startRecording(volumeCallback: (volume: number) => void): Promise<void> {
+    this.volumeCallback = volumeCallback;
+    this.audioChunks = [];
+    
+    try {
+      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+      this.mediaRecorder = new MediaRecorder(stream);
+      
+      // 设置音频分析
+      this.setupAudioAnalysis(stream);
+      
+      this.mediaRecorder.ondataavailable = (event) => {
+        this.audioChunks.push(event.data);
+      };
+      
+      this.mediaRecorder.start(100); // 每100ms收集一次数据
+      this.isRecording = true;
+    } catch (error) {
+      console.error('无法访问麦克风:', error);
+      throw error;
+    }
+  }
+
+  private setupAudioAnalysis(stream: MediaStream) {
+    this.microphone = this.audioContext.createMediaStreamSource(stream);
+    this.audioAnalyser = this.audioContext.createAnalyser();
+    this.audioAnalyser.fftSize = 256;
+    
+    this.scriptProcessor = this.audioContext.createScriptProcessor(2048, 1, 1);
+    
+    this.microphone.connect(this.audioAnalyser);
+    this.audioAnalyser.connect(this.scriptProcessor);
+    this.scriptProcessor.connect(this.audioContext.destination);
+    
+    const that = this;
+    this.scriptProcessor.onaudioprocess = function() {
+      if (!that.isRecording) return;
+      
+      const array = new Uint8Array(that.audioAnalyser.frequencyBinCount);
+      that.audioAnalyser.getByteFrequencyData(array);
+      
+      let values = 0;
+      const length = array.length;
+      
+      for (let i = 0; i < length; i++) {
+        values += array[i];
+      }
+      
+      const average = values / length;
+      that.volumeCallback(average);
+    };
+  }
+
+  async stopRecording(): Promise<Blob> {
+    return new Promise((resolve) => {
+      this.mediaRecorder.onstop = () => {
+        const audioBlob = new Blob(this.audioChunks, { type: 'audio/wav' });
+        resolve(audioBlob);
+      };
+      
+      this.mediaRecorder.stop();
+      this.cleanupAudioNodes();
+      this.isRecording = false;
+    });
+  }
+
+  getRecordedChunks(): Blob[] {
+    return this.audioChunks;
+  }
+
+  analyzeAudio(audioChunks: Blob[], callback: (dataArray: Uint8Array) => void) {
+    const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
+    const fileReader = new FileReader();
+    
+    fileReader.onload = () => {
+      const arrayBuffer = fileReader.result as ArrayBuffer;
+      this.audioContext.decodeAudioData(arrayBuffer).then((audioBuffer) => {
+        const offlineContext = new OfflineAudioContext(
+          audioBuffer.numberOfChannels,
+          audioBuffer.length,
+          audioBuffer.sampleRate
+        );
+        
+        const source = offlineContext.createBufferSource();
+        source.buffer = audioBuffer;
+        
+        const analyser = offlineContext.createAnalyser();
+        analyser.fftSize = 2048;
+        
+        source.connect(analyser);
+        analyser.connect(offlineContext.destination);
+        
+        source.start(0);
+        
+        offlineContext.startRendering().then(() => {
+          const bufferLength = analyser.frequencyBinCount;
+          const dataArray = new Uint8Array(bufferLength);
+          analyser.getByteTimeDomainData(dataArray);
+          callback(dataArray);
+        });
+      });
+    };
+    
+    fileReader.readAsArrayBuffer(audioBlob);
+  }
+
+  private cleanupAudioNodes() {
+    if (this.scriptProcessor) {
+      this.scriptProcessor.disconnect();
+      this.scriptProcessor.onaudioprocess = null;
+    }
+    
+    if (this.microphone) {
+      this.microphone.disconnect();
+    }
+    
+    if (this.mediaRecorder && this.mediaRecorder.stream) {
+      this.mediaRecorder.stream.getTracks().forEach(track => track.stop());
+    }
+  }
+}

+ 0 - 0
myapp/src/app/tab2/emotion-vent/emotion-vent-routing.module.ts → myapp/src/app/tab2/management/emotion-vent/emotion-vent-routing.module.ts


+ 0 - 0
myapp/src/app/tab2/emotion-vent/emotion-vent.module.ts → myapp/src/app/tab2/management/emotion-vent/emotion-vent.module.ts


+ 0 - 0
myapp/src/app/tab2/emotion-vent/emotion-vent.page.html → myapp/src/app/tab2/management/emotion-vent/emotion-vent.page.html


+ 0 - 0
myapp/src/app/tab2/emotion-vent/emotion-vent.page.scss → myapp/src/app/tab2/management/emotion-vent/emotion-vent.page.scss


+ 0 - 0
myapp/src/app/tab2/emotion-vent/emotion-vent.page.spec.ts → myapp/src/app/tab2/management/emotion-vent/emotion-vent.page.spec.ts


+ 0 - 0
myapp/src/app/tab2/emotion-vent/emotion-vent.page.ts → myapp/src/app/tab2/management/emotion-vent/emotion-vent.page.ts


+ 26 - 0
myapp/src/app/tab2/management/management-routing.module.ts

@@ -0,0 +1,26 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { ManagementPage } from './management.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: ManagementPage
+  },
+  {
+    path: 'emotion-vent',
+    loadChildren: () => import('./emotion-vent/emotion-vent.module').then( m => m.EmotionVentPageModule)
+  },
+  {
+    path: 'scream-room',
+    loadChildren: () => import('./scream-room/scream-room.module').then( m => m.ScreamRoomPageModule)
+  },
+
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class ManagementPageRoutingModule {}

+ 20 - 0
myapp/src/app/tab2/management/management.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { ManagementPageRoutingModule } from './management-routing.module';
+
+import { ManagementPage } from './management.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    ManagementPageRoutingModule
+  ],
+  declarations: [ManagementPage]
+})
+export class ManagementPageModule {}

+ 41 - 0
myapp/src/app/tab2/management/management.page.html

@@ -0,0 +1,41 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button></ion-back-button>
+    </ion-buttons>
+    <ion-title>Management</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">Management</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+  <!-- 添加卡片容器 -->
+  <div class="cards-container">
+    <!-- Scream 卡片 -->
+    <ion-card class="management-card" (click)="goToScreamRoom()">
+      <ion-card-header>
+        <ion-card-title>Scream</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <ion-icon name="mic-outline" size="large"></ion-icon>
+        <p>Manage scream recordings and settings</p>
+      </ion-card-content>
+    </ion-card>
+
+    <!-- Emotion 卡片 -->
+    <ion-card class="management-card" (click)="goVentRoom()">
+      <ion-card-header>
+        <ion-card-title>Emotion</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <ion-icon name="happy-outline" size="large"></ion-icon>
+        <p>Manage emotion tracking and analysis</p>
+      </ion-card-content>
+    </ion-card>
+  </div>
+</ion-content>

+ 33 - 0
myapp/src/app/tab2/management/management.page.scss

@@ -0,0 +1,33 @@
+.cards-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  padding: 20px;
+}
+
+.management-card {
+  cursor: pointer;
+  transition: transform 0.3s ease, box-shadow 0.3s ease;
+  text-align: center;
+  
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
+  }
+
+  ion-card-header {
+    padding-bottom: 0;
+  }
+
+  ion-card-content {
+    ion-icon {
+      color: var(--ion-color-primary);
+      margin-bottom: 10px;
+    }
+    
+    p {
+      color: var(--ion-color-medium);
+      margin: 0;
+    }
+  }
+}

+ 17 - 0
myapp/src/app/tab2/management/management.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ManagementPage } from './management.page';
+
+describe('ManagementPage', () => {
+  let component: ManagementPage;
+  let fixture: ComponentFixture<ManagementPage>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ManagementPage);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 27 - 0
myapp/src/app/tab2/management/management.page.ts

@@ -0,0 +1,27 @@
+import { Component, OnInit } from '@angular/core';
+import { NavController } from '@ionic/angular';
+
+@Component({
+  selector: 'app-management',
+  templateUrl: './management.page.html',
+  styleUrls: ['./management.page.scss'],
+  standalone:false,
+})
+export class ManagementPage implements OnInit {
+
+  constructor(
+    private navCtrl:NavController,
+  ) { }
+
+  ngOnInit() {
+  }
+
+  goVentRoom(){
+    this.navCtrl.navigateForward(["tabs","tab2","management","emotion-vent"])
+  }
+
+  goToScreamRoom(){
+    this.navCtrl.navigateForward(["tabs","tab2","management","scream-room"])
+  }
+
+}

+ 17 - 0
myapp/src/app/tab2/management/scream-room/scream-room-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { ScreamRoomPage } from './scream-room.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: ScreamRoomPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class ScreamRoomPageRoutingModule {}

+ 20 - 0
myapp/src/app/tab2/management/scream-room/scream-room.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { ScreamRoomPageRoutingModule } from './scream-room-routing.module';
+
+import { ScreamRoomPage } from './scream-room.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    ScreamRoomPageRoutingModule
+  ],
+  declarations: [ScreamRoomPage]
+})
+export class ScreamRoomPageModule {}

+ 45 - 0
myapp/src/app/tab2/management/scream-room/scream-room.page.html

@@ -0,0 +1,45 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button></ion-back-button>
+    </ion-buttons>
+    <ion-title>情绪发泄室</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <div class="scream-container">
+    <h2>释放你的情绪</h2>
+    <p>对着手机大声喊出来吧!我们会将你的声音转化为艺术</p>
+    
+    <div class="visualizer-container">
+      <canvas #visualizerCanvas class="visualizer"></canvas>
+    </div>
+    
+    <div class="controls">
+      <ion-button (click)="startRecording()" [disabled]="isRecording" shape="round" color="danger">
+        <ion-icon name="mic" slot="start"></ion-icon>
+        开始吼叫
+      </ion-button>
+      
+      <ion-button (click)="stopRecording()" [disabled]="!isRecording" shape="round" color="medium">
+        <ion-icon name="square" slot="start"></ion-icon>
+        停止
+      </ion-button>
+    </div>
+    
+    <div class="volume-display">
+      <ion-progress-bar [value]="volumeLevel" color="danger"></ion-progress-bar>
+      <p>当前音量: {{volumePercentage}}%</p>
+    </div>
+    
+    <div class="result" *ngIf="waveformImage">
+      <h3>你的声波艺术画</h3>
+      <img [src]="waveformImage" alt="声波艺术">
+      <ion-button (click)="saveArtwork()" expand="block" color="success">
+        <ion-icon name="download" slot="start"></ion-icon>
+        保存作品
+      </ion-button>
+    </div>
+  </div>
+</ion-content>

+ 69 - 0
myapp/src/app/tab2/management/scream-room/scream-room.page.scss

@@ -0,0 +1,69 @@
+.scream-container {
+  text-align: center;
+  padding: 20px;
+  
+  h2 {
+    color: var(--ion-color-danger);
+    font-weight: bold;
+    margin-bottom: 10px;
+  }
+  
+  p {
+    color: var(--ion-color-medium);
+    margin-bottom: 30px;
+  }
+}
+
+.visualizer-container {
+  margin: 20px 0;
+  border-radius: 10px;
+  background: rgba(var(--ion-color-light-rgb), 0.2);
+  padding: 10px;
+  
+  .visualizer {
+    width: 100%;
+    height: 200px;
+    display: block;
+    background: rgba(var(--ion-color-light-rgb), 0.1);
+    border-radius: 5px;
+  }
+}
+
+.controls {
+  display: flex;
+  justify-content: center;
+  gap: 15px;
+  margin: 30px 0;
+  
+  ion-button {
+    --padding-start: 20px;
+    --padding-end: 20px;
+  }
+}
+
+.volume-display {
+  margin: 20px 0;
+  
+  p {
+    margin-top: 5px;
+    font-size: 14px;
+    color: var(--ion-color-medium);
+  }
+}
+
+.result {
+  margin-top: 40px;
+  text-align: center;
+  
+  h3 {
+    color: var(--ion-color-primary);
+    margin-bottom: 15px;
+  }
+  
+  img {
+    max-width: 100%;
+    border-radius: 10px;
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+    margin-bottom: 20px;
+  }
+}

+ 17 - 0
myapp/src/app/tab2/management/scream-room/scream-room.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ScreamRoomPage } from './scream-room.page';
+
+describe('ScreamRoomPage', () => {
+  let component: ScreamRoomPage;
+  let fixture: ComponentFixture<ScreamRoomPage>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ScreamRoomPage);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 274 - 0
myapp/src/app/tab2/management/scream-room/scream-room.page.ts

@@ -0,0 +1,274 @@
+//import { Component, OnInit } from '@angular/core';
+import { Component, ElementRef, ViewChild, OnInit } from '@angular/core';
+import { Capacitor } from '@capacitor/core';
+import { Filesystem, Directory } from '@capacitor/filesystem';
+//import { AudioService } from '../../services/audio.service';
+import { Share } from '@capacitor/share';
+import { AlertController } from '@ionic/angular';
+import { AudioService } from 'src/app/services/audio.service';
+@Component({
+  selector: 'app-scream-room',
+  templateUrl: './scream-room.page.html',
+  styleUrls: ['./scream-room.page.scss'],
+  standalone:false,
+})
+export class ScreamRoomPage implements OnInit {
+
+  @ViewChild('visualizerCanvas') visualizerCanvas!: ElementRef<HTMLCanvasElement>;
+  
+  isRecording = false;
+  volumeLevel = 0;
+  volumePercentage = 0;
+  waveformImage: string | null = null;
+  private canvasCtx!: CanvasRenderingContext2D;
+  private animationId!: number;
+  private audioChunks: Blob[] = [];
+  
+  constructor(
+    private audioService: AudioService,
+    private alertController: AlertController
+  ) {}
+
+  ngOnInit() {
+    this.audioService.initAudioContext();
+  }
+
+  ngAfterViewInit() {
+    this.setupVisualizer();
+  }
+
+  private setupVisualizer() {
+    const canvas = this.visualizerCanvas.nativeElement;
+    this.canvasCtx = canvas.getContext('2d')!;
+    canvas.width = canvas.offsetWidth;
+    canvas.height = 200;
+    
+    this.drawInitialVisualizer();
+  }
+
+  private drawInitialVisualizer() {
+    const { width, height } = this.visualizerCanvas.nativeElement;
+    this.canvasCtx.clearRect(0, 0, width, height);
+    
+    // 绘制初始平静的波浪线
+    this.canvasCtx.lineWidth = 2;
+    this.canvasCtx.strokeStyle = '#3880ff';
+    this.canvasCtx.beginPath();
+    
+    for (let x = 0; x < width; x++) {
+      const y = height / 2 + Math.sin(x * 0.05) * 20;
+      if (x === 0) {
+        this.canvasCtx.moveTo(x, y);
+      } else {
+        this.canvasCtx.lineTo(x, y);
+      }
+    }
+    
+    this.canvasCtx.stroke();
+  }
+
+  async startRecording() {
+    try {
+      this.isRecording = true;
+      this.audioChunks = [];
+      await this.audioService.startRecording((volume) => {
+        this.volumeLevel = volume / 100; // 转换为0-1范围
+        this.volumePercentage = Math.round(volume);
+        this.updateVisualizer(volume);
+      });
+      
+      this.startVisualizerAnimation();
+    } catch (error) {
+      console.error('录音失败:', error);
+      this.presentAlert('录音失败', '无法访问麦克风,请检查权限设置');
+      this.isRecording = false;
+    }
+  }
+
+  async stopRecording() {
+    this.isRecording = false;
+    cancelAnimationFrame(this.animationId);
+    
+    try {
+      const audioBlob = await this.audioService.stopRecording();
+      this.audioChunks = this.audioService.getRecordedChunks();
+      
+      // 生成声波图
+      this.generateWaveformArt();
+    } catch (error) {
+      console.error('停止录音失败:', error);
+    }
+  }
+
+  private startVisualizerAnimation() {
+    const canvas = this.visualizerCanvas.nativeElement;
+    const { width, height } = canvas;
+    
+    const draw = () => {
+      if (!this.isRecording) return;
+      
+      this.canvasCtx.clearRect(0, 0, width, height);
+      
+      // 根据音量动态绘制
+      this.canvasCtx.lineWidth = 2;
+      this.canvasCtx.strokeStyle = '#ff3860';
+      this.canvasCtx.beginPath();
+      
+      const amplitude = this.volumePercentage / 2;
+      
+      for (let x = 0; x < width; x++) {
+        const y = height / 2 + 
+                 Math.sin(x * 0.05 + Date.now() * 0.005) * (20 + amplitude) +
+                 Math.sin(x * 0.1) * (10 + amplitude/2);
+        
+        if (x === 0) {
+          this.canvasCtx.moveTo(x, y);
+        } else {
+          this.canvasCtx.lineTo(x, y);
+        }
+      }
+      
+      this.canvasCtx.stroke();
+      this.animationId = requestAnimationFrame(draw);
+    };
+    
+    this.animationId = requestAnimationFrame(draw);
+  }
+
+  private updateVisualizer(volume: number) {
+    // 实时更新可视化效果
+    // 动画循环中已经处理,这里可以留空或添加额外效果
+  }
+
+  private generateWaveformArt() {
+    const canvas = document.createElement('canvas');
+    canvas.width = 800;
+    canvas.height = 400;
+    const ctx = canvas.getContext('2d')!;
+    
+    // 创建渐变背景
+    const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
+    gradient.addColorStop(0, '#ff758c');
+    gradient.addColorStop(1, '#ff7eb3');
+    ctx.fillStyle = gradient;
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+    
+    // 分析音频数据生成波形
+    this.audioService.analyzeAudio(this.audioChunks, (dataArray) => {
+      ctx.lineWidth = 4;
+      ctx.strokeStyle = '#ffffff';
+      ctx.globalCompositeOperation = 'overlay';
+      ctx.beginPath();
+      
+      const sliceWidth = canvas.width / dataArray.length;
+      let x = 0;
+      
+      for (let i = 0; i < dataArray.length; i++) {
+        const v = dataArray[i] / 255.0;
+        const y = v * canvas.height;
+        
+        if (i === 0) {
+          ctx.moveTo(x, y);
+        } else {
+          ctx.lineTo(x, y);
+        }
+        
+        x += sliceWidth;
+      }
+      
+      ctx.stroke();
+      
+      // 添加一些艺术效果
+      this.addArtisticEffects(ctx, canvas.width, canvas.height, dataArray);
+      
+      // 转换为图像URL
+      this.waveformImage = canvas.toDataURL('image/png');
+    });
+  }
+
+  private addArtisticEffects(ctx: CanvasRenderingContext2D, width: number, height: number, dataArray: Uint8Array) {
+    // 添加粒子效果
+    ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
+    for (let i = 0; i < dataArray.length; i += 10) {
+      const x = (i / dataArray.length) * width;
+      const y = (dataArray[i] / 255.0) * height;
+      const size = (dataArray[i] / 255.0) * 10 + 2;
+      
+      ctx.beginPath();
+      ctx.arc(x, y, size, 0, Math.PI * 2);
+      ctx.fill();
+    }
+    
+    // 添加一些随机线条增加艺术感
+    ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
+    ctx.lineWidth = 1;
+    for (let i = 0; i < 20; i++) {
+      ctx.beginPath();
+      ctx.moveTo(Math.random() * width, Math.random() * height);
+      ctx.lineTo(Math.random() * width, Math.random() * height);
+      ctx.stroke();
+    }
+  }
+
+  async saveArtwork() {
+    if (!this.waveformImage) return;
+    
+    try {
+      // 保存到相册
+      const base64Data = this.waveformImage.split(',')[1];
+      const fileName = `scream-art-${new Date().getTime()}.png`;
+      
+      await Filesystem.writeFile({
+        path: fileName,
+        data: base64Data,
+        directory: Directory.Documents,
+        recursive: true
+      });
+      
+      // 在Android上需要特殊处理才能显示在相册中
+      if (Capacitor.getPlatform() === 'android') {
+        await Filesystem.getUri({
+          directory: Directory.Documents,
+          path: fileName
+        });
+      }
+      
+      this.presentAlert('保存成功', '你的声波艺术画已保存到相册');
+      } catch (error) {
+        console.error('保存失败:', error);
+        this.presentAlert('保存失败', '无法保存图片,请重试');
+      }
+  }
+
+  async shareArtwork() {
+    if (!this.waveformImage) return;
+    
+    try {
+      await Share.share({
+        title: '我的声波艺术画',
+        text: '看看我用情绪发泄室创作的声波艺术!',
+        url: this.waveformImage,
+        dialogTitle: '分享我的艺术创作'
+      });
+    } catch (error) {
+      console.error('分享失败:', error);
+    }
+  }
+
+  async presentAlert(header: string, message: string) {
+    const alert = await this.alertController.create({
+      header,
+      message,
+      buttons: ['OK']
+    });
+    
+    await alert.present();
+  }
+
+  ngOnDestroy() {
+    if (this.isRecording) {
+      this.audioService.stopRecording();
+    }
+    cancelAnimationFrame(this.animationId);
+  }
+}

+ 2 - 2
myapp/src/app/tab2/tab2-routing.module.ts

@@ -20,8 +20,8 @@ const routes: Routes = [
     loadChildren: () => import('./dynamic-create/dynamic-create.module').then( m => m.DynamicCreatePageModule)
   },
   {
-    path: 'emotion-vent',
-    loadChildren: () => import('./emotion-vent/emotion-vent.module').then( m => m.EmotionVentPageModule)
+    path: 'management',
+    loadChildren: () => import('./management/management.module').then( m => m.ManagementPageModule)
   }
 ];
 

+ 1 - 1
myapp/src/app/tab2/tab2.page.html

@@ -22,7 +22,7 @@
     <div (click)="importData()" class="function-card card-1">树洞</div>
     <div (click)="goThankslist('清单')" class="function-card card-2">感恩清单</div>
     <div class="function-card card-3">漂流瓶</div>
-    <div (click)="goVentRoom()" class="function-card card-4">情绪发泄室</div>
+    <div (click)="goToManage()" class="function-card card-4">情绪发泄室</div>
   </div>
   
   <!-- 动态日记标题栏 -->

+ 3 - 2
myapp/src/app/tab2/tab2.page.ts

@@ -40,9 +40,10 @@ export class Tab2Page implements OnInit {
     })
   }
 
-  goVentRoom(){
-    this.navCtrl.navigateForward(["tabs","tab2","emotion-vent"])
+  goToManage(){
+    this.navCtrl.navigateForward(["tabs","tab2","management"])
   }
+
   // async chaXun(){
   //   //获取当前用户
   //   let user:any =new CloudUser();

+ 2 - 1
myapp/src/app/tab4/tab4-routing.module.ts

@@ -11,7 +11,8 @@ const routes: Routes = [
   {
     path: 'login',
     loadChildren: () => import('./login/login.module').then( m => m.LoginPageModule)
-  }
+  },
+
 
 
 ];

+ 2 - 2
myapp/src/app/tab4/tab4.page.html

@@ -110,7 +110,7 @@
                 </div>
             </div>
             
-            <div class="function-item">
+            <div class="function-item" (click)="goToAlbum()">
                 <div class="function-left">
                     <div class="function-icon">
                         <ion-icon name="images-outline"></ion-icon>
@@ -118,7 +118,7 @@
                     <div class="function-name">相册</div>
                 </div>
                 <div>
-                    <span class="function-badge">0张照片</span>
+                    <!-- <span class="function-badge">0张照片</span> -->
                     <ion-icon name="chevron-forward-outline" class="function-arrow"></ion-icon>
                 </div>
             </div>

+ 3 - 1
myapp/src/app/tab4/tab4.page.ts

@@ -17,7 +17,9 @@ export class Tab4Page implements OnInit {
   ngOnInit() {
   }
 
-  
+  goToAlbum() {
+  this.navCtrl.navigateForward(["tabs","tab4","album"]);
+  }
 
   goLogin(user?:string){
     this.navCtrl.navigateForward(["tabs","tab4","login"])