Browse Source

互动练习与收藏题库完成

15270821319 6 months ago
parent
commit
b67c521073

+ 64 - 6
AiStudy-app/src/agent/tasks/exercise/analyze-answers.ts

@@ -1,4 +1,4 @@
-import { Component } from '@angular/core';
+import { Component, OnInit, Inject } from '@angular/core';
 import { 
   IonHeader, 
   IonToolbar, 
@@ -16,8 +16,9 @@ import { NgFor, NgIf } from '@angular/common';
 import { AgentTaskStep } from '../../agent.task';
 import { ModalController } from '@ionic/angular/standalone';
 import { AnalyzeAnswersOptions } from './types';
-import { checkmarkCircle, closeCircle } from 'ionicons/icons';
+import { checkmarkCircle, closeCircle, heart, heartOutline } from 'ionicons/icons';
 import { addIcons } from 'ionicons';
+import { ExerciseService } from '../../../app/services/exercise.service';
 
 // 分析结果展示模态框
 @Component({
@@ -55,6 +56,14 @@ import { addIcons } from 'ionicons';
             <div class="explanation">
               <p>解析:{{ exercise.explanation }}</p>
             </div>
+            <div class="actions">
+              <ion-button fill="clear" (click)="toggleFavorite(exercise)">
+                <ion-icon [name]="exercise.isFavorited ? 'heart' : 'heart-outline'"
+                         [color]="exercise.isFavorited ? 'danger' : 'medium'">
+                </ion-icon>
+                {{exercise.isFavorited ? '已收藏' : '收藏'}}
+              </ion-button>
+            </div>
           </ion-label>
         </ion-item>
       </ion-list>
@@ -77,6 +86,11 @@ import { addIcons } from 'ionicons';
       vertical-align: middle;
       margin-right: 8px;
     }
+    .actions {
+      margin-top: 8px;
+      display: flex;
+      justify-content: flex-end;
+    }
   `],
   standalone: true,
   imports: [
@@ -95,15 +109,58 @@ import { addIcons } from 'ionicons';
     NgIf
   ]
 })
-export class AnalysisModalComponent {
+export class AnalysisModalComponent implements OnInit {
   exercises: any[] = [];
   score: number = 0;
   correctRate: number = 0;
+  options!: AnalyzeAnswersOptions;
 
   constructor(
-    private modalCtrl: ModalController
+    private modalCtrl: ModalController,
+    private exerciseService: ExerciseService
   ) {
-    addIcons({ checkmarkCircle, closeCircle });
+    addIcons({ checkmarkCircle, closeCircle, heart, heartOutline });
+  }
+
+  async ngOnInit() {
+    // 确保每个练习题都有唯一ID
+    for (const exercise of this.exercises) {
+      if (!exercise.id) {
+        exercise.id = crypto.randomUUID();
+      }
+      exercise.isFavorited = false; // 默认设置为未收藏
+      try {
+        // 检查是否已收藏
+        const isFavorited = await this.exerciseService.isFavorited(exercise.id);
+        exercise.isFavorited = isFavorited;
+      } catch (error) {
+        console.error('Error checking favorite status:', error);
+      }
+    }
+  }
+
+  async toggleFavorite(exercise: any) {
+    try {
+      if (!exercise.isFavorited) {
+        const exerciseData = {
+          id: exercise.id,
+          type: exercise.type,
+          question: exercise.question,
+          options: exercise.options,
+          answer: exercise.answer,
+          explanation: exercise.explanation,
+          knowledgePoint: this.options.shareData.exerciseSettings.knowledgePoint
+        };
+        
+        await this.exerciseService.favoriteExercise(exerciseData);
+        exercise.isFavorited = true;  // 只在收藏成功后更新状态
+      } else {
+        await this.exerciseService.unfavoriteExercise(exercise.id);
+        exercise.isFavorited = false;  // 只在取消收藏成功后更新状态
+      }
+    } catch (error) {
+      console.error('Failed to toggle favorite:', error);
+    }
   }
 
   dismiss() {
@@ -139,7 +196,8 @@ export function TaskAnalyzeAnswers(options: AnalyzeAnswersOptions): AgentTaskSte
           componentProps: {
             exercises,
             score,
-            correctRate
+            correctRate,
+            options
           }
         });
 

+ 1 - 0
AiStudy-app/src/agent/tasks/exercise/generate-answer.ts

@@ -140,6 +140,7 @@ class ExerciseModalComponent {
     if (this.currentIndex < this.exercises.length - 1) {
       this.currentIndex++;
     }
+
   }
 
   isAllAnswered(): boolean {

+ 2 - 4
AiStudy-app/src/app/app.config.ts

@@ -1,14 +1,12 @@
 import { ApplicationConfig } from '@angular/core';
 import { provideRouter } from '@angular/router';
 import { routes } from './app.routes';
-import { IonicModule } from '@ionic/angular';
-import { provideHttpClient } from '@angular/common/http';
+import { provideIonicAngular } from '@ionic/angular/standalone';
 
 export const appConfig: ApplicationConfig = {
   providers: [
     provideRouter(routes),
-    provideHttpClient(),
-    { provide: IonicModule, useFactory: () => IonicModule.forRoot() }
+    provideIonicAngular()
   ]
 };
 

+ 5 - 0
AiStudy-app/src/app/app.routes.ts

@@ -40,5 +40,10 @@ export const routes: Routes = [
     path: 'interactive-practice',
     loadComponent: () => import('./pages/interactive-practice/interactive-practice.page')
       .then(m => m.InteractivePracticePage)
+  },
+  {
+    path: 'favorite-exercises',
+    loadComponent: () => import('./pages/favorite-exercises/favorite-exercises.page')
+      .then(m => m.FavoriteExercisesPage)
   }
 ];

+ 464 - 10
AiStudy-app/src/app/pages/favorite-exercises/favorite-exercises.page.ts

@@ -1,4 +1,7 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+import { AlertController, ModalController, ToastController } from '@ionic/angular';
 import {
   IonHeader,
   IonToolbar,
@@ -18,17 +21,178 @@ import {
   IonAccordionGroup,
   IonAccordion,
   IonItem,
-  IonLabel
+  IonLabel,
+  IonSelect,
+  IonSelectOption,
+  IonGrid,
+  IonRow,
+  IonCol,
+  IonFooter,
+  IonList,
+  IonCheckbox,
+  IonBadge
 } from '@ionic/angular/standalone';
 import { NgFor, NgIf } from '@angular/common';
+import { addIcons } from 'ionicons';
+import { heart, searchOutline, pencilOutline, createOutline } from 'ionicons/icons';
 import { ExerciseService } from '../../services/exercise.service';
 
 @Component({
   selector: 'app-favorite-exercises',
-  templateUrl: './favorite-exercises.page.html',
-  styleUrls: ['./favorite-exercises.page.scss'],
   standalone: true,
+  template: `
+    <ion-header>
+      <ion-toolbar>
+        <ion-buttons slot="start">
+          <ion-back-button defaultHref="/tabs/tab2"></ion-back-button>
+        </ion-buttons>
+        <ion-title>收藏题库</ion-title>
+      </ion-toolbar>
+    </ion-header>
+
+    <ion-content>
+      <ion-refresher slot="fixed" (ionRefresh)="doRefresh($event)">
+        <ion-refresher-content></ion-refresher-content>
+      </ion-refresher>
+
+      <!-- 知识点筛选区域 -->
+      <div class="filter-section ion-padding">
+        <ion-item>
+          <ion-label>知识点筛选</ion-label>
+          <ion-select [(ngModel)]="selectedKnowledgePoint" 
+                     (ionChange)="filterExercises()"
+                     interface="action-sheet"
+                     placeholder="选择或输入知识点">
+            <ion-select-option value="">全部</ion-select-option>
+            <ion-select-option *ngFor="let point of knowledgePoints" 
+                             [value]="point">
+              {{point}}
+            </ion-select-option>
+          </ion-select>
+          <ion-button fill="clear" size="small" (click)="showCustomKnowledgePointInput()">
+            <ion-icon name="search-outline"></ion-icon>
+          </ion-button>
+          <ion-button fill="clear" size="small" (click)="toggleEditMode()">
+            <ion-icon name="create-outline"></ion-icon>
+          </ion-button>
+        </ion-item>
+      </div>
+
+      <!-- 题目列表 -->
+      <ion-list>
+        <ion-item *ngFor="let exercise of currentPageExercises">
+          <ion-checkbox *ngIf="isEditMode" 
+                       slot="start"
+                       [(ngModel)]="exercise.selected">
+          </ion-checkbox>
+          <ion-card class="exercise-card">
+            <ion-card-header>
+              <ion-chip color="primary" (click)="editKnowledgePoint(exercise)">
+                {{exercise.knowledgePoint}}
+                <ion-icon name="pencil-outline"></ion-icon>
+              </ion-chip>
+              <ion-button fill="clear" slot="end" (click)="confirmUnfavorite(exercise)">
+                <ion-icon name="heart" color="danger"></ion-icon>
+              </ion-button>
+            </ion-card-header>
+            
+            <ion-card-content>
+              <ion-accordion-group>
+                <ion-accordion>
+                  <ion-item slot="header">
+                    <ion-label>{{exercise.question}}</ion-label>
+                  </ion-item>
+                  <div slot="content" class="answer-section">
+                    <p><strong>答案:</strong>{{exercise.answer}}</p>
+                    <p><strong>解析:</strong>{{exercise.explanation}}</p>
+                  </div>
+                </ion-accordion>
+              </ion-accordion-group>
+            </ion-card-content>
+          </ion-card>
+        </ion-item>
+      </ion-list>
+
+      <!-- 编辑模式下的底部工具栏 -->
+      <ion-footer *ngIf="isEditMode">
+        <ion-toolbar>
+          <ion-buttons slot="start">
+            <ion-button (click)="selectAll()">
+              全选
+            </ion-button>
+          </ion-buttons>
+          <ion-buttons slot="end">
+            <ion-button color="danger" (click)="confirmDeleteSelected()">
+              删除
+              <ion-badge *ngIf="getSelectedCount() > 0" color="danger">
+                {{getSelectedCount()}}
+              </ion-badge>
+            </ion-button>
+          </ion-buttons>
+        </ion-toolbar>
+      </ion-footer>
+
+      <!-- 分页控制,仅在非编辑模式下显示 -->
+      <ion-footer *ngIf="!isEditMode" class="ion-padding">
+        <ion-grid>
+          <ion-row>
+            <ion-col>
+              <ion-button expand="block" 
+                        [disabled]="currentPage === 1"
+                        (click)="previousPage()">
+                上一页
+              </ion-button>
+            </ion-col>
+            <ion-col class="ion-text-center">
+              <ion-text>{{currentPage}} / {{totalPages}}</ion-text>
+            </ion-col>
+            <ion-col>
+              <ion-button expand="block" 
+                        [disabled]="currentPage === totalPages"
+                        (click)="nextPage()">
+                下一页
+              </ion-button>
+            </ion-col>
+          </ion-row>
+        </ion-grid>
+      </ion-footer>
+    </ion-content>
+  `,
+  styles: [`
+    .filter-section {
+      background: var(--ion-color-light);
+      margin-bottom: 16px;
+    }
+
+    .exercise-card {
+      width: 100%;
+      margin: 8px 0;
+    }
+
+    .answer-section {
+      padding: 16px;
+      background: var(--ion-color-light);
+    }
+
+    ion-chip {
+      margin: 0;
+    }
+
+    ion-accordion {
+      margin: 8px 0;
+    }
+
+    ion-checkbox {
+      margin-right: 8px;
+    }
+
+    ion-badge {
+      margin-left: 4px;
+    }
+  `],
   imports: [
+    CommonModule,
+    FormsModule,
     IonHeader,
     IonToolbar,
     IonTitle,
@@ -48,21 +212,311 @@ import { ExerciseService } from '../../services/exercise.service';
     IonAccordion,
     IonItem,
     IonLabel,
+    IonSelect,
+    IonSelectOption,
+    IonGrid,
+    IonRow,
+    IonCol,
+    IonFooter,
+    IonList,
+    IonCheckbox,
+    IonBadge,
     NgFor,
     NgIf
+  ],
+  providers: [
+    ModalController
   ]
 })
-export class FavoriteExercisesPage {
-  favorites: any[] = [];
+export class FavoriteExercisesPage implements OnInit {
+  currentPageExercises: any[] = [];
+  knowledgePoints: string[] = [];
+  selectedKnowledgePoint: string = '';
+  currentPage: number = 1;
+  totalPages: number = 1;
+  pageSize: number = 10;
+  allExercises: any[] = [];
+  isEditMode: boolean = false;
+  isSearchVisible: boolean = false;
+
+  constructor(
+    private modalCtrl: ModalController,
+    private alertController: AlertController,
+    private toastController: ToastController,
+    private exerciseService: ExerciseService
+  ) {
+    addIcons({ heart, searchOutline, pencilOutline, createOutline });
+  }
+
+  async ngOnInit() {
+    await this.loadFavorites();
+  }
+
+  async loadFavorites() {
+    try {
+      console.log('开始加载收藏题目');
+      const favorites = await this.exerciseService.getFavoriteExercises();
+      console.log('获取到的收藏题目:', favorites);
+      
+      this.knowledgePoints = [...new Set(favorites.map(f => 
+        f.customKnowledgePoint || f.knowledgePoint
+      ))];
+
+      this.allExercises = favorites.map(f => ({
+        id: f.id,
+        exerciseId: f.exerciseId,
+        question: f.exercise.question,
+        answer: f.exercise.answer,
+        explanation: f.exercise.explanation,
+        knowledgePoint: f.customKnowledgePoint || f.knowledgePoint,
+        type: f.exercise.type,
+        options: f.exercise.options
+      }));
+
+      console.log('处理后的题目:', this.allExercises);
+      this.updatePagination();
+    } catch (error) {
+      console.error('Failed to load favorites:', error);
+      const alert = await this.alertController.create({
+        header: '错误',
+        message: '加载收藏题目失败,请重试',
+        buttons: ['确定']
+      });
+      await alert.present();
+    }
+  }
+
+  updatePagination() {
+    const filteredExercises = this.selectedKnowledgePoint
+      ? this.allExercises.filter(e => e.knowledgePoint === this.selectedKnowledgePoint)
+      : this.allExercises;
+
+    this.totalPages = Math.ceil(filteredExercises.length / this.pageSize);
+    this.currentPage = Math.min(this.currentPage, this.totalPages);
+    
+    const start = (this.currentPage - 1) * this.pageSize;
+    this.currentPageExercises = filteredExercises.slice(start, start + this.pageSize);
+  }
+
+  previousPage() {
+    if (this.currentPage > 1) {
+      this.currentPage--;
+      this.updatePagination();
+    }
+  }
+
+  nextPage() {
+    if (this.currentPage < this.totalPages) {
+      this.currentPage++;
+      this.updatePagination();
+    }
+  }
+
+  filterExercises() {
+    this.currentPage = 1;
+    this.updatePagination();
+  }
 
-  constructor(private exerciseService: ExerciseService) {}
+  async showCustomKnowledgePointInput() {
+    const alert = await this.alertController.create({
+      header: '搜索题目类别',
+      inputs: [
+        {
+          name: 'knowledgePoint',
+          type: 'text',
+          placeholder: '请输入题目类别'
+        }
+      ],
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '确定',
+          handler: (data) => {
+            if (data.knowledgePoint?.trim()) {
+              this.selectedKnowledgePoint = data.knowledgePoint.trim();
+              this.filterExercises();
+            }
+          }
+        }
+      ]
+    });
+
+    await alert.present();
+  }
+
+  async confirmUnfavorite(exercise: any) {
+    const alert = await this.alertController.create({
+      header: '取消收藏',
+      message: '确定要取消收藏这道题目吗?',
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '确定',
+          handler: async () => {
+            try {
+              await this.exerciseService.unfavoriteExercise(exercise.exerciseId);
+              await this.loadFavorites();
+              const toast = await this.toastController.create({
+                message: '已取消收藏',
+                duration: 2000,
+                position: 'bottom'
+              });
+              await toast.present();
+            } catch (error) {
+              console.error('Failed to unfavorite exercise:', error);
+              const errorAlert = await this.alertController.create({
+                header: '错误',
+                message: '取消收藏失败,请重试',
+                buttons: ['确定']
+              });
+              await errorAlert.present();
+            }
+          }
+        }
+      ]
+    });
+
+    await alert.present();
+  }
+
+  async editKnowledgePoint(exercise: any) {
+    const alert = await this.alertController.create({
+      header: '编辑知识点',
+      inputs: [
+        {
+          name: 'knowledgePoint',
+          type: 'text',
+          value: exercise.knowledgePoint,
+          placeholder: '输入新的知识点'
+        }
+      ],
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '保存',
+          handler: async (data) => {
+            if (data.knowledgePoint?.trim()) {
+              try {
+                await this.exerciseService.updateKnowledgePoint(exercise.id, data.knowledgePoint.trim());
+                exercise.knowledgePoint = data.knowledgePoint.trim();
+                await this.loadFavorites();
+                const toast = await this.toastController.create({
+                  message: '知识点已更新',
+                  duration: 2000,
+                  position: 'bottom'
+                });
+                await toast.present();
+              } catch (error) {
+                console.error('Failed to update knowledge point:', error);
+                const errorAlert = await this.alertController.create({
+                  header: '错误',
+                  message: '更新知识点失败,请重试',
+                  buttons: ['确定']
+                });
+                await errorAlert.present();
+              }
+            }
+          }
+        }
+      ]
+    });
+
+    await alert.present();
+  }
 
   async doRefresh(event: any) {
-    // TODO: 实现刷新逻辑
+    await this.loadFavorites();
     event.target.complete();
   }
 
-  async removeFavorite(item: any) {
-    // TODO: 实现取消收藏逻辑
+  // 切换编辑模式
+  toggleEditMode() {
+    this.isEditMode = !this.isEditMode;
+    if (!this.isEditMode) {
+      // 退出编辑模式时清除选择
+      this.clearSelection();
+    }
+  }
+
+  // 切换搜索
+  toggleSearch() {
+    this.isSearchVisible = !this.isSearchVisible;
+  }
+
+  // 全选
+  selectAll() {
+    const allSelected = this.currentPageExercises.every(e => e.selected);
+    this.currentPageExercises.forEach(e => e.selected = !allSelected);
+  }
+
+  // 获取选中数量
+  getSelectedCount(): number {
+    return this.currentPageExercises.filter(e => e.selected).length;
+  }
+
+  // 清除选择
+  clearSelection() {
+    this.currentPageExercises.forEach(e => e.selected = false);
+  }
+
+  // 确认删除选中项
+  async confirmDeleteSelected() {
+    const selectedCount = this.getSelectedCount();
+    if (selectedCount === 0) return;
+
+    const alert = await this.alertController.create({
+      header: '确认删除',
+      message: `确定要删除选中的 ${selectedCount} 道题目吗?`,
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '删除',
+          role: 'destructive',
+          handler: () => this.deleteSelected()
+        }
+      ]
+    });
+
+    await alert.present();
+  }
+
+  // 删除选中项
+  async deleteSelected() {
+    const selectedExercises = this.currentPageExercises.filter(e => e.selected);
+    try {
+      for (const exercise of selectedExercises) {
+        await this.exerciseService.unfavoriteExercise(exercise.exerciseId);
+      }
+      
+      await this.loadFavorites();
+      this.isEditMode = false;
+      
+      const toast = await this.toastController.create({
+        message: '删除成功',
+        duration: 2000,
+        position: 'bottom'
+      });
+      await toast.present();
+    } catch (error) {
+      console.error('Failed to delete exercises:', error);
+      const alert = await this.alertController.create({
+        header: '错误',
+        message: '删除失败,请重试',
+        buttons: ['确定']
+      });
+      await alert.present();
+    }
   }
 } 

+ 2 - 1
AiStudy-app/src/app/pages/interactive-practice/interactive-practice.page.ts

@@ -93,7 +93,8 @@ import {
     IonButton,
     NgFor,
     NgIf
-  ]
+  ],
+  providers: [ModalController]
 })
 export class InteractivePracticePage implements OnInit {
   isComplete: boolean = false;

+ 136 - 106
AiStudy-app/src/app/services/exercise.service.ts

@@ -16,39 +16,21 @@ export interface ExerciseData {
   }>;
 }
 
-export class Exercise extends CloudObject {
-  static className = 'Exercise';
-  
-  constructor() {
-    super(Exercise.className);
-  }
-  
-  getData(): ExerciseData {
-    return {
-      sessionId: this.get('sessionId'),
-      userId: this.get('userId'),
-      knowledgePoint: this.get('knowledgePoint'),
-      exercises: this.get('exercises')
-    };
-  }
-
-  // 修改 setData 方法,使用 set
-  setData(data: Partial<ExerciseData>): void {
-    this.set(data);  // CloudObject.set 接收一个对象参数
-  }
-}
-
-export class Favorite extends CloudObject {
-  static className = 'Favorite';
-  
-  constructor() {
-    super(Favorite.className);
-  }
-
-  // 修改 setData 方法,使用 set
-  setData(data: { userId: string; exerciseId: string }): void {
-    this.set(data);  // CloudObject.set 接收一个对象参数
-  }
+// 收藏题目的数据结构
+export interface FavoriteExerciseData {
+  id: string;
+  userId: string;
+  exerciseId: string;
+  knowledgePoint: string;
+  customKnowledgePoint?: string;
+  exercise: {
+    type: string;
+    question: string;
+    options?: string[];
+    answer: string;
+    explanation: string;
+  };
+  createdAt: Date;
 }
 
 @Injectable({
@@ -57,104 +39,152 @@ export class Favorite extends CloudObject {
 export class ExerciseService {
   constructor() {}
 
-  // 保存练习题
-  async saveExercise(exerciseData: Partial<ExerciseData>): Promise<Exercise> {
-    const exercise = new Exercise();
-    
-    // 设置当前用户
-    const currentUser = new CloudUser();
-    if (!currentUser.id) throw new Error('User not logged in');
-    exerciseData.userId = currentUser.id;
-    
-    // 保存数据
-    exercise.setData(exerciseData);
-    
-    await exercise.save();
-    return exercise;
-  }
-
-  // 获取用户的练习记录
-  async getUserExercises(): Promise<Exercise[]> {
+  // 获取收藏的练习题
+  async getFavoriteExercises(): Promise<FavoriteExerciseData[]> {
     const currentUser = new CloudUser();
     if (!currentUser.id) throw new Error('User not logged in');
     
-    const query = new CloudQuery(Exercise.className);
+    const query = new CloudQuery('FavoriteExercise');
     query.equalTo('userId', currentUser.id);
     
-    // 修改:在 where 条件中添加排序
-    if (!query.queryParams["where"]) {
-      query.queryParams["where"] = {};
-    }
-    query.queryParams["order"] = "-createdAt";  // 使用 order 参数进行排序
+    query.queryParams = {
+      ...query.queryParams,
+      order: '-createdAt',
+      limit: 1000
+    };
     
-    const results = await query.find();
-    return results.map((result: CloudObject) => Object.assign(new Exercise(), result));
+    try {
+      const results = await query.find();
+      console.log('从数据库获取到的收藏记录:', results);
+      
+      return results
+        .filter(result => result.id !== null)  // 过滤掉没有 id 的记录
+        .map(result => ({
+          id: result.id!,  // 使用非空断言,因为已经过滤掉了 null
+          userId: result.get('userId') as string,
+          exerciseId: result.get('exerciseId') as string,
+          knowledgePoint: result.get('knowledgePoint') as string,
+          customKnowledgePoint: result.get('customKnowledgePoint') as string | undefined,
+          exercise: result.get('exercise') as {
+            type: string;
+            question: string;
+            options?: string[];
+            answer: string;
+            explanation: string;
+          },
+          createdAt: result.get('createdAt') as Date
+        }));
+    } catch (error) {
+      console.error('获取收藏题目失败:', error);
+      throw error;
+    }
   }
 
   // 收藏练习题
-  async favoriteExercise(exerciseId: string): Promise<Favorite> {
-    const favorite = new Favorite();
-    
+  async favoriteExercise(exercise: {
+    id: string;
+    type: string;
+    question: string;
+    options?: string[];
+    answer: string;
+    explanation: string;
+    knowledgePoint: string;
+  }): Promise<void> {
     const currentUser = new CloudUser();
     if (!currentUser.id) throw new Error('User not logged in');
-    
-    favorite.setData({
-      userId: currentUser.id,
-      exerciseId
-    });
-    
-    await favorite.save();
-    return favorite;
-  }
 
-  // 获取收藏的练习题
-  async getFavoriteExercises(): Promise<Exercise[]> {
-    const currentUser = new CloudUser();
-    if (!currentUser.id) throw new Error('User not logged in');
-    
-    // 先获取收藏记录
-    const favoriteQuery = new CloudQuery(Favorite.className);
-    favoriteQuery.equalTo('userId', currentUser.id);
-    const favorites = await favoriteQuery.find();
-    
-    // 获取对应的练习题
-    const exerciseIds = favorites.map((favorite: CloudObject) => favorite.get('exerciseId'));
-    if (exerciseIds.length === 0) return [];
-    
-    const exerciseQuery = new CloudQuery(Exercise.className);
-    // 修改:在 where 条件中添加 $in 查询
-    if (!exerciseQuery.queryParams["where"]) {
-      exerciseQuery.queryParams["where"] = {};
+    try {
+      // 检查是否已收藏
+      const isAlreadyFavorited = await this.isFavorited(exercise.id);
+      if (isAlreadyFavorited) {
+        console.log('题目已经收藏过了:', exercise.id);
+        return;
+      }
+
+      // 创建新的收藏记录
+      const favorite = new CloudObject('FavoriteExercise');
+      favorite.set({
+        userId: currentUser.id,
+        exerciseId: exercise.id,
+        knowledgePoint: exercise.knowledgePoint,
+        exercise: {
+          type: exercise.type,
+          question: exercise.question,
+          options: exercise.options,
+          answer: exercise.answer,
+          explanation: exercise.explanation
+        },
+        createdAt: new Date()
+      });
+
+      await favorite.save();
+      console.log('成功保存收藏:', exercise.id);
+    } catch (error) {
+      console.error('收藏题目失败:', error);
+      throw error;
     }
-    exerciseQuery.queryParams["where"]["objectId"] = { "$in": exerciseIds };
-    
-    const results = await exerciseQuery.find();
-    return results.map((result: CloudObject) => Object.assign(new Exercise(), result));
   }
 
   // 检查是否已收藏
   async isFavorited(exerciseId: string): Promise<boolean> {
     const currentUser = new CloudUser();
-    if (!currentUser.id) throw new Error('User not logged in');
+    if (!currentUser.id) return false;
     
-    const query = new CloudQuery(Favorite.className);
-    query.equalTo('userId', currentUser.id);
-    query.equalTo('exerciseId', exerciseId);
-    const results = await query.find();
-    return results.length > 0;
+    try {
+      const query = new CloudQuery('FavoriteExercise');
+      query.equalTo('userId', currentUser.id);
+      query.equalTo('exerciseId', exerciseId);
+      
+      const result = await query.first();
+      return !!result;
+    } catch (error) {
+      console.error('检查收藏状态失败:', error);
+      return false;
+    }
   }
 
   // 取消收藏
   async unfavoriteExercise(exerciseId: string): Promise<void> {
     const currentUser = new CloudUser();
     if (!currentUser.id) throw new Error('User not logged in');
-    
-    const query = new CloudQuery(Favorite.className);
-    query.equalTo('userId', currentUser.id);
-    query.equalTo('exerciseId', exerciseId);
-    const favorite = await query.first();
-    if (favorite) {
-      await favorite.destroy();
+
+    try {
+      const query = new CloudQuery('FavoriteExercise');
+      query.equalTo('userId', currentUser.id);
+      query.equalTo('exerciseId', exerciseId);
+
+      const favorite = await query.first();
+      if (favorite) {
+        await favorite.destroy();
+        console.log('成功取消收藏:', exerciseId);
+      }
+    } catch (error) {
+      console.error('取消收藏失败:', error);
+      throw error;
+    }
+  }
+
+  // 更新知识点
+  async updateKnowledgePoint(exerciseId: string, knowledgePoint: string): Promise<void> {
+    const currentUser = new CloudUser();
+    if (!currentUser.id) throw new Error('User not logged in');
+
+    try {
+      const query = new CloudQuery('FavoriteExercise');
+      query.equalTo('userId', currentUser.id);
+      query.equalTo('exerciseId', exerciseId);
+
+      const favorite = await query.first();
+      if (favorite) {
+        favorite.set({
+          customKnowledgePoint: knowledgePoint
+        });
+        await favorite.save();
+        console.log('成功更新知识点:', exerciseId);
+      }
+    } catch (error) {
+      console.error('更新知识点失败:', error);
+      throw error;
     }
   }
 } 

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

@@ -60,7 +60,7 @@
       <ion-col size="6">
         <ion-card class="feature-card" (click)="navigateToFeature('favorites')">
           <ion-card-content class="ion-text-center">
-            <ion-icon name="star" color="warning"></ion-icon>
+            <ion-icon name="heart" color="danger"></ion-icon>
             <h3>收藏题库</h3>
             <p>复习重点内容</p>
           </ion-card-content>

+ 5 - 3
AiStudy-app/src/app/tab2/tab2.page.ts

@@ -25,7 +25,8 @@ import {
   bulb, 
   barChart, 
   people, 
-  star 
+  star,
+  heart  // 添加 heart 图标
 } from 'ionicons/icons';
 
 interface Notification {
@@ -71,7 +72,8 @@ export class Tab2Page implements OnInit {
       bulb, 
       barChart, 
       people, 
-      star 
+      star,
+      heart  // 添加 heart 图标
     });
   }
 
@@ -101,7 +103,7 @@ export class Tab2Page implements OnInit {
         this.router.navigate(['/interactive-practice']);
         break;
       case 'favorites':
-        this.router.navigate(['/favorites']);
+        this.router.navigate(['/tabs/favorite-exercises']);
         break;
       default:
         console.log(`Navigating to ${feature}`);

+ 5 - 0
AiStudy-app/src/app/tabs/tabs.routes.ts

@@ -21,6 +21,11 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../tab3/tab3.page').then((m) => m.Tab3Page),
       },
+      {
+        path: 'favorite-exercises',
+        loadComponent: () =>
+          import('../pages/favorite-exercises/favorite-exercises.page').then((m) => m.FavoriteExercisesPage),
+      },
       {
         path: '',
         redirectTo: '/tabs/tab1',