pqy 7 сар өмнө
parent
commit
1ef4682eb6
67 өөрчлөгдсөн 2670 нэмэгдсэн , 82 устгасан
  1. 2 1
      angular.json
  2. 8 2
      capacitor.config.ts
  3. 685 15
      package-lock.json
  4. 2 0
      package.json
  5. 6 8
      src/app/app-routing.module.ts
  6. 5 0
      src/app/app.component.scss
  7. 3 2
      src/app/app.component.ts
  8. 17 4
      src/app/app.module.ts
  9. 17 0
      src/app/ceramic-details/ceramic-details-routing.module.ts
  10. 20 0
      src/app/ceramic-details/ceramic-details.module.ts
  11. 18 0
      src/app/ceramic-details/ceramic-details.page.html
  12. 29 0
      src/app/ceramic-details/ceramic-details.page.scss
  13. 17 0
      src/app/ceramic-details/ceramic-details.page.spec.ts
  14. 18 0
      src/app/ceramic-details/ceramic-details.page.ts
  15. 4 0
      src/app/explore-container/explore-container.component.html
  16. 0 0
      src/app/explore-container/explore-container.component.scss
  17. 7 7
      src/app/explore-container/explore-container.component.spec.ts
  18. 12 0
      src/app/explore-container/explore-container.component.ts
  19. 14 0
      src/app/explore-container/explore-container.module.ts
  20. 0 20
      src/app/home/home.page.html
  21. 0 12
      src/app/home/home.page.ts
  22. 12 0
      src/app/service/api.ts
  23. 3 3
      src/app/tab1/tab1-routing.module.ts
  24. 20 0
      src/app/tab1/tab1.module.ts
  25. 8 0
      src/app/tab1/tab1.page.html
  26. 5 0
      src/app/tab1/tab1.page.scss
  27. 26 0
      src/app/tab1/tab1.page.spec.ts
  28. 43 0
      src/app/tab1/tab1.page.ts
  29. 16 0
      src/app/tab2/tab2-routing.module.ts
  30. 20 0
      src/app/tab2/tab2.module.ts
  31. 89 0
      src/app/tab2/tab2.page.html
  32. 288 0
      src/app/tab2/tab2.page.scss
  33. 26 0
      src/app/tab2/tab2.page.spec.ts
  34. 246 0
      src/app/tab2/tab2.page.ts
  35. 16 0
      src/app/tab3/tab3-routing.module.ts
  36. 20 0
      src/app/tab3/tab3.module.ts
  37. 62 0
      src/app/tab3/tab3.page.html
  38. 146 0
      src/app/tab3/tab3.page.scss
  39. 23 0
      src/app/tab3/tab3.page.spec.ts
  40. 188 0
      src/app/tab3/tab3.page.ts
  41. 16 0
      src/app/tab4/tab4-routing.module.ts
  42. 20 0
      src/app/tab4/tab4.module.ts
  43. 27 0
      src/app/tab4/tab4.page.html
  44. 281 0
      src/app/tab4/tab4.page.scss
  45. 26 0
      src/app/tab4/tab4.page.spec.ts
  46. 21 0
      src/app/tab4/tab4.page.ts
  47. 43 0
      src/app/tabs/tabs-routing.module.ts
  48. 7 7
      src/app/tabs/tabs.module.ts
  49. 23 0
      src/app/tabs/tabs.page.html
  50. 1 0
      src/app/tabs/tabs.page.scss
  51. 26 0
      src/app/tabs/tabs.page.spec.ts
  52. 12 0
      src/app/tabs/tabs.page.ts
  53. BIN
      src/assets/images/1.jpg
  54. BIN
      src/assets/images/2.jpg
  55. BIN
      src/assets/images/background.jpg
  56. BIN
      src/assets/images/bubble-background.png
  57. BIN
      src/assets/images/button-background1.png
  58. BIN
      src/assets/images/button-background2(1).png
  59. BIN
      src/assets/images/button-background2.png
  60. BIN
      src/assets/images/divider.png
  61. BIN
      src/assets/images/recognition-background.png
  62. BIN
      src/assets/images/sidebarbox-background.png
  63. BIN
      src/assets/images/table-background.png
  64. BIN
      src/assets/images/user.jpg
  65. 24 0
      src/global.scss
  66. 1 1
      src/index.html
  67. 1 0
      tsconfig.json

+ 2 - 1
angular.json

@@ -136,7 +136,8 @@
   "cli": {
     "schematicCollections": [
       "@ionic/angular-toolkit"
-    ]
+    ],
+    "analytics": "ad84f43b-279a-4e8a-b404-93501bc91053"
   },
   "schematics": {
     "@ionic/angular-toolkit:component": {

+ 8 - 2
capacitor.config.ts

@@ -2,8 +2,14 @@ import type { CapacitorConfig } from '@capacitor/cli';
 
 const config: CapacitorConfig = {
   appId: 'io.ionic.starter',
-  appName: 'yaoyi',
-  webDir: 'www'
+  appName: 'myapp',
+  webDir: 'www',
+  bundledWebRuntime: false,
+  cordova: {
+    preferences: {
+      WechatAppId: 'wx2abfd8d0783919d7' // 替换为你的微信 AppID
+    }
+  }
 };
 
 export default config;

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 685 - 15
package-lock.json


+ 2 - 0
package.json

@@ -27,6 +27,8 @@
     "@capacitor/keyboard": "6.0.3",
     "@capacitor/status-bar": "6.0.2",
     "@ionic/angular": "^8.0.0",
+    "axios": "^1.7.8",
+    "fmode-ng": "^0.0.62",
     "ionicons": "^7.0.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",

+ 6 - 8
src/app/app-routing.module.ts

@@ -2,21 +2,19 @@ import { NgModule } from '@angular/core';
 import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
 
 const routes: Routes = [
-  {
-    path: 'home',
-    loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
-  },
   {
     path: '',
-    redirectTo: 'home',
-    pathMatch: 'full'
+    loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
   },
+  {
+    path: 'ceramic-details',
+    loadChildren: () => import('./ceramic-details/ceramic-details.module').then( m => m.CeramicDetailsPageModule)
+  }
 ];
-
 @NgModule({
   imports: [
     RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
   ],
   exports: [RouterModule]
 })
-export class AppRoutingModule { }
+export class AppRoutingModule {}

+ 5 - 0
src/app/app.component.scss

@@ -0,0 +1,5 @@
+.header-title {
+    font-family: "FangSong", serif; /* 使用仿宋体 */
+    font-weight: bold; /* 加粗 */
+    text-align: center; /* 居中 */
+  }

+ 3 - 2
src/app/app.component.ts

@@ -1,5 +1,6 @@
-import { Component } from '@angular/core';
-
+import { Component,EnvironmentInjector, inject } from '@angular/core';
+import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular';
+import { addIcons } from 'ionicons';
 @Component({
   selector: 'app-root',
   templateUrl: 'app.component.html',

+ 17 - 4
src/app/app.module.ts

@@ -4,13 +4,26 @@ import { RouteReuseStrategy } from '@angular/router';
 
 import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
 
-import { AppComponent } from './app.component';
 import { AppRoutingModule } from './app-routing.module';
-
+import { AppComponent } from './app.component';
+import { provideHttpClient } from '@angular/common/http'; // 引用HttpClient方法
+import { Diagnostic } from '@awesome-cordova-plugins/diagnostic/ngx'; // 引用移动端授权检测供应器
+import Parse from "parse";// 设置Parse服务属性
+Parse.initialize("ncloudmaster");
+Parse.serverURL = "https://server.fmode.cn/parse";
+localStorage.setItem("NOVA_APIG_SERVER", 'aHR0cHMlM0ElMkYlMkZzZXJ2ZXIuZm1vZGUuY24lMkZhcGklMkZhcGlnJTJG')
+// 注意:替换Token 根据Token设置Parse服务帐套权限
+Parse.User.become('r:b86ae57d94d26cbbfce6a1515c3946ee')
 @NgModule({
   declarations: [AppComponent],
   imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
-  providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
+  providers: [
+    // 添加HttpClient供应器
+    provideHttpClient(),
+    // 添加Diagnostic
+    Diagnostic,
+    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
+  ],
   bootstrap: [AppComponent],
 })
-export class AppModule {}
+export class AppModule {}

+ 17 - 0
src/app/ceramic-details/ceramic-details-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { CeramicDetailsPage } from './ceramic-details.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: CeramicDetailsPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class CeramicDetailsPageRoutingModule {}

+ 20 - 0
src/app/ceramic-details/ceramic-details.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CeramicDetailsPageRoutingModule } from './ceramic-details-routing.module';
+
+import { CeramicDetailsPage } from './ceramic-details.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CeramicDetailsPageRoutingModule
+  ],
+  declarations: [CeramicDetailsPage]
+})
+export class CeramicDetailsPageModule {}

+ 18 - 0
src/app/ceramic-details/ceramic-details.page.html

@@ -0,0 +1,18 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-title>{{ title }}</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="close()">关闭</ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <div class="ceramic-container">
+    <ion-img [src]="imageUrl" alt="{{ title }}"></ion-img>
+    <div class="description">
+      <h2>{{ title }}</h2>
+      <p>{{ description }}</p>
+    </div>
+  </div>
+</ion-content>

+ 29 - 0
src/app/ceramic-details/ceramic-details.page.scss

@@ -0,0 +1,29 @@
+.ceramic-container {
+    text-align: center;
+    padding: 16px;
+  }
+  
+  ion-img {
+    max-width: 100%;
+    max-height: 200px;
+    margin: 16px auto;
+    border-radius: 10px;
+    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+  }
+  
+  .description {
+    margin-top: 16px;
+    text-align: center;
+  }
+  
+  .description h2 {
+    font-size: 1.5rem;
+    font-weight: bold;
+    margin-bottom: 8px;
+  }
+  
+  .description p {
+    font-size: 1rem;
+    color: #666;
+  }
+  

+ 17 - 0
src/app/ceramic-details/ceramic-details.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { CeramicDetailsPage } from './ceramic-details.page';
+
+describe('CeramicDetailsPage', () => {
+  let component: CeramicDetailsPage;
+  let fixture: ComponentFixture<CeramicDetailsPage>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(CeramicDetailsPage);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 18 - 0
src/app/ceramic-details/ceramic-details.page.ts

@@ -0,0 +1,18 @@
+import { Component, Input } from '@angular/core';
+import { ModalController } from '@ionic/angular';
+@Component({
+  selector: 'app-ceramic-details',
+  templateUrl: './ceramic-details.page.html',
+  styleUrls: ['./ceramic-details.page.scss'],
+})
+export class CeramicDetailsPage {
+  @Input() title!: string; // 陶瓷名称
+  @Input() description!: string; // 陶瓷介绍内容
+  @Input() imageUrl!: string; // 陶瓷图片
+  constructor(private modalController: ModalController) {}
+
+  // 关闭模态框
+  close() {
+    this.modalController.dismiss();
+  }
+}

+ 4 - 0
src/app/explore-container/explore-container.component.html

@@ -0,0 +1,4 @@
+<div id="container">
+  <strong>{{ name }}</strong>
+  <p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
+</div>

+ 0 - 0
src/app/home/home.page.scss → src/app/explore-container/explore-container.component.scss


+ 7 - 7
src/app/home/home.page.spec.ts → src/app/explore-container/explore-container.component.spec.ts

@@ -1,19 +1,19 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { IonicModule } from '@ionic/angular';
 
-import { HomePage } from './home.page';
+import { ExploreContainerComponent } from './explore-container.component';
 
-describe('HomePage', () => {
-  let component: HomePage;
-  let fixture: ComponentFixture<HomePage>;
+describe('ExploreContainerComponent', () => {
+  let component: ExploreContainerComponent;
+  let fixture: ComponentFixture<ExploreContainerComponent>;
 
   beforeEach(async () => {
     await TestBed.configureTestingModule({
-      declarations: [HomePage],
+      declarations: [ExploreContainerComponent],
       imports: [IonicModule.forRoot()]
     }).compileComponents();
 
-    fixture = TestBed.createComponent(HomePage);
+    fixture = TestBed.createComponent(ExploreContainerComponent);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });
@@ -21,4 +21,4 @@ describe('HomePage', () => {
   it('should create', () => {
     expect(component).toBeTruthy();
   });
-});
+});

+ 12 - 0
src/app/explore-container/explore-container.component.ts

@@ -0,0 +1,12 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'app-explore-container',
+  templateUrl: './explore-container.component.html',
+  styleUrls: ['./explore-container.component.scss'],
+})
+export class ExploreContainerComponent {
+
+  @Input() name?: string;
+
+}

+ 14 - 0
src/app/explore-container/explore-container.module.ts

@@ -0,0 +1,14 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { ExploreContainerComponent } from './explore-container.component';
+
+@NgModule({
+  imports: [ CommonModule, FormsModule, IonicModule],
+  declarations: [ExploreContainerComponent],
+  exports: [ExploreContainerComponent]
+})
+export class ExploreContainerComponentModule {}

+ 0 - 20
src/app/home/home.page.html

@@ -1,20 +0,0 @@
-<ion-header [translucent]="true">
-  <ion-toolbar>
-    <ion-title>
-      Blank
-    </ion-title>
-  </ion-toolbar>
-</ion-header>
-
-<ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">Blank</ion-title>
-    </ion-toolbar>
-  </ion-header>
-
-  <div id="container">
-    <strong>Ready to create an app?</strong>
-    <p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
-  </div>
-</ion-content>

+ 0 - 12
src/app/home/home.page.ts

@@ -1,12 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
-  selector: 'app-home',
-  templateUrl: 'home.page.html',
-  styleUrls: ['home.page.scss'],
-})
-export class HomePage {
-
-  constructor() {}
-
-}

+ 12 - 0
src/app/service/api.ts

@@ -0,0 +1,12 @@
+import axios from 'axios';
+
+const BASE_URL = 'http://127.0.0.1:5000'; // 替换为 Flask 服务的实际地址
+export const predict = async (data: any) => {
+  try {
+    const response = await axios.post(`${BASE_URL}/predict`, { data });
+    return response.data;
+  } catch (error) {
+    console.error('Error while predicting:', error);
+    throw error;
+  }
+};

+ 3 - 3
src/app/home/home-routing.module.ts → src/app/tab1/tab1-routing.module.ts

@@ -1,11 +1,11 @@
 import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
-import { HomePage } from './home.page';
+import { Tab1Page } from './tab1.page';
 
 const routes: Routes = [
   {
     path: '',
-    component: HomePage,
+    component: Tab1Page,
   }
 ];
 
@@ -13,4 +13,4 @@ const routes: Routes = [
   imports: [RouterModule.forChild(routes)],
   exports: [RouterModule]
 })
-export class HomePageRoutingModule {}
+export class Tab1PageRoutingModule {}

+ 20 - 0
src/app/tab1/tab1.module.ts

@@ -0,0 +1,20 @@
+import { IonicModule } from '@ionic/angular';
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Tab1Page } from './tab1.page';
+import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+
+import { Tab1PageRoutingModule } from './tab1-routing.module';
+
+@NgModule({
+  imports: [
+    IonicModule,
+    CommonModule,
+    FormsModule,
+    ExploreContainerComponentModule,
+    Tab1PageRoutingModule
+  ],
+  declarations: [Tab1Page]
+})
+export class Tab1PageModule {}

+ 8 - 0
src/app/tab1/tab1.page.html

@@ -0,0 +1,8 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title class="header-title">窑忆</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true" style="--background: url('/assets/images/background.jpg') no-repeat center/cover;">
+</ion-content>

+ 5 - 0
src/app/tab1/tab1.page.scss

@@ -0,0 +1,5 @@
+.header-title {
+    font-family: "FangSong", serif; /* 使用仿宋体 */
+    font-weight: bold; /* 加粗 */
+    text-align: center; /* 居中 */
+  }

+ 26 - 0
src/app/tab1/tab1.page.spec.ts

@@ -0,0 +1,26 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { IonicModule } from '@ionic/angular';
+
+import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+
+import { Tab1Page } from './tab1.page';
+
+describe('Tab1Page', () => {
+  let component: Tab1Page;
+  let fixture: ComponentFixture<Tab1Page>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [Tab1Page],
+      imports: [IonicModule.forRoot(), ExploreContainerComponentModule]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(Tab1Page);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 43 - 0
src/app/tab1/tab1.page.ts

@@ -0,0 +1,43 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-tab1',
+  templateUrl: './tab1.page.html',
+  styleUrls: ['./tab1.page.scss'],
+})
+export class Tab1Page {
+  userQuery: string = ''; // 用户输入的文本
+  agentAnswer: string = ''; // 智能体的回答
+  recommendedQuestion: string = '吉州窑的典型纹饰是什么?'; // 每日推荐问题
+
+  constructor() {}
+
+  // 处理用户输入变化
+  onInputChange(event: any) {
+    this.userQuery = event.target.value;
+  }
+
+  // 提交问题并获取答案
+  fetchAnswer() {
+    if (this.userQuery.trim() === '') {
+      this.agentAnswer = '请输入有效的问题!';
+      return;
+    }
+
+    // 模拟调用智能体接口
+    this.agentAnswer = `这是关于 "${this.userQuery}" 的回答。`;
+  }
+
+  // 启动语音识别
+  startVoiceRecognition() {
+    // 模拟语音识别逻辑
+    this.userQuery = '模拟语音输入内容';
+    this.fetchAnswer();
+  }
+
+  // 设置推荐问题为查询内容
+  setQuery(question: string) {
+    this.userQuery = question;
+    this.fetchAnswer();
+  }
+}

+ 16 - 0
src/app/tab2/tab2-routing.module.ts

@@ -0,0 +1,16 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { Tab2Page } from './tab2.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: Tab2Page,
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class Tab2PageRoutingModule {}

+ 20 - 0
src/app/tab2/tab2.module.ts

@@ -0,0 +1,20 @@
+import { IonicModule } from '@ionic/angular';
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Tab2Page } from './tab2.page';
+import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+
+import { Tab2PageRoutingModule } from './tab2-routing.module';
+
+@NgModule({
+  imports: [
+    IonicModule,
+    CommonModule,
+    FormsModule,
+    ExploreContainerComponentModule,
+    Tab2PageRoutingModule
+  ],
+  declarations: [Tab2Page]
+})
+export class Tab2PageModule {}

+ 89 - 0
src/app/tab2/tab2.page.html

@@ -0,0 +1,89 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title class="header-title">窑忆</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true" style="--background: url('/assets/images/background.jpg') no-repeat center/cover;">
+  <!-- 左上角图标:控制左边栏 -->
+  <div class="icon top_left" (click)="toggleSidebar()">
+    <ion-icon [name]="isSidebarVisible ? 'chevron-back-outline' : 'menu-outline'"></ion-icon>
+  </div>
+  <!-- 左边栏 -->
+  <div class="left_sidebar" [ngClass]="{ hidden: !isSidebarVisible }">
+    <!-- 右上角图标:创建新对话 -->
+    <div class="icon top_right" (click)="createNewChat()">
+    <ion-icon name="add-circle-outline"></ion-icon>
+  </div>
+    <div class="sidebar_box">
+    <!-- 动态显示对话列表 -->
+      <div class="left_sidebar_content" *ngIf="isSidebarVisible">
+        <ul>
+          <li *ngFor="let conversation of conversations" (click)="switchConversation(conversation.id)" [ngClass]="{'active': conversation.id === currentConversationId}">
+            <div class="conversation-item">
+              <span>{{ conversation.title || '新对话' }}</span>
+            </div>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <!-- 导出当前对话 -->
+      <div class="transparent_box" (click)="exportConversation()">
+        <!-- 左侧图标 -->
+        <div class="export_icon">
+          <ion-icon name="download-outline"></ion-icon>
+        </div>
+        <!-- 右侧文字 -->
+        <div class="export_text">
+          导出对话
+        </div>
+      </div>
+  </div>
+    <!-- 右侧对话区域 -->
+  <div class="chat_area" [ngStyle]="{'margin-left': isSidebarVisible ? '30%' : '0'}">
+    <!-- 对话内容显示框 -->
+    <div class="chat-container">
+      <div *ngFor="let message of currentMessages" class="message-row" [ngClass]="{'user-message': message.role === 'user', 'ai-message': message.role === 'other'}">
+        <!-- 用户消息内容 -->
+        <div class="user-bubble" *ngIf="message.role === 'user'">
+          {{ message.content}}
+        </div>
+        <!-- AI 消息内容 -->
+        <div class="ai-message-row" *ngIf="message.role === 'other'">
+          <div class="ai-bubble">
+            <!-- 如果有图片,显示图片 -->
+            <div *ngIf="message.image" class="ai-image">
+              <img [src]="message.image" alt="生成图片" />
+            </div>
+            <!-- 如果是实时生成的内容,显示内容 -->
+            <div *ngIf="message.content" class="ai-text">
+              <div>{{ message.content }}</div>
+            </div>
+          </div>
+        </div>
+        <!-- 用户头像 -->
+        <div *ngIf="message.role === 'user'" class="user-avatar">
+          <img src="/assets/images/user.jpg" class="avatar" alt="用户头像">
+        </div>
+      </div>
+    </div>
+    <!-- 问题输入框 -->
+    <div class="input_box">
+      <!-- 语音输入图标 -->
+      <ion-button (click)="startSpeechRecognition()" class="voice_button">
+        <ion-icon name="mic-outline"></ion-icon>
+      </ion-button>
+      <!-- 输入框 -->
+      <ion-input [(ngModel)]="userMessage" placeholder="输入问题..." clearInput></ion-input>
+      <!-- 发送/终止按钮 -->
+      <ion-button *ngIf="isGenerating; else sendButton" (click)="stopGeneration()" class="stop_button">
+        <ion-icon name="pause-circle-outline"></ion-icon>
+      </ion-button>
+      <ng-template #sendButton>
+        <ion-button (click)="sendMessage()" class="send_button">
+          <ion-icon name="arrow-forward-circle-outline"></ion-icon>
+        </ion-button>
+      </ng-template>
+    </div>
+  </div>
+</ion-content>

+ 288 - 0
src/app/tab2/tab2.page.scss

@@ -0,0 +1,288 @@
+.header-title {
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+}
+/* 左边栏样式 */
+.left_sidebar {
+  width: 30%;
+  height: 100%;
+  background-color: #f0f0f0;
+  border-right: 1px solid #ccc;
+  position: absolute;
+  top: 0;
+  left: 0;
+  transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out; /* 动画效果 */
+}
+/* 左边栏隐藏时样式 */
+.left_sidebar.hidden {
+  transform: translateX(-100%); /* 左移隐藏 */
+  opacity: 0; /* 渐隐效果 */
+}
+/* 图标样式 */
+.icon {
+  position: absolute; /* 图标相对于窗口位置 */
+  font-size: calc(2vw + 10px);
+  color: gray;
+  cursor: pointer;
+  padding: 1vw; /* 动态调整内边距 */
+  background-color: transparent;
+  border-radius: 10%;
+  z-index: 100; /* 确保图标显示在最上层 */
+}
+/* 左上角图标 */
+.top_left {
+  top: calc(7vh); /* 距离页面顶部2% */
+  left: calc(3vw); /* 距离页面左侧2% */
+}
+/* 右上角图标 */
+.top_right {
+  top: calc(7vh); /* 距离页面顶部2% */
+  left: calc(20vw); /* 距离页面右侧10% */
+}
+/* 白色盒子样式 */
+.sidebar_box {
+  background: url('/assets/images/sidebarbox-background.png') no-repeat center/cover;
+  margin: 5px; /* 盒子内部与边栏之间的间距 */
+  padding: 2px; /* 内边距增加视觉空间 */
+  border-radius: 10px; /* 圆角 */
+  box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; /* 轻微阴影增强视觉层次感 */
+  height: calc(75%); /* 减去上下间距的高度 */
+  margin-top: 100px; /* 盒子与顶部的距离 */
+  overflow-y: scroll; /* 滚动支持 */
+}
+/* 左边栏内容样式 */
+.left_sidebar_content {
+  font-size: 16px;
+  color: #333; /* 深灰色字体 */
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  line-height: 1.5; /* 增加行高,提升可读性 */
+}
+/* 新增的透明盒子 */
+.transparent_box {
+  display: flex;
+  align-items: center;
+  background-color: transparent;
+  margin-top: 10px;
+  margin: 5px; /* 盒子内部与边栏之间的间距 */
+  padding: 10px;
+  border-radius: 10px;
+  cursor: pointer;
+  transition: background-color 0.3s ease;
+}
+.transparent_box:hover {
+  background-color: rgba(0, 0, 0, 0.1);
+}
+/* 左侧图标样式 */
+.export_icon {
+  margin-right: 10px;
+  font-size: calc(2vw + 10px);
+  color: gray;
+}
+/* 右侧文字样式 */
+.export_text {
+  color: gray;
+  font-size: 14px;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold;
+}
+/* 清理列表默认样式 */
+.left_sidebar_content ul {
+  list-style-type: none; /* 去掉列表前的点 */
+  padding: 0; /* 去掉内边距 */
+  margin: 0; /* 去掉外边距 */
+}
+/* 列表项基础样式 */
+.left_sidebar_content ul li {
+  cursor: pointer; /* 鼠标指针变为手型 */
+  padding: 10px 15px; /* 内边距使列表项看起来更分明 */
+  border-radius: 5px; /* 圆角样式 */
+  transition: background-color 0.3s ease; /* 鼠标悬停时的过渡效果 */
+  display: flex; /* 使用 flex 排版 */
+  align-items: center; /* 图标和文字垂直居中 */
+}
+
+/* 鼠标悬停时的样式 */
+.left_sidebar_content ul li:hover {
+  background-color: #f0f0f0; /* 鼠标悬停背景色 */
+}
+
+/* 当前选中项的样式 */
+.left_sidebar_content ul li.active {
+  background-color: rgba(196, 196, 196, 0.5); /* 选中项背景色 */
+  color: #fff; /* 选中项文字颜色 */
+}
+/* 对话项文字样式 */
+.conversation-item span {
+  font-size: 16px; /* 文本大小 */
+  white-space: nowrap; /* 不换行 */
+  overflow: hidden; /* 超出部分隐藏 */
+  text-overflow: ellipsis; /* 用省略号表示超出部分 */
+  flex: 1; /* 文字部分自动占据剩余空间 */
+}
+/* 右侧对话区域 */
+.chat_area {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  transition: margin-left 0.3s ease; /* 动画过渡 */
+}
+/* 对话内容显示框 */
+.chat-container{
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;  /* 默认左对齐 */
+  flex-grow: 1;
+  overflow-y: auto;
+  margin-bottom: 10px;
+  background-color: rgba(249, 249, 249, 0.7); 
+  padding: 10px;
+  height: calc(80% - 15px); /* 让它占据大部分空间 */
+}
+/* 单条消息容器 */
+.message-row {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;  /* 使内容右对齐,头像和气泡分别在左右 */
+  margin: 5px;
+  width: 100%;  /* 占满宽度 */
+}
+/* 用户消息气泡 */
+.user-bubble {
+  max-width: 80%;
+  padding: 10px;
+  border-radius: 20px;
+  font-size: 16px;
+  line-height: 1.4;
+  word-wrap: break-word;
+  white-space: pre-line; /* 保留换行 */
+  display: inline-block;
+  background: url('/assets/images/bubble-background.png') no-repeat center/cover;
+  color: black;
+  margin-right: 5px; /* 留出空间给头像 */
+  border-radius: 20px 20px 0 20px;  /* 气泡形状 */
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  /* 添加阴影效果 */
+  box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2), /* 外阴影 */
+              inset -2px -2px 5px rgba(255, 255, 255, 0.5), /* 内阴影高光 */
+              inset 2px 2px 5px rgba(0, 0, 0, 0.1); /* 内阴影暗部 */
+}
+/* 用户消息右对齐 */
+.user-bubble{
+  flex-direction: row;  /* 保证头像和气泡从左到右排列 */
+}
+/* AI 消息外层容器:左对齐 */
+.ai-message-row {
+  display: flex;
+  justify-content: flex-start; /* 左对齐 */
+  margin: 10px 0; /* 每条消息的上下间距 */
+  width: 100%; /* 占满父容器宽度 */
+}
+/* AI消息气泡 */
+.ai-bubble{
+  max-width: 80%;
+  padding: 10px;
+  border-radius: 20px;
+  font-size: 16px;
+  line-height: 1.4;
+  word-wrap: break-word;
+  white-space: pre-line; /* 保留换行 */
+  display: inline-block;
+  background: url('/assets/images/bubble-background.png') no-repeat center/cover;
+  color: black;
+  margin-right: 5px; /* 留出空间给头像 */
+  border-radius: 20px 20px 20px 0px;  /* 气泡形状 */
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  /* 添加阴影效果 */
+  box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.2), /* 外阴影 */
+              inset -2px -2px 5px rgba(255, 255, 255, 0.5), /* 内阴影高光 */
+              inset 2px 2px 5px rgba(0, 0, 0, 0.1); /* 内阴影暗部 */
+}
+/* AI 消息图片 */
+.ai-image img {
+  max-width: 100%;
+  height: auto;
+  border-radius: 10px; /* 图片圆角 */
+  margin-top: 5px;
+}
+/* 头像容器 */
+.user-avatar {
+  display: flex;
+  justify-content: flex-start;
+  align-items: center;
+  background-color: #f1f1f1;
+  color: #000;
+}
+/* 头像样式 */
+.avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;  /* 圆形头像 */
+  object-fit: cover;
+}
+/* 输入框盒子的样式 */
+.input_box {
+  display: flex;
+  align-items: center;           /* 垂直居中对齐 */
+  justify-content: center; 
+  justify-content: space-between; /* 左右对齐 */
+  width: 100%;                   /* 让输入框区域宽度充满父容器 */
+  padding: 5px;                 /* 内边距 */
+  margin-bottom: 10px;           /* 距离底部 5px */
+  background: url('/assets/images/button-background2.png') no-repeat center/cover;
+  border-radius: 30px;            /* 圆角 */
+  box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 12px; /* 阴影效果 */
+  position: relative;
+}
+/* 语音输入按钮 */
+.voice_button {
+  color: gray;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;  /* 去掉默认边框 */
+  --background: transparent;
+  cursor: pointer;
+  margin-right: 0px; /* 与输入框之间的小间隙 */
+}
+/* 输入框样式 */
+ion-input {
+  flex-grow: 1;  /* 使输入框占据剩余空间 */
+  max-width: calc(100%); /* 设置输入框的最大宽度,留出空间给发送按钮 */
+  --padding-start: 10px;
+  --padding-end: 10px;
+  --border-radius: 30px;
+  --background: #f9f9f9;
+  box-sizing: border-box;
+  font-family: "FangSong", serif;
+  font-weight: bold;
+  margin-right: 0px; /* 确保输入框与发送按钮之间有小间隙 */
+}
+/* 输入框在获得焦点时的样式 */
+ion-input:focus {
+  --background: #fff;  /* 获取焦点时背景变为白色 */
+  --box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);  /* 聚焦时显示阴影 */
+}
+/* 发送按钮 */
+.send_button{
+  color: gray;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  --background: transparent;
+  border: none;  /* 去掉默认边框 */
+  cursor: pointer;
+  margin-left: 0px; /* 与输入框之间的小间隙 */
+}
+/* 终止按钮 */
+.stop_button{
+  color: gray;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  --background: transparent;
+  border: none;  /* 去掉默认边框 */
+  cursor: pointer;
+  margin-left: 0px; /* 与输入框之间的小间隙 */
+}

+ 26 - 0
src/app/tab2/tab2.page.spec.ts

@@ -0,0 +1,26 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { IonicModule } from '@ionic/angular';
+
+import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+
+import { Tab2Page } from './tab2.page';
+
+describe('Tab2Page', () => {
+  let component: Tab2Page;
+  let fixture: ComponentFixture<Tab2Page>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [Tab2Page],
+      imports: [IonicModule.forRoot(), ExploreContainerComponentModule]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(Tab2Page);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 246 - 0
src/app/tab2/tab2.page.ts

@@ -0,0 +1,246 @@
+import { Component } from '@angular/core';
+import { ImagineWork, DalleOptions, FmodeChatCompletion} from 'fmode-ng';
+
+@Component({
+  selector: 'app-tab2',
+  templateUrl: './tab2.page.html',
+  styleUrls: ['./tab2.page.scss'],
+})
+export class Tab2Page {
+  isSidebarVisible: boolean = true; // 左边栏显示状态
+  conversations: { id: string; title: string; messages: { content: string, role: string, image?: string }[]; isTitled: boolean }[] = [];
+  currentConversationId: string | null = null; // 当前对话 ID
+  // 动态获取当前对话的消息
+  get currentMessages() {
+    const currentConversation = this.conversations.find(
+      (conversation) => conversation.id === this.currentConversationId
+    );
+    return currentConversation ? currentConversation.messages : [];
+  }
+  userMessage: string = '';  // 用户输入的消息
+  responseMsg: string = ''; // 实时消息内容
+  isComplete: boolean = true; // 消息是否生成完成
+  imagineWork: ImagineWork | undefined; // 当前的生成任务
+  isGenerating: boolean = false; // 当前是否正在生成
+  constructor() {}
+
+  // 切换左边栏显示和隐藏
+  toggleSidebar() {
+    this.isSidebarVisible = !this.isSidebarVisible;
+    console.log(`左边栏 ${this.isSidebarVisible ? '显示' : '隐藏'}`);
+  }
+
+  // 创建新对话
+  createNewChat() {
+    console.log('创建新对话');
+    const newId = `conversation_${Date.now()}`; // 生成唯一 ID
+    const newConversation = {
+      id: newId,
+      title: '', // 初始标题为空
+      messages: [], // 消息列表为空
+      isTitled: false, // 初始状态:未生成标题
+    };
+    this.conversations.push(newConversation);// 添加到对话列表
+    this.currentConversationId = newId; // 设置为当前对话
+    this.userMessage = ''; // 清空输入框
+  }
+  ngOnInit() {
+    // 初始化时检查是否有对话
+    if (this.conversations.length === 0) {
+      this.createNewChat(); // 自动创建默认对话
+    }
+  }
+  // 切换对话
+  switchConversation(conversationId: string) {
+    // 切换到新对话
+    this.currentConversationId = conversationId;
+    this.userMessage = ''; // 清空输入框
+    console.log('切换到对话:', conversationId);
+  }
+
+  // 导出当前对话
+  exportConversation() {
+    console.log('导出当前对话');
+    if (!this.currentConversationId) {
+      console.error('未选择任何对话,无法导出。');
+      return;
+    }
+    // 获取当前对话
+    const currentConversation = this.conversations.find(c => c.id === this.currentConversationId);
+    if (!currentConversation) {
+      console.error('当前对话不存在,无法导出。');
+      return;
+    }
+    // 构建 Markdown 内容
+    const title = currentConversation.title || '未命名对话';
+    let mdContent = `# ${title}\n\n`; // Markdown 标题
+    currentConversation.messages.forEach((message, index) => {
+      const role = message.role === 'user' ? '用户' : 'AI';
+      mdContent += `**${role}:** ${message.content}\n\n`; // 加粗显示角色
+    });
+    // 创建 Blob 对象
+    const blob = new Blob([mdContent], { type: 'text/markdown' });
+    // 创建临时下载链接
+    const url = window.URL.createObjectURL(blob);
+    const a = document.createElement('a');
+    a.href = url;
+    a.download = `${title}.md`; // 设置下载文件名
+    document.body.appendChild(a); // 将 <a> 添加到 DOM 中
+    a.click(); // 触发点击
+    document.body.removeChild(a); // 移除 <a>
+    window.URL.revokeObjectURL(url); // 释放 URL
+    console.log('对话已导出为 Markdown 文件:', title);
+  }
+
+// 发送消息,回答问题
+async sendMessage() {
+    if (this.userMessage.trim() && this.currentConversationId) {  // 如果输入的消息不为空
+      const currentMessage = this.userMessage.trim(); // 保留当前输入内容
+      console.log(currentMessage)
+      console.log('发送消息:', currentMessage);
+        const currentConversation = this.conversations.find(c => c.id === this.currentConversationId);
+        if (currentConversation) {
+          // 将用户消息推入聊天列表,并记录其索引
+          const userMessageIndex = currentConversation.messages.push({ content: currentMessage, role: 'user' }) - 1;
+          // 立即清空输入框
+          this.userMessage = '';
+          if (currentMessage.startsWith('请生成图片:')) {
+            // 调用图片生成逻辑
+          const generatedImage = await this.generateImage(currentMessage);
+          if (generatedImage) {
+            // 在用户消息后插入图片生成的内容
+            currentConversation.messages.push({
+              content: '',
+              role: 'other',
+              image: generatedImage,
+            });
+          } else {
+            // 插入失败消息
+            currentConversation.messages.push({
+              content: '图片生成失败,请重试。',
+              role: 'other',
+            });
+          }
+        } 
+        //文本生成逻辑
+        else{
+          // 初始化逐步生成状态
+          this.responseMsg = '';
+          this.isComplete = false; // 设置生成状态为未完成
+          // 调用逐步生成方法
+          this.generateText(currentMessage, currentConversation.messages, userMessageIndex + 1);
+        }
+      }else {
+        console.error('当前对话未找到');
+      }
+    }
+  }
+  // 停止生成
+  stopGeneration() {
+    console.log('终止生成请求');
+    this.isGenerating = false; // 将状态设置为false以停止生成
+  }
+  // 调用 ImagineWork 生成图片
+  private async generateImage(prompt: string): Promise<string | null> {
+    try {
+      console.log('调用图片生成工具...');
+      this.imagineWork = new ImagineWork();
+      const options: DalleOptions = { prompt };
+      console.log('调用生成工具参数:', options);
+      const work = await this.imagineWork.draw(options).toPromise();
+      console.log('生成任务结果:', work?.toJSON());
+      const images = work?.get('images');
+      if (images && images.length > 0) {
+        console.log('图片生成成功:', images[0]);
+        return images[0]; // 返回第一张图片 URL
+      } else {
+        console.error('图片生成失败: 未返回任何图片');
+        return null;
+      }
+    } catch (error) {
+      console.error('图片生成失败:', error);
+      return null;
+    }
+  }
+  // 调用 FmodeChatCompletion 生成回答
+  private generateText(prompt: string, messages: { content: string, role: string }[], insertIndex: number): void {
+    console.log('调用文本生成工具...');
+    // 设置生成状态为 true
+    this.isGenerating = true;
+    const completion = new FmodeChatCompletion([
+      { role: 'system', content: '您是一位提供详细回答的助手,请帮助用户回答以下问题。' },
+      { role: 'user', content: prompt },
+    ]);
+    // 插入占位消息
+    let newMessage = {role:"other",content:""}
+    messages.splice(insertIndex, 0, newMessage);
+    completion.sendCompletion().subscribe({
+      next: (message: any) => {
+        if (!this.isGenerating) {
+          console.log('生成已被终止,忽略后续内容');
+          return; // 如果状态标记为终止,跳过更新
+        }
+        console.log('生成的部分内容:', message.content);
+        newMessage.content = message?.content;
+        console.log(JSON.stringify(messages))
+      },
+      complete: async () => {
+        console.log('文本生成完成');
+        this.isGenerating = false; // 重置生成状态
+        this.isComplete = true; // 设置生成状态为完成
+        // 检查当前对话是否需要生成标题
+        const currentConversation = this.conversations.find(c => c.id === this.currentConversationId);
+        if (currentConversation && !currentConversation.isTitled) {
+          currentConversation.title = this.generateConversationTitle(currentConversation.messages);
+          currentConversation.isTitled = true; // 标记为已生成标题
+          console.log('对话标题已生成:', currentConversation.title);
+        }
+      },
+      error: (err: any) => {
+        console.error('文本生成失败:', err);
+        newMessage.content = '回答生成失败,请重试。'; // 更新占位消息为错误提示
+        this.isGenerating = false; // 重置生成状态
+        this.isComplete = true; // 重置生成状态
+      },
+    });
+  }
+  // 为对话生成标题
+  private generateConversationTitle(messages: { content: string; role: string }[]): string {
+    const userMessage = messages.find(msg => msg.role === 'user' && msg.content.trim() !== '');
+    if (userMessage) {
+      return `对话:${userMessage.content.slice(0, 5)}...`;
+    }
+    return '对话:无内容';
+  }
+  // 开始语音识别
+  startSpeechRecognition() {
+    // 检查浏览器是否支持 SpeechRecognition
+    if (!('webkitSpeechRecognition' in window)) {
+      alert('抱歉,您的浏览器不支持语音输入功能');
+      return;
+    }
+    const recognition = new (window as any).webkitSpeechRecognition();
+    recognition.lang = 'zh-CN';  // 设置语言为中文
+    recognition.interimResults = true; // 支持实时识别结果
+    let finalTranscript = ''; // 用于存储最终结果
+    recognition.start();
+    recognition.onresult = (event: any) => {
+      let interimTranscript = '';
+      for (let i = event.resultIndex; i < event.results.length; i++) {
+        if (event.results[i].isFinal) {
+          // 如果结果是最终结果,则存储到 finalTranscript 中
+          finalTranscript += event.results[i][0].transcript;
+        } else {
+          // 如果是临时结果,存储到 interimTranscript 中
+          interimTranscript += event.results[i][0].transcript;
+        }
+        console.log('识别结果:', interimTranscript);
+      }
+      this.userMessage = `${finalTranscript}${interimTranscript}`.trim();
+    };
+    recognition.onerror = (event: any) => {
+      console.error('语音识别错误:', event.error);
+      alert('语音识别失败,请重试!');
+    };
+  }
+}

+ 16 - 0
src/app/tab3/tab3-routing.module.ts

@@ -0,0 +1,16 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { Tab3Page } from './tab3.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: Tab3Page,
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class Tab3PageRoutingModule {}

+ 20 - 0
src/app/tab3/tab3.module.ts

@@ -0,0 +1,20 @@
+import { IonicModule } from '@ionic/angular';
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Tab3Page } from './tab3.page';
+import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+
+import { Tab3PageRoutingModule } from './tab3-routing.module';
+
+@NgModule({
+  imports: [
+    IonicModule,
+    CommonModule,
+    FormsModule,
+    ExploreContainerComponentModule,
+    Tab3PageRoutingModule
+  ],
+  declarations: [Tab3Page]
+})
+export class Tab3PageModule {}

+ 62 - 0
src/app/tab3/tab3.page.html

@@ -0,0 +1,62 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title class="header-title">窑忆</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true" style="--background: url('/assets/images/background.jpg') no-repeat center/cover;">
+  <div class="image-container">
+    <!-- 背景框 -->
+    <div class="background-frame">
+      <!-- 图片显示区域 -->
+      <img [src]="imageSrc" alt="Selected Image" class="image" *ngIf="!isCameraMode && imageSrc" />
+      <!-- 视频流显示 -->
+      <video #video *ngIf="isCameraMode && !imageCaptured && cameraAvailable" autoplay muted></video>
+      <!-- 占位文本 -->
+      <p class="placeholder-text" *ngIf="!imageSrc && !isCameraMode">No Image Selected</p>
+      <p class="placeholder-text" *ngIf="isCameraMode && (!cameraAvailable || imageCaptured)">Camera not available</p>
+    </div>
+    <!-- 切换图标 -->
+    <ion-button fill="clear" class="toggle-button" (click)="toggleMode()">
+      <ion-icon [name]="isCameraMode ? 'image' : 'camera'"></ion-icon>
+    </ion-button>
+  </div>
+  <!-- 上传图片按钮 -->
+  <div *ngIf="!isCameraMode" class="upload-container">
+    <ion-button class="primary-button" (click)="selectImage()">上传图片</ion-button>
+    <ion-button class="secondary-button" (click)="startDetection()">开始检测</ion-button>
+    <input type="file" hidden #fileInput (change)="onFileSelected($event)" />
+  </div>
+  <!-- 调用摄像头按钮 -->
+  <div *ngIf="isCameraMode" class="camera-container">
+    <ion-button class="primary-button" (click)="startCamera()">打开摄像头</ion-button>
+    <ion-button class="secondary-button" (click)="startDetection()">开始检测</ion-button>
+  </div>
+  <!-- Canvas 用于捕获图像 -->
+  <canvas #canvas hidden></canvas>
+  <!-- 分隔线 -->
+  <hr class="divider" />
+  <!-- 动态填充表格 -->
+  <div class="table-container">
+    <table>
+      <thead>
+        <tr>
+          <th>序号</th>
+          <th>识别结果</th>
+          <th>识别置信度</th>
+          <th>详情</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr *ngFor="let item of recognitionResults; let i = index">
+          <td>{{ i + 1 }}</td>
+          <td>{{ item.result }}</td>
+          <td>{{ item.confidence }}</td>
+          <td>
+            <ion-button size="small" fill="outline" (click)="viewDetails(item)">查看</ion-button>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</ion-content>

+ 146 - 0
src/app/tab3/tab3.page.scss

@@ -0,0 +1,146 @@
+.header-title {
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+}
+.image-container {
+position: relative;
+width: 430px;
+height: 430px;
+display: flex;
+justify-content: center;
+align-items: center;
+border-radius: 10px;
+overflow: hidden;
+margin-top: 0px;
+background: url('/assets/images/recognition-background.png') no-repeat center/cover;
+}
+.background-frame {
+position: relative;
+width: 87%; /* 内容框宽度 */
+height: 93%; /* 内容框高度 */
+background: rgb(196, 191, 191 ,0.8); /* 半透明灰色背景 */
+border-radius: 10px; /* 圆角与外框一致 */
+display: flex;
+justify-content: center;
+align-items: center;
+overflow: hidden; /* 确保内容不会溢出 */
+}
+.placeholder-text {
+color: white;
+font-size: 16px;
+text-align: center;
+}
+.image {
+width: 100%; /* 图片自适应内容框 */
+height: 100%;
+object-fit: cover; /* 确保图片完整覆盖框 */
+border-radius: 10px; /* 圆角与框一致 */
+}
+video {
+width: 100%;
+height: 100%;
+object-fit: cover;
+border-radius: 10px;
+}
+.toggle-button {
+position: absolute;
+top: 20px;
+right: 40px;
+z-index: 10;
+color: black;
+}
+.button-group {
+display: flex;
+flex-direction: column; /* 垂直排列 */
+justify-content: center; /* 垂直居中 */
+align-items: center; /* 水平居中 */
+height: 100vh; /* 页面高度,按钮垂直居中 */
+}
+.upload-container {
+display: flex;
+justify-content: space-between; /* 按钮之间均匀分布 */
+width: 100%; /* 按钮组整体宽度占页面100% */
+margin-bottom: 10px; /* 按钮组之间的间距 */
+margin-top: 10px;
+margin-right: 10px;
+}
+.camera-container {
+display: flex;
+justify-content: space-between; /* 按钮之间均匀分布 */
+width: 100%; /* 按钮组整体宽度占页面80% */
+margin-bottom: 10px; /* 按钮组之间的间距 */
+margin-top: 10px;
+margin-right: 10px;
+}
+.primary-button {
+--background: none;
+flex: 0 0 42.5%;
+height: 48px;
+font-size: 16px;
+font-family: "FangSong", serif; /* 使用仿宋体 */
+font-weight: bold; /* 加粗 */
+text-align: center;
+border-radius: 10px;
+color: black;
+background: url('/assets/images/button-background1.png') no-repeat center/cover;
+margin-left: 15px;
+margin-right: 15px;
+box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 12px; /* 阴影效果 */
+}
+.secondary-button {
+--background: none;
+flex: 0 0 42.5%;;
+height: 48px;
+font-size: 16px;
+font-family: "FangSong", serif; /* 使用仿宋体 */
+font-weight: bold; /* 加粗 */
+text-align: center;
+border-radius: 10px;
+color: black;
+background: url('/assets/images/button-background1.png') no-repeat center/cover;
+margin-left: 15px;
+margin-right: 15px;
+box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 12px; /* 阴影效果 */
+}
+.divider {
+border: none; /* 去掉默认边框 */
+height: 40px; /* 分割线高度 */
+width: calc 100%; /* 总宽度减去左右空出20px */
+margin: 16px auto; /* 自动居中 */
+background: url('/assets/images/divider.png') repeat-x center; /* 设置图片作为分割线 */
+background-size: contain; /* 确保图片在水平方向完整显示 */
+}
+.table-container {
+width: 90%;
+margin: 16px auto;
+max-height: 500px; /* 限制表格高度 */
+overflow-y: auto; /* 添加垂直滚动条 */
+border: 1px solid #ccc; /* 添加边框方便视觉分割 */
+border-radius: 10px; /* 圆角 */
+background: url('/assets/images/table-background.png') no-repeat center/cover;
+}
+table {
+width: 100%;
+border-collapse: collapse;
+font-family: "FangSong", serif; /* 使用仿宋体 */
+font-weight: bold; /* 加粗 */
+text-align: center; /* 居中 */
+}
+thead {
+position: sticky; /* 表头固定 */
+top: 0;
+background-color: #f4f4f4;
+z-index: 1;
+}
+th,
+td {
+padding: 12px;
+border: 1px solid #ccc;
+}
+th {
+font-weight: bold;
+}
+tbody tr:hover {
+background-color: #f9f9f9;
+}

+ 23 - 0
src/app/tab3/tab3.page.spec.ts

@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { IonicModule } from '@ionic/angular';
+
+import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+
+import { Tab3Page } from './tab3.page';
+
+describe('Tab3Page', () => {
+  let component: Tab3Page;
+  let fixture: ComponentFixture<Tab3Page>;
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [Tab3Page],
+      imports: [IonicModule.forRoot(), ExploreContainerComponentModule]
+    }).compileComponents();
+    fixture = TestBed.createComponent(Tab3Page);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 188 - 0
src/app/tab3/tab3.page.ts

@@ -0,0 +1,188 @@
+import { Component, ElementRef, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core';
+import { predict } from 'src/app/service/api'; // 引入 API 服务
+import { ModalController } from '@ionic/angular';
+import { CeramicDetailsPage } from '../ceramic-details/ceramic-details.page';
+@Component({
+  selector: 'app-tab3',
+  templateUrl: 'tab3.page.html',
+  styleUrls: ['tab3.page.scss']
+})
+export class Tab3Page implements AfterViewInit{
+  @ViewChild('video',{ static: false }) videoElement!: ElementRef<HTMLVideoElement>;
+  @ViewChild('canvas',{ static: false }) canvasElement!: ElementRef<HTMLCanvasElement>;
+  @ViewChild('fileInput',{ static: false }) fileInput!: ElementRef<HTMLInputElement>;
+  isCameraMode: boolean = false; // 是否为摄像头模式
+  imageCaptured: boolean = false; // 是否已捕获图像
+  cameraAvailable: boolean = false; // 摄像头是否可用
+  imageSrc: string | null = null; // 保存图片数据
+  videoStream: MediaStream | null = null; // 保存视频流
+  showAlert: boolean = false; // 是否显示弹窗
+  recognitionResults: { result: string; confidence: string; details: string }[] = []; // 动态识别结果
+  constructor(private modalController: ModalController) {
+    this.checkCameraAvailability();
+  }
+  checkCameraAvailability() {
+    navigator.mediaDevices
+      .getUserMedia({ video: true })
+      .then(() => {
+        this.cameraAvailable = true; // 摄像头可用
+      })
+      .catch(() => {
+        this.cameraAvailable = false; // 摄像头不可用
+      });
+  }
+  // 确保视图加载完成后 ViewChild 初始化
+  ngAfterViewInit() {
+    console.log('Video Element:', this.videoElement);
+    console.log('Canvas Element:', this.canvasElement);
+    console.log('File Input Element:', this.fileInput);
+  }
+  // 切换模式
+  toggleMode() {
+    this.isCameraMode = !this.isCameraMode;
+    this.imageSrc = null; // 切换模式时清除图片
+    if (!this.isCameraMode) {
+      this.stopCamera();
+    }
+  }
+  // 选择图片
+  selectImage() {
+    if (this.fileInput) {
+      this.fileInput.nativeElement.click();
+    } else {
+      console.error('File input is not available');
+    }
+  }
+  // 处理选择的图片
+  onFileSelected(event: any) {
+    const file = event.target.files[0];
+    if (file) {
+      const reader = new FileReader();
+      reader.onload = (e: any) => {
+        this.imageSrc = e.target.result;
+      };
+      reader.readAsDataURL(file);
+    }
+  }
+  // 启动摄像头
+  async startCamera() {
+    try {
+      this.isCameraMode = true;
+      this.imageCaptured = false;
+      this.videoStream = await navigator.mediaDevices.getUserMedia({ video: true });
+      const video = this.videoElement.nativeElement;
+      video.srcObject = this.videoStream;
+      video.play();
+      // 自动捕获图像逻辑:3秒后捕获最后一帧
+      setTimeout(() => {
+        if (this.isCameraMode && this.videoStream) {
+          this.captureImage();
+        }
+      }, 3000);
+    } catch (error) {
+      console.error('Error accessing camera:', error);
+    }
+  }
+  // 停止摄像头
+  stopCamera() {
+    if (this.videoStream) {
+      const tracks = this.videoStream.getTracks();
+      tracks.forEach(track => track.stop());
+      this.videoStream = null;
+    }
+    this.isCameraMode = false;
+  }
+  // 捕获图像
+  captureImage() {
+    if (!this.videoElement || !this.canvasElement) {
+      console.error('Video or Canvas element is not available');
+      return;
+    }
+    const video = this.videoElement.nativeElement;
+    const canvas = this.canvasElement.nativeElement;
+    const context = canvas.getContext('2d');
+    if (context) {
+      // 设置 Canvas 大小与视频相同
+      canvas.width = video.videoWidth;
+      canvas.height = video.videoHeight;
+      // 绘制当前视频帧到 Canvas
+      context.drawImage(video, 0, 0, canvas.width, canvas.height);
+      // 获取图片数据并保存到 imageSrc
+      this.imageSrc = canvas.toDataURL('image/png');
+      this.imageCaptured = true;
+      // 停止摄像头
+      this.stopCamera();
+    }
+  }
+  //开始检测
+  async startDetection() {
+    if (!this.imageSrc) {
+      console.error('No image available for detection');
+      alert('请先捕获或选择图像!');
+      return;
+    }
+    console.log('开始检测事件触发,调用后端接口');
+    //调用Flask API进行检测
+    try {
+      predict(this.imageSrc)
+        .then(async (response) => {
+          console.log('检测结果:', response);
+          // 假设后端返回的数据格式为 { prediction: string, confidence: string, details: string }
+          const newResult = {
+            result: response.prediction.predicted_label || '未知结果',
+            confidence: response.prediction.confidence
+            ? response.prediction.confidence.toFixed(3) 
+            : '未知置信度',
+            details: response.prediction.details || '未知详情',
+          };
+          // 将新结果添加到识别结果数组
+        this.recognitionResults.push(newResult);
+        // 显示陶瓷详情页面
+        await this.openCeramicDetails(newResult.result);
+      })
+    }
+    catch(error) {
+      console.error('检测失败:', error);
+      alert('检测失败,请检查网络或后端服务!');
+    };
+  }
+  // 显示陶瓷详情模态框
+  async openCeramicDetails(ceramicName: string) {
+    // 定义陶瓷信息映射表
+    const ceramicInfo: { [key: string]: { description: string; imageUrl: string } } = {
+      '兔毫盏': {
+        description: '兔毫盏是一种经典的陶瓷器具,以其细长如兔毫的纹路闻名。',
+        imageUrl: 'assets/images/tuhazhan.jpg',
+      },
+      '凤纹盏': {
+        description: '凤纹盏是一种以凤纹为装饰的陶瓷,展现高贵的文化气息。',
+        imageUrl: 'assets/images/fengwenzhan.jpg',
+      },
+      '剪纸贴花盏': {
+        description: '剪纸贴花盏以独特的剪纸贴花艺术装饰,工艺精美。',
+        imageUrl: 'assets/images/jianzhitiehuazhan.jpg',
+      },
+      // 添加其他陶瓷信息...
+    };
+    const ceramicDetails = ceramicInfo[ceramicName] || {
+      description: '暂无详细信息。',
+      imageUrl: 'assets/images/default.jpg',
+    };
+    const modal = await this.modalController.create({
+      component: CeramicDetailsPage,
+      componentProps: {
+        title: ceramicName,
+        description: ceramicDetails.description,
+        imageUrl: ceramicDetails.imageUrl,
+      },
+      cssClass: 'custom-modal', 
+    });
+    await modal.present();
+  }
+  // 查看详情事件
+  viewDetails(item: { result: string; confidence: string; details: string }) {
+    console.log('查看详情:', item);
+    // 在此实现查看详情的逻辑
+    alert(`结果: ${item.result}\n置信度: ${item.confidence}\n详情: ${item.details}`);
+  }
+}

+ 16 - 0
src/app/tab4/tab4-routing.module.ts

@@ -0,0 +1,16 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { Tab4Page } from './tab4.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: Tab4Page,
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class Tab4PageRoutingModule {}

+ 20 - 0
src/app/tab4/tab4.module.ts

@@ -0,0 +1,20 @@
+import { IonicModule } from '@ionic/angular';
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Tab4Page } from './tab4.page';
+import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+
+import { Tab4PageRoutingModule } from './tab4-routing.module';
+
+@NgModule({
+  imports: [
+    IonicModule,
+    CommonModule,
+    FormsModule,
+    ExploreContainerComponentModule,
+    Tab4PageRoutingModule
+  ],
+  declarations: [Tab4Page]
+})
+export class Tab4PageModule {}

+ 27 - 0
src/app/tab4/tab4.page.html

@@ -0,0 +1,27 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title class="header-title">窑忆</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true" style="--background: url('/assets/images/background.jpg') no-repeat center/cover;">
+  <div class="box"></div>
+  <div class="user">
+    <div class="face">
+      <img src="assets/images/user.jpg" class="img" alt="User Avatar">
+    </div>
+    <div class="username">
+      pqyyyyy
+    </div>
+    <div class="id">
+      ID:14945
+    </div>
+  </div>
+  <ion-list>
+    <ion-item *ngFor="let item of list" (click)="onItemClick(item)" lines="none">
+      <ion-icon [name]="item.icon" slot="start" class="setfont"></ion-icon>
+      <ion-label>{{ item.title }}</ion-label>
+      <ion-icon name="chevron-forward" slot="end"></ion-icon>
+    </ion-item>
+  </ion-list>
+</ion-content>

+ 281 - 0
src/app/tab4/tab4.page.scss

@@ -0,0 +1,281 @@
+.header-title {
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+} 
+.box {
+  position: absolute;
+  width: 100%;
+  height: 750px;
+  top: -400px;
+  z-index: -1;
+  border-radius: 50%;
+  background:
+    radial-gradient(circle, rgba(255, 255, 255, 0.8) 60%, rgba(255, 255, 255, 0) 100%) ,
+    url('/assets/images/1.jpg') no-repeat center/cover;
+}
+
+.user {
+  height: 300px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  margin: 10px;
+  margin-bottom: 100px;
+}
+
+.face {
+  position: relative;
+  width: 120px;
+  height: 120px;
+  background: url('/assets/images/2.jpg') no-repeat center/cover;
+  border-radius: 50%;
+  margin: 0px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  overflow: hidden;
+}
+
+.username, .id {
+  color: white;
+  text-align: center;
+}
+
+.img {
+  width: 80px; /* 头像大小 */
+  height: 80px;
+  object-fit: cover;
+  border-radius: 50%;
+}
+
+ion-list {
+  margin-top: 20px;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+}
+
+ion-item {
+  --background: white;
+  --color: black;
+  padding: 10px;
+}
+
+ion-icon.setfont {
+  color: gray;
+  font-size: 1.5rem;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+}.header-title {
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+} 
+.box {
+  position: absolute;
+  width: 100%;
+  height: 750px;
+  top: -400px;
+  z-index: -1;
+  border-radius: 50%;
+  background:
+    radial-gradient(circle, rgba(255, 255, 255, 0.8) 60%, rgba(255, 255, 255, 0) 100%) ,
+    url('/assets/images/1.jpg') no-repeat center/cover;
+}
+
+.user {
+  height: 300px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  margin: 10px;
+  margin-bottom: 100px;
+}
+
+.face {
+  position: relative;
+  width: 120px;
+  height: 120px;
+  background: url('/assets/images/2.jpg') no-repeat center/cover;
+  border-radius: 50%;
+  margin: 0px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  overflow: hidden;
+}
+
+.username, .id {
+  color: white;
+  text-align: center;
+}
+
+.img {
+  width: 80px; /* 头像大小 */
+  height: 80px;
+  object-fit: cover;
+  border-radius: 50%;
+}
+
+ion-list {
+  margin-top: 20px;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+}
+
+ion-item {
+  --background: white;
+  --color: black;
+  padding: 10px;
+}
+
+ion-icon.setfont {
+  color: gray;
+  font-size: 1.5rem;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+}.header-title {
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+} 
+.box {
+  position: absolute;
+  width: 100%;
+  height: 750px;
+  top: -400px;
+  z-index: -1;
+  border-radius: 50%;
+  background:
+    radial-gradient(circle, rgba(255, 255, 255, 0.8) 60%, rgba(255, 255, 255, 0) 100%) ,
+    url('/assets/images/1.jpg') no-repeat center/cover;
+}
+
+.user {
+  height: 300px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  margin: 10px;
+  margin-bottom: 100px;
+}
+
+.face {
+  position: relative;
+  width: 120px;
+  height: 120px;
+  background: url('/assets/images/2.jpg') no-repeat center/cover;
+  border-radius: 50%;
+  margin: 0px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  overflow: hidden;
+}
+
+.username, .id {
+  color: white;
+  text-align: center;
+}
+
+.img {
+  width: 80px; /* 头像大小 */
+  height: 80px;
+  object-fit: cover;
+  border-radius: 50%;
+}
+
+ion-list {
+  margin-top: 20px;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+}
+
+ion-item {
+  --background: white;
+  --color: black;
+  padding: 10px;
+}
+
+ion-icon.setfont {
+  color: gray;
+  font-size: 1.5rem;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+}.header-title {
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+} 
+.box {
+  position: absolute;
+  width: 100%;
+  height: 750px;
+  top: -400px;
+  z-index: -1;
+  border-radius: 50%;
+  background:
+    radial-gradient(circle, rgba(255, 255, 255, 0.8) 60%, rgba(255, 255, 255, 0) 100%) ,
+    url('/assets/images/1.jpg') no-repeat center/cover;
+}
+
+.user {
+  height: 300px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  margin: 10px;
+  margin-bottom: 100px;
+}
+
+.face {
+  position: relative;
+  width: 120px;
+  height: 120px;
+  background: url('/assets/images/2.jpg') no-repeat center/cover;
+  border-radius: 50%;
+  margin: 0px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  overflow: hidden;
+}
+
+.username, .id {
+  color: white;
+  text-align: center;
+}
+
+.img {
+  width: 80px; /* 头像大小 */
+  height: 80px;
+  object-fit: cover;
+  border-radius: 50%;
+}
+
+ion-list {
+  margin-top: 20px;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+  text-align: center; /* 居中 */
+}
+
+ion-item {
+  --background: white;
+  --color: black;
+  padding: 10px;
+}
+
+ion-icon.setfont {
+  color: gray;
+  font-size: 1.5rem;
+  font-family: "FangSong", serif; /* 使用仿宋体 */
+  font-weight: bold; /* 加粗 */
+}

+ 26 - 0
src/app/tab4/tab4.page.spec.ts

@@ -0,0 +1,26 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { IonicModule } from '@ionic/angular';
+
+import { ExploreContainerComponentModule } from '../explore-container/explore-container.module';
+
+import { Tab4Page } from './tab4.page';
+
+describe('Tab4Page', () => {
+  let component: Tab4Page;
+  let fixture: ComponentFixture<Tab4Page>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [Tab4Page],
+      imports: [IonicModule.forRoot(), ExploreContainerComponentModule]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(Tab4Page);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 21 - 0
src/app/tab4/tab4.page.ts

@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-tab4',
+  templateUrl: 'tab4.page.html',
+  styleUrls: ['tab4.page.scss']
+})
+export class Tab4Page {
+  list = [
+    { id: '1', icon: 'time-outline', title: '我的陶记' },
+    { id: '2', icon: 'chatbubbles-outline', title: '意见反馈' },
+    { id: '3', icon: 'settings-outline', title: '设置' },
+    { id: '4', icon: 'information-circle-outline', title: '关于我们' },
+  ];
+  onItemClick(item: any) {
+    console.log('Clicked item:', item);
+    // Handle the click action for the item
+  }
+  constructor() {}
+
+}

+ 43 - 0
src/app/tabs/tabs-routing.module.ts

@@ -0,0 +1,43 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { TabsPage } from './tabs.page';
+
+const routes: Routes = [
+  {
+    path: 'tabs',
+    component: TabsPage,
+    children: [
+      {
+        path: 'tab1',
+        loadChildren: () => import('../tab1/tab1.module').then(m => m.Tab1PageModule)
+      },
+      {
+        path: 'tab2',
+        loadChildren: () => import('../tab2/tab2.module').then(m => m.Tab2PageModule)
+      },
+      {
+        path: 'tab3',
+        loadChildren: () => import('../tab3/tab3.module').then(m => m.Tab3PageModule)
+      },
+      {
+        path: 'tab4',
+        loadChildren: () => import('../tab4/tab4.module').then(m => m.Tab4PageModule)
+      },
+      {
+        path: '',
+        redirectTo: '/tabs/tab1',
+        pathMatch: 'full'
+      }
+    ]
+  },
+  {
+    path: '',
+    redirectTo: '/tabs/tab1',
+    pathMatch: 'full'
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+})
+export class TabsPageRoutingModule {}

+ 7 - 7
src/app/home/home.module.ts → src/app/tabs/tabs.module.ts

@@ -1,19 +1,19 @@
+import { IonicModule } from '@ionic/angular';
 import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
-import { IonicModule } from '@ionic/angular';
 import { FormsModule } from '@angular/forms';
-import { HomePage } from './home.page';
 
-import { HomePageRoutingModule } from './home-routing.module';
+import { TabsPageRoutingModule } from './tabs-routing.module';
 
+import { TabsPage } from './tabs.page';
 
 @NgModule({
   imports: [
+    IonicModule,
     CommonModule,
     FormsModule,
-    IonicModule,
-    HomePageRoutingModule
+    TabsPageRoutingModule
   ],
-  declarations: [HomePage]
+  declarations: [TabsPage]
 })
-export class HomePageModule {}
+export class TabsPageModule {}

+ 23 - 0
src/app/tabs/tabs.page.html

@@ -0,0 +1,23 @@
+<ion-tabs>
+  <ion-tab-bar slot="bottom">
+    <ion-tab-button tab="tab1" href="/tabs/tab1">
+      <ion-icon name="home-outline"></ion-icon>
+      <ion-label>首页</ion-label>
+    </ion-tab-button>
+
+    <ion-tab-button tab="tab2" href="/tabs/tab2">
+      <ion-icon name="sparkles-outline"></ion-icon>
+      <ion-label>智能ai</ion-label>
+    </ion-tab-button>
+
+    <ion-tab-button tab="tab3" href="/tabs/tab3">
+      <ion-icon name="eye-outline"></ion-icon>
+      <ion-label>陶瓷识别</ion-label>
+    </ion-tab-button>
+
+  <ion-tab-button tab="tab4" href="/tabs/tab4">
+      <ion-icon name="person-outline"></ion-icon>
+      <ion-label>我的</ion-label>
+    </ion-tab-button>
+  </ion-tab-bar>
+</ion-tabs>

+ 1 - 0
src/app/tabs/tabs.page.scss

@@ -0,0 +1 @@
+

+ 26 - 0
src/app/tabs/tabs.page.spec.ts

@@ -0,0 +1,26 @@
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TabsPage } from './tabs.page';
+
+describe('TabsPage', () => {
+  let component: TabsPage;
+  let fixture: ComponentFixture<TabsPage>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [TabsPage],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA],
+    }).compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TabsPage);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 12 - 0
src/app/tabs/tabs.page.ts

@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-tabs',
+  templateUrl: 'tabs.page.html',
+  styleUrls: ['tabs.page.scss']
+})
+export class TabsPage {
+
+  constructor() {}
+
+}

BIN
src/assets/images/1.jpg


BIN
src/assets/images/2.jpg


BIN
src/assets/images/background.jpg


BIN
src/assets/images/bubble-background.png


BIN
src/assets/images/button-background1.png


BIN
src/assets/images/button-background2(1).png


BIN
src/assets/images/button-background2.png


BIN
src/assets/images/divider.png


BIN
src/assets/images/recognition-background.png


BIN
src/assets/images/sidebarbox-background.png


BIN
src/assets/images/table-background.png


BIN
src/assets/images/user.jpg


+ 24 - 0
src/global.scss

@@ -35,3 +35,27 @@
 /* @import "@ionic/angular/css/palettes/dark.always.css"; */
 /* @import "@ionic/angular/css/palettes/dark.class.css"; */
 @import "@ionic/angular/css/palettes/dark.system.css";
+
+.custom-modal {
+    --width: 100%; /* 宽度占满屏幕 */
+    --height: 50%; /* 高度占据屏幕下半部分 */
+    --border-radius: 20px 20px 0 0; /* 顶部圆角 */
+    --margin-top: auto; /* 将模态框置于底部 */
+    --box-shadow: 0px -4px 10px rgba(0, 0, 0, 0.2); /* 添加顶部阴影 */
+    --background: white; /* 背景色 */
+  
+    position: fixed; /* 使用固定定位 */
+    bottom: 0; /* 固定在屏幕底部 */
+    left: 0; /* 水平居中 */
+    animation: slide-up 0.8s ease-in-out; /* 滑动动画 */
+  }
+  
+  /* 动画:模态框从底部滑入 */
+  @keyframes slide-up {
+    from {
+      transform: translateY(100%); /* 初始位置在屏幕下方 */
+    }
+    to {
+      transform: translateY(0); /* 滑动到原始位置 */
+    }
+  }

+ 1 - 1
src/index.html

@@ -23,4 +23,4 @@
   <app-root></app-root>
 </body>
 
-</html>
+</html>

+ 1 - 0
tsconfig.json

@@ -2,6 +2,7 @@
 {
   "compileOnSave": false,
   "compilerOptions": {
+    "allowSyntheticDefaultImports":true,
     "baseUrl": "./",
     "outDir": "./dist/out-tsc",
     "forceConsistentCasingInFileNames": true,

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно