Browse Source

加载轮播

15270821319 6 months ago
parent
commit
0143d12b97

+ 91 - 0
AiStudy-app/package-lock.json

@@ -22,8 +22,10 @@
         "@capacitor/keyboard": "6.0.3",
         "@capacitor/status-bar": "6.0.2",
         "@ionic/angular": "^8.0.0",
+        "@lottiefiles/lottie-player": "^2.0.12",
         "fmode-ng": "^0.0.63",
         "ionicons": "^7.2.1",
+        "lottie-web": "^5.12.2",
         "parse": "^5.3.0",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
@@ -4919,6 +4921,21 @@
         "@inquirer/prompts": ">= 3 < 6"
       }
     },
+    "node_modules/@lit-labs/ssr-dom-shim": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz",
+      "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@lit/reactive-element": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmmirror.com/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
+      "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@lit-labs/ssr-dom-shim": "^1.0.0"
+      }
+    },
     "node_modules/@lmdb/lmdb-darwin-arm64": {
       "version": "3.0.13",
       "resolved": "https://registry.npmmirror.com/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.13.tgz",
@@ -5003,6 +5020,19 @@
         "win32"
       ]
     },
+    "node_modules/@lottiefiles/lottie-player": {
+      "version": "2.0.12",
+      "resolved": "https://registry.npmmirror.com/@lottiefiles/lottie-player/-/lottie-player-2.0.12.tgz",
+      "integrity": "sha512-VuQnB+IFaY4ijrUTByth7jOLz9p7xK6goeYr/MtyGOVIggSl/TDCcSp6qztltdflFhyZrFpfbHEZNxeK5AiVgg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/pako": "^1.0.1",
+        "lit": "^2.1.2",
+        "lottie-web": "^5.12.2",
+        "pako": "^2.0.4",
+        "resize-observer-polyfill": "^1.5.1"
+      }
+    },
     "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
       "version": "3.0.3",
       "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
@@ -6030,6 +6060,12 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/pako": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/pako/-/pako-1.0.7.tgz",
+      "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==",
+      "license": "MIT"
+    },
     "node_modules/@types/parse": {
       "version": "3.0.9",
       "resolved": "https://registry.npmmirror.com/@types/parse/-/parse-3.0.9.tgz",
@@ -6125,6 +6161,12 @@
       "license": "MIT",
       "peer": true
     },
+    "node_modules/@types/trusted-types": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+      "license": "MIT"
+    },
     "node_modules/@types/uuid": {
       "version": "10.0.0",
       "resolved": "https://registry.npmmirror.com/@types/uuid/-/uuid-10.0.0.tgz",
@@ -12961,6 +13003,37 @@
         "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
       }
     },
+    "node_modules/lit": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmmirror.com/lit/-/lit-2.8.0.tgz",
+      "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@lit/reactive-element": "^1.6.0",
+        "lit-element": "^3.3.0",
+        "lit-html": "^2.8.0"
+      }
+    },
+    "node_modules/lit-element": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmmirror.com/lit-element/-/lit-element-3.3.3.tgz",
+      "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@lit-labs/ssr-dom-shim": "^1.1.0",
+        "@lit/reactive-element": "^1.3.0",
+        "lit-html": "^2.8.0"
+      }
+    },
+    "node_modules/lit-html": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmmirror.com/lit-html/-/lit-html-2.8.0.tgz",
+      "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@types/trusted-types": "^2.0.2"
+      }
+    },
     "node_modules/lmdb": {
       "version": "3.0.13",
       "resolved": "https://registry.npmmirror.com/lmdb/-/lmdb-3.0.13.tgz",
@@ -13207,6 +13280,12 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/lottie-web": {
+      "version": "5.12.2",
+      "resolved": "https://registry.npmmirror.com/lottie-web/-/lottie-web-5.12.2.tgz",
+      "integrity": "sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==",
+      "license": "MIT"
+    },
     "node_modules/lru-cache": {
       "version": "5.1.1",
       "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -14886,6 +14965,12 @@
         "node": "^16.14.0 || >=18.0.0"
       }
     },
+    "node_modules/pako": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz",
+      "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+      "license": "(MIT AND Zlib)"
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
@@ -16084,6 +16169,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/resize-observer-polyfill": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+      "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
+      "license": "MIT"
+    },
     "node_modules/resolve": {
       "version": "1.22.8",
       "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",

+ 2 - 0
AiStudy-app/package.json

@@ -27,8 +27,10 @@
     "@capacitor/keyboard": "6.0.3",
     "@capacitor/status-bar": "6.0.2",
     "@ionic/angular": "^8.0.0",
+    "@lottiefiles/lottie-player": "^2.0.12",
     "fmode-ng": "^0.0.63",
     "ionicons": "^7.2.1",
+    "lottie-web": "^5.12.2",
     "parse": "^5.3.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",

+ 3 - 1
AiStudy-app/src/app/app.config.ts

@@ -2,11 +2,13 @@ import { ApplicationConfig } from '@angular/core';
 import { provideRouter } from '@angular/router';
 import { routes } from './app.routes';
 import { provideIonicAngular } from '@ionic/angular/standalone';
+import { provideAnimations } from '@angular/platform-browser/animations';
 
 export const appConfig: ApplicationConfig = {
   providers: [
     provideRouter(routes),
-    provideIonicAngular()
+    provideIonicAngular(),
+    provideAnimations()
   ]
 };
 

+ 227 - 0
AiStudy-app/src/app/components/quote-carousel/quote-carousel.component.ts

@@ -0,0 +1,227 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from '@ionic/angular';
+import { quotes, Quote } from '../../data/quotes';
+import { addIcons } from 'ionicons';
+import { refreshOutline } from 'ionicons/icons';
+
+@Component({
+  selector: 'app-quote-carousel',
+  standalone: true,
+  imports: [
+    CommonModule, 
+    IonicModule
+  ],
+  template: `
+    <div class="quote-container">
+      <div class="refresh-button">
+        <ion-button fill="clear" size="small" (click)="restartWithNewQuotes()">
+          <ion-icon slot="icon-only" name="refresh-outline"></ion-icon>
+        </ion-button>
+      </div>
+
+      <div class="quote-content" [class.changing]="isChanging">
+        <p class="quote-text">{{ currentQuote.text }}</p>
+        <p class="quote-translation">{{ currentQuote.translation }}</p>
+        <p class="quote-author">—— {{ currentQuote.author }}</p>
+      </div>
+      <div class="quote-dots">
+        <span *ngFor="let dot of quotes; let i = index" 
+              [class.active]="i === currentIndex"
+              (click)="setQuote(i)">
+        </span>
+      </div>
+    </div>
+  `,
+  styles: [`
+    .quote-container {
+      position: relative;
+      padding: 32px;
+      margin: 20px auto;
+      max-width: 800px;
+      text-align: center;
+      background: rgba(var(--ion-color-primary-rgb), 0.03);
+      border-radius: 24px;
+      backdrop-filter: blur(10px);
+    }
+
+    .refresh-button {
+      position: absolute;
+      top: 8px;
+      right: 8px;
+      z-index: 10;
+
+      ion-button {
+        --padding-start: 8px;
+        --padding-end: 8px;
+        --color: var(--ion-color-medium);
+        
+        &:hover {
+          --color: var(--ion-color-primary);
+        }
+      }
+
+      ion-icon {
+        font-size: 20px;
+      }
+    }
+
+    .quote-content {
+      min-height: 200px;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      transition: opacity 0.5s ease, transform 0.5s ease;
+    }
+
+    .quote-text {
+      font-size: 18px;
+      line-height: 1.6;
+      color: var(--ion-color-dark);
+      margin-bottom: 12px;
+      font-weight: 300;
+      transition: all 0.5s ease;
+      opacity: 1;
+      transform: translateY(0);
+    }
+
+    .quote-translation {
+      font-size: 16px;
+      color: var(--ion-color-medium);
+      margin-bottom: 16px;
+      font-weight: 300;
+      transition: all 0.5s ease;
+      opacity: 1;
+      transform: translateY(0);
+    }
+
+    .quote-author {
+      font-size: 14px;
+      color: var(--ion-color-primary);
+      margin: 0;
+      transition: all 0.5s ease;
+      opacity: 1;
+      transform: translateY(0);
+    }
+
+    .quote-content.changing {
+      .quote-text,
+      .quote-translation,
+      .quote-author {
+        opacity: 0;
+        transform: translateY(10px);
+      }
+    }
+
+    .quote-dots {
+      display: flex;
+      justify-content: center;
+      gap: 8px;
+      margin-top: 24px;
+
+      span {
+        width: 6px;
+        height: 6px;
+        border-radius: 50%;
+        background: var(--ion-color-medium);
+        opacity: 0.3;
+        cursor: pointer;
+        transition: all 0.3s ease;
+
+        &.active {
+          opacity: 1;
+          background: var(--ion-color-primary);
+          transform: scale(1.2);
+        }
+      }
+    }
+  `]
+})
+export class QuoteCarouselComponent implements OnInit, OnDestroy {
+  quotes: Quote[] = [];
+  currentQuote: Quote;
+  currentIndex = 0;
+  private intervalId: any;
+  isChanging = false;
+
+  constructor() {
+    addIcons({ refreshOutline });
+    this.quotes = this.getRandomQuotes(7);
+    this.currentQuote = this.quotes[0];
+  }
+
+  private getRandomQuotes(count: number): Quote[] {
+    const shuffled = [...quotes].sort(() => 0.5 - Math.random());
+    return shuffled.slice(0, count);
+  }
+
+  ngOnInit() {
+    this.startAutoRotate();
+  }
+
+  ngOnDestroy() {
+    if (this.intervalId) {
+      clearInterval(this.intervalId);
+    }
+  }
+
+  startAutoRotate() {
+    this.intervalId = setInterval(() => {
+      this.nextQuote();
+    }, 5000); // 每5秒切换一次
+  }
+
+  setQuote(index: number) {
+    if (index === this.currentIndex) return;
+    
+    this.isChanging = true;
+    
+    setTimeout(() => {
+      this.currentIndex = index;
+      this.currentQuote = this.quotes[index];
+      
+      setTimeout(() => {
+        this.isChanging = false;
+      }, 50);
+    }, 300);
+    
+    this.resetAutoRotate();
+  }
+
+  nextQuote() {
+    this.isChanging = true;
+    
+    setTimeout(() => {
+      this.currentIndex = (this.currentIndex + 1) % this.quotes.length;
+      this.currentQuote = this.quotes[this.currentIndex];
+      
+      setTimeout(() => {
+        this.isChanging = false;
+      }, 50);
+    }, 300);
+  }
+
+  resetAutoRotate() {
+    if (this.intervalId) {
+      clearInterval(this.intervalId);
+    }
+    this.startAutoRotate();
+  }
+
+  restartWithNewQuotes() {
+    this.isChanging = true;
+    
+    setTimeout(() => {
+      this.quotes = this.getRandomQuotes(7);
+      this.currentIndex = 0;
+      this.currentQuote = this.quotes[0];
+      
+      setTimeout(() => {
+        this.isChanging = false;
+      }, 50);
+    }, 300);
+
+    this.resetAutoRotate();
+  }
+} 

+ 384 - 0
AiStudy-app/src/app/data/quotes.ts

@@ -0,0 +1,384 @@
+export interface Quote {
+  text: string;
+  author?: string;
+  translation?: string;
+}
+
+export const quotes: Quote[] = [
+  {
+    text: "The journey of a thousand miles begins with one step.",
+    author: "老子",
+    translation: "千里之行,始于足下。"
+  },
+  {
+    text: "Learning is like rowing upstream: not to advance is to drop back.",
+    author: "中国谚语",
+    translation: "学如逆水行舟,不进则退。"
+  },
+  {
+    text: "Knowledge is not knowledge until someone else knows that one knows.",
+    author: "庄子",
+    translation: "知之为知之,不知为不知,是知也。"
+  },
+  {
+    text: "The master said, 'Is it not pleasant to learn with constant perseverance and application?'",
+    author: "孔子",
+    translation: "学而时习之,不亦说乎?"
+  },
+  {
+    text: "Education breeds confidence. Confidence breeds hope. Hope breeds peace.",
+    author: "孔子",
+    translation: "学而后知不足,教而后知困。"
+  },
+  {
+    text: "He who has a why to live can bear almost any how.",
+    author: "尼采",
+    translation: "有了活着的理由,便能承受任何生活方式。"
+  },
+  {
+    text: "In the midst of winter, I found there was, within me, an invincible summer.",
+    author: "加缪",
+    translation: "在严冬之中,我发现在我心里有一个不可战胜的夏天。"
+  },
+  {
+    text: "Man is condemned to be free.",
+    author: "萨特",
+    translation: "人是注定要自由的。"
+  },
+  {
+    text: "Don't bend; don't water it down; don't try to make it logical; don't edit your own soul according to the fashion.",
+    author: "卡夫卡",
+    translation: "不要屈服;不要妥协;不要试图讲道理;不要按照潮流编辑自己的灵魂。"
+  },
+  {
+    text: "Time is the substance I am made of. Time is a river which sweeps me along, but I am the river.",
+    author: "博尔赫斯",
+    translation: "时间是构成我的物质,时间是载我前行的河流,而我就是那条河。"
+  },
+  {
+    text: "The world is a book and those who do not travel read only one page.",
+    author: "奥古斯丁",
+    translation: "世界是一本书,不旅行的人只读了其中一页。"
+  },
+  {
+    text: "The unexamined life is not worth living.",
+    author: "苏格拉底",
+    translation: "未经省察的人生不值得过。"
+  },
+  {
+    text: "Happiness is not an ideal of reason but of imagination.",
+    author: "康德",
+    translation: "幸福不是理性的理想,而是想象力的理想。"
+  },
+  {
+    text: "Between stimulus and response there is a space. In that space is our power to choose our response.",
+    author: "维克多·弗兰克尔",
+    translation: "在刺激与反应之间有一个空间,在那个空间里存在着我们选择如何回应的力量。"
+  },
+  {
+    text: "Life can only be understood backwards; but it must be lived forwards.",
+    author: "克尔凯郭尔",
+    translation: "生活只能向后理解,但必须向前生活。"
+  },
+  {
+    text: "Everything that irritates us about others can lead us to an understanding of ourselves.",
+    author: "荣格",
+    translation: "那些让我们对他人感到恼火的事物,正是帮助我们认识自己的线索。"
+  },
+  {
+    text: "One must imagine Sisyphus happy.",
+    author: "加缪",
+    translation: "我们必须想象西西弗斯是幸福的。"
+  },
+  {
+    text: "Art is the lie that enables us to realize the truth.",
+    author: "毕加索",
+    translation: "艺术是让我们认识真理的谎言。"
+  },
+  {
+    text: "We are what we repeatedly do. Excellence, then, is not an act, but a habit.",
+    author: "亚里士多德",
+    translation: "我们的本质由我们重复的行为所决定。因此,卓越不是一个行为,而是一种习惯。"
+  },
+  {
+    text: "The limits of my language mean the limits of my world.",
+    author: "维特根斯坦",
+    translation: "我的语言的界限就是我的世界的界限。"
+  },
+  {
+    text: "There is only one way to avoid criticism: do nothing, say nothing, and be nothing.",
+    author: "亚里士多德",
+    translation: "避免批评只有一个方法:什么都不做,什么都不说,什么都不是。"
+  },
+  {
+    text: "The purpose of art is washing the dust of daily life off our souls.",
+    author: "毕加索",
+    translation: "艺术的目的是洗去我们灵魂上的日常尘埃。"
+  },
+  {
+    text: "To do two things at once is to do neither.",
+    author: "普布利利乌斯·塞鲁斯",
+    translation: "一心二用,等于没用。"
+  },
+  {
+    text: "The more I read, the more I acquire, the more certain I am that I know nothing.",
+    author: "伏尔泰",
+    translation: "我读得越多,学得越多,就越确信自己一无所知。"
+  },
+  {
+    text: "The secret of life is enjoying the passage of time.",
+    author: "叔本华",
+    translation: "生命的秘密在于享受时光的流逝。"
+  },
+  {
+    text: "The only true wisdom is in knowing you know nothing.",
+    author: "苏格拉底",
+    translation: "唯一真正的智慧是知道自己一无所知。"
+  },
+  {
+    text: "A room without books is like a body without a soul.",
+    author: "西塞罗",
+    translation: "没有书籍的房间就像没有灵魂的躯体。"
+  },
+  {
+    text: "What you are is what you have been. What you'll be is what you do now.",
+    author: "佛陀",
+    translation: "你现在的模样源于过去,你将来的模样取决于现在。"
+  },
+  {
+    text: "The greatest happiness of life is the conviction that we are loved.",
+    author: "雨果",
+    translation: "生命中最大的幸福是确信有人爱我们。"
+  },
+  {
+    text: "To live is to suffer, to survive is to find some meaning in the suffering.",
+    author: "尼采",
+    translation: "活着就是受苦,生存下去就是在苦难中找到意义。"
+  },
+  {
+    text: "The most beautiful experience we can have is the mysterious.",
+    author: "爱因斯坦",
+    translation: "我们能拥有的最美的体验是神秘。"
+  },
+  {
+    text: "The quieter you become, the more you can hear.",
+    author: "卢米",
+    translation: "你越是安静,听到的就越多。"
+  },
+  {
+    text: "The real voyage of discovery consists not in seeking new landscapes, but in having new eyes.",
+    author: "普鲁斯特",
+    translation: "真正的发现之旅不在于寻找新的风景,而在于拥有新的眼光。"
+  },
+  {
+    text: "The wound is the place where the Light enters you.",
+    author: "鲁米",
+    translation: "伤口是光照进你内心的地方。"
+  },
+  {
+    text: "Out of suffering have emerged the strongest souls.",
+    author: "纪伯伦",
+    translation: "最强大的灵魂,都是从苦难中成长起来的。"
+  },
+  {
+    text: "Beauty will save the world.",
+    author: "陀思妥耶夫斯基",
+    translation: "美将拯救世界。"
+  },
+  {
+    text: "We read to know we are not alone.",
+    author: "C.S.路易斯",
+    translation: "我们阅读,是为了知道我们并不孤单。"
+  },
+  {
+    text: "The most thought-provoking thing in our thought-provoking time is that we are still not thinking.",
+    author: "海德格尔",
+    translation: "在这个引人深思的时代,最引人深思的是我们仍然没有在思考。"
+  },
+  {
+    text: "The only way out is through.",
+    author: "罗伯特·弗罗斯特",
+    translation: "唯一的出路就是穿越。"
+  },
+  {
+    text: "Life shrinks or expands in proportion to one's courage.",
+    author: "阿娜伊斯·宁",
+    translation: "生命会随着人的勇气而扩大或收缩。"
+  },
+  {
+    text: "The soul becomes dyed with the color of its thoughts.",
+    author: "马可·奥勒留",
+    translation: "灵魂会被思想的颜色所染。"
+  },
+  {
+    text: "Wheresoever you go, go with all your heart.",
+    author: "孔子",
+    translation: "无论你去哪里,都要全心全意。"
+  },
+  {
+    text: "The universe is not outside of you. Look inside yourself; everything that you want, you already are.",
+    author: "鲁米",
+    translation: "宇宙不在你之外。看看你的内心,你想要的一切,你已经拥有。"
+  },
+  {
+    text: "In order to be irreplaceable, one must always be different.",
+    author: "香奈儿",
+    translation: "要想无可替代,就必须与众不同。"
+  },
+  {
+    text: "The most common form of despair is not being who you are.",
+    author: "克尔凯郭尔",
+    translation: "最常见的绝望,是不能成为真实的自己。"
+  },
+  {
+    text: "The meaning of life is to find your gift. The purpose of life is to give it away.",
+    author: "毕加索",
+    translation: "生命的意义在于发现你的天赋,生命的目的在于将它奉献出去。"
+  },
+  {
+    text: "The cave you fear to enter holds the treasure you seek.",
+    author: "约瑟夫·坎贝尔",
+    translation: "你害怕进入的洞穴里,正藏着你要寻找的宝藏。"
+  },
+  {
+    text: "What matters most is how well you walk through the fire.",
+    author: "查尔斯·布考斯基",
+    translation: "最重要的不是你经历了什么,而是你如何度过烈火的考验。"
+  },
+  {
+    text: "The deeper that sorrow carves into your being, the more joy you can contain.",
+    author: "纪伯伦",
+    translation: "悲伤在你生命中刻得越深,你就能容纳越多的欢乐。"
+  },
+  {
+    text: "The privilege of a lifetime is to become who you truly are.",
+    author: "荣格",
+    translation: "一生最大的特权,就是成为真正的自己。"
+  },
+  {
+    text: "The truth is rarely pure and never simple.",
+    author: "王尔德",
+    translation: "真相很少是纯粹的,从来都不是简单的。"
+  },
+  {
+    text: "Every saint has a past, and every sinner has a future.",
+    author: "王尔德",
+    translation: "每个圣人都有过去,每个罪人都有未来。"
+  },
+  {
+    text: "The only person you are destined to become is the person you decide to be.",
+    author: "拉尔夫·沃尔多·爱默生",
+    translation: "你注定会成为的那个人,正是你决定要成为的人。"
+  },
+  {
+    text: "The best way out is always through.",
+    author: "罗伯特·弗罗斯特",
+    translation: "最好的出路永远是穿越。"
+  },
+  {
+    text: "Life is not a problem to be solved, but a reality to be experienced.",
+    author: "克尔凯郭尔",
+    translation: "生活不是一个待解决的问题,而是一个需要体验的现实。"
+  },
+  {
+    text: "Everything you can imagine is real.",
+    author: "毕加索",
+    translation: "一切你能想象的都是真实的。"
+  },
+  {
+    text: "The world breaks everyone, and afterward, some are strong at the broken places.",
+    author: "海明威",
+    translation: "这个世界打破了每个人,然后有些人在破碎处变得更强大。"
+  },
+  {
+    text: "To understand is to perceive patterns.",
+    author: "以赛亚·柏林",
+    translation: "理解就是察觉模式。"
+  },
+  {
+    text: "The most beautiful things in the world cannot be seen or touched, they are felt with the heart.",
+    author: "圣埃克苏佩里",
+    translation: "世界上最美的东西既看不见也摸不着,只能用心去感受。"
+  },
+  {
+    text: "We must be willing to let go of the life we planned so as to have the life that is waiting for us.",
+    author: "约瑟夫·坎贝尔",
+    translation: "我们必须愿意放下我们计划的生活,才能拥有等待着我们的生活。"
+  },
+  {
+    text: "The only impossible journey is the one you never begin.",
+    author: "托尼·罗宾斯",
+    translation: "唯一不可能的旅程,是你从未启程的那个。"
+  },
+  {
+    text: "The most important things are the hardest to say.",
+    author: "斯蒂芬·金",
+    translation: "最重要的事往往最难说出口。"
+  },
+  {
+    text: "The past is a foreign country; they do things differently there.",
+    author: "L.P.哈特利",
+    translation: "过去是一个异国;那里的人们做事方式与我们不同。"
+  },
+  {
+    text: "The eyes of others our prisons; their thoughts our cages.",
+    author: "弗吉尼亚·伍尔夫",
+    translation: "他人的眼光是我们的牢笼;他们的想法是我们的囚室。"
+  },
+  {
+    text: "The only way to deal with an unfree world is to become so absolutely free that your very existence is an act of rebellion.",
+    author: "加缪",
+    translation: "应对不自由世界的唯一方式,就是让自己变得如此自由,以至于你的存在本身就是一种反抗。"
+  },
+  {
+    text: "In three words I can sum up everything I've learned about life: it goes on.",
+    author: "罗伯特·弗罗斯特",
+    translation: "用三个字我可以总结我对生活的所有领悟:它继续。"
+  },
+  {
+    text: "The most beautiful experience we can have is the mysterious.",
+    author: "爱因斯坦",
+    translation: "我们能拥有的最美的体验是神秘。"
+  },
+  {
+    text: "The cure for pain is in the pain.",
+    author: "鲁米",
+    translation: "痛苦的解药就在痛苦之中。"
+  },
+  {
+    text: "The greatest glory in living lies not in never falling, but in rising every time we fall.",
+    author: "纳尔逊·曼德拉",
+    translation: "生命最大的光荣不在于从不跌倒,而在于每次跌倒后都能站起来。"
+  },
+  {
+    text: "The most wasted of all days is one without laughter.",
+    author: "E.E.卡明斯",
+    translation: "最浪费的一天,是没有笑容的一天。"
+  },
+  {
+    text: "The future belongs to those who believe in the beauty of their dreams.",
+    author: "埃莉诺·罗斯福",
+    translation: "未来属于那些相信梦想之美的人。"
+  },
+  {
+    text: "The only real mistake is the one from which we learn nothing.",
+    author: "亨利·福特",
+    translation: "唯一真正的错误是我们从中什么都没学到的错误。"
+  },
+  {
+    text: "The beginning of wisdom is the definition of terms.",
+    author: "苏格拉底",
+    translation: "智慧的开始是对概念的界定。"
+  },
+  {
+    text: "The happiness of your life depends upon the quality of your thoughts.",
+    author: "马可·奥勒留",
+    translation: "你生活的幸福取决于你思想的质量。"
+  },
+  {
+    text: "The best revenge is to be unlike him who performed the injury.",
+    author: "马可·奥勒留",
+    translation: "最好的报复是不要变得和伤害你的人一样。"
+  }
+  // 你可以继续添加更多名言
+]; 

+ 177 - 16
AiStudy-app/src/app/pages/interactive-practice/interactive-practice.page.ts

@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
 import { 
   IonHeader, 
   IonToolbar, 
@@ -8,6 +8,7 @@ import {
   IonBackButton,
   IonProgressBar,
   IonButton,
+  IonIcon,
   ModalController
 } from '@ionic/angular/standalone';
 import { NgFor, NgIf } from '@angular/common';
@@ -23,6 +24,9 @@ import {
   GenerateAnswerOptions,
   AnalyzeAnswersOptions
 } from '../../../agent/tasks/exercise';
+import { alertCircleOutline, refreshOutline } from 'ionicons/icons';
+import { addIcons } from 'ionicons';
+import { QuoteCarouselComponent } from '../../components/quote-carousel/quote-carousel.component';
 
 @Component({
   selector: 'app-interactive-practice',
@@ -38,47 +42,186 @@ import {
 
     <ion-content class="ion-padding">
       <!-- 任务进度显示 -->
-      <div *ngFor="let task of taskList" class="task-item">
-        <div class="task-header">
-          <h3>{{ task.title }}</h3>
-          <span *ngIf="task.status">{{ task.status }}</span>
-        </div>
-        <ion-progress-bar [value]="task.progress"></ion-progress-bar>
-        <div *ngIf="task.error" class="error-message">
-          {{ task.error }}
+      <div class="task-container">
+        <div *ngFor="let task of taskList; let i = index" class="task-card">
+          <div class="task-header" [class.active]="task.progress > 0">
+            <div class="task-number">{{ i + 1 }}</div>
+            <h3>{{ task.title }}</h3>
+            <div class="task-status" [class.status-pending]="task.progress === 0"
+                                   [class.status-running]="task.progress > 0 && task.progress < 1"
+                                   [class.status-complete]="task.progress === 1"
+                                   [class.status-error]="task.error">
+              {{ getStatusText(task) }}
+            </div>
+          </div>
+
+          <div class="progress-container">
+            <div class="progress-bar">
+              <div class="progress-fill" [style.width.%]="task.progress * 100"></div>
+            </div>
+            <span class="progress-text">{{ (task.progress * 100).toFixed(0) }}%</span>
+          </div>
+
+          <div *ngIf="task.error" class="error-message">
+            <ion-icon name="alert-circle-outline"></ion-icon>
+            {{ task.error }}
+          </div>
         </div>
       </div>
 
+      <!-- 添加名言轮播 -->
+      <app-quote-carousel #quoteCarousel></app-quote-carousel>
+
       <!-- 完成后的操作按钮 -->
       <div *ngIf="isComplete" class="completion-actions">
         <ion-button expand="block" (click)="restartExercise()">
+          <ion-icon name="refresh-outline" slot="start"></ion-icon>
           再来一次
         </ion-button>
       </div>
     </ion-content>
   `,
   styles: [`
-    .task-item {
+    .task-container {
+      max-width: 800px;
+      margin: 20px auto;
+      padding: 0 16px;
+    }
+
+    .task-card {
+      background: var(--ion-card-background);
+      border-radius: 16px;
+      padding: 20px;
       margin-bottom: 20px;
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+      transition: all 0.3s ease;
+      border: 1px solid rgba(var(--ion-color-primary-rgb), 0.1);
     }
+
     .task-header {
       display: flex;
-      justify-content: space-between;
       align-items: center;
-      margin-bottom: 8px;
+      gap: 16px;
+      margin-bottom: 16px;
+      opacity: 0.7;
+      transition: opacity 0.3s ease;
+
+      &.active {
+        opacity: 1;
+      }
+    }
+
+    .task-number {
+      width: 32px;
+      height: 32px;
+      border-radius: 50%;
+      background: var(--ion-color-primary);
+      color: white;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-weight: 600;
+      font-size: 14px;
     }
-    .task-header h3 {
+
+    h3 {
       margin: 0;
       font-size: 16px;
+      font-weight: 600;
+      color: var(--ion-color-dark);
+      flex: 1;
+    }
+
+    .task-status {
+      font-size: 14px;
+      padding: 4px 12px;
+      border-radius: 20px;
       font-weight: 500;
+
+      &.status-pending {
+        background: #f3f4f6;
+        color: #6b7280;
+      }
+
+      &.status-running {
+        background: #dbeafe;
+        color: #2563eb;
+      }
+
+      &.status-complete {
+        background: #dcfce7;
+        color: #16a34a;
+      }
+
+      &.status-error {
+        background: #fee2e2;
+        color: #dc2626;
+      }
+    }
+
+    .progress-container {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+    }
+
+    .progress-bar {
+      flex: 1;
+      height: 6px;
+      background: rgba(var(--ion-color-primary-rgb), 0.1);
+      border-radius: 3px;
+      overflow: hidden;
+    }
+
+    .progress-fill {
+      height: 100%;
+      background: var(--ion-color-primary);
+      border-radius: 3px;
+      transition: width 0.3s ease;
     }
+
+    .progress-text {
+      font-size: 14px;
+      color: var(--ion-color-medium);
+      min-width: 48px;
+      text-align: right;
+    }
+
     .error-message {
+      margin-top: 12px;
       color: var(--ion-color-danger);
-      margin-top: 8px;
       font-size: 14px;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 12px;
+      background: rgba(var(--ion-color-danger-rgb), 0.1);
+      border-radius: 8px;
+
+      ion-icon {
+        font-size: 20px;
+      }
     }
+
     .completion-actions {
       margin-top: 32px;
+      padding: 0 16px;
+      max-width: 800px;
+      margin-left: auto;
+      margin-right: auto;
+
+      ion-button {
+        --border-radius: 12px;
+        --padding-top: 16px;
+        --padding-bottom: 16px;
+      }
+    }
+
+    // 深色模式适配
+    @media (prefers-color-scheme: dark) {
+      .task-card {
+        background: rgba(var(--ion-card-background-rgb), 0.8);
+      }
     }
   `],
   standalone: true,
@@ -91,12 +234,16 @@ import {
     IonBackButton,
     IonProgressBar,
     IonButton,
+    IonIcon,
     NgFor,
-    NgIf
+    NgIf,
+    QuoteCarouselComponent
   ],
   providers: [ModalController]
 })
 export class InteractivePracticePage implements OnInit {
+  @ViewChild('quoteCarousel') quoteCarousel!: QuoteCarouselComponent;
+
   isComplete: boolean = false;
   taskList: AgentTaskStep[] = [];
   shareData: any = {};
@@ -105,7 +252,9 @@ export class InteractivePracticePage implements OnInit {
     private modalCtrl: ModalController,
     private exerciseService: ExerciseService,
     private apiService: ApiService
-  ) {}
+  ) {
+    addIcons({ alertCircleOutline, refreshOutline });
+  }
 
   ngOnInit() {
     this.startExerciseTask();
@@ -115,6 +264,11 @@ export class InteractivePracticePage implements OnInit {
     this.isComplete = false;
     this.shareData = {};
 
+    // 重新随机选择名言
+    if (this.quoteCarousel) {
+      this.quoteCarousel.restartWithNewQuotes();
+    }
+
     try {
       // 创建任务链
       const tasks = [
@@ -159,4 +313,11 @@ export class InteractivePracticePage implements OnInit {
   restartExercise() {
     this.startExerciseTask();
   }
+
+  getStatusText(task: any): string {
+    if (task.error) return '出错';
+    if (task.progress === 0) return '等待中';
+    if (task.progress === 1) return '已完成';
+    return '进行中';
+  }
 } 

+ 29 - 5
AiStudy-app/src/app/pages/learning-design/learning-design.page.html

@@ -108,12 +108,33 @@
       </div>
     </div>
 
-    <div class="plan-section">
+    <div class="plan-section" *ngIf="shareData.learningPlan.milestones?.length">
       <h3>关键里程碑</h3>
-      <ul>
-        <li *ngFor="let milestone of shareData.learningPlan.milestones">
-          {{ milestone }}
-        </li>
+      <div class="milestone-list">
+        <div *ngFor="let milestone of shareData.learningPlan.milestones" class="milestone-item">
+          <h4>{{ milestone.name }}</h4>
+          <p class="milestone-timing">预计达成时间:{{ milestone.timing }}</p>
+          <div class="milestone-criteria">
+            <strong>达成标准:</strong>
+            <ul>
+              <li *ngFor="let criterion of milestone.criteria">{{ criterion }}</li>
+            </ul>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="plan-section" *ngIf="shareData.learningPlan.successCriteria?.length">
+      <h3>成功标准</h3>
+      <ul class="success-criteria">
+        <li *ngFor="let criteria of shareData.learningPlan.successCriteria">{{ criteria }}</li>
+      </ul>
+    </div>
+
+    <div class="plan-section" *ngIf="shareData.learningPlan.additionalSuggestions?.length">
+      <h3>学习建议</h3>
+      <ul class="suggestions">
+        <li *ngFor="let suggestion of shareData.learningPlan.additionalSuggestions">{{ suggestion }}</li>
       </ul>
     </div>
 
@@ -127,6 +148,9 @@
     </div>
   </div>
 
+  <!-- 添加名言轮播组件 -->
+  <app-quote-carousel #quoteCarousel></app-quote-carousel>
+
   <!-- 操作按钮 -->
   <div class="action-buttons" *ngIf="isComplete">
     <ion-button expand="block" (click)="saveLearningPlan()" *ngIf="showPlanResult">

+ 74 - 0
AiStudy-app/src/app/pages/learning-design/learning-design.page.scss

@@ -310,4 +310,78 @@ ion-progress-bar {
     margin: 12px 0 8px;
     color: var(--ion-color-dark);
   }
+}
+
+.milestone-list {
+  .milestone-item {
+    background: var(--ion-color-light);
+    border-radius: 12px;
+    padding: 16px;
+    margin-bottom: 16px;
+
+    h4 {
+      color: var(--ion-color-primary);
+      margin: 0 0 8px;
+      font-size: 16px;
+      font-weight: 500;
+    }
+
+    .milestone-timing {
+      color: var(--ion-color-medium);
+      font-size: 14px;
+      margin-bottom: 12px;
+    }
+
+    .milestone-criteria {
+      strong {
+        display: block;
+        margin-bottom: 8px;
+        color: var(--ion-color-dark);
+      }
+
+      ul {
+        margin: 0;
+        padding-left: 20px;
+        
+        li {
+          color: var(--ion-color-medium);
+          margin-bottom: 4px;
+        }
+      }
+    }
+  }
+}
+
+.success-criteria,
+.suggestions {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+
+  li {
+    display: flex;
+    align-items: flex-start;
+    padding: 12px;
+    background: var(--ion-color-light);
+    border-radius: 8px;
+    margin-bottom: 8px;
+    color: var(--ion-color-medium);
+
+    &:before {
+      content: "•";
+      color: var(--ion-color-primary);
+      margin-right: 8px;
+    }
+  }
+}
+
+.plan-section {
+  margin-bottom: 24px;
+  
+  h3 {
+    color: var(--ion-color-dark);
+    font-size: 18px;
+    font-weight: 600;
+    margin-bottom: 16px;
+  }
 } 

+ 10 - 2
AiStudy-app/src/app/pages/learning-design/learning-design.page.ts

@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
 import { Router } from '@angular/router';
 import { 
   IonHeader, 
@@ -23,6 +23,7 @@ import { AgentTaskStep } from '../../../agent/agent.task';
 import { TaskCollectDetails } from '../../../agent/tasks/learning-plan/2.5.collect-details';
 import { LearningPlanService } from '../../services/learning-plan.service';
 import { CloudUser } from 'src/lib/ncloud';
+import { QuoteCarouselComponent } from '../../components/quote-carousel/quote-carousel.component';
 
 interface LearningPlan {
   userId: string;
@@ -53,10 +54,12 @@ interface LearningPlan {
     IonIcon,
     NgClass,
     NgFor,
-    NgIf
+    NgIf,
+    QuoteCarouselComponent
   ],
 })
 export class LearningDesignPage implements OnInit {
+  @ViewChild('quoteCarousel') quoteCarousel?: QuoteCarouselComponent;
   isComplete: boolean = false;
   taskList: AgentTaskStep[] = [];
   shareData: any = {};
@@ -78,6 +81,11 @@ export class LearningDesignPage implements OnInit {
     this.showPlanResult = false;
     this.shareData = {};
 
+    // 重新随机选择名言
+    if (this.quoteCarousel) {
+      this.quoteCarousel.restartWithNewQuotes();
+    }
+
     // 创建任务链
     const task1 = TaskCollectBasicInfo({
       modalCtrl: this.modalCtrl,