18079408532 hai 1 ano
pai
achega
f4fd73ae2f

+ 5 - 1
src/app/app.routes.ts

@@ -4,5 +4,9 @@ export const routes: Routes = [
   {
     path: '',
     loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
+  },
+  {
+    path: 'countdown',
+    loadComponent: () => import('./countdown/countdown.page').then(m => m.CountdownPage)
   }
-];
+];

+ 15 - 29
src/app/countdown/countdown.page.html

@@ -1,44 +1,30 @@
-<ion-header>
+<ion-header class="ion-no-border">
   <ion-toolbar>
     <ion-buttons slot="start">
       <ion-button (click)="exit()">
-        <ion-icon name="arrow-back-outline" slot="icon-only"></ion-icon>
+        <ion-icon name="arrow-back-outline"></ion-icon>
       </ion-button>
     </ion-buttons>
-    <ion-title>专注计时</ion-title>
+    <ion-title>专注计时</ion-title>
   </ion-toolbar>
 </ion-header>
 
-<ion-content class="ion-text-center">
+<ion-content class="ion-padding">
   <div class="countdown-container">
-    <div class="activity-info">
-      <ion-icon [name]="activityType"></ion-icon>
-      <h2>{{ activityName }}</h2>
+    <ion-icon [name]="activityType" class="activity-icon"></ion-icon>
+    <h2>{{ activityName }}</h2>
+    
+    <div class="timer">
+      <h1>{{ remainingTime }}</h1>
+      <ion-progress-bar [value]="progress"></ion-progress-bar>
     </div>
 
-    <div class="timer-circle">
-      <div class="progress-ring">
-        <ion-progress-bar [value]="progress"></ion-progress-bar>
-      </div>
-      <div class="time">{{ remainingTime }}</div>
-    </div>
-
-    <div class="controls">
-      <ion-button 
-        fill="clear" 
-        [class.running]="isRunning"
-        (click)="togglePause()">
-        <ion-icon 
-          [name]="isRunning ? 'pause-outline' : 'play-outline'" 
-          slot="icon-only">
-        </ion-icon>
+    <div class="control-buttons">
+      <ion-button (click)="togglePause()" fill="clear">
+        <ion-icon [name]="isRunning ? 'pause-outline' : 'play-outline'"></ion-icon>
       </ion-button>
-      
-      <ion-button 
-        fill="clear" 
-        (click)="reset()"
-        [disabled]="!isRunning && remainingSeconds === duration * 60">
-        <ion-icon name="stop-outline" slot="icon-only"></ion-icon>
+      <ion-button (click)="reset()" fill="clear">
+        <ion-icon name="stop-outline"></ion-icon>
       </ion-button>
     </div>
   </div>

+ 106 - 61
src/app/countdown/countdown.page.scss

@@ -1,84 +1,129 @@
+ion-content {
+  --background: linear-gradient(135deg, var(--ion-color-primary) 0%, var(--ion-color-secondary) 100%);
+}
+
 .countdown-container {
-  height: 100%;
   display: flex;
   flex-direction: column;
-  justify-content: center;
   align-items: center;
-  padding: 20px;
-  background: var(--ion-background-color);
-}
-
-.activity-info {
-  margin-bottom: 40px;
+  justify-content: center;
+  height: 100%;
   text-align: center;
-  
-  ion-icon {
+  color: white;
+
+  .activity-icon {
     font-size: 64px;
-    color: var(--ion-color-primary);
+    margin-bottom: 24px;
+    padding: 20px;
+    border-radius: 50%;
+    background: rgba(255, 255, 255, 0.1);
+    backdrop-filter: blur(10px);
   }
-  
+
   h2 {
-    margin: 16px 0;
-    font-size: 28px;
-    color: var(--ion-text-color);
+    font-size: 24px;
     font-weight: 500;
+    margin-bottom: 40px;
+    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
   }
-}
 
-.timer-circle {
-  position: relative;
-  width: 300px;
-  height: 300px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  margin-bottom: 40px;
-  
-  .progress-ring {
-    position: absolute;
-    width: 100%;
-    height: 100%;
-    border-radius: 50%;
-    overflow: hidden;
+  .timer {
+    background: rgba(255, 255, 255, 0.1);
+    backdrop-filter: blur(10px);
+    border-radius: 20px;
+    padding: 32px;
+    margin: 32px 0;
+    width: 80%;
+    max-width: 300px;
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
     
-    ion-progress-bar {
-      position: absolute;
-      bottom: 0;
-      width: 100%;
-      height: 100%;
-      --progress-background: var(--ion-color-primary);
-      --buffer-background: rgba(var(--ion-color-primary-rgb), 0.2);
-      transform-origin: center;
-      transform: rotate(270deg);
+    h1 {
+      font-size: 72px;
+      font-weight: bold;
+      margin-bottom: 24px;
+      font-family: 'SF Mono', 'Courier New', monospace;
+      text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
     }
   }
-  
-  .time {
-    font-size: 56px;
-    font-weight: 300;
-    color: var(--ion-text-color);
-    z-index: 1;
+
+  .control-buttons {
+    display: flex;
+    gap: 24px;
+    margin-top: 40px;
+
+    ion-button {
+      --background: rgba(255, 255, 255, 0.1);
+      --background-hover: rgba(255, 255, 255, 0.2);
+      --background-activated: rgba(255, 255, 255, 0.15);
+      --border-radius: 50%;
+      --padding-start: 0;
+      --padding-end: 0;
+      width: 64px;
+      height: 64px;
+      margin: 0;
+
+      ion-icon {
+        font-size: 28px;
+      }
+
+      &::part(native) {
+        border-radius: 50%;
+      }
+    }
   }
 }
 
-.controls {
-  display: flex;
-  gap: 20px;
-  margin-top: 20px;
+ion-progress-bar {
+  height: 8px;
+  border-radius: 4px;
+  --buffer-background: rgba(255, 255, 255, 0.2);
+  --progress-background: rgba(255, 255, 255, 0.8);
+  overflow: hidden;
+}
+
+ion-header ion-toolbar {
+  --background: transparent;
+  --border-width: 0;
+  
+  ion-title {
+    color: white;
+    font-weight: 500;
+  }
 
   ion-button {
-    --padding-start: 20px;
-    --padding-end: 20px;
-    --border-radius: 50%;
-    width: 64px;
-    height: 64px;
-    
-    ion-icon {
-      font-size: 32px;
+    --color: white;
+  }
+}
+
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.05);
+  }
+  100% {
+    transform: scale(1);
+  }
+}
+
+.timer h1 {
+  animation: pulse 2s infinite ease-in-out;
+}
+
+@media (max-width: 360px) {
+  .countdown-container {
+    .activity-icon {
+      font-size: 48px;
     }
 
-    &.running {
-      --background: var(--ion-color-danger);
+    .timer h1 {
+      font-size: 56px;
+    }
+
+    .control-buttons ion-button {
+      width: 56px;
+      height: 56px;
     }
   }
-}
+}

+ 156 - 27
src/app/countdown/countdown.page.ts

@@ -1,52 +1,70 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonButton,
-         IonIcon, IonProgressBar } from '@ionic/angular/standalone';
-import { AlertController } from '@ionic/angular/standalone';
+import { IonicModule } from '@ionic/angular';
+import { AlertController } from '@ionic/angular';
 import { addIcons } from 'ionicons';
-import { arrowBackOutline, pauseOutline, playOutline, stopOutline } from 'ionicons/icons';
+import { 
+  arrowBackOutline, 
+  pauseOutline, 
+  playOutline, 
+  stopOutline 
+} from 'ionicons/icons';
+import { ActivatedRoute } from '@angular/router';
+import { Location } from '@angular/common';
+import { FocusDataService } from '../services/focus-data.service';
 
 @Component({
   selector: 'app-countdown',
   templateUrl: './countdown.page.html',
   styleUrls: ['./countdown.page.scss'],
   standalone: true,
-  imports: [
-    CommonModule,
-    FormsModule,
-    IonHeader,
-    IonToolbar,
-    IonTitle,
-    IonContent,
-    IonButtons,
-    IonButton,
-    IonIcon,
-    IonProgressBar
-  ]
+  imports: [IonicModule, CommonModule, FormsModule]
 })
-export class CountdownPage implements OnInit {
+export class CountdownPage implements OnInit, OnDestroy {
   activityName: string = '';
-  activityType: string = '工作';
+  activityType: string = '';
   progress: number = 0;
   remainingTime: string = '';
   remainingSeconds: number = 0;
   isRunning: boolean = false;
   duration: number = 25;
   timer: any;
+  startTime: Date | null = null;
+  category: string = '';
+  private audio: HTMLAudioElement;
 
-  constructor(private alertController: AlertController) {
+  constructor(
+    private alertController: AlertController,
+    private route: ActivatedRoute,
+    private location: Location,
+    private focusDataService: FocusDataService
+  ) {
     addIcons({ 
       arrowBackOutline, 
       pauseOutline, 
       playOutline, 
       stopOutline 
     });
+    this.audio = new Audio('assets/alert.mp3');
   }
 
   ngOnInit() {
-    this.remainingSeconds = this.duration * 60;
-    this.updateRemainingTime();
+    this.route.queryParams.subscribe(params => {
+      this.activityType = params['type'] || '';
+      this.activityName = params['name'] || '';
+      this.duration = parseInt(params['duration']) || 25;
+      this.category = params['category'] || '';
+      this.remainingSeconds = this.duration * 60;
+      this.updateRemainingTime();
+      this.startTimer();
+    });
+  }
+
+  ngOnDestroy() {
+    if (this.timer) {
+      clearInterval(this.timer);
+    }
   }
 
   private updateRemainingTime() {
@@ -56,15 +74,126 @@ export class CountdownPage implements OnInit {
     this.progress = 1 - (this.remainingSeconds / (this.duration * 60));
   }
 
-  exit() {
-    // 实现退出方法
+  startTimer() {
+    if (!this.isRunning) {
+      this.isRunning = true;
+      if (!this.startTime) {
+        this.startTime = new Date();
+      }
+      this.timer = setInterval(() => {
+        if (this.remainingSeconds > 0) {
+          this.remainingSeconds--;
+          this.updateRemainingTime();
+        } else {
+          this.completeCountdown();
+        }
+      }, 1000);
+    }
+  }
+
+  async exit() {
+    if (this.isRunning) {
+      const alert = await this.alertController.create({
+        header: '确认退出',
+        message: '正在进行专注计时,确定要退出吗?',
+        buttons: [
+          {
+            text: '取消',
+            role: 'cancel'
+          },
+          {
+            text: '退出',
+            handler: () => {
+              this.saveAndExit();
+            }
+          }
+        ]
+      });
+      await alert.present();
+    } else {
+      this.location.back();
+    }
   }
 
   togglePause() {
-    // 实现暂停方法
+    if (this.isRunning) {
+      clearInterval(this.timer);
+      this.isRunning = false;
+    } else {
+      this.startTimer();
+    }
+  }
+
+  async reset() {
+    const alert = await this.alertController.create({
+      header: '确认重置',
+      message: '确定要重置计时器吗?',
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '重置',
+          handler: () => {
+            clearInterval(this.timer);
+            this.remainingSeconds = this.duration * 60;
+            this.updateRemainingTime();
+            this.isRunning = false;
+            this.startTime = null;
+          }
+        }
+      ]
+    });
+    await alert.present();
+  }
+
+  private async completeCountdown() {
+    clearInterval(this.timer);
+    this.isRunning = false;
+    await this.saveRecord();
+    
+    try {
+      await this.audio.play();
+    } catch (error) {
+      console.error('播放提示音失败:', error);
+    }
+    
+    const alert = await this.alertController.create({
+      header: '专注完成',
+      message: '太棒了!你完成了这次专注。',
+      buttons: [{
+        text: '确定',
+        handler: () => {
+          this.location.back();
+        }
+      }]
+    });
+    await alert.present();
+  }
+
+  private async saveAndExit() {
+    if (this.startTime) {
+      await this.saveRecord();
+    }
+    this.location.back();
   }
 
-  reset() {
-    // 实现重置方法
+  private async saveRecord() {
+    if (!this.startTime) return;
+    
+    const endTime = new Date();
+    const duration = Math.floor((endTime.getTime() - this.startTime.getTime()) / 60000);
+    
+    try {
+      await this.focusDataService.addFocusRecord({
+        duration: duration,
+        category: this.category,
+        startTime: this.startTime,
+        endTime: endTime
+      });
+    } catch (error) {
+      console.error('保存专注记录失败:', error);
+    }
   }
 }

+ 12 - 0
src/app/services/focus-data.service.ts

@@ -39,6 +39,7 @@ export class FocusDataService {
       
       const results = await query.find();
       return results.map(record => ({
+        id: record.id,
         date: record.get('startTime'),
         duration: record.get('duration'),
         category: record.get('category')
@@ -67,4 +68,15 @@ export class FocusDataService {
       return [];
     }
   }
+
+  async deleteFocusRecord(recordId: string) {
+    try {
+      const query = new Parse.Query('FocusRecord');
+      const record = await query.get(recordId);
+      await record.destroy();
+    } catch (error) {
+      console.error('删除专注记录失败:', error);
+      throw error;
+    }
+  }
 }

+ 96 - 49
src/app/tab2/tab2.page.ts

@@ -1,4 +1,4 @@
-import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { IonicModule } from '@ionic/angular';
@@ -16,9 +16,14 @@ import {
   languageOutline,
   pencilOutline,
   trashOutline,
-  addOutline
+  addOutline,
+  arrowBackOutline,
+  pauseOutline,
+  playOutline,
+  stopOutline
 } from 'ionicons/icons';
 import { AlertController } from '@ionic/angular';
+import { FocusDataService } from '../services/focus-data.service';
 
 interface TimerRecord {
   id: number;
@@ -26,6 +31,7 @@ interface TimerRecord {
   startTime: string;
   endTime: string;
   duration: number;
+  parseId?: string;
 }
 
 @Component({
@@ -36,7 +42,7 @@ interface TimerRecord {
   schemas: [CUSTOM_ELEMENTS_SCHEMA],
   imports: [IonicModule, CommonModule, FormsModule]
 })
-export class Tab2Page {
+export class Tab2Page implements OnInit {
   activityTypes = [
     { id: 'study', name: '学习', icon: 'school-outline' },
     { id: 'work', name: '工作', icon: 'briefcase-outline' },
@@ -56,31 +62,51 @@ export class Tab2Page {
   currentTime: string = '00:00:00';
   records: TimerRecord[] = [];
   timer: any;
-  countdownMinutes: number = 25; // 默认25分钟
+  countdownMinutes: number = 25;
 
   constructor(
     private router: Router,
-    private alertController: AlertController
+    private alertController: AlertController,
+    private focusDataService: FocusDataService
   ) {
-    // 注册图标
     addIcons({
-      'school-outline': schoolOutline,
-      'briefcase-outline': briefcaseOutline,
-      'moon-outline': moonOutline,
-      'barbell-outline': barbellOutline,
-      'book-outline': bookOutline,
-      'code-slash-outline': codeSlashOutline,
-      'leaf-outline': leafOutline,
-      'musical-notes-outline': musicalNotesOutline,
-      'language-outline': languageOutline,
-      'pencil-outline': pencilOutline,
-      'trash-outline': trashOutline,
-      'add-outline': addOutline
+      schoolOutline,
+      briefcaseOutline,
+      moonOutline,
+      barbellOutline,
+      bookOutline,
+      codeSlashOutline,
+      leafOutline,
+      musicalNotesOutline,
+      languageOutline,
+      pencilOutline,
+      trashOutline,
+      addOutline,
+      arrowBackOutline,
+      pauseOutline,
+      playOutline,
+      stopOutline
     });
+  }
+
+  ngOnInit() {
+    this.loadRecords();
+  }
 
-    const savedRecords = localStorage.getItem('timerRecords');
-    if (savedRecords) {
-      this.records = JSON.parse(savedRecords);
+  async loadRecords() {
+    try {
+      const records = await this.focusDataService.getFocusRecords();
+      this.records = records.map(record => ({
+        id: Date.now(),
+        type: record.category,
+        startTime: record.date.toISOString(),
+        endTime: record.date.toISOString(),
+        duration: record.duration,
+        parseId: record.id
+      }));
+    } catch (error) {
+      console.error('加载记录失败:', error);
+      this.showErrorAlert('加载记录失败');
     }
   }
 
@@ -108,22 +134,34 @@ export class Tab2Page {
     }, 1000);
   }
 
-  stopTimer() {
+  async stopTimer() {
     if (this.timer) {
       clearInterval(this.timer);
       const endTime = new Date();
-      const duration = Math.floor((endTime.getTime() - this.timerStartTime!.getTime()) / 60000); // 转换为分钟
+      const duration = Math.floor((endTime.getTime() - this.timerStartTime!.getTime()) / 60000);
       
-      const record: TimerRecord = {
-        id: Date.now(),
-        type: this.selectedType,
-        startTime: this.timerStartTime!.toISOString(),
-        endTime: endTime.toISOString(),
-        duration: duration
-      };
-      
-      this.records.unshift(record);
-      this.saveRecords();
+      try {
+        const savedRecord = await this.focusDataService.addFocusRecord({
+          duration: duration,
+          category: this.selectedType,
+          startTime: this.timerStartTime!,
+          endTime: endTime
+        });
+
+        const record: TimerRecord = {
+          id: Date.now(),
+          type: this.selectedType,
+          startTime: this.timerStartTime!.toISOString(),
+          endTime: endTime.toISOString(),
+          duration: duration,
+          parseId: savedRecord.id
+        };
+        
+        this.records.unshift(record);
+      } catch (error) {
+        console.error('保存专注记录失败:', error);
+        this.showErrorAlert('保存记录失败');
+      }
       
       this.isTimerRunning = false;
       this.timerStartTime = null;
@@ -139,17 +177,12 @@ export class Tab2Page {
     
     const selectedActivity = this.activityTypes.find(t => t.id === this.selectedType);
     if (selectedActivity) {
-      console.log('Navigating to countdown with params:', {
-        type: selectedActivity.icon,
-        name: selectedActivity.name,
-        duration: this.countdownMinutes
-      });
-      
-      this.router.navigate(['/tabs/countdown'], {
+      this.router.navigate(['/countdown'], {
         queryParams: {
           type: selectedActivity.icon,
           name: selectedActivity.name,
-          duration: this.countdownMinutes
+          duration: this.countdownMinutes,
+          category: selectedActivity.id
         }
       });
     }
@@ -159,10 +192,6 @@ export class Tab2Page {
     return num.toString().padStart(2, '0');
   }
 
-  private saveRecords() {
-    localStorage.setItem('timerRecords', JSON.stringify(this.records));
-  }
-
   getTypeName(typeId: string): string {
     const type = this.activityTypes.find(t => t.id === typeId);
     return type ? type.name : '';
@@ -180,7 +209,11 @@ export class Tab2Page {
   }
 
   async deleteRecord(index: number) {
-    // 创建一个警告对话框
+    const record = this.records[index];
+    if (!record.parseId) {
+      return;
+    }
+
     const alert = await this.alertController.create({
       header: '确认删除',
       message: '你确定要删除这条记录吗?',
@@ -192,9 +225,14 @@ export class Tab2Page {
         {
           text: '删除',
           role: 'destructive',
-          handler: () => {
-            // 从数组中删除指定索引的记录
-            this.records.splice(index, 1);
+          handler: async () => {
+            try {
+              await this.focusDataService.deleteFocusRecord(record.parseId!);
+              this.records.splice(index, 1);
+            } catch (error) {
+              console.error('删除记录失败:', error);
+              this.showErrorAlert('删除记录失败');
+            }
           }
         }
       ]
@@ -202,4 +240,13 @@ export class Tab2Page {
 
     await alert.present();
   }
+
+  private async showErrorAlert(message: string) {
+    const alert = await this.alertController.create({
+      header: '错误',
+      message,
+      buttons: ['确定']
+    });
+    await alert.present();
+  }
 }