lfgldr 1 hete
szülő
commit
fabfc47d17

+ 19 - 1
myapp/src/app/app-routing.module.ts

@@ -1,11 +1,29 @@
 import { NgModule } from '@angular/core';
 import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
+import { CropIntroPage } from './crop-intro/crop-intro.page';
+import { PestIntroPage } from './pest-intro/pest-intro.page';
 
 const routes: Routes = [
   {
     path: '',
     loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
-  }
+  },
+  {
+    path: 'crop-intro',
+    loadChildren: () => import('./crop-intro/crop-intro.module').then( m => m.CropIntroPageModule)
+  },
+  {
+    path: 'pest-intro',
+    loadChildren: () => import('./pest-intro/pest-intro.module').then( m => m.PestIntroPageModule)
+  },
+  {
+    path: 'crop-intro',
+    component: CropIntroPage
+  },
+  {
+    path: 'pest-intro',
+    component: PestIntroPage
+  },
 
 ];
 @NgModule({

+ 17 - 0
myapp/src/app/crop-intro/crop-intro-routing.module.ts

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

+ 20 - 0
myapp/src/app/crop-intro/crop-intro.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 { CropIntroPageRoutingModule } from './crop-intro-routing.module';
+
+import { CropIntroPage } from './crop-intro.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CropIntroPageRoutingModule
+  ],
+  declarations: [CropIntroPage]
+})
+export class CropIntroPageModule {}

+ 27 - 0
myapp/src/app/crop-intro/crop-intro.page.html

@@ -0,0 +1,27 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/"></ion-back-button>
+    </ion-buttons>
+    <ion-title>农作物百科</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <ion-searchbar placeholder="搜索农作物..."></ion-searchbar>
+
+  <ion-list>
+    <ion-item *ngFor="let crop of crops" [detail]="true" (click)="openCropDetail(crop)">
+      <ion-avatar slot="start">
+        <img [src]="crop.image">
+      </ion-avatar>
+      <ion-label>
+        <h2>{{crop.name}}</h2>
+        <p>{{crop.summary}}</p>
+      </ion-label>
+      <ion-badge slot="end" [color]="crop.season === '春季' ? 'success' : 'warning'">
+        {{crop.season}}
+      </ion-badge>
+    </ion-item>
+  </ion-list>
+</ion-content>

+ 0 - 0
myapp/src/app/crop-intro/crop-intro.page.scss


+ 17 - 0
myapp/src/app/crop-intro/crop-intro.page.spec.ts

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

+ 42 - 0
myapp/src/app/crop-intro/crop-intro.page.ts

@@ -0,0 +1,42 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+
+@Component({
+  selector: 'app-crop-intro',
+  templateUrl: './crop-intro.page.html',
+  styleUrls: ['./crop-intro.page.scss'],
+})
+export class CropIntroPage {
+  crops = [
+    {
+      id: 1,
+      name: '小麦',
+      image: 'assets/images/wheat.jpg',
+      summary: '主要粮食作物,耐寒性强',
+      season: '冬季',
+      details: '小麦是我国北方主要粮食作物...'
+    },
+    {
+      id: 2,
+      name: '水稻',
+      image: 'assets/images/rice.jpg',
+      summary: '喜水作物,南方主要粮食',
+      season: '夏季',
+      details: '水稻是我国南方主要粮食作物...'
+    },
+    {
+      id: 3,
+      name: '玉米',
+      image: 'assets/images/corn.jpg',
+      summary: '高产作物,用途广泛',
+      season: '夏季',
+      details: '玉米是我国重要的粮食和饲料作物...'
+    }
+  ];
+
+  constructor(private router: Router) {}
+
+  openCropDetail(crop: any) {
+    this.router.navigate(['/crop-detail', crop.id]);
+  }
+}

+ 17 - 0
myapp/src/app/pest-intro/pest-intro-routing.module.ts

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

+ 20 - 0
myapp/src/app/pest-intro/pest-intro.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 { PestIntroPageRoutingModule } from './pest-intro-routing.module';
+
+import { PestIntroPage } from './pest-intro.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    PestIntroPageRoutingModule
+  ],
+  declarations: [PestIntroPage]
+})
+export class PestIntroPageModule {}

+ 35 - 0
myapp/src/app/pest-intro/pest-intro.page.html

@@ -0,0 +1,35 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/"></ion-back-button>
+    </ion-buttons>
+    <ion-title>病虫害图鉴</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <ion-segment value="all" (ionChange)="segmentChanged($event)">
+    <ion-segment-button value="all">
+      <ion-label>全部</ion-label>
+    </ion-segment-button>
+    <ion-segment-button value="disease">
+      <ion-label>病害</ion-label>
+    </ion-segment-button>
+    <ion-segment-button value="pest">
+      <ion-label>虫害</ion-label>
+    </ion-segment-button>
+  </ion-segment>
+
+  <ion-list>
+    <ion-item *ngFor="let item of filteredItems" [detail]="true" (click)="openPestDetail(item)">
+      <ion-thumbnail slot="start">
+        <img [src]="item.image">
+      </ion-thumbnail>
+      <ion-label>
+        <h2>{{item.name}}</h2>
+        <p>{{item.crop}}常见{{item.type === 'disease' ? '病害' : '虫害'}}</p>
+      </ion-label>
+      <ion-badge slot="end" color="danger" *ngIf="item.level === 'high'">严重</ion-badge>
+    </ion-item>
+  </ion-list>
+</ion-content>

+ 0 - 0
myapp/src/app/pest-intro/pest-intro.page.scss


+ 17 - 0
myapp/src/app/pest-intro/pest-intro.page.spec.ts

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

+ 57 - 0
myapp/src/app/pest-intro/pest-intro.page.ts

@@ -0,0 +1,57 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+
+@Component({
+  selector: 'app-pest-intro',
+  templateUrl: './pest-intro.page.html',
+  styleUrls: ['./pest-intro.page.scss'],
+})
+export class PestIntroPage {
+  currentSegment = 'all';
+  pests = [
+    {
+      id: 1,
+      name: '小麦赤霉病',
+      image: 'assets/images/wheat-disease.jpg',
+      type: 'disease',
+      crop: '小麦',
+      level: 'high',
+      details: '小麦赤霉病是由真菌引起的病害...'
+    },
+    {
+      id: 2,
+      name: '稻飞虱',
+      image: 'assets/images/rice-pest.jpg',
+      type: 'pest',
+      crop: '水稻',
+      level: 'medium',
+      details: '稻飞虱是水稻主要害虫之一...'
+    },
+    {
+      id: 3,
+      name: '玉米螟',
+      image: 'assets/images/corn-pest.jpg',
+      type: 'pest',
+      crop: '玉米',
+      level: 'high',
+      details: '玉米螟是玉米主要害虫...'
+    }
+  ];
+
+  get filteredItems() {
+    if (this.currentSegment === 'all') {
+      return this.pests;
+    }
+    return this.pests.filter(item => item.type === this.currentSegment);
+  }
+
+  constructor(private router: Router) {}
+
+  segmentChanged(ev: any) {
+    this.currentSegment = ev.detail.value;
+  }
+
+  openPestDetail(pest: any) {
+    this.router.navigate(['/pest-detail', pest.id]);
+  }
+}

+ 28 - 0
myapp/src/app/tab1/tab1.page.html

@@ -53,6 +53,34 @@
     </ion-row>
   </ion-grid>
 
+  <!-- 新增的农业知识卡片 -->
+  <ion-grid class="knowledge-cards">
+    <ion-row>
+      <ion-col size="6">
+        <ion-card (click)="navigateTo('/crop-intro')" class="knowledge-card">
+          <img src="assets/images/crops.jpg" alt="农作物介绍"/>
+          <ion-card-header>
+            <ion-card-title>农作物百科</ion-card-title>
+          </ion-card-header>
+          <ion-card-content>
+            了解各类农作物的生长特性、种植技术和品种特点
+          </ion-card-content>
+        </ion-card>
+      </ion-col>
+      <ion-col size="6">
+        <ion-card (click)="navigateTo('/pest-intro')" class="knowledge-card">
+          <img src="assets/images/pests.jpg" alt="病虫害防治"/>
+          <ion-card-header>
+            <ion-card-title>病虫害图鉴</ion-card-title>
+          </ion-card-header>
+          <ion-card-content>
+            识别常见病虫害,学习防治方法和应对措施
+          </ion-card-content>
+        </ion-card>
+      </ion-col>
+    </ion-row>
+  </ion-grid>
+
   <!-- 动态信息流 -->
   <ion-list lines="none" class="info-stream">
     <!-- 气象预警 -->

+ 210 - 166
myapp/src/app/tab1/tab1.page.scss

@@ -1,206 +1,250 @@
 ion-header {
-    ion-toolbar {
-      --background: linear-gradient(135deg, var(--ion-color-primary) 0%, #2e7d32 100%);
-      
-      .logo {
-        width: 40px;
-        height: 40px;
-        margin-right: 10px;
-        border: 2px solid white;
-      }
-      
-      .location {
-        font-size: 12px;
-        opacity: 0.8;
-        
-        ion-icon {
-          font-size: 14px;
-          vertical-align: middle;
-          margin-right: 4px;
-        }
-      }
+  ion-toolbar {
+    --background: linear-gradient(135deg, var(--ion-color-primary) 0%, #2e7d32 100%);
+    
+    .logo {
+      width: 40px;
+      height: 40px;
+      margin-right: 10px;
+      border: 2px solid white;
     }
-  }
-  
-  .voice-search-container {
-    text-align: center;
-    margin: 20px 0;
     
-    .voice-button {
-      --padding-start: 0;
-      --padding-end: 0;
-      --border-radius: 50%;
-      width: 80px;
-      height: 80px;
-      margin: 0 auto;
-      
-      .voice-button-inner {
-        position: relative;
-        width: 100%;
-        height: 100%;
-        
-        .mic-icon {
-          font-size: 36px;
-          position: relative;
-          z-index: 2;
-          color: var(--ion-color-primary);
-        }
-        
-        .ripple {
-          position: absolute;
-          top: 0;
-          left: 0;
-          width: 100%;
-          height: 100%;
-          background: rgba(var(--ion-color-primary-rgb), 0.1);
-          border-radius: 50%;
-          transform: scale(0);
-          opacity: 1;
-          z-index: 1;
-        }
-      }
+    .location {
+      font-size: 12px;
+      opacity: 0.8;
       
-      &.activated {
-        .ripple {
-          animation: ripple 1.5s infinite;
-        }
+      ion-icon {
+        font-size: 14px;
+        vertical-align: middle;
+        margin-right: 4px;
       }
     }
-    
-    .voice-hint {
-      margin-top: 10px;
-      color: var(--ion-color-medium);
-      font-size: 14px;
-    }
   }
+}
+
+.voice-search-container {
+  text-align: center;
+  margin: 20px 0;
   
-  .feature-grid {
-    margin: 20px 0;
+  .voice-button {
+    --padding-start: 0;
+    --padding-end: 0;
+    --border-radius: 50%;
+    width: 80px;
+    height: 80px;
+    margin: 0 auto;
     
-    .feature-button {
-      --padding-start: 0;
-      --padding-end: 0;
+    .voice-button-inner {
+      position: relative;
+      width: 100%;
       height: 100%;
       
-      .feature-icon {
+      .mic-icon {
+        font-size: 36px;
         position: relative;
-        width: 60px;
-        height: 60px;
-        margin: 0 auto 8px;
+        z-index: 2;
+        color: var(--ion-color-primary);
+      }
+      
+      .ripple {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
         background: rgba(var(--ion-color-primary-rgb), 0.1);
         border-radius: 50%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        
-        ion-icon {
-          font-size: 28px;
-          color: var(--ion-color-primary);
-        }
+        transform: scale(0);
+        opacity: 1;
+        z-index: 1;
       }
-      
-      ion-label {
-        font-size: 12px;
-        white-space: normal;
+    }
+    
+    &.activated {
+      .ripple {
+        animation: ripple 1.5s infinite;
       }
     }
   }
   
-  .info-stream {
-    background: transparent;
+  .voice-hint {
+    margin-top: 10px;
+    color: var(--ion-color-medium);
+    font-size: 14px;
+  }
+}
+
+.feature-grid {
+  margin: 20px 0;
+  
+  .feature-button {
+    --padding-start: 0;
+    --padding-end: 0;
+    height: 100%;
     
-    .weather-alert {
-      --background: rgba(var(--ion-color-danger-rgb), 0.1);
-      border-radius: 12px;
-      margin-bottom: 16px;
+    .feature-icon {
+      position: relative;
+      width: 60px;
+      height: 60px;
+      margin: 0 auto 8px;
+      background: rgba(var(--ion-color-primary-rgb), 0.1);
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
       
       ion-icon {
-        font-size: 24px;
+        font-size: 28px;
+        color: var(--ion-color-primary);
       }
     }
     
-    .agri-news {
-      --padding-start: 0;
-      --inner-padding-end: 0;
-      margin-bottom: 12px;
-      
-      ion-thumbnail {
-        width: 80px;
-        height: 60px;
-        border-radius: 8px;
-        overflow: hidden;
-      }
-      
-      .news-meta {
-        font-size: 12px;
-        color: var(--ion-color-medium);
-        
-        ion-icon {
-          font-size: 12px;
-          vertical-align: middle;
-          margin: 0 4px 0 8px;
-        }
-      }
+    ion-label {
+      font-size: 12px;
+      white-space: normal;
     }
   }
+}
+
+/* 新增农业知识卡片样式 */
+.knowledge-cards {
+  margin: 15px 0;
+  
+  ion-col {
+    padding: 5px;
+  }
   
-  ion-footer {
-    ion-tab-bar {
-      border-radius: 20px 20px 0 0;
-      padding-top: 8px;
-      --background: white;
-      box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1);
+  ion-card {
+    margin: 0;
+    border-radius: 12px;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+    transition: transform 0.3s ease;
+    
+    &:active {
+      transform: scale(0.98);
+    }
+    
+    img {
+      height: 100px;
+      object-fit: cover;
+      width: 100%;
+    }
+    
+    ion-card-header {
+      padding: 10px 16px 0;
       
-      ion-tab-button {
-        --color: var(--ion-color-medium);
-        --color-selected: var(--ion-color-primary);
-        
-        ion-icon {
-          font-size: 24px;
-        }
+      ion-card-title {
+        font-size: 16px;
+        font-weight: bold;
+        color: var(--ion-color-dark);
       }
     }
+    
+    ion-card-content {
+      padding: 10px 16px 16px;
+      font-size: 14px;
+      color: var(--ion-color-medium);
+      line-height: 1.4;
+    }
   }
+}
+
+.info-stream {
+  background: transparent;
   
-  @keyframes ripple {
-    0% {
-      transform: scale(0.8);
-      opacity: 0.8;
-    }
-    100% {
-      transform: scale(1.4);
-      opacity: 0;
+  .weather-alert {
+    --background: rgba(var(--ion-color-danger-rgb), 0.1);
+    border-radius: 12px;
+    margin-bottom: 16px;
+    
+    ion-icon {
+      font-size: 24px;
     }
   }
   
-  /* 害虫图标动画 */
-  .pest-animation {
-    animation: bounce 1s infinite alternate;
+  .agri-news {
+    --padding-start: 0;
+    --inner-padding-end: 0;
+    margin-bottom: 12px;
+    
+    ion-thumbnail {
+      width: 80px;
+      height: 60px;
+      border-radius: 8px;
+      overflow: hidden;
+    }
+    
+    .news-meta {
+      font-size: 12px;
+      color: var(--ion-color-medium);
+      
+      ion-icon {
+        font-size: 12px;
+        vertical-align: middle;
+        margin: 0 4px 0 8px;
+      }
+    }
   }
-  
-  /* 水滴动画 */
-  .water-animation {
-    position: relative;
-    &::after {
-      content: "";
-      position: absolute;
-      bottom: -5px;
-      left: 50%;
-      width: 8px;
-      height: 8px;
-      background: rgba(var(--ion-color-primary-rgb), 0.5);
-      border-radius: 50%;
-      transform: translateX(-50%);
-      animation: drip 2s infinite;
+}
+
+ion-footer {
+  ion-tab-bar {
+    border-radius: 20px 20px 0 0;
+    padding-top: 8px;
+    --background: white;
+    box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.1);
+    
+    ion-tab-button {
+      --color: var(--ion-color-medium);
+      --color-selected: var(--ion-color-primary);
+      
+      ion-icon {
+        font-size: 24px;
+      }
     }
   }
-  
-  @keyframes bounce {
-    0% { transform: translateY(0); }
-    100% { transform: translateY(-5px); }
+}
+
+/* 动画效果 */
+@keyframes ripple {
+  0% {
+    transform: scale(0.8);
+    opacity: 0.8;
   }
-  
-  @keyframes drip {
-    0% { transform: translateY(0) scale(1); opacity: 1; }
-    100% { transform: translateY(20px) scale(0.5); opacity: 0; }
-  }
+  100% {
+    transform: scale(1.4);
+    opacity: 0;
+  }
+}
+
+/* 害虫图标动画 */
+.pest-animation {
+  animation: bounce 1s infinite alternate;
+}
+
+/* 水滴动画 */
+.water-animation {
+  position: relative;
+  &::after {
+    content: "";
+    position: absolute;
+    bottom: -5px;
+    left: 50%;
+    width: 8px;
+    height: 8px;
+    background: rgba(var(--ion-color-primary-rgb), 0.5);
+    border-radius: 50%;
+    transform: translateX(-50%);
+    animation: drip 2s infinite;
+  }
+}
+
+@keyframes bounce {
+  0% { transform: translateY(0); }
+  100% { transform: translateY(-5px); }
+}
+
+@keyframes drip {
+  0% { transform: translateY(0) scale(1); opacity: 1; }
+  100% { transform: translateY(20px) scale(0.5); opacity: 0; }
+}

+ 4 - 6
myapp/src/app/tab1/tab1.page.ts

@@ -5,9 +5,8 @@ import { Router } from '@angular/router';
   selector: 'app-tab1',
   templateUrl: './tab1.page.html',
   styleUrls: ['./tab1.page.scss'],
-  standalone: false,
 })
-export class Tab1Page {
+export class Tab1Page implements OnInit {
   isListening = false;
   currentLocation = '正在定位...';
   
@@ -35,7 +34,9 @@ export class Tab1Page {
       icon: 'people', 
       path: '/experts',
       animation: ''
-    }
+    },
+    // 新增两个功能入口
+    
   ];
   
   newsList = [
@@ -70,13 +71,11 @@ export class Tab1Page {
   startVoiceInput() {
     this.isListening = true;
     console.log('开始语音输入...');
-    // 这里实际应调用语音识别API
   }
 
   stopVoiceInput() {
     this.isListening = false;
     console.log('结束语音输入...');
-    // 处理语音识别结果
   }
 
   navigateTo(path: string) {
@@ -85,6 +84,5 @@ export class Tab1Page {
 
   openWeatherAlert() {
     console.log('打开天气预警详情');
-    // 实际应用中这里应该导航到天气预警详情页
   }
 }

+ 44 - 4
myapp/src/app/tab2/tab2.page.html

@@ -11,7 +11,6 @@
     </ion-buttons>
   </ion-toolbar>
 
-  <!-- 搜索栏 -->
   <ion-toolbar class="search-toolbar">
     <ion-searchbar 
       [(ngModel)]="searchQuery" 
@@ -36,12 +35,53 @@
     </ion-segment>
   </div>
 
-  <!-- 专家在线 -->
+  <!-- 专家在线咨询卡片 -->
   <ion-card class="expert-card">
     <ion-card-header>
-      <ion-card-title>👨🌾 专家在线咨询</ion-card-title>
-      <ion-card-subtitle>立即连线农业专家解答疑难</ion-card-subtitle>
+      <div class="expert-card-header">
+        <div>
+          <ion-card-title>👨🌾 专家在线咨询</ion-card-title>
+          <ion-card-subtitle>立即连线农业专家解答疑难</ion-card-subtitle>
+        </div>
+        <div class="expert-card-actions">
+          <ion-button 
+            *ngIf="consultList.length > 0"
+            fill="solid" 
+            color="primary"
+            size="small"
+            (click)="clearAllChatRecords()">
+            <ion-icon name="trash" slot="start"></ion-icon>
+            清空聊天记录
+          </ion-button>
+        </div>
+      </div>
     </ion-card-header>
+
+    <!-- 历史咨询记录 - 已修复部分 -->
+    <ion-list *ngIf="consultList.length">
+      <ion-item-sliding *ngFor="let consult of consultList">
+        <ion-item (click)="viewChatDetail(consult.get('chatId'))">
+          <ion-avatar slot="start">
+            <img [src]="consult.get('avatar')">
+          </ion-avatar>
+          
+          <ion-label>
+            <h2>{{ consult.get('agentName') }}</h2>
+            <p>{{ consult.get('lastMessage') | slice:0:30 }}...</p>
+            <p>{{ consult.get('lastMessageTime') | date:'yyyy-MM-dd HH:mm' }}</p>
+          </ion-label>
+          
+          <ion-badge slot="end">{{ consult.get('messageCount') }}</ion-badge>
+        </ion-item>
+        
+        <ion-item-options side="end">
+          <ion-item-option color="danger" (click)="deleteChatRecord(consult.get('chatId'))">
+            <ion-icon slot="icon-only" name="trash"></ion-icon>
+          </ion-item-option>
+        </ion-item-options>
+      </ion-item-sliding>
+    </ion-list>
+
     <ion-card-content>
       <div class="expert-list">
         <div class="expert-item" *ngFor="let expert of onlineExperts">

+ 211 - 168
myapp/src/app/tab2/tab2.page.scss

@@ -1,188 +1,231 @@
-/* 全局样式变量 */
 :host {
-    --knowledge-card-width: calc(100% - 20px);
+  --knowledge-card-width: calc(100% - 20px);
+}
+
+/* 大字体模式 */
+ion-content.large-font {
+  font-size: 1.2rem;
+
+  ion-card-title {
+    font-size: 1.4rem;
   }
-  
-  /* 大字体模式 */
-  ion-content.large-font {
-    font-size: 1.2rem;
-  
-    ion-card-title {
-      font-size: 1.4rem;
-    }
+}
+
+/* 搜索工具栏 */
+.search-toolbar {
+  --background: var(--ion-color-light);
+  padding: 0 10px;
+
+  ion-searchbar {
+    padding: 0;
+    --background: var(--ion-color-light-shade);
+    --border-radius: 20px;
   }
-  
-  /* 搜索工具栏 */
-  .search-toolbar {
-    --background: var(--ion-color-light);
-    padding: 0 10px;
-  
-    ion-searchbar {
-      padding: 0;
-      --background: var(--ion-color-light-shade);
-      --border-radius: 20px;
+}
+
+/* 分类导航 */
+.category-container {
+  display: flex;
+  padding: 10px;
+  background: var(--ion-color-light);
+  overflow-x: auto;
+
+  ion-segment {
+    flex: 1;
+    min-width: max-content;
+
+    ion-segment-button {
+      --padding-top: 8px;
+      --padding-bottom: 8px;
+      min-width: 80px;
+
+      ion-icon {
+        font-size: 1.8rem;
+        margin-bottom: 5px;
+      }
     }
   }
-  
-  /* 分类导航 */
-  .category-container {
+}
+
+/* 专家卡片头部 */
+.expert-card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+  
+  .expert-card-actions {
     display: flex;
-    padding: 10px;
-    background: var(--ion-color-light);
-    overflow-x: auto;
-  
-    ion-segment {
-      flex: 1;
-      min-width: max-content;
-  
-      ion-segment-button {
-        --padding-top: 8px;
-        --padding-bottom: 8px;
-        min-width: 80px;
-  
-        ion-icon {
-          font-size: 1.8rem;
-          margin-bottom: 5px;
-        }
+    gap: 8px;
+    
+    ion-button {
+      --padding-start: 12px;
+      --padding-end: 12px;
+      height: 32px;
+      font-size: 0.8rem;
+      
+      ion-icon {
+        font-size: 1rem;
+        margin-right: 4px;
       }
     }
   }
-  
-  /* 专家卡片 */
-  .expert-card {
-    margin: 10px;
-  
-    .expert-list {
-      .expert-item {
-        display: flex;
-        align-items: center;
-        padding: 10px 0;
-        border-bottom: 1px solid var(--ion-color-light-shade);
-  
-        ion-avatar {
-          width: 50px;
-          height: 50px;
-          margin-right: 12px;
-        }
-  
-        .expert-info {
-          flex: 1;
-          
-          h3 {
-            margin: 0 0 4px;
-            font-weight: 500;
-          }
-          
-          p {
-            margin: 0;
-            font-size: 0.8rem;
-            color: var(--ion-color-medium);
-          }
-          
-          ion-badge {
-            margin-top: 4px;
-          }
+}
+
+/* 历史咨询记录 */
+ion-list {
+  margin-bottom: 20px;
+  
+  ion-item {
+    --padding-start: 10px;
+    --inner-padding-end: 10px;
+    
+    ion-label {
+      margin: 10px 0;
+      
+      h2 {
+        font-weight: 600;
+      }
+      
+      p {
+        color: var(--ion-color-medium);
+        font-size: 0.8em;
+      }
+    }
+  }
+}
+
+/* 专家卡片 */
+.expert-card {
+  margin: 10px;
+
+  .expert-list {
+    .expert-item {
+      display: flex;
+      align-items: center;
+      padding: 10px 0;
+      border-bottom: 1px solid var(--ion-color-light-shade);
+
+      ion-avatar {
+        width: 50px;
+        height: 50px;
+        margin-right: 12px;
+      }
+
+      .expert-info {
+        flex: 1;
+        
+        h3 {
+          margin: 0 0 4px;
+          font-weight: 500;
         }
         
-        &:last-child {
-          border-bottom: none;
+        p {
+          margin: 0;
+          font-size: 0.8rem;
+          color: var(--ion-color-medium);
         }
       }
+      
+      &:last-child {
+        border-bottom: none;
+      }
     }
   }
-  
-  /* 知识网格布局 */
-  .knowledge-grid {
-    display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(var(--knowledge-card-width), 1fr));
-    gap: 15px;
-    padding: 10px;
-  
-    @media (min-width: 576px) {
-      --knowledge-card-width: calc(50% - 15px);
-    }
-  
-    @media (min-width: 992px) {
-      --knowledge-card-width: calc(33.33% - 15px);
-    }
+}
+
+/* 知识网格布局 */
+.knowledge-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(var(--knowledge-card-width), 1fr));
+  gap: 15px;
+  padding: 10px;
+
+  @media (min-width: 576px) {
+    --knowledge-card-width: calc(50% - 15px);
   }
-  
-  /* 知识卡片 */
-  ion-card {
-    margin: 0;
-    border-radius: 12px;
-    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
-    transition: transform 0.3s ease;
-  
-    &:active {
-      transform: scale(0.98);
-    }
-  
-    &.emergency {
-      border-left: 4px solid var(--ion-color-danger);
-    }
-  
-    ion-badge {
-      position: absolute;
-      top: 10px;
-      right: 10px;
-      z-index: 2;
-    }
-  
-    ion-img {
-      height: 120px;
-      object-fit: cover;
-    }
-  
-    ion-card-header {
-      padding: 16px 16px 0;
-    }
-  
-    ion-card-content {
-      padding: 0 16px 16px;
-      font-size: 0.9rem;
-      color: var(--ion-color-medium-shade);
-    }
-  
-    ion-footer {
-      padding: 0 16px 16px;
-    }
+
+  @media (min-width: 992px) {
+    --knowledge-card-width: calc(33.33% - 15px);
   }
-  
-  /* 空状态 */
-  .empty-state {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    height: 50vh;
-    text-align: center;
-    padding: 20px;
-    color: var(--ion-color-medium);
-  
-    ion-icon {
-      font-size: 3rem;
-      margin-bottom: 15px;
-      opacity: 0.5;
-    }
-  
-    h3 {
-      margin: 0 0 10px;
-      font-weight: bold;
-    }
+}
+
+/* 知识卡片 */
+ion-card {
+  margin: 0;
+  border-radius: 12px;
+  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+  transition: transform 0.3s ease;
+
+  &:active {
+    transform: scale(0.98);
   }
-  
-  /* 动画效果 */
-  @keyframes fadeIn {
-    from { opacity: 0; transform: translateY(10px); }
-    to { opacity: 1; transform: translateY(0); }
+
+  &.emergency {
+    border-left: 4px solid var(--ion-color-danger);
   }
-  
-  ion-card {
-    animation: fadeIn 0.5s ease forwards;
-    @for $i from 1 through 10 {
-      &:nth-child(#{$i}) {
-        animation-delay: $i * 0.1s;
-      }
+
+  ion-badge {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    z-index: 2;
+  }
+
+  ion-img {
+    height: 120px;
+    object-fit: cover;
+  }
+
+  ion-card-header {
+    padding: 16px 16px 0;
+  }
+
+  ion-card-content {
+    padding: 0 16px 16px;
+    font-size: 0.9rem;
+    color: var(--ion-color-medium-shade);
+  }
+
+  ion-footer {
+    padding: 0 16px 16px;
+  }
+}
+
+/* 空状态 */
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 50vh;
+  text-align: center;
+  padding: 20px;
+  color: var(--ion-color-medium);
+
+  ion-icon {
+    font-size: 3rem;
+    margin-bottom: 15px;
+    opacity: 0.5;
+  }
+
+  h3 {
+    margin: 0 0 10px;
+    font-weight: bold;
+  }
+}
+
+/* 动画效果 */
+@keyframes fadeIn {
+  from { opacity: 0; transform: translateY(10px); }
+  to { opacity: 1; transform: translateY(0); }
+}
+
+ion-card {
+  animation: fadeIn 0.5s ease forwards;
+  @for $i from 1 through 10 {
+    &:nth-child(#{$i}) {
+      animation-delay: $i * 0.1s;
     }
-  }
+  }
+}

+ 213 - 191
myapp/src/app/tab2/tab2.page.ts

@@ -1,10 +1,11 @@
 import { Component, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
-//import { AlertController, ModalController } from '@ionic/angular';
+import { AlertController } from '@ionic/angular';
+import { ModalController } from '@ionic/angular/standalone';
 import { ChatPanelOptions, FmChatModalInput, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
 import Parse from "parse";
-import { CloudObject, CloudQuery } from 'src/lib/ncloud';
-import { ModalController } from '@ionic/angular/standalone';  // 统一使用 standalone 路径
+import { CloudObject, CloudQuery, CloudUser } from 'src/lib/ncloud';
+
 interface KnowledgeItem {
   id: string;
   title: string;
@@ -30,194 +31,12 @@ interface Expert {
   selector: 'app-tab2',
   templateUrl: './tab2.page.html',
   styleUrls: ['./tab2.page.scss'],
-  standalone: false,
 })
 export class Tab2Page implements OnInit {
-
-
-
-
-  openConsult(chatId?:string){
-    localStorage.setItem("company","E4KpGvTEto")
-    let options:ChatPanelOptions = {
-      roleId:"2DXJkRsjXK", // 预设,无需更改
-      // chatId:chatId, // 若存在,则恢复会话。若不存在,则开启新会话
-      onChatInit:(chat:FmodeChat)=>{
-        console.log("onChatInit");
-        console.log("Chat类",chat);
-        console.log("预设角色",chat.role);
-        // 角色名称
-        chat.role.set("name","林舒窈");
-        // 角色称号
-        chat.role.set("title","东方食养家");
-        // 角色描述
-        chat.role.set("desc","谈吐带有中医师的沉稳,擅长用生活化比喻解释复杂理论,林舒窈,年龄26岁");
-        // 角色标签
-        chat.role.set("tags",['跑步', '动感单车']);
-        // 角色头像
-        chat.role.set("avatar","/assets/lin.jpg")
-        // 角色提示词
-        chat.role.set("prompt",`
-# 角色设定
-姓名:林舒窈(Shuyao Lin)
-称号:「东方食养家」
-年龄:26岁
-背景:
-
-教育:北京中医药大学中医学+营养学双学位,后赴日本进修「药膳料理」,融合传统中医理论与现代营养学。
-
-职业经历:曾任北京某三甲医院临床营养科主任,后创立个人品牌「四季食养」,为明星、企业家定制高端养生膳食。
-
-社会身份:央视《健康中国》栏目常驻嘉宾,著有《本草厨房》《节气餐桌》等畅销书。
-
-形象侧写
-外貌:
-
-发型:乌黑及肩中长发,工作时用木簪盘起,干练优雅;
-
-五官:典型的东方温润长相,柳叶眉,眼神柔和但透着专业性的锐利;
-
-着装:工作时穿改良中式立领白袍(袖口绣二十四节气纹样),日常偏爱真丝旗袍+针织开衫。
-
-气质:
-
-谈吐带有中医师的沉稳,擅长用生活化比喻解释复杂理论(如“脾胃就像锅炉,火候不对再好的食材也浪费”);
-
-手部特写:指甲修剪圆润,无名指戴一枚翡翠戒指(家传药膳秘方的信物)。
-`);
-        // 对话灵感分类
-        let promptCates = [
-          {
-            "img": "/assets/icon/yy.jpg",
-            "name": "营养"
-          },
-          {
-            "img": "/assets/icon/rl.jpg",
-            "name": "热量"
-          },
-          {
-            "img": "/assets/icon/aq.jpg",
-            "name": "安全"
-          }
-        ]
-        setTimeout(() => {
-          chat.role.set("promptCates",promptCates)
-        }, 500);
-        // 对话灵感列表
-        let promptList = [
-          {
-            cate:"营养",img:"/assets/icon/yy.jpg",
-            messageList:[
-              "如何在不减少食物种类的情况下保证营养均衡?",
-              "有哪些高蛋白但低脂肪的美食推荐?",
-              "素食者如何确保摄入足够的蛋白质和铁?",
-              "怎样搭配碳水化合物、蛋白质和脂肪的比例更健康?"
-            ]
-          },
-          {
-            cate:"热量",img:"/assets/icon/rl.jpg",
-            messageList:[
-              "有哪些低卡路里但依然美味的零食选择?",
-              "如何在享受甜点的同时减少糖分摄入?",
-              "外出就餐时如何选择既健康又美味的菜品?",
-              "有哪些烹饪方式可以降低食物的热量但保留风味?"
-            ]
-          },
-          {
-            cate:"安全",img:"/assets/icon/aq.jpg",
-            messageList: [
-              "如何判断食材是否新鲜,避免食物中毒?",
-              "长期吃外卖如何降低对健康的影响?",
-              "有哪些容易被忽视的饮食习惯可能危害健康?",
-              "如何合理规划一周的饮食,避免营养单一?"
-            ]
-          },
-        ]
-        let ChatPrompt = Parse.Object.extend("ChatPrompt");
-        setTimeout(() => {
-          chat.promptList = promptList.map(item=>{
-            let prompt = new ChatPrompt();
-            prompt.set(item);
-            prompt.img = item.img;
-            return prompt;
-          })
-        }, 500);
-
-        // 功能按钮区域预设
-        chat.leftButtons = [
-          { // 提示 当角色配置预设提示词时 显示
-           title:"话题灵感", // 按钮标题
-           showTitle:true, // 是否显示标题文字
-           icon:"color-wand-outline", // 标题icon图标
-           onClick:()=>{ // 按钮点击事件
-               chat.isPromptModalOpen = true
-           },
-           show:()=>{ // 按钮显示条件
-             return chat?.promptList?.length // 存在话题提示词时显示
-           }
-         },
-      ]
-
-      },
-      onMessage:(chat:FmodeChat,message:FmodeChatMessage)=>{
-        console.log("onMessage",message)
-        let content:any = message?.content
-        if(typeof content == "string"){
-          // 根据阶段标记判断下一步处理过程
-          if (content.includes('[导诊完成]')) {
-            // 进入问诊环节
-            console.log('进入问诊环节');
-          } else if (content.includes('[问诊完成]')) {
-            // 进入检查环节
-            console.log('进入检查环节');
-          } else if (content.includes('[检查完成]')) {
-            // 进入诊断与处方环节
-            console.log('进入诊断与处方环节');
-          } else if (content.includes('[处方完成]')) {
-            // 结束会话或其他逻辑
-            console.log('结束会话');
-          }
-        }
-      },
-      /* onChatSaved 生命周期 保存聊天记录
-        会话ID
-        聊天内容
-        用户的参数 */
-      onChatSaved:async (chat:FmodeChat)=>{
-        // chat?.chatSession?.id 本次会话的 chatId
-        console.log("onChatSaved",chat,chat?.chatSession,chat?.chatSession?.id)
-        let chatId = chat?.chatSession?.id;
-        console.log("chatId",chatId);
-        let query = new CloudQuery("NutritionConsult");
-        let nutritionConsult = await query.get(chatId);
-        console.log("nutritionConsult",nutritionConsult)
-        //若无重复记录,则实例化一个新的咨询记录
-        if(!nutritionConsult?.id){
-         nutritionConsult = new CloudObject("NutritionConsult"); 
-        }
-        nutritionConsult.set({
-          "chatId":chatId,
-          "messageList":chat.messageList,
-          "name" :chat.role.get("name"),
-          "avatar":chat.role.get("avatar"),
-        });
-
-        console.log("nutritionConsult",nutritionConsult);
-        nutritionConsult.save();
-      }
-    }
-    openChatPanelModal(this.modalCtrl,options)
-  }
-  consultList:Array<CloudObject> = [];
-
-
-
-
-
-
   searchQuery = '';
   isLargeFont = false;
   currentCategory = 'all';
+  consultList: Array<CloudObject> = [];
 
   categories = [
     { id: 'all', name: '全部', icon: 'apps' },
@@ -305,15 +124,220 @@ export class Tab2Page implements OnInit {
   ];
 
   filteredKnowledge: KnowledgeItem[] = [];
+  //alertCtrl: any;
 
   constructor(
-    private modalCtrl:ModalController,
-    private router: Router,
-    //private alertCtrl: AlertController
+    private modalCtrl: ModalController,
+    private alertCtrl: AlertController,
+    private router: Router
   ) {}
 
   ngOnInit() {
     this.filteredKnowledge = [...this.knowledgeList];
+    this.loadConsultList();
+  }
+
+  async clearAllChatRecords() {
+    if (this.consultList.length === 0) {
+      return;
+    }
+
+    const alert = await this.alertCtrl.create({
+      header: '确认清空',
+      message: '确定要清空所有聊天记录吗?此操作不可恢复',
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '确定',
+          handler: async () => {
+            try {
+              // 创建批量删除查询
+              const query = new CloudQuery("LiaoTianJiLu");
+              const currentUser = new CloudUser();
+              await currentUser.current();
+              
+              if (currentUser.id) {
+                query.equalTo("userId", currentUser.id);
+                
+                // 先获取所有要删除的记录
+                const recordsToDelete = await query.find();
+                
+                // 批量删除
+                const deletePromises = recordsToDelete.map(record => record.destroy());
+                await Promise.all(deletePromises);
+                
+                // 更新界面
+                this.consultList = [];
+              }
+            } catch (error) {
+              console.error('删除聊天记录失败:', error);
+              // 可以添加错误提示
+              const errorAlert = await this.alertCtrl.create({
+                header: '操作失败',
+                message: '清空聊天记录时发生错误',
+                buttons: ['确定']
+              });
+              await errorAlert.present();
+            }
+          }
+        }
+      ]
+    });
+
+    await alert.present();
+  }
+
+  
+
+  async loadConsultList() {
+    let query = new CloudQuery("LiaoTianJiLu");
+    let currentUser = new CloudUser();
+    await currentUser.current();
+    
+    if (currentUser.id) {
+      query.equalTo("userId", currentUser.id);
+      query.queryParams["order"] = "-lastMessageTime";
+      this.consultList = await query.find();
+    }
+  }
+
+  openConsult(chatId?: string) {
+    localStorage.setItem("company", "E4KpGvTEto");
+    let options: ChatPanelOptions = {
+      roleId: "2DXJkRsjXK",
+      chatId: chatId,
+      
+      onChatInit: (chat: FmodeChat) => {
+        chat.role.set("name", "林舒窈");
+        chat.role.set("title", "东方食养家");
+        chat.role.set("desc", "谈吐带有中医师的沉稳,擅长用生活化比喻解释复杂理论,林舒窈,年龄26岁");
+        chat.role.set("tags", ['跑步', '动感单车']);
+        chat.role.set("avatar", "/assets/lin.jpg");
+        chat.role.set("prompt", `...`); // 保持原有prompt内容
+        
+        let promptCates = [
+          { img: "/assets/icon/yy.jpg", name: "营养" },
+          { img: "/assets/icon/rl.jpg", name: "热量" },
+          { img: "/assets/icon/aq.jpg", name: "安全" }
+        ];
+        
+        setTimeout(() => {
+          chat.role.set("promptCates", promptCates);
+        }, 500);
+        
+        let promptList = [
+          {
+            cate: "营养", img: "/assets/icon/yy.jpg",
+            messageList: [
+              "如何在不减少食物种类的情况下保证营养均衡?",
+              "有哪些高蛋白但低脂肪的美食推荐?",
+              "素食者如何确保摄入足够的蛋白质和铁?",
+              "怎样搭配碳水化合物、蛋白质和脂肪的比例更健康?"
+            ]
+          },
+          // 其他提示类别...
+        ];
+        
+        let ChatPrompt = Parse.Object.extend("ChatPrompt");
+        setTimeout(() => {
+          chat.promptList = promptList.map(item => {
+            let prompt = new ChatPrompt();
+            prompt.set(item);
+            prompt.img = item.img;
+            return prompt;
+          });
+        }, 500);
+        
+        chat.leftButtons = [
+          {
+            title: "话题灵感",
+            showTitle: true,
+            icon: "color-wand-outline",
+            onClick: () => { chat.isPromptModalOpen = true },
+            show: () => chat?.promptList?.length
+          },
+        ];
+      },
+      
+      onMessage: (chat: FmodeChat, message: FmodeChatMessage) => {
+        console.log("onMessage", message);
+      },
+      
+      onChatSaved: async (chat: FmodeChat) => {
+        const chatId = chat?.chatSession?.id;
+        
+        // 保存到 NutritionConsult 表
+        let nutritionQuery = new CloudQuery("NutritionConsult");
+        let nutritionConsult = await nutritionQuery.get(chatId);
+        if (!nutritionConsult?.id) {
+          nutritionConsult = new CloudObject("NutritionConsult");
+        }
+        nutritionConsult.set({
+          "chatId": chatId,
+          "messageList": chat.messageList,
+          "name": chat.role.get("name"),
+          "avatar": chat.role.get("avatar"),
+        });
+        await nutritionConsult.save();
+        
+        // 保存到 LiaoTianJiLu 表
+        let chatRecordQuery = new CloudQuery("LiaoTianJiLu");
+        let chatRecord = await chatRecordQuery.get(chatId);
+        if (!chatRecord?.id) {
+          chatRecord = new CloudObject("LiaoTianJiLu");
+        }
+        
+        let currentUser = new CloudUser();
+        await currentUser.current();
+        
+        chatRecord.set({
+          "chatId": chatId,
+          "userId": currentUser.id,
+          "userName": currentUser.get("username"),
+          "agentName": chat.role.get("name"),
+          "avatar": chat.role.get("avatar"),
+          "messages": chat.messageList,
+          "lastMessage": chat.messageList[chat.messageList.length - 1]?.content,
+          "lastMessageTime": new Date().toISOString(),
+          "messageCount": chat.messageList.length
+        });
+        
+        await chatRecord.save();
+        this.loadConsultList();
+      }
+    };
+    
+    openChatPanelModal(this.modalCtrl, options);
+  }
+
+  async viewChatDetail(chatId: string) {
+    this.openConsult(chatId);
+  }
+
+  async deleteChatRecord(chatId: string) {
+    try {
+      const query = new CloudQuery("LiaoTianJiLu");
+      const record = await query.get(chatId);
+      
+      if (record) {
+        await record.destroy();
+        // 从本地列表中移除
+        this.consultList = this.consultList.filter(
+          item => item.get('chatId') !== chatId
+        );
+      }
+    } catch (error) {
+      console.error('删除单条记录失败:', error);
+      const errorAlert = await this.alertCtrl.create({
+        header: '操作失败',
+        message: '删除聊天记录时发生错误',
+        buttons: ['确定']
+      });
+      await errorAlert.present();
+    }
   }
 
   toggleFontSize() {
@@ -321,7 +345,6 @@ export class Tab2Page implements OnInit {
   }
 
   refreshData() {
-    // 模拟刷新数据
     this.filteredKnowledge = [...this.knowledgeList];
     this.searchQuery = '';
     this.currentCategory = 'all';
@@ -346,7 +369,6 @@ export class Tab2Page implements OnInit {
 
   startVoiceSearch() {
     console.log('启动语音搜索');
-    // 实际应集成语音识别API
   }
 
   filterByCategory() {