|
@@ -12,16 +12,13 @@ import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { Router } from '@angular/router';
|
|
import { Router } from '@angular/router';
|
|
|
import { WordDetailService } from '../word-detail/word-detail.service';
|
|
import { WordDetailService } from '../word-detail/word-detail.service';
|
|
|
-import {
|
|
|
|
|
- IonButton,
|
|
|
|
|
- IonContent,
|
|
|
|
|
- IonHeader,
|
|
|
|
|
- IonToolbar,
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ IonButton,
|
|
|
|
|
+ IonContent,
|
|
|
IonSpinner,
|
|
IonSpinner,
|
|
|
IonIcon,
|
|
IonIcon,
|
|
|
- IonButtons,
|
|
|
|
|
AlertController,
|
|
AlertController,
|
|
|
- NavController
|
|
|
|
|
|
|
+ NavController
|
|
|
} from '@ionic/angular/standalone';
|
|
} from '@ionic/angular/standalone';
|
|
|
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
|
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
|
|
import { addIcons } from 'ionicons';
|
|
import { addIcons } from 'ionicons';
|
|
@@ -43,6 +40,7 @@ import { PronunciationButtonComponent } from '../pronunciation-button/pronunciat
|
|
|
import { WordDetailComponent } from '../word-detail/word-detail.component';
|
|
import { WordDetailComponent } from '../word-detail/word-detail.component';
|
|
|
import { WordProcessingService } from './word-processing.service';
|
|
import { WordProcessingService } from './word-processing.service';
|
|
|
import { WordGroupService } from './word-group.service';
|
|
import { WordGroupService } from './word-group.service';
|
|
|
|
|
+import { WordProgressStorageService } from './word-progress-storage.service';
|
|
|
import { Word, WordOption } from './word-interfaces';
|
|
import { Word, WordOption } from './word-interfaces';
|
|
|
import { finalize } from 'rxjs/operators'; // 导入finalize操作符
|
|
import { finalize } from 'rxjs/operators'; // 导入finalize操作符
|
|
|
|
|
|
|
@@ -66,10 +64,15 @@ addIcons({
|
|
|
styleUrls: ['./recite-words.page.scss'],
|
|
styleUrls: ['./recite-words.page.scss'],
|
|
|
standalone: true,
|
|
standalone: true,
|
|
|
imports: [
|
|
imports: [
|
|
|
- IonContent, IonHeader, IonToolbar,
|
|
|
|
|
- CommonModule, FormsModule, IonButton, HttpClientModule,
|
|
|
|
|
- IonSpinner, IonIcon, IonButtons, PronunciationButtonComponent,
|
|
|
|
|
- WordDetailComponent,
|
|
|
|
|
|
|
+ IonContent,
|
|
|
|
|
+ CommonModule,
|
|
|
|
|
+ FormsModule,
|
|
|
|
|
+ IonButton,
|
|
|
|
|
+ HttpClientModule,
|
|
|
|
|
+ IonSpinner,
|
|
|
|
|
+ IonIcon,
|
|
|
|
|
+ PronunciationButtonComponent,
|
|
|
|
|
+ WordDetailComponent
|
|
|
],
|
|
],
|
|
|
providers: [WordProcessingService, WordGroupService,WordDetailService]
|
|
providers: [WordProcessingService, WordGroupService,WordDetailService]
|
|
|
})
|
|
})
|
|
@@ -116,6 +119,13 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
|
|
|
|
|
wordOptions: WordOption[] | null = null; // 当前显示的选项列表
|
|
wordOptions: WordOption[] | null = null; // 当前显示的选项列表
|
|
|
|
|
|
|
|
|
|
+ /** 云端进度:当前登录用户 id */
|
|
|
|
|
+ private dbUserId: string | null = null;
|
|
|
|
|
+ /** 云端进度:wordList.objectId */
|
|
|
|
|
+ private dbListObjectId: string | null = null;
|
|
|
|
|
+ /** 云端进度:复习间隔(天),来自 wordReviewLadderPreset */
|
|
|
|
|
+ private dbIntervalDays: number[] = [];
|
|
|
|
|
+
|
|
|
constructor(
|
|
constructor(
|
|
|
private http: HttpClient, // HTTP客户端,用于加载单词文件
|
|
private http: HttpClient, // HTTP客户端,用于加载单词文件
|
|
|
private router: Router, // 路由服务,用于页面导航
|
|
private router: Router, // 路由服务,用于页面导航
|
|
@@ -125,6 +135,7 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
private wordGroupService: WordGroupService, // 单词组服务(管理学习进度)
|
|
private wordGroupService: WordGroupService, // 单词组服务(管理学习进度)
|
|
|
private navCtrl: NavController, // 导航控制器,用于页面导航
|
|
private navCtrl: NavController, // 导航控制器,用于页面导航
|
|
|
private wordDetailService: WordDetailService, // 单词详情服务(获取单词详细信息)
|
|
private wordDetailService: WordDetailService, // 单词详情服务(获取单词详细信息)
|
|
|
|
|
+ private wordProgressStorage: WordProgressStorageService,
|
|
|
) {}
|
|
) {}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -150,6 +161,11 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 进度在作答当下保存;离开页面不再承担写库职责。
|
|
|
|
|
+ */
|
|
|
|
|
+ async ionViewWillLeave() {}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Angular 生命周期钩子:视图初始化完成后调用
|
|
* Angular 生命周期钩子:视图初始化完成后调用
|
|
|
* 滚动页面到顶部
|
|
* 滚动页面到顶部
|
|
@@ -236,10 +252,6 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
* @param option 选项对象
|
|
* @param option 选项对象
|
|
|
* @returns 颜色字符串
|
|
* @returns 颜色字符串
|
|
|
*/
|
|
*/
|
|
|
- getOptionColor(option: WordOption): string {
|
|
|
|
|
- return 'medium';
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 获取选项词性
|
|
* 获取选项词性
|
|
|
* @param option 选项对象
|
|
* @param option 选项对象
|
|
@@ -297,6 +309,8 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
console.warn('未知的学习模式:', studyCount);
|
|
console.warn('未知的学习模式:', studyCount);
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ this.persistAnsweredProgressNow();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -392,6 +406,7 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
this.selectedOption = this.currentWord?.options.find((opt: WordOption) => opt.isCorrect) || null;
|
|
this.selectedOption = this.currentWord?.options.find((opt: WordOption) => opt.isCorrect) || null;
|
|
|
|
|
+ this.persistAnsweredProgressNow();
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
if (this.pronunciationButton) {
|
|
if (this.pronunciationButton) {
|
|
@@ -452,6 +467,7 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
this.isAnswered = true;
|
|
this.isAnswered = true;
|
|
|
this.isCorrect = know; // 认识视为正确,不认识视为错误
|
|
this.isCorrect = know; // 认识视为正确,不认识视为错误
|
|
|
this.showWordDetail = true; // 直接显示单词详情
|
|
this.showWordDetail = true; // 直接显示单词详情
|
|
|
|
|
+ this.persistAnsweredProgressNow();
|
|
|
// 延迟播放发音
|
|
// 延迟播放发音
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
if (this.pronunciationButton) {
|
|
if (this.pronunciationButton) {
|
|
@@ -462,20 +478,68 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ==================== 通用导航方法 ====================
|
|
// ==================== 通用导航方法 ====================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 作答当下立即持久化(不依赖「下一个」或离开页面)。
|
|
|
|
|
+ * 注意:这里用“模拟更新后的 studyCount”写库,不改当前页面展示状态。
|
|
|
|
|
+ */
|
|
|
|
|
+ private persistAnsweredProgressNow(): void {
|
|
|
|
|
+ if (!this.currentWord || !this.isAnswered) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.dbUserId || !this.dbListObjectId || this.dbIntervalDays.length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const wordRef = this.currentWord;
|
|
|
|
|
+ const prevStudyCount = wordRef.studyCount;
|
|
|
|
|
+ const nextStudyCount = this.isCorrect ? Math.min(prevStudyCount + 1, 3) : 0;
|
|
|
|
|
+
|
|
|
|
|
+ const saveWord: Word = {
|
|
|
|
|
+ ...wordRef,
|
|
|
|
|
+ studyCount: nextStudyCount,
|
|
|
|
|
+ isMastered: nextStudyCount >= 3
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.wordProgressStorage
|
|
|
|
|
+ .upsertWordProgress(
|
|
|
|
|
+ saveWord,
|
|
|
|
|
+ this.dbUserId,
|
|
|
|
|
+ this.dbListObjectId,
|
|
|
|
|
+ this.dbIntervalDays,
|
|
|
|
|
+ prevStudyCount
|
|
|
|
|
+ )
|
|
|
|
|
+ .then(() => {
|
|
|
|
|
+ // 回写 objectId,后续写入走 update 而不是重复 create
|
|
|
|
|
+ if (this.currentWord === wordRef && saveWord.progressObjectId) {
|
|
|
|
|
+ this.currentWord.progressObjectId = saveWord.progressObjectId;
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((e) => console.error('[WordProgress] 作答即时保存失败', e));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 仅当当前词已作答(选题 / 认识·不认识 / 「看答案」)时,按结果更新 studyCount。
|
|
|
|
|
+ * @returns 更新前的 studyCount 与词引用,供云端 upsert;未作答返回 null。
|
|
|
|
|
+ */
|
|
|
|
|
+ private applyAnsweredWordStudyUpdate(): { wordRef: Word; prevStudyCount: number } | null {
|
|
|
|
|
+ if (!this.currentWord || !this.isAnswered) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ const wordRef = this.currentWord;
|
|
|
|
|
+ const prevStudyCount = wordRef.studyCount;
|
|
|
|
|
+ this.wordGroupService.updateWordStudyCount(wordRef, this.isCorrect);
|
|
|
|
|
+ return { wordRef, prevStudyCount };
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 进入下一个学习阶段
|
|
* 进入下一个学习阶段
|
|
|
* 处理从一个单词到下一个单词的过渡
|
|
* 处理从一个单词到下一个单词的过渡
|
|
|
*/
|
|
*/
|
|
|
goToNextStudy() {
|
|
goToNextStudy() {
|
|
|
- // 在进入下一个单词前,更新当前单词的学习次数
|
|
|
|
|
- if (this.currentWord) {
|
|
|
|
|
- // 根据当前回答是否正确来更新学习次数
|
|
|
|
|
- if (this.isCorrect !== undefined) {
|
|
|
|
|
- this.wordGroupService.updateWordStudyCount(this.currentWord!, this.isCorrect);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // 云端已在作答当下保存;这里只做本地学习次数更新和切词
|
|
|
|
|
+ this.applyAnsweredWordStudyUpdate();
|
|
|
|
|
+
|
|
|
// 重置状态
|
|
// 重置状态
|
|
|
this.resetCurrentWordState();
|
|
this.resetCurrentWordState();
|
|
|
|
|
|
|
@@ -490,13 +554,7 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
|
|
|
|
|
this.loadWordDetails();// 加载单词详情
|
|
this.loadWordDetails();// 加载单词详情
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- // 手动触发重新播放发音
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- if (this.pronunciationButton) {
|
|
|
|
|
- this.pronunciationButton.replayAudio();
|
|
|
|
|
- }
|
|
|
|
|
- }, 100);
|
|
|
|
|
|
|
+ // 发音由 app-pronunciation-button 在 [word] 变化时自动播放,此处再 replay 会与子组件定时播放叠成「重音」
|
|
|
} else {
|
|
} else {
|
|
|
// 所有单词都完成了
|
|
// 所有单词都完成了
|
|
|
this.completeGroup();
|
|
this.completeGroup();
|
|
@@ -603,12 +661,12 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 加载单词
|
|
* 加载单词
|
|
|
- * 从assets/words.txt文件加载单词数据
|
|
|
|
|
|
|
+ * 从assets/words_cet6.txt文件加载单词数据
|
|
|
*/
|
|
*/
|
|
|
loadWords() {
|
|
loadWords() {
|
|
|
this.isLoading = true;
|
|
this.isLoading = true;
|
|
|
- this.http.get('assets/words.txt', { responseType: 'text' }).subscribe({
|
|
|
|
|
- next: (data) => {
|
|
|
|
|
|
|
+ this.http.get('assets/words_cet6.txt', { responseType: 'text' }).subscribe({
|
|
|
|
|
+ next: async (data) => {
|
|
|
console.log('成功加载单词文件');
|
|
console.log('成功加载单词文件');
|
|
|
|
|
|
|
|
// 使用服务解析单词
|
|
// 使用服务解析单词
|
|
@@ -635,6 +693,8 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
this.words = validWords;
|
|
this.words = validWords;
|
|
|
|
|
+
|
|
|
|
|
+ await this.mergeServerProgressIntoWords();
|
|
|
|
|
|
|
|
if (this.words.length > 0) {
|
|
if (this.words.length > 0) {
|
|
|
console.log(`成功为 ${this.words.length} 个单词生成有效选项`);
|
|
console.log(`成功为 ${this.words.length} 个单词生成有效选项`);
|
|
@@ -651,6 +711,7 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
|
|
|
|
|
this.isLoading = false;
|
|
this.isLoading = false;
|
|
|
this.scrollToTop();
|
|
this.scrollToTop();
|
|
|
|
|
+ this.cdr.detectChanges();
|
|
|
},
|
|
},
|
|
|
error: (err) => {
|
|
error: (err) => {
|
|
|
console.error('加载单词文件失败:', err);
|
|
console.error('加载单词文件失败:', err);
|
|
@@ -661,6 +722,74 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 已登录且库中存在 wordList(assetKey=words_cet6) 时,合并 wordUserProgress 到 this.words
|
|
|
|
|
+ */
|
|
|
|
|
+ private async mergeServerProgressIntoWords(): Promise<void> {
|
|
|
|
|
+ this.dbUserId = await this.wordProgressStorage.getCurrentUserId();
|
|
|
|
|
+ this.dbListObjectId = null;
|
|
|
|
|
+ this.dbIntervalDays = [];
|
|
|
|
|
+
|
|
|
|
|
+ if (!this.dbUserId) {
|
|
|
|
|
+ console.log('[WordProgress] 未登录,不同步云端进度');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const ctx = await this.wordProgressStorage.resolveWordListContext();
|
|
|
|
|
+ if (!ctx) {
|
|
|
|
|
+ console.warn('[WordProgress] 未配置 wordList 或查询失败,不同步云端进度');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.dbListObjectId = ctx.listObjectId;
|
|
|
|
|
+ this.dbIntervalDays = ctx.intervalDays;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const map = await this.wordProgressStorage.loadProgressByList(this.dbUserId, this.dbListObjectId);
|
|
|
|
|
+ for (const w of this.words) {
|
|
|
|
|
+ const row = map.get(w.word.trim().toLowerCase());
|
|
|
|
|
+ if (row) {
|
|
|
|
|
+ this.wordProgressStorage.applyProgressRowToWord(w, row);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log('[WordProgress] 已合并云端进度');
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('[WordProgress] 加载云端进度失败', e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 「再学一遍」:本地清零进度并同步到云端(快照写入,清空复习阶梯与时间)
|
|
|
|
|
+ */
|
|
|
|
|
+ private async resetAllWordsProgressAndSync(): Promise<void> {
|
|
|
|
|
+ this.words.forEach((word) => {
|
|
|
|
|
+ word.studyCount = 0;
|
|
|
|
|
+ word.isMastered = false;
|
|
|
|
|
+ word.isNewDone = false;
|
|
|
|
|
+ word.ladderStep = 0;
|
|
|
|
|
+ word.lastReviewAt = undefined;
|
|
|
|
|
+ word.nextReviewAt = undefined;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (this.dbUserId && this.dbListObjectId && this.dbIntervalDays.length > 0) {
|
|
|
|
|
+ for (const w of this.words) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await this.wordProgressStorage.upsertWordProgress(
|
|
|
|
|
+ w,
|
|
|
|
|
+ this.dbUserId,
|
|
|
|
|
+ this.dbListObjectId,
|
|
|
|
|
+ this.dbIntervalDays,
|
|
|
|
|
+ undefined
|
|
|
|
|
+ );
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('[WordProgress] 重置同步失败', w.word, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.startNextGroup();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// ==================== 完成页面操作方法 ====================
|
|
// ==================== 完成页面操作方法 ====================
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -735,14 +864,7 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
{
|
|
{
|
|
|
text: '再学一遍',
|
|
text: '再学一遍',
|
|
|
handler: () => {
|
|
handler: () => {
|
|
|
- // 重置所有单词的学习次数
|
|
|
|
|
- this.words.forEach(word => {
|
|
|
|
|
- word.studyCount = 0;
|
|
|
|
|
- word.isMastered = false;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // 重新开始
|
|
|
|
|
- this.startNextGroup();
|
|
|
|
|
|
|
+ void this.resetAllWordsProgressAndSync();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
]
|
|
]
|
|
@@ -807,6 +929,15 @@ export class ReciteWordsPage implements OnInit, AfterViewInit, OnDestroy {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ getStudyTimeNumber(): string {
|
|
|
|
|
+ const matched = this.studyTime.match(/^(.+?)(分钟)$/);
|
|
|
|
|
+ return matched ? matched[1] : this.studyTime;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ getStudyTimeUnit(): string {
|
|
|
|
|
+ return this.studyTime.includes('分钟') ? '分钟' : '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 加载当前单词的详细信息
|
|
* 加载当前单词的详细信息
|
|
|
* 包括例句、短语、派生词、近义词
|
|
* 包括例句、短语、派生词、近义词
|
|
@@ -825,9 +956,7 @@ private loadWordDetails(): void {
|
|
|
|
|
|
|
|
console.log(`获取单词 "${this.currentWord.word}" 的详细信息...`);
|
|
console.log(`获取单词 "${this.currentWord.word}" 的详细信息...`);
|
|
|
|
|
|
|
|
- // 显示加载状态(可选)
|
|
|
|
|
- this.isLoading = true;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // 勿使用全局 isLoading:会隐藏整块背单词 UI、销毁发音组件,返回后又重建并再次自动发音,造成叠音/重音
|
|
|
this.wordDetailService.fetchWordDetails(this.currentWord.word).subscribe({
|
|
this.wordDetailService.fetchWordDetails(this.currentWord.word).subscribe({
|
|
|
next: (details) => {
|
|
next: (details) => {
|
|
|
console.log(`单词 "${this.currentWord?.word}" 详细信息获取成功`);
|
|
console.log(`单词 "${this.currentWord?.word}" 详细信息获取成功`);
|
|
@@ -842,12 +971,9 @@ private loadWordDetails(): void {
|
|
|
// 手动触发变更检测
|
|
// 手动触发变更检测
|
|
|
this.cdr.detectChanges();
|
|
this.cdr.detectChanges();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- this.isLoading = false;
|
|
|
|
|
},
|
|
},
|
|
|
error: (error) => {
|
|
error: (error) => {
|
|
|
console.error(`获取单词 "${this.currentWord?.word}" 详细信息失败:`, error);
|
|
console.error(`获取单词 "${this.currentWord?.word}" 详细信息失败:`, error);
|
|
|
- this.isLoading = false;
|
|
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|