Jelajahi Sumber

new fix update

17870762608 6 bulan lalu
induk
melakukan
e8b760dcd6

+ 340 - 0
mcbridge-app/docs-prod/schema.md

@@ -0,0 +1,340 @@
+# 医护云桥应用UML编写
+## 一、Schema范式设计
+### 用户模块
+- 功能描述:支持患者、专家医生、医学生等多种用户角色的注册和登录。
+- 分析:这一模块是用户体验的基础,确保用户能够顺利进入平台。需要考虑用户身份验证的安全性和便捷性,可能包括多因素认证等措施。
+为了设计一个用户模块的Parse Schema,我们需要考虑不同用户角色的需求,包括患者、专家医生和医学生。我们将创建一个用户表和相关的角色表,并确保遵循设计范式。以下是所需的Parse Schema范式表及具体字段名称的分析。
+
+#### 1.表设计
+
+1.1 **User** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **username**: 用户名
+   - **password**: 密码(建议加密存储)
+   - **email**: 邮箱
+   - **role**: 用户角色(Pointer<Role>)
+   - **isVerified**: 是否已验证(boolean)
+   - **lastLogin**: 最后登录时间
+
+1.2 **Role** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **roleName**: 角色名称(如“患者”、“专家医生”、“医学生”)
+   - **permissions**: 权限列表(array)
+
+1.3 **UserProfile** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **user**: 用户(Pointer<User>)
+   - **firstName**: 名
+   - **lastName**: 姓
+   - **phoneNumber**: 电话号码
+   - **profilePicture**: 头像(File)
+   - **bio**: 简介
+
+#### 2.PlantUML 类图表示
+
+以下是使用PlantUML表示的类图:
+
+```plantuml
+@startuml
+class User {
+    + objectId: String
+    + createdAt: Date
+    + username: String
+    + password: String
+    + email: String
+    + role: Pointer<Role>
+    + isVerified: Boolean
+    + lastLogin: Date
+}
+
+class Role {
+    + objectId: String
+    + createdAt: Date
+    + roleName: String
+    + permissions: Array
+}
+
+class UserProfile {
+    + objectId: String
+    + createdAt: Date
+    + user: Pointer<User>
+    + firstName: String
+    + lastName: String
+    + phoneNumber: String
+    + profilePicture: File
+    + bio: String
+}
+
+User "1" -- "1" Role : has
+User "1" -- "1" UserProfile : has
+@enduml
+```
+
+#### 3.设计说明
+
+- **User 表**: 主要存储用户的基本信息,包括用户名、密码、邮箱和角色。角色通过指针关联到 Role 表,以便于管理不同用户的权限。
+- **Role 表**: 定义不同角色的名称和相应的权限,支持后续的权限管理和扩展。
+- **UserProfile 表**: 存储用户的详细个人信息,便于展示和管理。
+
+通过上述设计,可以确保用户模块的功能性和安全性,为患者、专家医生和医学生提供良好的用户体验。
+### 知识模块
+- 功能描述:提供各学科的知识模块,用户可以选择学习。
+- 分析:知识模块的多样性和系统性是提升用户学习效果的关键。需要确保内容的权威性和更新频率,以满足医护人员和医学生的学习需求。
+为了设计一个知识模块的Parse Schema,我们需要考虑不同学科的知识内容、用户的学习需求以及内容的更新机制。以下是所需的Parse Schema范式表及具体字段名称的分析。
+
+#### 1.表设计
+
+1.1 **KnowledgeModule** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **title**: 模块标题
+   - **description**: 模块描述
+   - **category**: 学科分类(Pointer<Category>)
+   - **content**: 知识内容(Text)
+   - **author**: 作者(Pointer<User>)
+   - **updatedAt**: 最后更新时间
+   - **isPublished**: 是否已发布(boolean)
+
+1.2 **Category** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **name**: 类别名称(如“内科”、“外科”、“药理学”等)
+   - **description**: 类别描述
+
+1.3 **KnowledgeResource** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **module**: 知识模块(Pointer<KnowledgeModule>)
+   - **resourceType**: 资源类型(如“视频”、“文档”、“图表”等)
+   - **resourceLink**: 资源链接(URL)
+   - **description**: 资源描述
+
+#### 2.PlantUML 类图表示
+
+以下是使用PlantUML表示的类图:
+
+```plantuml
+@startuml
+class KnowledgeModule {
+    + objectId: String
+    + createdAt: Date
+    + title: String
+    + description: String
+    + category: Pointer<Category>
+    + content: Text
+    + author: Pointer<User>
+    + updatedAt: Date
+    + isPublished: Boolean
+}
+
+class Category {
+    + objectId: String
+    + createdAt: Date
+    + name: String
+    + description: String
+}
+
+class KnowledgeResource {
+    + objectId: String
+    + createdAt: Date
+    + module: Pointer<KnowledgeModule>
+    + resourceType: String
+    + resourceLink: String
+    + description: String
+}
+
+KnowledgeModule "1" -- "1" Category : belongs to
+KnowledgeModule "1" -- "*" KnowledgeResource : has
+@enduml
+```
+
+#### 3.设计说明
+
+- **KnowledgeModule 表**: 存储知识模块的基本信息,包括标题、描述、内容、分类、作者以及发布状态。通过指针关联到 Category 表和 User 表,以便于管理模块的分类和作者信息。
+- **Category 表**: 定义不同学科的分类,支持知识模块的组织和检索。
+- **KnowledgeResource 表**: 存储与知识模块相关的资源,如视频、文档等,便于用户获取更多学习材料。
+
+通过上述设计,可以确保知识模块的功能性和系统性,为医护人员和医学生提供丰富的学习资源和良好的学习体验。
+### 专家咨询
+- 功能描述:患者可以预约专家进行在线咨询,提供视频咨询功能。
+- 分析:在线咨询功能能够极大地方便患者,尤其是在偏远地区。视频咨询的引入提升了沟通的有效性,建议考虑技术支持的稳定性和用户隐私保护。
+为了设计一个专家咨询的Parse Schema,我们需要考虑患者预约专家的需求、视频咨询的实现、以及用户隐私的保护。以下是所需的Parse Schema范式表及具体字段名称的分析。
+
+#### 1.表设计
+
+1.1 **Consultation** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **patient**: 患者(Pointer<User>)
+   - **doctor**: 专家医生(Pointer<User>)
+   - **appointmentTime**: 预约时间(Date)
+   - **status**: 咨询状态(如“待确认”、“已确认”、“已完成”、“已取消”)
+   - **videoLink**: 视频咨询链接(URL)
+   - **notes**: 咨询备注(Text)
+   - **createdBy**: 创建者(Pointer<User>)
+
+1.2 **User** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **username**: 用户名
+   - **password**: 密码(建议加密存储)
+   - **email**: 邮箱
+   - **role**: 用户角色(如“患者”、“医生”)(Pointer<Role>)
+   - **isVerified**: 是否已验证(boolean)
+   - **lastLogin**: 最后登录时间
+
+1.3 **Role** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **roleName**: 角色名称(如“患者”、“专家医生”)
+   - **permissions**: 权限列表(array)
+
+#### 2.PlantUML 类图表示
+
+以下是使用PlantUML表示的类图:
+
+```plantuml
+@startuml
+class Consultation {
+    + objectId: String
+    + createdAt: Date
+    + patient: Pointer<User>
+    + doctor: Pointer<User>
+    + appointmentTime: Date
+    + status: String
+    + videoLink: String
+    + notes: Text
+    + createdBy: Pointer<User>
+}
+
+class User {
+    + objectId: String
+    + createdAt: Date
+    + username: String
+    + password: String
+    + email: String
+    + role: Pointer<Role>
+    + isVerified: Boolean
+    + lastLogin: Date
+}
+
+class Role {
+    + objectId: String
+    + createdAt: Date
+    + roleName: String
+    + permissions: Array
+}
+
+Consultation "1" -- "1" User : patient
+Consultation "1" -- "1" User : doctor
+Consultation "1" -- "1" User : createdBy
+@enduml
+```
+
+#### 3.设计说明
+
+- **Consultation 表**: 存储在线咨询的基本信息,包括患者和医生的指针、预约时间、咨询状态、视频链接以及备注。通过指针关联到 User 表,以便于管理患者和医生的信息。
+- **User 表**: 存储用户的基本信息,包括用户名、密码、邮箱、角色等。角色通过指针关联到 Role 表,以便于管理用户的权限。
+- **Role 表**: 定义不同角色的名称和相应的权限,支持对用户的访问控制。
+
+通过上述设计,可以确保专家咨询模块的功能性、用户隐私保护以及良好的用户体验,为患者和医生提供高效的在线咨询服务。
+### 智能诊疗模块
+- 功能描述:利用人工智能进行初步自我诊断和健康评估。
+- 分析:智能诊疗模块能够帮助患者在就医前进行初步的健康评估,减轻医疗资源的压力。然而,需要确保系统的准确性和安全性,避免误诊的风险。
+为了设计一个智能诊疗模块的Parse Schema,我们需要考虑患者进行初步自我诊断和健康评估的需求,以及系统的准确性和安全性。以下是所需的Parse Schema范式表及具体字段名称的分析。
+
+#### 1.表设计
+
+1.1 **Symptom** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **name**: 症状名称(如“发热”、“咳嗽”)
+   - **description**: 症状描述
+   - **severity**: 严重程度(如“轻微”、“中等”、“严重”)
+
+1.2 **Diagnosis** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **patient**: 患者(Pointer<User>)
+   - **symptoms**: 相关症状(Array<Pointer<Symptom>>)
+   - **suggestedCondition**: 建议的疾病或状况(String)
+   - **recommendations**: 建议的后续步骤(Text)
+   - **confidenceScore**: 诊断的置信度评分(Float)
+   - **createdBy**: 创建者(Pointer<User>)
+
+1.3 **User** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **username**: 用户名
+   - **password**: 密码(建议加密存储)
+   - **email**: 邮箱
+   - **role**: 用户角色(如“患者”、“医生”)(Pointer<Role>)
+   - **isVerified**: 是否已验证(boolean)
+   - **lastLogin**: 最后登录时间
+
+1.4 **Role** 表
+   - **objectId**: 唯一标识符
+   - **createdAt**: 创建时间
+   - **roleName**: 角色名称(如“患者”、“医生”)
+   - **permissions**: 权限列表(array)
+
+#### 2.PlantUML 类图表示
+
+以下是使用PlantUML表示的类图:
+
+```plantuml
+@startuml
+class Symptom {
+    + objectId: String
+    + createdAt: Date
+    + name: String
+    + description: String
+    + severity: String
+}
+
+class Diagnosis {
+    + objectId: String
+    + createdAt: Date
+    + patient: Pointer<User>
+    + symptoms: Array<Pointer<Symptom>>
+    + suggestedCondition: String
+    + recommendations: Text
+    + confidenceScore: Float
+    + createdBy: Pointer<User>
+}
+
+class User {
+    + objectId: String
+    + createdAt: Date
+    + username: String
+    + password: String
+    + email: String
+    + role: Pointer<Role>
+    + isVerified: Boolean
+    + lastLogin: Date
+}
+
+class Role {
+    + objectId: String
+    + createdAt: Date
+    + roleName: String
+    + permissions: Array
+}
+
+Diagnosis "1" -- "1" User : patient
+Diagnosis "*" -- "*" Symptom : includes
+Diagnosis "1" -- "1" User : createdBy
+@enduml
+```
+
+#### 3.设计说明
+
+- **Symptom 表**: 存储各种症状的信息,包括名称、描述和严重程度。此表用于帮助患者选择和描述他们的症状。
+- **Diagnosis 表**: 存储智能诊疗的结果,包括患者信息、相关症状、建议的疾病、后续建议和置信度评分。通过指针关联到 User 表和 Symptom 表,以便于管理患者和症状信息。
+- **User 表**: 存储用户的基本信息,包括用户名、密码、邮箱、角色等。角色通过指针关联到 Role 表,以便于管理用户的权限。
+- **Role 表**: 定义不同角色的名称和相应的权限,支持对用户的访问控制。
+
+通过上述设计,可以确保智能诊疗模块的功能性、准确性和用户隐私保护,为患者提供高效的初步健康评估服务。

+ 4 - 4
mcbridge-app/package-lock.json

@@ -22,7 +22,7 @@
         "@capacitor/keyboard": "6.0.3",
         "@capacitor/status-bar": "6.0.2",
         "@ionic/angular": "^8.0.0",
-        "fmode-ng": "^0.0.62",
+        "fmode-ng": "^0.0.63",
         "ionicons": "^7.2.1",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
@@ -10378,9 +10378,9 @@
       "license": "ISC"
     },
     "node_modules/fmode-ng": {
-      "version": "0.0.62",
-      "resolved": "https://registry.npmmirror.com/fmode-ng/-/fmode-ng-0.0.62.tgz",
-      "integrity": "sha512-F0RzEu47NgKpaHp/vBEzjsU4efJ1lKLAbbdPE5hltj1W1cDaeht/i6UlEidid4FAEdAg7c9rrQrLgOh/zUfCsg==",
+      "version": "0.0.63",
+      "resolved": "https://registry.npmmirror.com/fmode-ng/-/fmode-ng-0.0.63.tgz",
+      "integrity": "sha512-gTiDZO2CchcTYAmlaweapasqV/8PdhG2vizJNn5dYZyXjgtrjyW+KeW5k2EVyIDvM1+bMGjjhGmr76Fc0TElxw==",
       "license": "COPYRIGHT © 未来飞马 未来全栈 www.fmode.cn All RIGHTS RESERVED",
       "dependencies": {
         "tslib": "^2.3.0"

+ 1 - 1
mcbridge-app/package.json

@@ -27,7 +27,7 @@
     "@capacitor/keyboard": "6.0.3",
     "@capacitor/status-bar": "6.0.2",
     "@ionic/angular": "^8.0.0",
-    "fmode-ng": "^0.0.62",
+    "fmode-ng": "^0.0.63",
     "ionicons": "^7.2.1",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",

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

@@ -23,6 +23,10 @@ ion-searchbar {
     margin: 10px 0;  
   }
 
+  ion-button{
+    background-color: #f8f9fa;
+  }
+
   .button-container {
     display: flex;
     justify-content: space-between; /* 在按钮之间留出空间 */

+ 72 - 15
mcbridge-app/src/app/tab3/tab3.page.html

@@ -1,17 +1,74 @@
-<ion-header [translucent]="true">
-  <ion-toolbar>
-    <ion-title>
-      Tab 3
-    </ion-title>
-  </ion-toolbar>
-</ion-header>
+<ion-content>
+  <div class="profile-container">
+    <!-- 个人信息 -->
+    <div class="user-info">
+      <img src="https://cdn.wantuju.com/pngsucai/imgRegular/20210801/ea7d60e8-133f-40b9-9f12-4ad402c97c62%20_63338_wantuju.jpg" alt="用户头像" class="avatar" />
+      <div class="user-details">
+        <h2>昵称</h2>
+        <p>基本信息:年龄、性别等</p>
+      </div>
+      <div class="auth-text">
+        <span class="login">登录</span>/<span class="register">注册</span>
+      </div>
+    </div>
 
-<ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">Tab 3</ion-title>
-    </ion-toolbar>
-  </ion-header>
+    <!-- 医疗服务 -->
+      <ion-card-header>
+        <ion-card-title>医疗服务</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <div class="button-row">
+          <ion-button expand="full" routerLink="/tabs/inquiry">
+            <img src="https://img.tukuppt.com/png_preview/00/06/02/Ci3mfMQItP.jpg!/fw/780" alt="报病查诊" class="button-icon"/>
+          </ion-button>
+          <ion-button expand="full" routerLink="/appointments">
+            <img src="assets/appointments.png" alt="预约记录" class="button-icon" />
+          </ion-button>
+          <ion-button expand="full" routerLink="/discussions">
+            <img src="assets/discussions.png" alt="讨论记录" class="button-icon" />
+          </ion-button>
+          <ion-button expand="full" routerLink="/doctors">
+            <img src="assets/doctors.png" alt="关注的医生" class="button-icon" />
+          </ion-button>
+        </div>
+      </ion-card-content>
 
-  <app-explore-container name="Tab 3 page"></app-explore-container>
-</ion-content>
+    <!-- 医生关注 -->
+      <ion-card-header>
+        <ion-card-title>医生关注</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <ion-list>
+          <ion-item>
+            <ion-label>医生姓名 1</ion-label>
+            <ion-icon slot="end" name="chevron-forward"></ion-icon>
+          </ion-item>
+          <ion-item>
+            <ion-label>医生姓名 2</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-list>
+          <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-list>
+      </ion-card-content>
+  </div>
+</ion-content>

+ 62 - 0
mcbridge-app/src/app/tab3/tab3.page.scss

@@ -0,0 +1,62 @@
+
+
+.profile-container {
+    padding: 16px;
+  }
+  
+  .user-info {
+    display: flex;
+    align-items: center;
+    margin-bottom: 16px;
+  }
+  
+  .avatar {
+    width: 80px;
+    height: 80px;
+    border-radius: 50%;
+    margin-right: 16px;
+  }
+  
+  .auth-text {
+    text-align: center; /* 中间对齐 */
+    margin-left: 16px; /* 与用户信息之间的间距 */
+  }
+
+  .auth-text span{
+    color: blue;
+  }
+
+  ion-card-title {
+    margin: 0px 0;
+    font-size: 20px;
+  }
+  
+  ion-item {
+    --background: transparent; /* 使按钮背景透明 */
+    --box-shadow: none; /* 去掉卡片阴影 */
+  }
+
+  /* 设置顶部工具栏的背景颜色为白色 */
+ion-toolbar {
+    --background: transparent;
+    --color: black; /* 设置文字颜色为黑色 */
+  }
+
+  /* 按钮行样式 */
+.button-row {
+    display: flex;
+    justify-content: space-between; /* 按钮均匀分布 */
+    flex-wrap: wrap; /* 允许换行 */
+  }
+  
+  .button-row ion-button {
+    flex: 1; /* 使按钮均匀分布 */
+    margin: 4px; /* 按钮之间的间距 */
+  }
+
+
+  /* 鼠标按下效果 */
+  .button-row ion-button:active {
+    transform: scale(0.95); /* 按钮缩小 */
+    background-color: gray; /* 按下时背景颜色设为灰色 */
+  }

+ 10 - 2
mcbridge-app/src/app/tab3/tab3.page.ts

@@ -1,13 +1,21 @@
 import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } 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';
+import { addIcons } from 'ionicons';
+import { calendarOutline, chatbubbleEllipsesOutline, chatbubbleOutline, documentTextOutline, personOutline, settingsOutline } from 'ionicons/icons';
+
+addIcons({ settingsOutline, chatbubbleEllipsesOutline, documentTextOutline, calendarOutline, chatbubbleOutline, personOutline}) 
 
 @Component({
   selector: 'app-tab3',
   templateUrl: 'tab3.page.html',
   styleUrls: ['tab3.page.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,
+    IonButtons, IonIcon, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonList, IonItem, IonLabel,IonButton
+  ],
 })
 export class Tab3Page {
   constructor() {}

+ 194 - 0
mcbridge-app/src/lib/ncloud.ts

@@ -0,0 +1,194 @@
+// CloudObject.ts
+export class CloudObject {
+    className: string;
+    id: string | null = null;
+    createdAt:any;
+    updatedAt:any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt", "ACL"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            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;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = null;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    whereOptions: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    greaterThan(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        this.whereOptions[key] = value;
+    }
+
+    async get(id: string) {
+        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        return json || {};
+    }
+
+    async find() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.whereOptions).length) {
+            const whereStr = JSON.stringify(this.whereOptions);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item:any)=>this.dataToObj(item))
+        return objList || [];
+    }
+
+    async first() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.whereOptions).length) {
+            const whereStr = JSON.stringify(this.whereOptions);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists:any):CloudObject{
+        let existsObject = new CloudObject(this.className);
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}

+ 163 - 0
mcbridge-server/lib/ncloud.js

@@ -0,0 +1,163 @@
+class CloudObject{
+    id
+    className
+    data = {}
+    constructor(className){
+        this.className = className
+    }
+    toPointer(){
+        return {"__type":"Pointer","className":this.className,"objectId":this.id}
+    }
+    set(json){
+        Object.keys(json).forEach(key=>{
+            if(["objectId","id","createdAt","updatedAt","ACL"].indexOf(key)>-1){
+                return
+            }
+            this.data[key] = json[key]
+        })
+    }
+    get(key){
+        return this.data[key] || null
+    }
+    async save(){
+        let method = "POST"
+        let url = "http://dev.fmode.cn:1337/parse/classes/" + this.className
+        // 更新
+        if(this.id){
+            url += "/"+this.id
+            method = "PUT"
+        } 
+        let body = JSON.stringify(this.data)
+        let response = await fetch(url, {
+            "headers": {
+              "content-type": "application/json;charset=UTF-8",
+              "x-parse-application-id": "dev"
+            },
+            "body": body,
+            "method": method,
+            "mode": "cors",
+            "credentials": "omit"
+          });
+          let result = await response?.json();
+          if(result?.error){
+            console.error(result?.error)
+          }
+          if(result?.objectId){this.id = result?.objectId}
+          return this
+    }
+    async destory(){
+        if(!this.id) return
+        let response = await fetch("http://dev.fmode.cn:1337/parse/classes/Doctor/"+this.id, {
+            "headers": {
+              "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "DELETE",
+            "mode": "cors",
+            "credentials": "omit"
+          });
+          let result = await response?.json();
+          if(result){
+            this.id = null
+        }
+        return true
+    }
+}
+
+class CloudQuery{
+    className
+    constructor(className){
+        this.className = className
+    }
+
+    whereOptions = {}
+    greaterThan(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gt"] = value
+    }
+    greaterThanAndEqualTo(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gte"] = value
+    }
+    lessThan(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lt"] = value
+    }
+    lessThanAndEqualTo(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lte"] = value
+    }
+    equalTo(key,value){
+        this.whereOptions[key] = value
+    }
+
+    async get(id){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"/"+id+"?"
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        return json || {}
+    }
+    async find(){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"?"
+        
+        if(Object.keys(this.whereOptions)?.length){
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+        }
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        return json?.results || []
+    }
+    async first(){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"?"
+        
+        if(Object.keys(this.whereOptions)?.length){
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+        }
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        let exists = json?.results?.[0] || null
+        if(exists){
+            let existsObject = new CloudObject(this.className)
+            existsObject.set(exists)
+            existsObject.id = exists.objectId
+            existsObject.createdAt = exists.createdAt
+            existsObject.updatedAt = exists.updatedAt
+            return existsObject
+        }
+    }
+}
+
+module.exports.CloudObject = CloudObject
+module.exports.CloudQuery = CloudQuery

+ 0 - 0
mcbridge-server/migration/data.js


+ 58 - 0
mcbridge-server/migration/import-data.js

@@ -0,0 +1,58 @@
+const { CloudQuery, CloudObject } = require("../lib/ncloud");
+const { DepartList, DoctorList } = require("./data");
+inportDapartAndDoctor()
+
+DataMap = {
+    Doctor:{},
+    Department:{}
+}
+
+async function inportDapartAndDoctor(){
+    // 导入科室数据
+    let departList = DepartList
+    for (let index = 0; index < departList.length; index++) {
+        let depart = departList[index];
+        depart = await importObject("Department",depart)
+    }
+    // 导入医生数据
+    let doctorList = DoctorList
+    for (let index = 0; index < doctorList.length; index++) {
+        let doctor = doctorList[index];
+        doctor = await importObject("Doctor",doctor)
+    }
+    // console.log(DataMap["Doctor"])
+}
+
+async function importObject(className,data){
+
+    // 查重 srcId 数据源列表中的objectId并非数据库生成的唯一ID,因此需要有一个srcId字段进行记录,并查重
+    let query = new CloudQuery(className)
+    let srcId = data.objectId
+    query.equalTo("srcId",srcId)
+    let importObj = await query.first()
+    console.log(importObj)
+
+    // 导入
+    // 导入前批量处理Pointer类型数据,进行重定向
+    Object.keys(data)?.forEach(key=>{
+        let field = data[key]
+        let srcId = field?.objectId
+        if(srcId){ // 是数组字段
+            if(key=="depart"){
+                data[key] = DataMap?.["Department"]?.[srcId]?.toPointer();
+            }
+        }
+    })
+
+    // 若未添加,则创建新对象并保存
+    if(!importObj?.id){
+        importObj = new CloudObject(className)
+    }
+
+    // 保存或更新数据
+    data.srcId = srcId;
+    importObj.set(data);
+    importObj = await importObj.save();
+
+    DataMap[className][srcId] = importObj
+}