18079408532 1 year ago
parent
commit
1cc5c667a1

+ 56 - 77
src/app/models/Task.ts

@@ -1,23 +1,43 @@
-import { CloudObject } from '../../lib/ncloud';
 import * as Parse from 'parse';
 
-export class Task extends CloudObject {
-  protected override parseObject: Parse.Object;
-  override id: string | null = null;
-  title: string = '';
-  content: string = '';
-  startTime: string = new Date().toISOString();
-  endTime: string = new Date().toISOString();
-  completed: boolean = false;
-  userId: string | null = null;
+export enum TaskStatus {
+  InProgress = 'in_progress',
+  Completed = 'completed',
+  Overdue = 'overdue'
+}
 
+export class Task extends Parse.Object {
   constructor() {
     super('Task');
-    this.parseObject = new Parse.Object('Task');
   }
 
+  // 属性访问器
+  get title(): string { return this.get('title') || ''; }
+  set title(value: string) { this.set('title', value); }
+
+  get content(): string { return this.get('content') || ''; }
+  set content(value: string) { this.set('content', value); }
+
+  get startTime(): Date { return this.get('startTime') || new Date(); }
+  set startTime(value: Date) { this.set('startTime', value); }
+
+  get endTime(): Date { return this.get('endTime') || new Date(); }
+  set endTime(value: Date) { this.set('endTime', value); }
+
+  get completed(): boolean { return this.get('completed') || false; }
+  set completed(value: boolean) { this.set('completed', value); }
+
+  get userId(): string | null { return this.get('userId') || null; }
+  set userId(value: string | null) { this.set('userId', value); }
+
   // 创建新任务
-  static async create(data: any): Promise<Task> {
+  static async create(data: {
+    title: string;
+    content?: string;
+    startTime?: Date;
+    endTime?: Date;
+    completed?: boolean;
+  }): Promise<Task> {
     try {
       const currentUser = Parse.User.current();
       if (!currentUser) {
@@ -32,29 +52,20 @@ export class Task extends CloudObject {
       acl.setPublicWriteAccess(false);
       acl.setReadAccess(currentUser.id, true);
       acl.setWriteAccess(currentUser.id, true);
-      task.parseObject.setACL(acl);
+      task.setACL(acl);
 
       // 设置任务数据
-      task.parseObject.set('title', data.title);
-      task.parseObject.set('content', data.content || '');
-      task.parseObject.set('startTime', data.startTime || new Date().toISOString());
-      task.parseObject.set('endTime', data.endTime || new Date().toISOString());
-      task.parseObject.set('completed', data.completed || false);
-      task.parseObject.set('userId', currentUser.id);
-      task.parseObject.set('user', currentUser);
-
-      // 保存到数据库
-      await task.parseObject.save();
-      
-      // 设置本地数据
-      task.id = task.parseObject.id;
       task.title = data.title;
       task.content = data.content || '';
-      task.startTime = data.startTime || new Date().toISOString();
-      task.endTime = data.endTime || new Date().toISOString();
+      task.startTime = data.startTime || new Date();
+      task.endTime = data.endTime || new Date();
       task.completed = data.completed || false;
       task.userId = currentUser.id;
+      task.set('user', currentUser);
 
+      // 保存到数据库
+      await task.save();
+      
       return task;
     } catch (error) {
       console.error('Failed to create task:', error);
@@ -69,24 +80,11 @@ export class Task extends CloudObject {
         throw new Error('userId is required');
       }
       
-      const query = new Parse.Query('Task');
+      const query = new Parse.Query(Task);
       query.equalTo('userId', userId);
       query.descending('createdAt');
       
-      const results = await query.find();
-      return results.map(result => {
-        const task = new Task();
-        task.parseObject = result;
-        task.id = result.id;
-        const data = result.toJSON();
-        task.title = data['title'];
-        task.content = data['content'];
-        task.startTime = data['startTime'];
-        task.endTime = data['endTime'];
-        task.completed = data['completed'];
-        task.userId = data['userId'];
-        return task;
-      });
+      return await query.find();
     } catch (error) {
       console.error('Failed to get user tasks:', error);
       throw error;
@@ -94,51 +92,32 @@ export class Task extends CloudObject {
   }
 
   // 保存任务更新
-  async saveTask(): Promise<void> {
+  async saveTask(): Promise<Task> {
     try {
       if (!this.id) {
         throw new Error('Task ID is required for update');
       }
-
-      // 更新所有属性到 parseObject
-      this.parseObject.set('title', this.title);
-      this.parseObject.set('content', this.content);
-      this.parseObject.set('startTime', this.startTime);
-      this.parseObject.set('endTime', this.endTime);
-      this.parseObject.set('completed', this.completed);
       
-      await this.parseObject.save();
+      await this.save();
+      return this;
     } catch (error) {
       console.error('Failed to save task:', error);
       throw error;
     }
   }
 
-  // 删除任务
-  async delete(): Promise<void> {
-    try {
-      if (!this.id) {
-        throw new Error('Task ID is required for deletion');
-      }
-      await this.parseObject.destroy();
-    } catch (error) {
-      console.error('Failed to delete task:', error);
-      throw error;
+  // 获取任务状态
+  getStatus(): TaskStatus {
+    if (this.completed) {
+      return TaskStatus.Completed;
     }
-  }
-
-  // 从 Parse.Object 创建 Task 实例
-  static fromParseObject(parseObject: Parse.Object): Task {
-    const task = new Task();
-    task.parseObject = parseObject;
-    task.id = parseObject.id;
-    const data = parseObject.toJSON();
-    task.title = data['title'];
-    task.content = data['content'];
-    task.startTime = data['startTime'];
-    task.endTime = data['endTime'];
-    task.completed = data['completed'];
-    task.userId = data['userId'];
-    return task;
+    
+    if (this.endTime < new Date()) {
+      return TaskStatus.Overdue;
+    }
+    
+    return TaskStatus.InProgress;
   }
 }
+
+Parse.Object.registerSubclass('Task', Task);

+ 97 - 0
src/app/models/data.model.ts

@@ -0,0 +1,97 @@
+import * as Parse from 'parse';
+
+export class Task extends Parse.Object {
+  constructor() {
+    super('Task');
+  }
+
+  // 基本属性
+  getTitle(): string {
+    return this.get('title');
+  }
+
+  setTitle(title: string) {
+    this.set('title', title);
+  }
+
+  getContent(): string {
+    return this.get('content');
+  }
+
+  setContent(content: string) {
+    this.set('content', content);
+  }
+
+  getCompleted(): boolean {
+    return this.get('completed');
+  }
+
+  setCompleted(completed: boolean) {
+    this.set('completed', completed);
+  }
+
+  getStartTime(): Date {
+    return this.get('startTime');
+  }
+
+  setStartTime(startTime: Date) {
+    this.set('startTime', startTime);
+  }
+
+  getEndTime(): Date {
+    return this.get('endTime');
+  }
+
+  setEndTime(endTime: Date) {
+    this.set('endTime', endTime);
+  }
+
+  getUserId(): string {
+    return this.get('userId');
+  }
+
+  setUserId(userId: string) {
+    this.set('userId', userId);
+  }
+
+  // 静态方法
+  static async create(data: {
+    title: string;
+    content?: string;
+    startTime?: Date;
+    endTime?: Date;
+    completed?: boolean;
+    userId: string;
+  }): Promise<Task> {
+    const task = new Task();
+    task.setTitle(data.title);
+    if (data.content) task.setContent(data.content);
+    if (data.startTime) task.setStartTime(data.startTime);
+    if (data.endTime) task.setEndTime(data.endTime);
+    task.setCompleted(data.completed || false);
+    task.setUserId(data.userId);
+    await task.save();
+    return task;
+  }
+
+  static fromParseObject(parseObject: Parse.Object): Task {
+    const task = new Task();
+    task.id = parseObject.id;
+    task.set('title', parseObject.get('title'));
+    task.set('content', parseObject.get('content'));
+    task.set('completed', parseObject.get('completed'));
+    task.set('startTime', parseObject.get('startTime'));
+    task.set('endTime', parseObject.get('endTime'));
+    task.set('userId', parseObject.get('userId'));
+    return task;
+  }
+
+  // 实例方法
+  async saveTask(): Promise<Task> {
+    await this.save();
+    return this;
+  }
+}
+
+// 注册 Parse 子类
+Parse.Object.registerSubclass('Task', Task);

+ 96 - 0
src/app/services/data.service.ts

@@ -0,0 +1,96 @@
+import { Injectable } from '@angular/core';
+import { AuthService } from './auth.service';
+import * as Parse from 'parse';
+import { Task } from '../models/data.model';
+import { BehaviorSubject } from 'rxjs';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class DataService {
+  private tasksSubject = new BehaviorSubject<Task[]>([]);
+  tasks$ = this.tasksSubject.asObservable();
+
+  constructor(private authService: AuthService) {
+    // 监听用户登录状态
+    this.authService.currentUser$.subscribe(user => {
+      if (user) {
+        this.loadTasks();
+      } else {
+        this.tasksSubject.next([]);
+      }
+    });
+  }
+
+  private async loadTasks() {
+    try {
+      const tasks = await this.getTasks();
+      this.tasksSubject.next(tasks);
+    } catch (error) {
+      console.error('Failed to load tasks:', error);
+      this.tasksSubject.next([]);
+    }
+  }
+
+  async getTasks(): Promise<Task[]> {
+    const currentUser = this.authService.getCurrentUser();
+    if (!currentUser) {
+      throw new Error('用户未登录');
+    }
+
+    const query = new Parse.Query(Task);
+    query.equalTo('userId', currentUser.id);
+    query.descending('createdAt');
+    const results = await query.find();
+    return results.map(result => Task.fromParseObject(result));
+  }
+
+  async saveTask(taskData: {
+    title: string;
+    content?: string;
+    startTime?: Date;
+    endTime?: Date;
+    completed?: boolean;
+  }): Promise<Task> {
+    const currentUser = this.authService.getCurrentUser();
+    if (!currentUser) {
+      throw new Error('用户未登录');
+    }
+
+    const task = await Task.create({
+      ...taskData,
+      userId: currentUser.id
+    });
+
+    await this.loadTasks();
+    return task;
+  }
+
+  async updateTask(taskId: string, data: {
+    title?: string;
+    content?: string;
+    completed?: boolean;
+    startTime?: Date;
+    endTime?: Date;
+  }): Promise<Task> {
+    const query = new Parse.Query(Task);
+    const task = await query.get(taskId);
+    
+    if (data.title) task.setTitle(data.title);
+    if (data.content) task.setContent(data.content);
+    if (data.completed !== undefined) task.setCompleted(data.completed);
+    if (data.startTime) task.setStartTime(data.startTime);
+    if (data.endTime) task.setEndTime(data.endTime);
+    
+    await task.save();
+    await this.loadTasks();
+    return task;
+  }
+
+  async deleteTask(taskId: string): Promise<void> {
+    const query = new Parse.Query(Task);
+    const task = await query.get(taskId);
+    await task.destroy();
+    await this.loadTasks();
+  }
+}

+ 129 - 71
src/app/services/focus-data.service.ts

@@ -1,74 +1,94 @@
 import { Injectable } from '@angular/core';
-import Parse from 'parse';
+import * as Parse from 'parse';
+
+export interface FocusRecord {
+  id?: string;
+  duration: number;
+  category: string;
+  startTime: Date;
+  endTime: Date;
+  userId?: string;
+}
 
 @Injectable({
   providedIn: 'root'
 })
 export class FocusDataService {
-  constructor() {}
-
-  async getFocusRecords(): Promise<any[]> {
+  
+  async addFocusRecord(record: FocusRecord): Promise<Parse.Object> {
     try {
+      const currentUser = Parse.User.current();
+      if (!currentUser) {
+        throw new Error('No user logged in');
+      }
+
       const FocusRecord = Parse.Object.extend('FocusRecord');
-      const query = new Parse.Query(FocusRecord);
-      query.descending('startTime');
-      const results = await query.find();
-      
-      return results.map(record => ({
-        id: record.id,
-        category: record.get('category'),
-        duration: record.get('duration'),
-        startTime: record.get('startTime'),
-        endTime: record.get('endTime')
-      }));
+      const focusRecord = new FocusRecord();
+
+      // 设置记录数据
+      focusRecord.set({
+        duration: record.duration,
+        category: record.category,
+        startTime: record.startTime,
+        endTime: record.endTime,
+        user: currentUser
+      });
+
+      // 设置 ACL
+      const acl = new Parse.ACL();
+      acl.setPublicReadAccess(false);
+      acl.setPublicWriteAccess(false);
+      acl.setReadAccess(currentUser.id, true);
+      acl.setWriteAccess(currentUser.id, true);
+      focusRecord.setACL(acl);
+
+      return await focusRecord.save();
     } catch (error) {
-      console.error('获取专注记录失败:', error);
+      console.error('保存专注记录失败:', error);
       throw error;
     }
   }
 
-  async getTasks(): Promise<any[]> {
+  async getFocusRecords(): Promise<Parse.Object[]> {
     try {
-      const Task = Parse.Object.extend('Task');
-      const query = new Parse.Query(Task);
+      const currentUser = Parse.User.current();
+      if (!currentUser) {
+        throw new Error('No user logged in');
+      }
+
+      const query = new Parse.Query('FocusRecord');
+      query.equalTo('user', currentUser);
       query.descending('createdAt');
-      const results = await query.find();
       
-      return results.map(task => ({
-        id: task.id,
-        title: task.get('title'),
-        category: task.get('category'),
-        completed: task.get('completed'),
-        createdAt: task.get('createdAt')
-      }));
-    } catch (error) {
-      console.error('获取任务数据失败:', error);
-      throw error;
-    }
-  }
-
-  async addFocusRecord(record: any): Promise<Parse.Object> {
-    try {
-      const FocusRecord = Parse.Object.extend('FocusRecord');
-      const newRecord = new FocusRecord();
+      // 添加调试日志
+      console.log('Querying records for user:', currentUser.id);
       
-      newRecord.set('category', record.category);
-      newRecord.set('duration', record.duration);
-      newRecord.set('startTime', record.startTime);
-      newRecord.set('endTime', record.endTime);
+      const results = await query.find();
+      console.log('Found records:', results.length);
       
-      return await newRecord.save();
+      return results;
     } catch (error) {
-      console.error('保存专注记录失败:', error);
+      console.error('获取专注记录失败:', error);
       throw error;
     }
   }
 
   async deleteFocusRecord(recordId: string): Promise<void> {
     try {
-      const FocusRecord = Parse.Object.extend('FocusRecord');
-      const query = new Parse.Query(FocusRecord);
+      const currentUser = Parse.User.current();
+      if (!currentUser) {
+        throw new Error('No user logged in');
+      }
+
+      const query = new Parse.Query('FocusRecord');
       const record = await query.get(recordId);
+
+      // 验证记录所有权
+      const recordUser = record.get('user');
+      if (recordUser.id !== currentUser.id) {
+        throw new Error('无权删除此记录');
+      }
+
       await record.destroy();
     } catch (error) {
       console.error('删除专注记录失败:', error);
@@ -76,48 +96,86 @@ export class FocusDataService {
     }
   }
 
-  async addTask(task: any): Promise<string> {
+  // 获取今日专注统计
+  async getTodayStats(): Promise<{ totalDuration: number; recordCount: number }> {
     try {
-      const Task = Parse.Object.extend('Task');
-      const newTask = new Task();
-      
-      newTask.set('title', task.title);
-      newTask.set('category', task.category);
-      newTask.set('completed', task.completed || false);
+      const currentUser = Parse.User.current();
+      if (!currentUser) {
+        throw new Error('No user logged in');
+      }
+
+      const today = new Date();
+      today.setHours(0, 0, 0, 0);
+
+      const query = new Parse.Query('FocusRecord');
+      query.equalTo('user', currentUser);
+      query.greaterThanOrEqualTo('startTime', today);
       
-      const result = await newTask.save();
-      return result.id;
+      const records = await query.find();
+      const totalDuration = records.reduce((sum, record) => sum + record.get('duration'), 0);
+
+      return {
+        totalDuration,
+        recordCount: records.length
+      };
     } catch (error) {
-      console.error('保存任务失败:', error);
+      console.error('获取今日统计失败:', error);
       throw error;
     }
   }
 
-  async updateTask(taskId: string, updates: any): Promise<void> {
+  // 获取指定日期范围的统计
+  async getStatsForDateRange(startDate: Date, endDate: Date): Promise<{ totalDuration: number; recordCount: number }> {
     try {
-      const Task = Parse.Object.extend('Task');
-      const query = new Parse.Query(Task);
-      const task = await query.get(taskId);
-      
-      Object.keys(updates).forEach(key => {
-        task.set(key, updates[key]);
-      });
+      const currentUser = Parse.User.current();
+      if (!currentUser) {
+        throw new Error('No user logged in');
+      }
+
+      const query = new Parse.Query('FocusRecord');
+      query.equalTo('user', currentUser);
+      query.greaterThanOrEqualTo('startTime', startDate);
+      query.lessThan('startTime', endDate);
       
-      await task.save();
+      const records = await query.find();
+      const totalDuration = records.reduce((sum, record) => sum + record.get('duration'), 0);
+
+      return {
+        totalDuration,
+        recordCount: records.length
+      };
     } catch (error) {
-      console.error('更新任务失败:', error);
+      console.error('获取日期范围统计失败:', error);
       throw error;
     }
   }
 
-  async deleteTask(taskId: string): Promise<void> {
+  // 获取按类别分组的统计
+  async getStatsByCategory(): Promise<{ [category: string]: { totalDuration: number; recordCount: number } }> {
     try {
-      const Task = Parse.Object.extend('Task');
-      const query = new Parse.Query(Task);
-      const task = await query.get(taskId);
-      await task.destroy();
+      const currentUser = Parse.User.current();
+      if (!currentUser) {
+        throw new Error('No user logged in');
+      }
+
+      const query = new Parse.Query('FocusRecord');
+      query.equalTo('user', currentUser);
+      
+      const records = await query.find();
+      const stats: { [category: string]: { totalDuration: number; recordCount: number } } = {};
+
+      records.forEach(record => {
+        const category = record.get('category');
+        if (!stats[category]) {
+          stats[category] = { totalDuration: 0, recordCount: 0 };
+        }
+        stats[category].totalDuration += record.get('duration');
+        stats[category].recordCount += 1;
+      });
+
+      return stats;
     } catch (error) {
-      console.error('删除任务失败:', error);
+      console.error('获取类别统计失败:', error);
       throw error;
     }
   }

+ 13 - 3
src/app/tab1/tab1.page.html

@@ -1,6 +1,6 @@
 <ion-header>
   <ion-toolbar>
-    <ion-title>任务列表</ion-title>
+    <ion-title>任务清单</ion-title>
     <ion-buttons slot="end">
       <ion-button (click)="addTask()">
         <ion-icon slot="icon-only" name="add-outline"></ion-icon>
@@ -30,19 +30,29 @@
               {{ getTaskStatusText(task) }}
             </ion-badge>
           </div>
-          <p>{{ task.content }}</p>
+          <p *ngIf="task.content">{{ task.content }}</p>
           <p *ngIf="task.startTime || task.endTime">
             <ion-icon name="calendar-outline"></ion-icon>
-            {{ task.startTime | date:'short' }} - {{ task.endTime | date:'short' }}
+            {{ task.startTime | date:'short' }}
+            <span *ngIf="task.endTime">
+              - {{ task.endTime | date:'short' }}
+            </span>
           </p>
         </ion-label>
       </ion-item>
 
       <ion-item-options side="end">
+        <ion-item-option color="primary" (click)="editTask(task)">
+          <ion-icon slot="icon-only" name="create-outline"></ion-icon>
+        </ion-item-option>
         <ion-item-option color="danger" (click)="deleteTask(task)">
           <ion-icon slot="icon-only" name="trash-outline"></ion-icon>
         </ion-item-option>
       </ion-item-options>
     </ion-item-sliding>
   </ion-list>
+
+  <ion-text color="medium" class="ion-text-center ion-padding" *ngIf="tasks.length === 0">
+    <p>暂无任务</p>
+  </ion-text>
 </ion-content>

+ 174 - 68
src/app/tab1/tab1.page.ts

@@ -1,15 +1,31 @@
 import { Component, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonButton, 
-         IonIcon, IonList, IonItemSliding, IonItem, IonCheckbox, IonLabel, 
-         IonItemOptions, IonItemOption, IonProgressBar, IonRefresher, 
-         IonRefresherContent, IonBadge } from '@ionic/angular/standalone';
-import { AlertController, ModalController } from '@ionic/angular/standalone';
+import { 
+  IonHeader, 
+  IonToolbar, 
+  IonTitle, 
+  IonContent, 
+  IonButtons, 
+  IonButton,
+  IonIcon, 
+  IonList, 
+  IonItemSliding, 
+  IonItem, 
+  IonCheckbox, 
+  IonLabel,
+  IonItemOptions, 
+  IonItemOption, 
+  IonProgressBar, 
+  IonRefresher,
+  IonRefresherContent, 
+  IonBadge,
+  IonText,
+  AlertController
+} from '@ionic/angular/standalone';
 import { addIcons } from 'ionicons';
-import { addOutline, calendarOutline, trashOutline } from 'ionicons/icons';
-import { Task, TaskStatus } from '../../lib/models/Task';
-import { TaskModalComponent } from '../components/task-modal/task-modal.component';
+import { addOutline, calendarOutline, trashOutline, createOutline } from 'ionicons/icons';
+import { Task, TaskStatus } from '../models/Task';
 import * as Parse from 'parse';
 
 @Component({
@@ -19,12 +35,26 @@ import * as Parse from 'parse';
   standalone: true,
   imports: [
     CommonModule, 
-    FormsModule, 
-    TaskModalComponent,
-    IonHeader, IonToolbar, IonTitle, IonContent, IonButtons, IonButton,
-    IonIcon, IonList, IonItemSliding, IonItem, IonCheckbox, IonLabel,
-    IonItemOptions, IonItemOption, IonProgressBar, IonRefresher,
-    IonRefresherContent, IonBadge
+    FormsModule,
+    IonHeader, 
+    IonToolbar, 
+    IonTitle, 
+    IonContent, 
+    IonButtons, 
+    IonButton,
+    IonIcon, 
+    IonList, 
+    IonItemSliding, 
+    IonItem, 
+    IonCheckbox, 
+    IonLabel,
+    IonItemOptions, 
+    IonItemOption, 
+    IonProgressBar, 
+    IonRefresher,
+    IonRefresherContent, 
+    IonBadge,
+    IonText
   ]
 })
 export class Tab1Page implements OnInit {
@@ -32,10 +62,9 @@ export class Tab1Page implements OnInit {
   loading = false;
 
   constructor(
-    private alertController: AlertController,
-    private modalCtrl: ModalController
+    private alertController: AlertController
   ) {
-    addIcons({ addOutline, calendarOutline, trashOutline });
+    addIcons({ addOutline, calendarOutline, createOutline, trashOutline });
   }
 
   ngOnInit() {
@@ -49,16 +78,15 @@ export class Tab1Page implements OnInit {
   async loadTasks(event?: any) {
     try {
       this.loading = true;
-      const query = new Parse.Query('Task');
-      if (Parse.User.current()) {
-        query.equalTo('userId', Parse.User.current()?.id);
+      const currentUser = Parse.User.current();
+      if (!currentUser) {
+        this.tasks = [];
+        return;
       }
-      query.descending('createdAt');
-      
-      const results = await query.find();
-      this.tasks = results.map(result => Task.fromParseObject(result));
 
-      // 根据状态排序:进行中 -> 逾期 -> 已完成
+      this.tasks = await Task.getUserTasks(currentUser.id);
+
+      // 根据状态排序
       this.tasks.sort((a, b) => {
         const statusOrder = {
           [TaskStatus.InProgress]: 0,
@@ -80,46 +108,76 @@ export class Tab1Page implements OnInit {
   }
 
   async addTask() {
-    try {
-      const modal = await this.modalCtrl.create({
-        component: TaskModalComponent,
-        componentProps: {
-          task: new Task()
+    const alert = await this.alertController.create({
+      header: '新建任务',
+      inputs: [
+        {
+          name: 'title',
+          type: 'text',
+          placeholder: '任务标题'
+        },
+        {
+          name: 'content',
+          type: 'textarea',
+          placeholder: '任务描述'
+        },
+        {
+          name: 'startTime',
+          type: 'datetime-local',
+          label: '开始时间',
+          value: new Date().toISOString().slice(0, 16)
+        },
+        {
+          name: 'endTime',
+          type: 'datetime-local',
+          label: '截止时间',
+          value: new Date().toISOString().slice(0, 16)
         }
-      });
-      await modal.present();
-
-      const { data: task, role } = await modal.onDidDismiss();
-      if (role === 'confirm' && task) {
-        const newTask = await Task.create({
-          title: task.title,
-          content: task.content,
-          startTime: task.startTime ? new Date(task.startTime) : undefined,
-          endTime: task.endTime ? new Date(task.endTime) : undefined,
-          completed: false,
-          userId: Parse.User.current()?.id
-        });
-        
-        this.tasks.unshift(newTask);
-        // 重新排序
-        await this.loadTasks();
-      }
-    } catch (error) {
-      console.error('创建任务失败:', error);
-      await this.showErrorAlert('创建任务失败');
-    }
+      ],
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '确定',
+          handler: async (data) => {
+            if (!data.title?.trim()) {
+              return false;
+            }
+
+            try {
+              await Task.create({
+                title: data.title.trim(),
+                content: data.content?.trim() || '',
+                startTime: new Date(data.startTime),
+                endTime: new Date(data.endTime),
+                completed: false
+              });
+
+              await this.loadTasks();
+              return true;
+            } catch (error) {
+              console.error('创建任务失败:', error);
+              await this.showErrorAlert('创建任务失败');
+              return false;
+            }
+          }
+        }
+      ]
+    });
+
+    await alert.present();
   }
 
   async toggleComplete(task: Task) {
     try {
       task.completed = !task.completed;
       await task.saveTask();
-      
-      // 重新加载以保持正确的排序
-      await this.loadTasks();
+      await this.loadTasks(); // 重新加载以保持正确的排序
     } catch (error) {
       console.error('更新任务失败:', error);
-      task.completed = !task.completed;
+      task.completed = !task.completed; // 恢复状态
       await this.showErrorAlert('更新任务状态失败');
     }
   }
@@ -138,8 +196,8 @@ export class Tab1Page implements OnInit {
           role: 'destructive',
           handler: async () => {
             try {
-              await task.delete();
-              this.tasks = this.tasks.filter(t => t.id !== task.id);
+              await task.destroy();
+              await this.loadTasks();
             } catch (error) {
               console.error('删除任务失败:', error);
               await this.showErrorAlert('删除任务失败');
@@ -151,6 +209,54 @@ export class Tab1Page implements OnInit {
     await alert.present();
   }
 
+  async editTask(task: Task) {
+    const alert = await this.alertController.create({
+      header: '编辑任务',
+      inputs: [
+        {
+          name: 'title',
+          type: 'text',
+          value: task.title,
+          placeholder: '任务标题'
+        },
+        {
+          name: 'content',
+          type: 'textarea',
+          value: task.content,
+          placeholder: '任务描述'
+        }
+      ],
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '确定',
+          handler: async (data) => {
+            if (!data.title?.trim()) {
+              return false;
+            }
+
+            try {
+              task.title = data.title.trim();
+              task.content = data.content?.trim() || '';
+              await task.saveTask();
+              await this.loadTasks();
+              return true;
+            } catch (error) {
+              console.error('编辑任务失败:', error);
+              await this.showErrorAlert('编辑任务失败');
+              return false;
+            }
+          }
+        }
+      ]
+    });
+
+    await alert.present();
+  }
+
   getTaskStatusClass(task: Task): string {
     const status = task.getStatus();
     switch (status) {
@@ -165,31 +271,31 @@ export class Tab1Page implements OnInit {
     }
   }
 
-  getTaskStatusText(task: Task): string {
+  getTaskStatusColor(task: Task): string {
     const status = task.getStatus();
     switch (status) {
       case TaskStatus.Completed:
-        return '已完成';
+        return 'success';
       case TaskStatus.Overdue:
-        return '已逾期';
+        return 'danger';
       case TaskStatus.InProgress:
-        return '进行中';
+        return 'primary';
       default:
-        return '';
+        return 'medium';
     }
   }
 
-  getTaskStatusColor(task: Task): string {
+  getTaskStatusText(task: Task): string {
     const status = task.getStatus();
     switch (status) {
       case TaskStatus.Completed:
-        return 'success';
+        return '已完成';
       case TaskStatus.Overdue:
-        return 'danger';
+        return '已逾期';
       case TaskStatus.InProgress:
-        return 'primary';
+        return '进行中';
       default:
-        return 'medium';
+        return '';
     }
   }
 

+ 8 - 8
src/app/tab2/tab2.page.ts

@@ -26,7 +26,7 @@ import { AlertController } from '@ionic/angular';
 import { FocusDataService } from '../services/focus-data.service';
 
 interface TimerRecord {
-  id: number;
+  id: string;
   type: string;
   startTime: string;
   endTime: string;
@@ -97,16 +97,16 @@ export class Tab2Page implements OnInit {
     try {
       const records = await this.focusDataService.getFocusRecords();
       this.records = records.map(record => ({
-        id: Date.now(),
-        type: record.category,
-        startTime: new Date(record.startTime).toISOString(),
-        endTime: new Date(record.endTime).toISOString(),
-        duration: record.duration,
+        id: record.id,
+        type: record.get('category'),
+        startTime: record.get('startTime').toISOString(),
+        endTime: record.get('endTime').toISOString(),
+        duration: record.get('duration'),
         parseId: record.id
       }));
     } catch (error) {
       console.error('加载记录失败:', error);
-      this.showErrorAlert('加载记录失败');
+      await this.showErrorAlert('加载记录失败');
     }
   }
 
@@ -149,7 +149,7 @@ export class Tab2Page implements OnInit {
         });
 
         const record: TimerRecord = {
-          id: Date.now(),
+          id: savedRecord.id,
           type: this.selectedType,
           startTime: this.timerStartTime!.toISOString(),
           endTime: endTime.toISOString(),

+ 48 - 3
src/app/tab3/tab3.page.html

@@ -17,11 +17,56 @@
     </ion-segment-button>
   </ion-segment>
 
+  <ion-segment [(ngModel)]="selectedChart" (ionChange)="onChartTypeChange($event)">
+    <ion-segment-button value="focus">
+      <ion-label>专注时间</ion-label>
+    </ion-segment-button>
+    <ion-segment-button value="task">
+      <ion-label>任务完成</ion-label>
+    </ion-segment-button>
+    <ion-segment-button value="category">
+      <ion-label>分类统计</ion-label>
+    </ion-segment-button>
+  </ion-segment>
+
   <ion-progress-bar type="indeterminate" *ngIf="isLoading"></ion-progress-bar>
 
   <div class="chart-container">
-    <canvas #focusChart></canvas>
-    <canvas #taskChart></canvas>
-    <canvas #categoryChart></canvas>
+    <div [ngStyle]="{'display': selectedChart === 'focus' ? 'block' : 'none'}">
+      <canvas #focusChart></canvas>
+    </div>
+    <div [ngStyle]="{'display': selectedChart === 'task' ? 'block' : 'none'}">
+      <canvas #taskChart></canvas>
+    </div>
+    <div [ngStyle]="{'display': selectedChart === 'category' ? 'block' : 'none'}">
+      <canvas #categoryChart></canvas>
+    </div>
   </div>
+
+  <!-- 统计数据展示 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>统计概览</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list>
+        <ion-item>
+          <ion-label>总专注时间</ion-label>
+          <ion-note slot="end">{{ stats.totalFocusTime / 60 | number:'1.0-0' }} 分钟</ion-note>
+        </ion-item>
+        <ion-item>
+          <ion-label>总任务数</ion-label>
+          <ion-note slot="end">{{ stats.totalTasks }}</ion-note>
+        </ion-item>
+        <ion-item>
+          <ion-label>已完成任务</ion-label>
+          <ion-note slot="end">{{ stats.completedTasks }}</ion-note>
+        </ion-item>
+        <ion-item>
+          <ion-label>平均专注时长</ion-label>
+          <ion-note slot="end">{{ stats.averageFocusTime / 60 | number:'1.0-0' }} 分钟</ion-note>
+        </ion-item>
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
 </ion-content>

+ 13 - 17
src/app/tab3/tab3.page.scss

@@ -17,16 +17,18 @@ ion-content {
 }
 
 .chart-container {
+  position: relative;
   height: 300px;
-  margin: 16px;
-  padding: 16px;
-  background: var(--ion-card-background);
-  border-radius: 16px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  margin: 20px;
+  
+  > div {
+    height: 100%;
+    width: 100%;
+  }
 }
 
 ion-card {
-  margin: 8px;
+  margin: 16px;
   border-radius: 16px;
   background: var(--ion-card-background);
   box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
@@ -51,19 +53,13 @@ ion-card {
 }
 
 ion-segment {
-  margin: 16px;
-  padding: 4px;
-  background: var(--ion-card-background);
-  border-radius: 16px;
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
-
-  ion-segment-button {
-    --color: var(--ion-color-medium);
-    --color-checked: var(--ion-color-primary);
-    --indicator-color: var(--ion-color-primary);
-  }
+  padding: 5px;
 }
 
 ion-header ion-toolbar {
   --background: var(--ion-background-color);
+}
+
+ion-note {
+  font-size: 1rem;
 }

+ 45 - 17
src/app/tab3/tab3.page.ts

@@ -2,8 +2,10 @@ import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
 import { IonicModule } from '@ionic/angular';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { Chart, registerables, ChartTypeRegistry } from 'chart.js';
+import { Chart, registerables } from 'chart.js';
 import { FocusDataService } from '../services/focus-data.service';
+import { Task } from '../models/Task';
+import * as Parse from 'parse';
 
 Chart.register(...registerables);
 
@@ -40,7 +42,7 @@ export class Tab3Page implements OnInit {
     { id: 'sport', name: '运动', icon: 'barbell-outline' },
     { id: 'reading', name: '阅读', icon: 'book-outline' },
     { id: 'coding', name: '编程', icon: 'code-slash-outline' },
-    { id: 'meditation', name: '���想', icon: 'leaf-outline' },
+    { id: 'meditation', name: '想', icon: 'leaf-outline' },
     { id: 'music', name: '音乐', icon: 'musical-notes-outline' },
     { id: 'language', name: '语言', icon: 'language-outline' },
     { id: 'writing', name: '写作', icon: 'pencil-outline' }
@@ -61,13 +63,32 @@ export class Tab3Page implements OnInit {
   async loadData() {
     this.isLoading = true;
     try {
-      const [focusRecords, tasks] = await Promise.all([
-        this.focusDataService.getFocusRecords(),
-        this.focusDataService.getTasks()
-      ]);
+      const currentUser = Parse.User.current();
+      if (!currentUser) {
+        throw new Error('No user logged in');
+      }
+
+      // 获取专注记录
+      const focusRecords = await this.focusDataService.getFocusRecords();
+      this.focusRecords = focusRecords.map(record => ({
+        duration: record.get('duration'),
+        category: record.get('category'),
+        startTime: record.get('startTime'),
+        endTime: record.get('endTime')
+      }));
+
+      // 获取用户任务
+      const query = new Parse.Query('Task');
+      query.equalTo('user', currentUser);
+      const tasks = await query.find();
+      this.tasks = tasks.map(task => ({
+        id: task.id,
+        title: task.get('title'),
+        completed: task.get('completed'),
+        category: task.get('category'),
+        createdAt: task.get('createdAt')
+      }));
 
-      this.focusRecords = focusRecords;
-      this.tasks = tasks;
       this.calculateStats();
       this.updateCharts();
     } catch (error) {
@@ -78,6 +99,8 @@ export class Tab3Page implements OnInit {
   }
 
   calculateStats() {
+    if (!this.focusRecords || !this.tasks) return;
+
     // 计算专注时间统计
     this.stats.totalFocusTime = this.focusRecords.reduce((sum, record) => 
       sum + record.duration, 0);
@@ -89,11 +112,11 @@ export class Tab3Page implements OnInit {
     this.stats.completedTasks = this.tasks.filter(task => task.completed).length;
 
     // 计算最常见类别
-    const categoryCount = [...this.focusRecords, ...this.tasks].reduce((acc, item) => {
+    const categoryCount: { [key: string]: number } = {};
+    [...this.focusRecords, ...this.tasks].forEach(item => {
       const category = item.category || '未分类';
-      acc[category] = (acc[category] || 0) + 1;
-      return acc;
-    }, {});
+      categoryCount[category] = (categoryCount[category] || 0) + 1;
+    });
 
     this.stats.mostFrequentCategory = Object.entries(categoryCount)
       .sort(([,a]: any, [,b]: any) => b - a)[0][0];
@@ -144,7 +167,7 @@ export class Tab3Page implements OnInit {
       ...this.filterDataByTimeRange(this.tasks, 'createdAt')
     ];
     
-    const categoryData = filteredData.reduce((acc, item) => {
+    const categoryData = filteredData.reduce((acc: { [key: string]: number }, item) => {
       const category = item.category || '未分类';
       acc[category] = (acc[category] || 0) + 1;
       return acc;
@@ -169,6 +192,8 @@ export class Tab3Page implements OnInit {
   }
 
   private filterDataByTimeRange(data: any[], dateField: string) {
+    if (!data) return [];
+    
     const now = new Date();
     let startDate: Date;
 
@@ -177,16 +202,19 @@ export class Tab3Page implements OnInit {
         startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
         break;
       case 'month':
-        startDate = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());
+        startDate = new Date(now.getFullYear(), now.getMonth(), 1);
         break;
       case 'year':
-        startDate = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
+        startDate = new Date(now.getFullYear(), 0, 1);
         break;
       default:
-        startDate = new Date(0);
+        startDate = new Date(now.setHours(0, 0, 0, 0));
     }
 
-    return data.filter(item => new Date(item[dateField]) >= startDate);
+    return data.filter(item => {
+      const itemDate = new Date(item[dateField]);
+      return itemDate >= startDate && itemDate <= now;
+    });
   }
 
   private groupByDate(data: any[], dateField: string, valueField: string) {

+ 30 - 12
src/app/tab4/tab4.page.html

@@ -50,7 +50,7 @@
     </ion-card-content>
   </ion-card>
 
-  @if(currentUser?.id){
+  <!-- @if(currentUser?.id){
     <ion-card>
       <ion-card-header>
         <ion-card-title>个性头像生成器</ion-card-title>
@@ -60,19 +60,37 @@
         <ion-button expand="block" (click)="goToAvatar()" color="success">前往生成</ion-button>
       </ion-card-content>
     </ion-card>
-  }
+  } -->
 
   @if(currentUser?.id){
-    <ion-card class="memo-card">
-      <h2 class="memo-title">健康备忘录</h2>
-      <p class="memo-description">写下您问诊的医生名或者心动的科普知识,便于您下次查找(点击标签可删除)</p>
+    <ion-card class="stats-card">
+      <h2 class="stats-title">今日专注统计</h2>
+      <p class="stats-description">记录您今天的专注时光,让每一分钟都更有价值</p>
+      <div class="stats-grid">
+        <div class="stats-item">
+          <ion-icon name="time-outline" class="stats-icon"></ion-icon>
+          <div class="stats-content">
+            <h3>专注次数</h3>
+            <p>{{ todayStats.recordCount || 0 }} 次</p>
+          </div>
+        </div>
+        
+        <div class="stats-item">
+          <ion-icon name="hourglass-outline" class="stats-icon"></ion-icon>
+          <div class="stats-content">
+            <h3>专注时长</h3>
+            <p>{{ (todayStats.totalDuration || 0) / 60 | number:'1.0-0' }} 分钟</p>
+          </div>
+        </div>
 
-      <h2 class="memo-title">收藏夹</h2>
-      <ul class="tag-list">
-        @for(tag of editTags; track tag;){
-          <li class="tag-item">{{tag}}</li>
-        }
-      </ul>
+        <div class="stats-item">
+          <ion-icon name="trophy-outline" class="stats-icon"></ion-icon>
+          <div class="stats-content">
+            <h3>平均时长</h3>
+            <p>{{ todayStats.recordCount ? (todayStats.totalDuration / todayStats.recordCount / 60 | number:'1.0-0') : 0 }} 分钟</p>
+          </div>
+        </div>
+      </div>
     </ion-card>
   }
-</ion-content>
+</ion-content> 

+ 60 - 1
src/app/tab4/tab4.page.scss

@@ -72,7 +72,7 @@ ion-card:hover {
     border-radius: 5px; /* 标签圆角 */
     padding: 10px; /* 标签内边距 */
     margin: 5px 0; /* 标签外边距 */
-    transition: background-color 0.3s; /* 背景色���化的过渡效果 */
+    transition: background-color 0.3s; /* 背景色化的过渡效果 */
     cursor: pointer; /* 鼠标悬停时显示为可点击 */
 }
 
@@ -123,3 +123,62 @@ ion-content {
     height: auto;
     object-fit: cover;
 }
+
+.stats-card {
+    margin: 16px;
+    border-radius: 12px;
+    background: linear-gradient(135deg, #4CAF50, #2E7D32);
+    color: white;
+}
+
+.stats-title {
+    padding: 16px 16px 8px;
+    margin: 0;
+    font-size: 1.3rem;
+    font-weight: 600;
+    color: white;
+}
+
+.stats-description {
+    padding: 0 16px 16px;
+    margin: 0;
+    color: rgba(255, 255, 255, 0.9);
+    font-size: 0.9rem;
+}
+
+.stats-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
+    gap: 16px;
+    padding: 16px;
+}
+
+.stats-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 12px;
+    background: rgba(255, 255, 255, 0.1);
+    border-radius: 8px;
+    backdrop-filter: blur(5px);
+}
+
+.stats-icon {
+    font-size: 24px;
+    color: rgba(255, 255, 255, 0.9);
+}
+
+.stats-content {
+    h3 {
+        margin: 0;
+        font-size: 0.9rem;
+        color: rgba(255, 255, 255, 0.9);
+    }
+
+    p {
+        margin: 4px 0 0;
+        font-size: 1.2rem;
+        font-weight: 600;
+        color: white;
+    }
+}

+ 26 - 12
src/app/tab4/tab4.page.ts

@@ -2,13 +2,15 @@ import { Component, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, 
-         IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle } from '@ionic/angular/standalone';
+         IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, IonIcon } from '@ionic/angular/standalone';
 import { ModalController, AlertController } from '@ionic/angular/standalone';
 import { Router } from '@angular/router';
 import { openUserLoginModal } from '../../lib/user/modal-user-login/modal-user-login.component';
 import { CloudUser } from '../../lib/ncloud';
 import { openUserEditModal } from '../../lib/user/modal-user-edit/modal-user-edit.component';
 import { AuthService } from '../services/auth.service';
+import { FocusDataService } from '../services/focus-data.service';
+import * as Parse from 'parse';
 
 @Component({
   selector: 'app-tab4',
@@ -27,29 +29,28 @@ import { AuthService } from '../services/auth.service';
     IonButton,
     IonCardHeader,
     IonCardTitle,
-    IonCardSubtitle
+    IonCardSubtitle,
+    IonIcon
   ]
 })
 export class Tab4Page implements OnInit {
-  currentUser: CloudUser | undefined;
+  currentUser: any;
   editTags: Array<String> = [];
+  todayStats = {
+    recordCount: 0,
+    totalDuration: 0
+  };
 
   constructor(
     private router: Router,
     private modalCtrl: ModalController,
     private alertController: AlertController,
-    private auth: AuthService
+    private auth: AuthService,
+    private focusDataService: FocusDataService
   ) {}
 
   ngOnInit() {
-    // 监听认证状态变化
-    this.auth.currentUser$.subscribe(user => {
-      if (user) {
-        this.currentUser = user as unknown as CloudUser;
-      } else {
-        this.currentUser = undefined;
-      }
-    });
+    this.currentUser = Parse.User.current();
   }
 
   goToCollection() {
@@ -145,4 +146,17 @@ export class Tab4Page implements OnInit {
   signup() {
     this.openSignupModal();
   }
+
+  async ionViewWillEnter() {
+    this.currentUser = Parse.User.current();
+    if (this.currentUser) {
+      try {
+        // 获取今日统计数据
+        this.todayStats = await this.focusDataService.getTodayStats();
+        console.log('获取到的统计数据:', this.todayStats); // 添加日志查看数据
+      } catch (error) {
+        console.error('获取今日统计失败:', error);
+      }
+    }
+  }
 }