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