19870608669 před 6 měsíci
rodič
revize
1d5132310e

+ 13 - 0
.hintrc

@@ -0,0 +1,13 @@
+{
+  "extends": [
+    "development"
+  ],
+  "hints": {
+    "axe/text-alternatives": [
+      "default",
+      {
+        "image-alt": "off"
+      }
+    ]
+  }
+}

+ 18 - 0
mcbridge-app/src/app/consultation-page/consultation-page.component.html

@@ -0,0 +1,18 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/tabs/consultation-total"></ion-back-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <ion-list>
+    <ion-item lines="none">
+      <div class="doctor-info">
+        <h1 style="text-align: center;">{{ consultation?.get('title') }}</h1>
+        <h3>{{ consultation?.get('content') }}</h3>
+      </div>
+    </ion-item>
+  </ion-list>
+</ion-content>

+ 0 - 0
mcbridge-app/src/app/consultation-page/consultation-page.component.scss


+ 22 - 0
mcbridge-app/src/app/consultation-page/consultation-page.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { ConsultationPageComponent } from './consultation-page.component';
+
+describe('ConsultationPageComponent', () => {
+  let component: ConsultationPageComponent;
+  let fixture: ComponentFixture<ConsultationPageComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [ConsultationPageComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ConsultationPageComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 43 - 0
mcbridge-app/src/app/consultation-page/consultation-page.component.ts

@@ -0,0 +1,43 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { IonButtons, IonContent, IonHeader, IonItem, IonList, IonToolbar } from '@ionic/angular/standalone';
+import { IonBackButton } from '@ionic/angular/standalone';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-consultation-page',
+  templateUrl: './consultation-page.component.html',
+  styleUrls: ['./consultation-page.component.scss'],
+  standalone: true,
+  imports: [
+    IonHeader, IonToolbar, IonButtons, IonBackButton, IonContent, IonList, IonItem
+  ]
+})
+export class ConsultationPageComponent  implements OnInit {
+
+  consultation: CloudObject | null = null; // 初始化为 null
+
+  constructor(private route: ActivatedRoute) {}
+
+  ngOnInit() {
+    const objectId = this.route.snapshot.paramMap.get('id'); // 获取传递的 ObjectId
+    if (objectId) { // 检查 objectId 是否为 null
+      this.loadConsultation(objectId); // 加载对应的知识信息
+    } else {
+      console.error("No ObjectId found in route parameters.");
+    }
+  }
+
+  async loadConsultation(objectId: string) {
+    let query = new CloudQuery("Consultation");
+    query.equalTo("title", objectId); // 根据 ObjectId 查询
+    const consultation = await query.first(); // 获取单条记录
+    if (consultation) {
+      this.consultation = consultation; // 将获取的知识信息赋值给 consultation 属性
+    } else {
+      console.error("consultation not found for ObjectId:", objectId);
+      // 可以考虑在这里处理找不到知识的情况,比如导航到错误页面或者显示提示
+    }
+  }
+
+}

+ 29 - 0
mcbridge-app/src/app/consultation-total/consultation-total.component.html

@@ -0,0 +1,29 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/tabs/tab3"></ion-back-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>咨询记录</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list>
+        <ion-item  *ngFor="let consultation of consultationList" lines="none" class="doctor-item"
+        (click)="goToConsultationPage(consultation)">
+          <ion-thumbnail slot="start">
+            <img [src]=" consultation.get('expect')?.avatar || 'https://bpic.588ku.com/element_origin_min_pic/23/02/19/f9a91c8ed1a3beb5d3d22d56c8959cca.jpg' " [alt]="consultation.get('expect')?.name" />
+          </ion-thumbnail>
+          <div class="doctor-info">
+            <h3>{{ consultation.get('title') }}</h3>
+            <p>{{ consultation.get('expect')?.name }}</p>
+          </div>
+        </ion-item>
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 23 - 0
mcbridge-app/src/app/consultation-total/consultation-total.component.scss

@@ -0,0 +1,23 @@
+.doctor-item {
+    display: flex;
+    align-items: center;
+    // padding: 15px; /* 增加内边距 */
+    margin-bottom: 10px; /* 增加每个医生项之间的间距 */
+    border: 1px solid #e0e0e0; /* 添加边框 */
+    border-radius: 8px; /* 圆角边框 */
+    background-color: #f9f9f9; /* 背景颜色 */
+  }
+
+  .doctor-item ion-thumbnail {
+    margin-right: 15px; /* 增加缩略图和文本之间的间距 */
+  }
+
+  .doctor-info h3 {
+    margin: 0; /* 去掉默认的外边距 */
+    font-size: 1.2em; /* 调整字体大小 */
+  }
+
+  .doctor-info p {
+    margin: 5px 0; /* 增加段落之间的间距 */
+    color: #666; /* 调整字体颜色 */
+  }

+ 22 - 0
mcbridge-app/src/app/consultation-total/consultation-total.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { ConsultationTotalComponent } from './consultation-total.component';
+
+describe('ConsultationTotalComponent', () => {
+  let component: ConsultationTotalComponent;
+  let fixture: ComponentFixture<ConsultationTotalComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [ConsultationTotalComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ConsultationTotalComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 47 - 0
mcbridge-app/src/app/consultation-total/consultation-total.component.ts

@@ -0,0 +1,47 @@
+import { CommonModule } from '@angular/common';
+import { Component, OnInit } from '@angular/core';
+import {  Router } from '@angular/router';
+import { IonButtons, IonHeader, IonToolbar } from '@ionic/angular/standalone';
+import { IonBackButton } from '@ionic/angular/standalone';
+import { IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonContent, IonItem, IonList, IonThumbnail } from '@ionic/angular/standalone';
+import { CloudObject, CloudQuery, CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-consultation-total',
+  templateUrl: './consultation-total.component.html',
+  styleUrls: ['./consultation-total.component.scss'],
+  standalone: true,
+  imports: [
+    IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonList, IonItem, IonThumbnail,
+    CommonModule, IonHeader, IonToolbar, IonButtons, IonBackButton
+  ]
+})
+export class ConsultationTotalComponent  implements OnInit {
+
+  constructor(private router: Router) { }
+
+  ngOnInit() {
+    // 生命周期:页面加载后,运行医生列表加载函数
+    this.loadConsultationList()
+  }
+
+  currentUser = new CloudUser();
+
+  goToConsultationPage(consultation: CloudObject) {
+    const objectId = consultation.get('title'); // 假设你的主键字段为 'title'
+    this.router.navigate(['/tabs/consultation-page', { id: objectId }]);
+  }
+
+  // 创建用于数据列表存储的属性
+  consultationList:Array<CloudObject> = []
+  // 查询并加载医生列表的函数
+  async loadConsultationList(){
+    let query = new CloudQuery("Consultation");
+    query.equalTo("user", this.currentUser.id);
+    query.include("expect");
+    // query.include("user");
+    this.consultationList = await query.find()
+    console.log(this.consultationList)
+  }
+
+}

+ 21 - 19
mcbridge-app/src/app/knowledge/knowledge-video/knowledge-video.component.html

@@ -1,23 +1,25 @@
 <ion-header>
-    <ion-toolbar>
-      <ion-buttons slot="start">
-        <ion-back-button defaultHref="/tabs/knowledge-total"></ion-back-button>
-      </ion-buttons>
-      <ion-title></ion-title>
-    </ion-toolbar>
-  </ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/tabs/knowledge-total"></ion-back-button>
+    </ion-buttons>
+    <ion-title></ion-title>
+  </ion-toolbar>
+</ion-header>
 
 <ion-content>
-    <ion-list>
-        <ion-item lines="none">
-            <div class="doctor-info">
-                <video id="video" controls>
-                    <source src="{{ knowledge?.get('video') }}" type="video/mp4">
-                    您的浏览器不支持视频标签。
-                </video>
-                <h3 style="text-align: center;">{{ knowledge?.get('title') }}</h3>
-                <p>{{ knowledge?.get('content') }}</p>
-            </div>
-        </ion-item>
-    </ion-list>
+  <ion-list>
+    <ion-item lines="none">
+      <div class="doctor-info">
+        <video id="video" controls>
+          <source src="{{ knowledge?.get('video') }}" type="video/mp4">
+          您的浏览器不支持视频标签。
+        </video>
+        <ion-card>
+          <h3 style="text-align: center;">{{ knowledge?.get('title') }}</h3>
+          <p>{{ knowledge?.get('content') }}</p>
+        </ion-card>
+      </div>
+    </ion-item>
+  </ion-list>
 </ion-content>

+ 4 - 0
mcbridge-app/src/app/knowledge/knowledge-video/knowledge-video.component.scss

@@ -1,3 +1,7 @@
+ion-card{
+    margin: 10px 5px 0px 5px;
+}
+
 video{
     width: 380px;
     height: 200px;

+ 3 - 2
mcbridge-app/src/app/knowledge/knowledge-video/knowledge-video.component.ts

@@ -1,5 +1,6 @@
 import { Component, OnInit } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
+import { IonCard } from '@ionic/angular/standalone';
 import { IonBackButton, IonButtons, IonTitle, IonToolbar } from '@ionic/angular/standalone';
 import { IonIcon } from '@ionic/angular/standalone';
 import { IonHeader } from '@ionic/angular/standalone';
@@ -16,7 +17,7 @@ addIcons({ arrowBackOutline })
   styleUrls: ['./knowledge-video.component.scss'],
   standalone: true,
   imports: [
-    IonContent, IonList, IonItem,IonHeader, IonIcon, IonTitle, IonToolbar, IonButtons, IonBackButton
+    IonContent, IonList, IonItem,IonHeader, IonIcon, IonTitle, IonToolbar, IonButtons, IonBackButton, IonCard
   ]
 })
 export class KnowledgeVideoComponent  implements OnInit {
@@ -50,4 +51,4 @@ export class KnowledgeVideoComponent  implements OnInit {
     this.loute.navigate(['/tabs/knowledge-total'])
   }
 
-}
+}

+ 4 - 7
mcbridge-app/src/app/tab1/tab1.page.html

@@ -4,7 +4,6 @@
 
   </ion-searchbar>
 
-
   <div class="button-row1">
     <ion-button expand="full" fill="outline" color="light" (click)="goToPage1()">
       <img src="https://s2.loli.net/2024/12/22/sUMkpe2LqnmCxRo.png" alt="报病查诊" class="button-icon" />
@@ -16,22 +15,22 @@
      <span >症状查询</span>
     </ion-button>
 
-    <ion-button expand="full" fill="outline" color="light" routerLink="/discussions">
+    <ion-button expand="full" fill="outline" color="light" (click)="presentAlert()">
       <img src="https://s2.loli.net/2024/12/22/mvLC76a9nYf5sXb.png" alt="讨论记录" class="button-icon" />
       <span >电子病历</span>
     </ion-button>
   </div>
 
   <div class="button-row2">
-    <ion-button expand="full" fill="outline" color="light" routerLink="/tabs/inquiry">
+    <ion-button expand="full" fill="outline" color="light" (click)="presentAlert()">
       <img src="https://s2.loli.net/2024/12/22/rswh2qgoJt1FX3x.png" alt="报病查诊" class="button-icon" />
       <span >药品百科</span>
     </ion-button>
-    <ion-button expand="full" fill="outline" color="light" routerLink="/appointments">
+    <ion-button expand="full" fill="outline" color="light" (click)="presentAlert()">
       <img src="https://s2.loli.net/2024/12/22/VAiBb7lGp2RLjSO.png" alt="预约记录" class="button-icon" />
       <span >预约挂号</span>
     </ion-button>
-    <ion-button expand="full" fill="outline" color="light" routerLink="/discussions">
+    <ion-button expand="full" fill="outline" color="light" (click)="presentAlert()">
      <img src="https://s2.loli.net/2024/12/22/GPOgHbex6jWXD4m.png" alt="讨论记录" class="button-icon" />
      <span >急救指南</span>
     </ion-button>
@@ -41,7 +40,6 @@
 
 
 
-
   <!-- 轮播图区域 -->
   <div class="carousel-container">
     <div class="carousel" [style.transform]="'translateX(-' + currentSlide * 100 + '%)'">
@@ -59,7 +57,6 @@
     </div>
   </div>
 
-
   <!-- 知识模块 -->
 
     <ion-card-header >

+ 4 - 7
mcbridge-app/src/app/tab1/tab1.page.scss

@@ -11,7 +11,6 @@ ion-title p {
   text-align: center; /* 标题居中 */
 }
 
-
 ion-searchbar {  
     --background: #f8f9fa;  
     --border-radius: 8px;  
@@ -91,7 +90,6 @@ ion-button p{
   }
 
 
-
   ion-button{
     border-radius: 200px;
   }
@@ -168,12 +166,11 @@ ion-button p{
 } 
 
 
-
 // 知识区域
-.hot{
-  width: 370px;
-  margin: 5px 10px 0px 10px;
-}
+// .hot{
+//   width: 370px;
+//   margin: 5px 10px 0px 10px;
+// }
 
 .doctor-item {
   display: flex;

+ 10 - 5
mcbridge-app/src/app/tab1/tab1.page.ts

@@ -1,6 +1,6 @@
 import { Component } from '@angular/core';  
 import { CommonModule } from '@angular/common'; // 导入 CommonModule  
-import { IonHeader, IonToolbar, IonTitle, IonContent, IonSearchbar, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonItem, IonLabel, IonList, IonFooter } from '@ionic/angular/standalone';  
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonSearchbar, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent, IonItem, IonLabel, IonList, IonFooter, AlertController } from '@ionic/angular/standalone';  
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';  
 import { EditRatingStarComponent } from '../edit-rating-star/edit-rating-star.component';
 import { Router } from '@angular/router';
@@ -27,7 +27,7 @@ addIcons({ bagAddOutline, imagesOutline, ellipsisHorizontalOutline})
   ],  
 })  
 export class Tab1Page {
-   constructor(private router: Router) {} 
+   constructor(private router: Router, private alertController: AlertController) {} 
 
    /**
   * 轮播图
@@ -64,7 +64,6 @@ export class Tab1Page {
     }
   }
 
-
   goToPage1(){
     this.router.navigate(['/tabs/picture'])
   }
@@ -107,7 +106,6 @@ export class Tab1Page {
   //   }  
   // ];  
 
-
     ngOnInit() {
       // 生命周期:页面加载后,运行医生列表加载函数
       this.loadKnowledgeList()
@@ -122,7 +120,14 @@ export class Tab1Page {
       this.knowledgeList = allKnowledge.slice(0, 4);
     }
 
-
+    async presentAlert() {
+      const alert = await this.alertController.create({
+        message: '敬请期待!',
+        buttons: ['确定'],
+      });
+  
+      await alert.present();
+    }
 
   search(event: CustomEvent) {  
     const searchTerm = event.detail.value;  

+ 7 - 2
mcbridge-app/src/app/tab2/tab2.page.html

@@ -6,7 +6,6 @@
   </ion-toolbar>
 </ion-header>
 
-
 <ion-content>
   <ion-card>
     <ion-card-header>
@@ -15,7 +14,7 @@
     </ion-card-header>
     <ion-card-content>
       <ion-list>
-        <ion-item (click)="openInquiry(expect)" *ngFor="let expect of expectList" lines="none" class="doctor-item">
+        <ion-item  *ngFor="let expect of expectList" lines="none" class="doctor-item">
           <ion-thumbnail slot="start">
             <img [src]="expect.get('avatar')" [alt]="expect.get('name')" />
           </ion-thumbnail>
@@ -23,6 +22,12 @@
             <h3>{{ expect.get('name') }} ({{ expect.get('age') }}岁)</h3>
             <p>{{ expect.get('title') }} {{ expect.get('specialist') }}</p>
             <p>飞码互联网医院</p>
+            <p *ngIf="expect.isFollowed" style="color: green;">已关注</p> <!-- 显示已关注 -->
+          </div>
+          <div slot="end" class="button-group">
+            <ion-button (click)="followDoctor(expect)" fill="outline" size="small" *ngIf="!expect.isFollowed">关注</ion-button>
+            <ion-button (click)="unfollowDoctor(expect)" color="danger" fill="solid" size="small" *ngIf="expect.isFollowed">已关注</ion-button> <!-- 禁用已关注按钮 -->
+            <ion-button (click)="openInquiry(expect)" fill="solid" size="small">咨询</ion-button>
           </div>
         </ion-item>
       </ion-list>

+ 131 - 19
mcbridge-app/src/app/tab2/tab2.page.ts

@@ -1,5 +1,5 @@
 import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, AlertController } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
 import { extactAndParseJsonFromString } from 'src/agent/agent.json';
 import { TaskInqueryUserStory } from 'src/agent/tasks/inquiry/1.inquiry-user-story';
@@ -18,6 +18,11 @@ import { CloudObject, CloudQuery, CloudUser } from 'src/lib/ncloud';
 import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
 import { ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
 
+// 创建一个接口,扩展 CloudObject
+interface DoctorExpect extends CloudObject {
+  isFollowed?: boolean; // 可选属性
+}
+
 addIcons({radioButtonOffOutline, reloadOutline, checkmarkCircleOutline, closeCircleOutline}) 
 @Component({
   selector: 'app-tab2',
@@ -30,8 +35,7 @@ addIcons({radioButtonOffOutline, reloadOutline, checkmarkCircleOutline, closeCir
 })
 export class Tab2Page {
 
-  constructor(private modalCtrl:ModalController) {
-
+  constructor(private modalCtrl:ModalController, private alertController: AlertController) {
 
   }
 
@@ -63,7 +67,8 @@ export class Tab2Page {
   // }
 
 
-  // testJSON(){
+
+   // testJSON(){
   //   let string = `
   //         ''''json
   //             {
@@ -107,13 +112,30 @@ export class Tab2Page {
   }
 
   // 创建用于数据列表存储的属性
-  expectList:Array<CloudObject> = []
+  expectList:Array<DoctorExpect> = []
   // 查询并加载医生列表的函数
   async loadExpectList(){
     let query = new CloudQuery("Expect");
-    this.expectList = await query.find()
-  }
+    this.expectList = await query.find() as DoctorExpect[];
+
+    let currentUser = new CloudUser();
+    if (currentUser?.id) {
+      let attentionQuery = new CloudQuery("Attention");
+      attentionQuery.equalTo("user", currentUser.toPointer());
+      attentionQuery.include("expect");
+
+      let attentions = await attentionQuery.find();
+      // console.log(attentions);
+      let followedExpectIds = attentions.map(att => att.get("expect").objectId);
+      // console.log(followedExpectIds);
 
+      this.expectList.forEach(expect => {
+        expect.isFollowed = followedExpectIds.includes(expect.id);
+        // console.log(expect.isFollowed);
+      });
+    }
+
+  }
 
      /** 示例:问诊根据expect拼接提示词 */
      async openInquiry(expect:any){
@@ -148,12 +170,12 @@ export class Tab2Page {
       let now = new Date();
       let dateStr = `${now.getFullYear()}-${now.getMonth()+1}-${now.getDate()}`
       // 对象权限的精确指定
-      let ACL:any = {
-        "*":{read:false,write:false}
-      }
-      if(currentUser?.id){
-        ACL[currentUser?.id] = {read:true,write:true}
-      }
+      // let ACL:any = {
+      //   "*":{read:false,write:false}
+      // }
+      // if(currentUser?.id){
+      //   ACL[currentUser?.id] = {read:true,write:true}
+      // }
       consult.set({
         title:`${expect.get('depart')?.name || ""}门诊记录${dateStr}-${expect?.get("name")}`,
         expect:expect.toPointer(),
@@ -163,9 +185,10 @@ export class Tab2Page {
           objectId:expect.get("depart")?.objectId
         },
         user:currentUser.toPointer(),
-        ACL:ACL
+        // ACL:ACL
       })
-  
+
+
       let options:ChatPanelOptions = {
         roleId:"2DXJkRsjXK",
         onChatInit:(chat:FmodeChat)=>{
@@ -206,10 +229,23 @@ export class Tab2Page {
           if(typeof content == "string"){
             if(content?.indexOf("[处方完成]")>-1){
               console.log("门诊已完成")
+              // consult.set({
+              //   content:content // 处方内容
+              // })
+              // consult.save();
+
               consult.set({
-                content:content // 处方内容
-              })
-              consult.save();
+                content: content, // 处方内容
+            });
+
+              console.log("准备保存的对象:", consult);
+
+              consult.save().then(() => {
+                console.log("数据成功保存到数据库");
+            }).catch((error) => {
+                console.error("保存数据时出错:", error);
+            });
+
             }
           }
         },
@@ -220,5 +256,81 @@ export class Tab2Page {
       }
       openChatPanelModal(this.modalCtrl,options)
     }
-  }
+
+    // 关注
+    async followDoctor(expect: DoctorExpect) {
+      // 验证用户登录
+      let currentUser = new CloudUser();
+      if (!currentUser?.id) {
+        console.log("用户未登录,请登录后重试");
+        let user = await openUserLoginModal(this.modalCtrl);
+        if (!user?.id) {
+          return;
+        }
+        currentUser = user;
+      }
+  
+    // 查询 Attention 表,检查用户是否已关注该医生
+    let attentionQuery = new CloudQuery("Attention");
+    attentionQuery.equalTo("user", currentUser.toPointer());
+    attentionQuery.equalTo("expect", expect.toPointer());
+    let existingAttention = await attentionQuery.first();
+
+    if (existingAttention) {
+      console.log("您已关注该医生");
+      // 在此处可以显示提示消息,告知用户已关注
+      return; // 退出函数,不再执行关注操作
+    }
+
+      // 创建 Attention 对象
+      let attention = new CloudObject("Attention");
+      attention.set({
+        user: currentUser.toPointer(), // 当前用户
+        expect: expect.toPointer()      // 被关注的医生
+      });
   
+      // 保存到数据库
+      try {
+        await attention.save();
+        console.log("关注成功,已将医生加入关注列表");
+        // 可选:在此处显示一个提示消息,通知用户关注成功
+        expect.isFollowed = true; // 更新本地状态
+      } catch (error) {
+        console.error("关注医生时出错:", error);
+        // 可选:在此处显示一个错误提示消息
+      }
+    }
+
+    async unfollowDoctor(expect: DoctorExpect) {
+      // 验证用户登录
+      let currentUser = new CloudUser();
+      if (!currentUser?.id) {
+        console.log("用户未登录,请登录后重试");
+        let user = await openUserLoginModal(this.modalCtrl);
+        if (!user?.id) {
+          return;
+        }
+        currentUser = user;
+      }
+    
+      // 查询 Attention 表,找到要移除的关注记录
+      let attentionQuery = new CloudQuery("Attention");
+      attentionQuery.equalTo("user", currentUser.toPointer());
+      attentionQuery.equalTo("expect", expect.toPointer());
+      let existingAttention = await attentionQuery.first();
+    
+      if (existingAttention) {
+        // 删除关注记录
+        try {
+          await existingAttention.destroy();
+          console.log("已成功取消关注该医生");
+          expect.isFollowed = false; // 更新本地状态
+        } catch (error) {
+          console.error("取消关注时出错:", error);
+        }
+      } else {
+        console.log("您未关注该医生");
+      }
+    }
+
+  }

+ 80 - 76
mcbridge-app/src/app/tab3/tab3.page.html

@@ -31,19 +31,19 @@
     </ion-card-header>
     <ion-card-content>
       <div class="button-row">
-        <ion-button expand="full" fill="outline" color="light" routerLink="/tabs/inquiry">
-          <img src="https://s2.loli.net/2024/12/22/E8MtQxYUqd52ojT.png" alt="报病查诊" class="button-icon"/>
-          <span>门诊服务</span>
+        <ion-button expand="full" fill="outline" color="light" (click)="goToConsultationTotal()">
+          <img src="https://s2.loli.net/2024/12/22/E8MtQxYUqd52ojT.png" alt="诊疗记录" class="button-icon"/>
+          <span>诊疗记录</span>
         </ion-button>
-        <ion-button expand="full" fill="outline" color="light" routerLink="/appointments">
+        <ion-button expand="full" fill="outline" color="light" (click)="presentAlert()">
           <img src="https://s2.loli.net/2024/12/22/BioXWPwmzSI5uY4.png" alt="预约记录" class="button-icon" />
           <span>急诊服务</span>
         </ion-button>
-        <ion-button expand="full" fill="outline" color="light" routerLink="/discussions">
+        <ion-button expand="full" fill="outline" color="light" (click)="presentAlert()">
           <img src="https://s2.loli.net/2024/12/22/v2QYj6NdJVEpnlk.png" alt="讨论记录" class="button-icon" />
           <span>住院治疗</span>
         </ion-button>
-        <ion-button expand="full" fill="outline" color="light" routerLink="/doctors">
+        <ion-button expand="full" fill="outline" color="light" (click)="presentAlert()">
           <img src="https://s2.loli.net/2024/12/22/qdl95uJwIY23HVr.png" alt="关注的医生" class="button-icon" />
           <span>预防保健</span>
         </ion-button>
@@ -59,76 +59,80 @@
       <P>请登录后查看...</P>
       }
       @if(currentUser?.id){
-      <ion-list>
-        <ion-item *ngFor="let expert of expertList" lines="none" class="doctor-item">
-          <ion-thumbnail slot="start">
-            <img [src]="expert.get('avatar')" [alt]="expert.get('name')" />
-          </ion-thumbnail>
-          <div class="doctor-info">
-            <h3>{{ expert.get('name') }}({{ expert.get('age') }}岁)</h3>
-            <p>{{ expert.get('title') }} {{ expert.get('specialist') }}</p>
-          </div>
+        <ion-list>
+          <ion-item *ngFor="let attention of attentionList" lines="none" class="doctor-item">
+            <ion-thumbnail slot="start">
+              <img [src]="attention.get('expect')?.avatar" [alt]="attention.get('expect')?.name" />
+            </ion-thumbnail>
+            <div class="doctor-info">
+              <h3>{{ attention.get('expect')?.name }}({{ attention.get('expect')?.age }}岁)</h3>
+              <p>{{ attention.get('expect')?.title }} {{ attention.get('expect')?.specialist }}</p>
+            </div>
+          </ion-item>
+        </ion-list>
+        }
+      </ion-card-content>
+  
+      <!-- 常用服务 -->
+      <ion-card-header >
+        <ion-card-title>常用服务</ion-card-title>
+      </ion-card-header>
+      <ion-card-content >
+  
+        <ion-item button (click)="presentAlert()">
+          <ion-label>我的账单</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
         </ion-item>
-      </ion-list>
-      }
-    </ion-card-content>
-
-    <!-- 常用服务 -->
-    <ion-card-header >
-      <ion-card-title>常用服务</ion-card-title>
-    </ion-card-header>
-    <ion-card-content >
-
-      <ion-item button routerLink="/bills">
-        <ion-label>我的账单</ion-label>
-        <ion-icon slot="end" name="chevron-forward"></ion-icon>
-      </ion-item>
-      <ion-item button routerLink="/orders">
-        <ion-label>商城订单</ion-label>
-        <ion-icon slot="end" name="chevron-forward"></ion-icon>
-      </ion-item>
-      <ion-item button routerLink="/checkups">
-        <ion-label>体检订单</ion-label>
-        <ion-icon slot="end" name="chevron-forward"></ion-icon>
-      </ion-item>
-
-    </ion-card-content>
-
-    <ion-card-content>
-
-      @if(!currentUser?.id){
-      <ion-item button id="err">
-        <ion-label>个人信息</ion-label>
-        <ion-icon slot="end" name="chevron-forward"></ion-icon>
-        <ion-alert trigger="err" header="注意" message="你还未登录,请先登录!" [buttons]="alertButtons"></ion-alert>
-      </ion-item>
-      }
-      @if(currentUser?.id){
-      <ion-item button (click)="editUser()">
-        <ion-label>个人信息</ion-label>
-        <ion-icon slot="end" name="chevron-forward"></ion-icon>
-      </ion-item>
-      }
-      <ion-item button routerLink="/we">
-        <ion-label>关于我们</ion-label>
-        <ion-icon slot="end" name="chevron-forward"></ion-icon>
-      </ion-item>
-      <ion-item button routerLink="/fankui">
-        <ion-label>帮助与反馈</ion-label>
-        <ion-icon slot="end" name="chevron-forward"></ion-icon>
-      </ion-item>
-      <ion-item button routerLink="/shezhi">
-        <ion-label>设置</ion-label>
-        <ion-icon slot="end" name="chevron-forward"></ion-icon>
-      </ion-item>
-
-    </ion-card-content>
-
-  </div>
-
+        <ion-item button (click)="presentAlert()">
+          <ion-label>商城订单</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
+        </ion-item>
+        <ion-item button (click)="presentAlert()">
+          <ion-label>体检订单</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
+        </ion-item>
+        <ion-item button (click)="presentAlert()">
+          <ion-label>专家认证</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
+        </ion-item>
+      </ion-card-content>
+  
+      <ion-card-content>
+  
+        @if(!currentUser?.id){
+        <ion-item button id="err">
+          <ion-label>个人信息</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
+          <ion-alert trigger="err" header="注意" message="你还未登录,请先登录!" [buttons]="alertButtons"></ion-alert>
+        </ion-item>
+        }
+        @if(currentUser?.id){
+        <ion-item button (click)="editUser()">
+          <ion-label>个人信息</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
+        </ion-item>
+        }
+        <ion-item button (click)="presentAlert()">
+          <ion-label>关于我们</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
+        </ion-item>
+        <ion-item button (click)="presentAlert()">
+          <ion-label>帮助与反馈</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
+        </ion-item>
+        <ion-item button (click)="presentAlert()">
+          <ion-label>设置</ion-label>
+          <ion-icon slot="end" name="chevron-forward"></ion-icon>
+        </ion-item>
+  
+      </ion-card-content>
+  
+    </div>
+  
+  </ion-content>
+  
+  @if(currentUser?.id){
+  <ion-button (click)="logout()" color="light">登出</ion-button>
+  }
 
-</ion-content>
 
-@if(currentUser?.id){
-<ion-button (click)="logout()" color="light">登出</ion-button>
-}

+ 41 - 9
mcbridge-app/src/app/tab3/tab3.page.ts

@@ -1,5 +1,5 @@
 import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, AlertController } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
 import { IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonIcon, IonItem, IonLabel, IonList } from '@ionic/angular/standalone';
 import { IonButtons } from '@ionic/angular/standalone';
@@ -14,6 +14,7 @@ import { IonThumbnail } from '@ionic/angular/standalone';
 import { CommonModule } from '@angular/common';
 import { IonModal } from '@ionic/angular/standalone';
 import { IonAlert } from '@ionic/angular/standalone';
+import { Router } from '@angular/router';
 
 addIcons({ settingsOutline, chatbubbleEllipsesOutline, documentTextOutline, calendarOutline, chatbubbleOutline, personOutline}) 
 
@@ -34,7 +35,7 @@ export class Tab3Page {
   alertButtons = ['Action'];
 
   currentUser:CloudUser|undefined
-  constructor(private modalCtrl:ModalController) {
+  constructor(private modalCtrl:ModalController, private router: Router, private alertController: AlertController) {
     this.currentUser = new CloudUser();
   }
   async login(){
@@ -77,16 +78,47 @@ export class Tab3Page {
 
   ngOnInit() {
     // 生命周期:页面加载后,运行专家列表加载函数
-    this.loadExpertList()
+    this.loadAttentionList()
   }
 
+  nowUser = new CloudUser();
+
   // 创建用于数据列表存储的属性
-  expertList:Array<CloudObject> = []
+  attentionList:Array<CloudObject> = []
   // 查询并加载医生列表的函数
-  async loadExpertList(){
-    let query = new CloudQuery("Expect");
-    this.expertList = await query.find()
-    console.log(this.expertList)
+  async loadAttentionList(){
+    let query = new CloudQuery("Attention");
+    query.equalTo("user", this.nowUser.id);
+    query.include("expect");
+    this.attentionList = await query.find()
+    console.log(this.attentionList)
+  }
+  goToConsultationTotal(){
+    this.router.navigate(['/tabs/consultation-total']);
   }
 
-}
+  async presentAlert() {
+    const alert = await this.alertController.create({
+      message: '敬请期待!',
+      buttons: ['确定'],
+    });
+
+    await alert.present();
+  }
+
+  // ngOnInit() {
+  //   // 生命周期:页面加载后,运行专家列表加载函数
+  //   this.loadUserList()
+  // }
+
+  // // 创建用于数据列表存储的属性
+  // userList:Array<CloudObject> = []
+  // // 查询并加载医生列表的函数
+  // async loadUserList(){
+  //   let query = new CloudQuery("_User");
+  //   // query.include("depart");
+  //   this.userList = await query.find()
+  //   console.log(this.userList)
+  // }
+
+}

+ 10 - 0
mcbridge-app/src/app/tabs/tabs.routes.ts

@@ -51,6 +51,16 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../knowledge/knowledge-page1/knowledge-page1.component').then((m) => m.KnowledgePage1Component),
       },
+      {
+        path: 'consultation-page',
+        loadComponent: () =>
+          import('../consultation-page/consultation-page.component').then((m) => m.ConsultationPageComponent),
+      },
+      {
+        path: 'consultation-total',
+        loadComponent: () =>
+          import('../consultation-total/consultation-total.component').then((m) => m.ConsultationTotalComponent),
+      },
       {
         path: '',
         redirectTo: '/tabs/tab1',

+ 135 - 34
mcbridge-app/src/lib/ncloud.ts

@@ -16,7 +16,7 @@ export class CloudObject {
 
     set(json: Record<string, any>) {
         Object.keys(json).forEach(key => {
-            if (["objectId", "id", "createdAt", "updatedAt", "ACL"].indexOf(key) > -1) {
+            if (["objectId", "id", "createdAt", "updatedAt"].indexOf(key) > -1) {
                 return;
             }
             this.data[key] = json[key];
@@ -79,37 +79,46 @@ export class CloudObject {
     }
 }
 
+
+
+
+
+
 // CloudQuery.ts
 export class CloudQuery {
     className: string;
-    whereOptions: Record<string, any> = {};
+    queryParams: Record<string, any> = {};
 
     constructor(className: string) {
         this.className = className;
     }
 
+    include(...fileds:string[]) {
+        this.queryParams["include"] = fileds;
+    }
     greaterThan(key: string, value: any) {
-        if (!this.whereOptions[key]) this.whereOptions[key] = {};
-        this.whereOptions[key]["$gt"] = value;
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
     }
 
     greaterThanAndEqualTo(key: string, value: any) {
-        if (!this.whereOptions[key]) this.whereOptions[key] = {};
-        this.whereOptions[key]["$gte"] = value;
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
     }
 
     lessThan(key: string, value: any) {
-        if (!this.whereOptions[key]) this.whereOptions[key] = {};
-        this.whereOptions[key]["$lt"] = value;
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
     }
 
     lessThanAndEqualTo(key: string, value: any) {
-        if (!this.whereOptions[key]) this.whereOptions[key] = {};
-        this.whereOptions[key]["$lte"] = value;
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
     }
 
     equalTo(key: string, value: any) {
-        this.whereOptions[key] = value;
+        if (!this.queryParams["where"]) this.queryParams["where"] = {};
+        this.queryParams["where"][key] = value;
     }
 
     async get(id: string) {
@@ -130,13 +139,24 @@ export class CloudQuery {
         return json || {};
     }
 
-    async find() {
+    async find():Promise<Array<CloudObject>> {
         let url = `https://dev.fmode.cn/parse/classes/${this.className}?`;
 
-        if (Object.keys(this.whereOptions).length) {
-            const whereStr = JSON.stringify(this.whereOptions);
-            url += `where=${whereStr}`;
-        }
+        let queryStr = ``
+        Object.keys(this.queryParams).forEach(key=>{
+            let paramStr = JSON.stringify(this.queryParams[key]);
+            if(key=="include"){
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if(queryStr) {
+                url += `${key}=${paramStr}`;
+            }else{
+                url += `&${key}=${paramStr}`;
+            }
+        })
+        // if (Object.keys(this.queryParams["where"]).length) {
+            
+        // }
 
         const response = await fetch(url, {
             headers: {
@@ -158,8 +178,8 @@ export class CloudQuery {
     async first() {
         let url = `https://dev.fmode.cn/parse/classes/${this.className}?`;
 
-        if (Object.keys(this.whereOptions).length) {
-            const whereStr = JSON.stringify(this.whereOptions);
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
             url += `where=${whereStr}`;
         }
 
@@ -192,6 +212,14 @@ export class CloudQuery {
         return existsObject;
     }
 }
+
+
+
+
+
+
+
+
 // CloudUser.ts
 export class CloudUser extends CloudObject {
     constructor() {
@@ -214,21 +242,21 @@ export class CloudUser extends CloudObject {
             console.error("用户未登录");
             return null;
         }
-        
-        const response = await fetch(`https://dev.fmode.cn/parse/users/me`, {
-            headers: {
-                "x-parse-application-id": "dev",
-                "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
-            },
-            method: "GET"
-        });
-
-        const result = await response?.json();
-        if (result?.error) {
-            console.error(result?.error);
-            return null;
-        }
-        return result;
+        return this;
+        // const response = await fetch(`https://dev.fmode.cn/parse/users/me`, {
+        //     headers: {
+        //         "x-parse-application-id": "dev",
+        //         "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+        //     },
+        //     method: "GET"
+        // });
+
+        // const result = await response?.json();
+        // if (result?.error) {
+        //     console.error(result?.error);
+        //     return null;
+        // }
+        // return result;
     }
 
     /** 登录 */
@@ -287,7 +315,8 @@ export class CloudUser extends CloudObject {
         return true;
     }
 
-    /** 注册 */
+
+ /** 注册 */
     async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
         const userData = {
             username,
@@ -311,9 +340,81 @@ export class CloudUser extends CloudObject {
         }
 
         // 设置用户信息
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User",JSON.stringify(result))
         this.id = result?.objectId;
         this.sessionToken = result?.sessionToken;
         this.data = result; // 保存用户数据
         return this;
     }
+
+    override async save() {
+        let method = "POST";
+        let url = `https://dev.fmode.cn/parse/users`;
+    
+        // 更新用户信息
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+    
+        let data:any = JSON.parse(JSON.stringify(this.data))
+        delete data.createdAt
+        delete data.updatedAt
+        delete data.ACL
+        delete data.objectId
+        const body = JSON.stringify(data);
+        let headersOptions:any = {
+            "content-type": "application/json;charset=UTF-8",
+            "x-parse-application-id": "dev",
+            "x-parse-session-token": this.sessionToken, // 添加sessionToken以进行身份验证
+        }
+        const response = await fetch(url, {
+            headers: headersOptions,
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+    
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        localStorage.setItem("NCloud/dev/User",JSON.stringify(this.data))
+        return this;
+    }
+}
+
+export class CloudApi{
+    async fetch(path:string,body:any,options?:{
+        method:string
+        body:any
+    }){
+
+        let reqOpts:any =  {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            method: options?.method || "POST",
+            mode: "cors",
+            credentials: "omit"
+        }
+        if(body||options?.body){
+            reqOpts.body = JSON.stringify(body || options?.body);
+            reqOpts.json = true;
+        }
+        let host = `https://dev.fmode.cn`
+        // host = `http://127.0.0.1:1337`
+        let url = `${host}/api/`+path
+        console.log(url,reqOpts)
+        const response = await fetch(url,reqOpts);
+        let json = await response.json();
+        return json
+    }
 }