Browse Source

Merge branch 'master' of http://git.fmode.cn:3000/0224918/app-snbw

lfgldr 1 tuần trước cách đây
mục cha
commit
9aba031189

+ 3 - 0
.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+     "plantuml.server":"http://www.plantuml.com/plantuml"
+}

+ 71 - 0
docs/info-map.md

@@ -0,0 +1,71 @@
+# 智慧农田数据模型
+
+## _User (Parse内置类)
+- **字段**
+  - username: String
+  - password: String
+  - email: String
+  - mobilePhone: String
+  - ...其他内置字段
+
+## Farmland
+- **基础字段**
+  - objectId
+  - createdAt
+  - updatedAt
+- **业务字段**
+  - farmlandName: String
+  - area: Number
+  - cropType: String
+  - geoLocation: GeoPoint
+    - latitude
+    - longitude
+  - owner: Pointer<_User>
+  - status: String
+    - 播种期
+    - 生长期
+    - 成熟期
+  - irrigationSystem: String
+- **关系**
+  - 1 → n FarmingReminder
+  - 1 → n GrowthRecord
+
+## FarmingReminder
+- **基础字段**
+  - objectId
+  - createdAt
+  - updatedAt
+- **业务字段**
+  - reminderType: String
+    - 灌溉
+    - 施肥
+    - 除虫
+  - content: String
+  - dueDate: Date
+  - isCompleted: bool
+  - priority: Number
+- **关系**
+  - farmland: Pointer<Farmland>
+
+## GrowthRecord
+- **基础字段**
+  - objectId
+  - createdAt
+  - updatedAt
+- **业务字段**
+  - recordType: String
+    - 文字记录
+    - 图片记录
+  - description: String
+  - recordDate: Date
+  - images: Array<Parse.File>
+  - growthStage: String
+  - temperature: Number
+  - humidity: Number
+- **关系**
+  - farmland: Pointer<Farmland>
+
+## 关系图谱
+- _User 1 → n Farmland (owns)
+- Farmland 1 → n FarmingReminder (has)
+- Farmland 1 → n GrowthRecord (contains)

+ 206 - 0
docs/schema.md

@@ -0,0 +1,206 @@
+# AI智慧农业项目
+
+# 数据范式设计
+> 设定要求
+您是一名专业的数据库工程师,熟悉PostgreSQL和ParseServer。
+请注意表名用大驼峰,字段小驼峰。
+有预留字段:objectId、updatedAt、createdAt。
+关于ParseServer中数据类的描述,字段的主要类型有:
+String => String
+Number => Number
+Bool => bool
+Array => JSON Array
+Object => JSON Object
+Date => Date
+File => Parse.File
+Pointer => other Parse.Object
+Relation => Parse.Relation
+Null => null
+GeoPoint => {latitude: 40.0, longitude: -30.0}
+
+> 项目需求
+智慧农田的辅助AI应用,农民(_User)、农田、农事提醒、生长记录,请您根据农业种植的行业经验,设计以上四张表,农民直接用预留的_User表即可。
+
+> 输出结果(UML类图)
+请您帮我用plantuml的类图描述设计好的几张表及其关系
+
+> 输出结果(信息结构图)
+请您帮我用markmap格式表示上面的信息结构图
+
+> 输出结果(SQL语句)
+请您帮我用sql格式给我建表语句和测试数据插入语句,注意字段请使用小驼峰用""引起来。
+
+# UML类图
+```plantuml
+@startuml
+class _User {
+  <<Parse内置类>>
+  + username: String
+  + password: String
+  + email: String
+  + mobilePhone: String
+  + ...其他内置字段
+}
+
+class Farmland {
+  + objectId: String
+  + createdAt: Date
+  + updatedAt: Date
+  + farmlandName: String
+  + area: Number
+  + cropType: String
+  + geoLocation: {latitude: Number, longitude: Number}
+  + owner: Pointer<_User>
+  + status: String  // 如"播种期","生长期","成熟期"
+  + irrigationSystem: String
+}
+
+class FarmingReminder {
+  + objectId: String
+  + createdAt: Date
+  + updatedAt: Date
+  + reminderType: String  // 如"灌溉","施肥","除虫"
+  + content: String
+  + dueDate: Date
+  + isCompleted: bool
+  + farmland: Pointer<Farmland>
+  + priority: Number
+}
+
+class GrowthRecord {
+  + objectId: String
+  + createdAt: Date
+  + updatedAt: Date
+  + recordType: String  // 如"文字记录","图片记录"
+  + description: String
+  + recordDate: Date
+  + images: Array<Parse.File>
+  + farmland: Pointer<Farmland>
+  + growthStage: String
+  + temperature: Number
+  + humidity: Number
+}
+
+_User "1" -- "n" Farmland : owns
+Farmland "1" -- "n" FarmingReminder : has
+Farmland "1" -- "n" GrowthRecord : contains
+@enduml
+```
+
+# SQL语句
+
+以下是使用小驼峰命名并用双引号包裹字段名的PostgreSQL建表语句和测试数据插入语句:
+
+### 1. 建表语句
+
+```sql
+-- 用户表(使用Parse内置的_User表,此处仅创建扩展表)
+CREATE TABLE "User" (
+    "objectId" VARCHAR(36) PRIMARY KEY,
+    "username" VARCHAR(50) NOT NULL UNIQUE,
+    "email" VARCHAR(100),
+    "mobilePhone" VARCHAR(20),
+    "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 农田表
+CREATE TABLE "Farmland" (
+    "objectId" VARCHAR(36) PRIMARY KEY,
+    "farmlandName" VARCHAR(100) NOT NULL,
+    "area" NUMERIC(10, 2),
+    "cropType" VARCHAR(50),
+    "latitude" NUMERIC(9, 6),
+    "longitude" NUMERIC(9, 6),
+    "ownerId" VARCHAR(36) REFERENCES "User"("objectId"),
+    "status" VARCHAR(20) CHECK ("status" IN ('播种期', '生长期', '成熟期')),
+    "irrigationSystem" VARCHAR(50),
+    "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 农事提醒表
+CREATE TABLE "FarmingReminder" (
+    "objectId" VARCHAR(36) PRIMARY KEY,
+    "reminderType" VARCHAR(20) CHECK ("reminderType" IN ('灌溉', '施肥', '除虫')),
+    "content" TEXT,
+    "dueDate" TIMESTAMP NOT NULL,
+    "isCompleted" BOOLEAN DEFAULT false,
+    "farmlandId" VARCHAR(36) REFERENCES "Farmland"("objectId"),
+    "priority" INTEGER DEFAULT 1,
+    "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 生长记录表
+CREATE TABLE "GrowthRecord" (
+    "objectId" VARCHAR(36) PRIMARY KEY,
+    "recordType" VARCHAR(20) CHECK ("recordType" IN ('文字记录', '图片记录')),
+    "description" TEXT,
+    "recordDate" TIMESTAMP NOT NULL,
+    "growthStage" VARCHAR(50),
+    "temperature" NUMERIC(5, 2),
+    "humidity" NUMERIC(5, 2),
+    "farmlandId" VARCHAR(36) REFERENCES "Farmland"("objectId"),
+    "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 图片存储关联表
+CREATE TABLE "GrowthRecordImages" (
+    "recordId" VARCHAR(36) REFERENCES "GrowthRecord"("objectId"),
+    "fileUrl" VARCHAR(255) NOT NULL,
+    "uploadTime" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY ("recordId", "fileUrl")
+);
+```
+
+### 2. 测试数据插入语句
+
+```sql
+-- 插入测试用户
+INSERT INTO "User" ("objectId", "username", "email", "mobilePhone") VALUES
+('usr001', 'farmer_wang', 'wang@example.com', '13800138001'),
+('usr002', 'farmer_li', 'li@example.com', '13900139001');
+
+-- 插入测试农田
+INSERT INTO "Farmland" ("objectId", "farmlandName", "area", "cropType", "latitude", "longitude", "ownerId", "status", "irrigationSystem") VALUES
+('fld001', '王家的麦田', 5.2, '小麦', 39.9042, 116.4074, 'usr001', '生长期', '滴灌系统'),
+('fld002', '李家的玉米地', 8.5, '玉米', 34.3416, 108.9398, 'usr002', '播种期', '喷灌系统');
+
+-- 插入农事提醒
+INSERT INTO "FarmingReminder" ("objectId", "reminderType", "content", "dueDate", "isCompleted", "farmlandId", "priority") VALUES
+('rem001', '灌溉', '麦田需要浇水', CURRENT_TIMESTAMP + INTERVAL '2 days', false, 'fld001', 1),
+('rem002', '施肥', '玉米地需要追肥', CURRENT_TIMESTAMP + INTERVAL '5 days', false, 'fld002', 2);
+
+-- 插入生长记录
+INSERT INTO "GrowthRecord" ("objectId", "recordType", "description", "recordDate", "growthStage", "temperature", "humidity", "farmlandId") VALUES
+('rec001', '文字记录', '小麦抽穗期开始', CURRENT_TIMESTAMP - INTERVAL '3 days', '抽穗期', 22.5, 65.0, 'fld001'),
+('rec002', '图片记录', '玉米幼苗生长情况', CURRENT_TIMESTAMP - INTERVAL '1 day', '幼苗期', 25.0, 70.0, 'fld002');
+
+-- 插入生长记录图片
+INSERT INTO "GrowthRecordImages" ("recordId", "fileUrl") VALUES
+('rec002', 'https://example.com/images/corn_seedling_1.jpg'),
+('rec002', 'https://example.com/images/corn_seedling_2.jpg');
+```
+
+### 修改说明:
+
+1. **命名规范**:
+   - 所有字段名使用小驼峰命名并用双引号包裹
+   - 表名使用大驼峰命名并用双引号包裹
+   - 保留Parse Server原始字段名(如objectId/createdAt/updatedAt)
+
+2. **修正问题**:
+   - 修正了农事提醒表中的字段名拼写错误(reminderType)
+   - 确保所有引用字段名都使用小驼峰格式
+
+3. **兼容性**:
+   - 完全兼容PostgreSQL的标识符引用规则
+   - 保留所有原始设计的约束和关系
+
+4. **测试数据**:
+   - 保持与之前相同的测试场景
+   - 所有字段引用都使用新的命名格式
+
+这种格式可以直接在PostgreSQL中执行,且与Parse Server的命名规范完全一致。如需在其它数据库中使用,可能需要根据数据库特性调整引号的使用方式。

+ 7 - 7
myapp/src/app/tab3/tab3.page.html

@@ -1,6 +1,6 @@
 <ion-header>
   <ion-toolbar color="primary">
-    <ion-title>我的农场</ion-title>
+    <ion-title (click)="initializeSampleData()">我的农场</ion-title>
     <ion-buttons slot="end">
       <ion-button (click)="toggleFieldForm()">
         <ion-icon [name]="showFieldForm ? 'close' : 'add'" slot="icon-only"></ion-icon>
@@ -21,17 +21,17 @@
           <ion-label position="floating">农田名称</ion-label>
           <ion-input formControlName="name" type="text"></ion-input>
         </ion-item>
-        
+
         <ion-item>
           <ion-label position="floating">面积(亩)</ion-label>
           <ion-input formControlName="area" type="number"></ion-input>
         </ion-item>
-        
+
         <ion-item>
           <ion-label position="floating">位置</ion-label>
           <ion-input formControlName="location" type="text"></ion-input>
         </ion-item>
-        
+
         <ion-item>
           <ion-label>当前作物</ion-label>
           <ion-select formControlName="currentCrop">
@@ -41,7 +41,7 @@
             <ion-select-option value="大豆">大豆</ion-select-option>
           </ion-select>
         </ion-item>
-        
+
         <ion-item>
           <ion-label>土壤类型</ion-label>
           <ion-select formControlName="soilType">
@@ -51,7 +51,7 @@
             <ion-select-option value="粘土">粘土</ion-select-option>
           </ion-select>
         </ion-item>
-        
+
         <div class="form-buttons">
           <ion-button type="button" fill="outline" (click)="cancelEdit()">取消</ion-button>
           <ion-button type="submit" [disabled]="!fieldForm.valid">保存</ion-button>
@@ -166,4 +166,4 @@
       </div>
     </ion-card-content>
   </ion-card>
-</ion-content>
+</ion-content>

+ 134 - 121
myapp/src/app/tab3/tab3.page.ts

@@ -1,6 +1,7 @@
 import { Component, OnInit } from '@angular/core';
 import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { AlertController } from '@ionic/angular';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
 
 interface FarmField {
   id: string;
@@ -43,81 +44,16 @@ interface GrowthRecord {
   standalone: false,
 })
 export class Tab3Page implements OnInit {
-  fields: FarmField[] = [
-    {
-      id: '1',
-      name: '北区小麦田',
-      area: 5.2,
-      location: '农场北侧',
-      currentCrop: '冬小麦',
-      daysPlanted: 45,
-      status: '需施肥',
-      nextTask: '明日追肥',
-      soilType: '粘壤土'
-    },
-    {
-      id: '2',
-      name: '南区玉米地',
-      area: 3.8,
-      location: '农场南侧',
-      currentCrop: '春玉米',
-      daysPlanted: 22,
-      status: '正常',
-      nextTask: '下周除草',
-      soilType: '砂壤土'
-    }
-  ];
-
-  tasks: FarmTask[] = [
-    {
-      id: '1',
-      fieldId: '1',
-      fieldName: '北区小麦田',
-      name: '追施氮肥',
-      type: 'fertilization',
-      dueDate: new Date(new Date().setDate(new Date().getDate() + 1)),
-      urgent: true,
-      completed: false
-    },
-    {
-      id: '2',
-      fieldId: '2',
-      fieldName: '南区玉米地',
-      name: '除草作业',
-      type: 'pestControl',
-      dueDate: new Date(new Date().setDate(new Date().getDate() + 7)),
-      urgent: false,
-      completed: false
-    }
-  ];
-
-  records: GrowthRecord[] = [
-    {
-      id: '1',
-      fieldId: '1',
-      fieldName: '北区小麦田',
-      date: new Date(new Date().setDate(new Date().getDate() - 3)),
-      activity: '病虫害检查',
-      notes: '发现少量蚜虫,已喷施防治',
-      images: [
-        'assets/images/wheat-pest1.jpg',
-        'assets/images/wheat-pest2.jpg'
-      ]
-    },
-    {
-      id: '2',
-      fieldId: '2',
-      fieldName: '南区玉米地',
-      date: new Date(new Date().setDate(new Date().getDate() - 5)),
-      activity: '追肥',
-      notes: '施用复合肥20kg/亩'
-    }
-  ];
+  fields: FarmField[] = []; // 初始化为空数组,从后端加载数据
+  tasks: FarmTask[] = [];
+  records: GrowthRecord[] = [];
 
   showFieldForm = false;
   editingField: FarmField | null = null;
   fieldForm: FormGroup;
 
+  private cloudQuery = new CloudQuery('FarmField');
+
   constructor(
     private fb: FormBuilder,
     private alertCtrl: AlertController
@@ -131,7 +67,133 @@ export class Tab3Page implements OnInit {
     });
   }
 
-  ngOnInit() {}
+  async ngOnInit() {
+    await this.loadFields();
+    await this.initializeSampleData(); // 可选:初始化示例数据
+  }
+
+  // 从后端加载农田数据
+  async loadFields() {
+    try {
+      const cloudFields = await this.cloudQuery.find();
+      this.fields = cloudFields.map(field => ({
+        id: field.id || '',
+        name: field.get('name'),
+        area: field.get('area'),
+        location: field.get('location'),
+        currentCrop: field.get('currentCrop'),
+        daysPlanted: field.get('daysPlanted') || 0,
+        status: field.get('status') || '正常',
+        soilType: field.get('soilType')
+      }));
+    } catch (error) {
+      console.error('加载农田数据失败:', error);
+    }
+  }
+
+  // 保存农田到后端
+  async saveField() {
+    if (this.fieldForm.invalid) return;
+
+    const fieldData = this.fieldForm.value;
+    const fieldObject = new CloudObject('FarmField');
+
+    if (this.editingField && this.editingField.id) {
+      // 更新现有农田
+      fieldObject.id = this.editingField.id;
+      fieldObject.set(fieldData);
+      await fieldObject.save();
+    } else {
+      // 添加新农田
+      fieldObject.set({
+        ...fieldData,
+        daysPlanted: 0,
+        status: '正常'
+      });
+      const savedField = await fieldObject.save();
+
+      this.fields.push({
+        id: savedField.id || '',
+        daysPlanted: 0,
+        status: '正常',
+        ...fieldData
+      });
+    }
+
+    await this.loadFields(); // 重新加载数据确保同步
+    this.cancelEdit();
+  }
+
+  // 删除农田
+  async deleteField(fieldId: string) {
+    const alert = await this.alertCtrl.create({
+      header: '确认删除',
+      message: '确定要删除这个农田地块吗?相关记录也将被删除',
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel'
+        },
+        {
+          text: '删除',
+          handler: async () => {
+            const fieldObject = new CloudObject('FarmField');
+            fieldObject.id = fieldId;
+            await fieldObject.destroy();
+
+            this.fields = this.fields.filter(f => f.id !== fieldId);
+            this.tasks = this.tasks.filter(t => t.fieldId !== fieldId);
+            this.records = this.records.filter(r => r.fieldId !== fieldId);
+          }
+        }
+      ]
+    });
+
+    await alert.present();
+  }
+
+  // 初始化示例数据(可选)
+  async initializeSampleData() {
+    const query = new CloudQuery('FarmField');
+    const existingFields = await query.find();
+
+    if (existingFields.length === 0) {
+      const sampleFields = [
+        {
+          name: '北区小麦田',
+          area: 5.2,
+          location: '农场北侧',
+          currentCrop: '冬小麦',
+          daysPlanted: 45,
+          status: '需施肥',
+          nextTask: '明日追肥',
+          soilType: '粘壤土'
+        },
+        {
+          name: '南区玉米地',
+          area: 3.8,
+          location: '农场南侧',
+          currentCrop: '春玉米',
+          daysPlanted: 22,
+          status: '正常',
+          nextTask: '下周除草',
+          soilType: '砂壤土'
+        }
+      ];
+
+      for (const field of sampleFields) {
+        const fieldObject = new CloudObject('FarmField');
+        fieldObject.set(field);
+        await fieldObject.save();
+      }
+
+      // 重新加载数据
+      await this.loadFields();
+    }
+  }
+
+
+  // 原有代码不变
 
   get totalArea(): number {
     return this.fields.reduce((sum, field) => sum + field.area, 0);
@@ -217,32 +279,6 @@ export class Tab3Page implements OnInit {
     this.showFieldForm = true;
   }
 
-  saveField() {
-    if (this.fieldForm.invalid) return;
-
-    const fieldData = this.fieldForm.value;
-    
-    if (this.editingField) {
-      // 更新现有农田
-      const index = this.fields.findIndex(f => f.id === this.editingField?.id);
-      if (index >= 0) {
-        this.fields[index] = {
-          ...this.fields[index],
-          ...fieldData
-        };
-      }
-    } else {
-      // 添加新农田
-      this.fields.push({
-        id: Date.now().toString(),
-        daysPlanted: 0,
-        status: '正常',
-        ...fieldData
-      });
-    }
-
-    this.cancelEdit();
-  }
 
   cancelEdit() {
     this.showFieldForm = false;
@@ -253,31 +289,8 @@ export class Tab3Page implements OnInit {
     });
   }
 
-  async deleteField(fieldId: string) {
-    const alert = await this.alertCtrl.create({
-      header: '确认删除',
-      message: '确定要删除这个农田地块吗?相关记录也将被删除',
-      buttons: [
-        {
-          text: '取消',
-          role: 'cancel'
-        },
-        {
-          text: '删除',
-          handler: () => {
-            this.fields = this.fields.filter(f => f.id !== fieldId);
-            this.tasks = this.tasks.filter(t => t.fieldId !== fieldId);
-            this.records = this.records.filter(r => r.fieldId !== fieldId);
-          }
-        }
-      ]
-    });
-
-    await alert.present();
-  }
-
   viewTaskDetail(task: FarmTask) {
     console.log('查看任务详情:', task);
     // 实际应导航到任务详情页
   }
-}
+}

+ 420 - 0
myapp/src/lib/ncloud.ts

@@ -0,0 +1,420 @@
+// 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"].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;
+    queryParams: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    include(...fileds:string[]) {
+        this.queryParams["include"] = fileds;
+    }
+    greaterThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        if (!this.queryParams["where"]) this.queryParams["where"] = {};
+        this.queryParams["where"][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();
+        if (json) {
+            let existsObject = this.dataToObj(json)
+            return existsObject;
+        }
+        return null
+    }
+
+    async find():Promise<Array<CloudObject>> {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        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: {
+                "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.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            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;
+    }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if(userCacheStr){
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken:string|null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        return this;
+        // const response = await fetch(`http://dev.fmode.cn:1337/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;
+    }
+
+    /** 登录 */
+    async login(username: string, password: string):Promise<CloudUser|null> {
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/login`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify({ username, password }),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User",JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/logout`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken
+            },
+            method: "POST"
+        });
+
+        let result = await response?.json();
+
+        if (result?.error) {
+            console.error(result?.error);
+            if(result?.error=="Invalid session token"){
+                this.clearUserCache()
+                return true;
+            }
+            return false;
+        }
+
+        this.clearUserCache()
+        return true;
+    }
+    clearUserCache(){
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = null;
+        this.sessionToken = null;
+        this.data = {};
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/users`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(userData),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        // 缓存用户信息
+        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 = `http://dev.fmode.cn:1337/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 = `http://dev.fmode.cn:1337`
+        // 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
+    }
+}