Browse Source

feat: save Book & BookChapter

ryanemax 3 months ago
parent
commit
7cec138fcc

+ 2 - 0
novel-app/src/app/ai-continue-modal/ai-continue-modal.component.ts

@@ -4,6 +4,7 @@ import { ModalController } from '@ionic/angular';
 import { IonButton, IonIcon, IonInput, IonItem, IonLabel, IonToolbar, IonHeader, IonContent, IonTitle, IonButtons, IonTextarea } from '@ionic/angular/standalone';
 import { FormsModule } from '@angular/forms';
 import { FmodeChatCompletion, MarkdownPreviewModule } from 'fmode-ng';
+import { CloudObject } from '../lib/ncloud';
 
 @Component({
   selector: 'app-ai-continue-modal',
@@ -55,6 +56,7 @@ export class AiContinueModalComponent {
       this.continuedText = message.content; // 将生成的内容赋值给 continuedText
       if (message?.complete) { // 判断message为完成状态,则设置isComplete为完成
         this.isComplete = true;
+        
       }
     });
   }

+ 1 - 1
novel-app/src/app/chapter-generator/chapter-generator.page.html

@@ -29,7 +29,7 @@
       style="display: flex; width: 200px; height: 90%; flex-direction: column; z-index: 1000; overflow-y: auto; max-height: 100%;">
       <ion-list>
         <ion-item *ngFor="let chapter of chapters; let i = index;" (click)="selectChapter(i)">
-          <ion-label>{{ chapter.title }}</ion-label>
+          <ion-label>{{ chapter?.get("title") }}</ion-label>
           <ion-buttons slot="end">
             <ion-button (click)="deleteChapter(i)">
               <ion-icon name="trash"></ion-icon>

+ 61 - 16
novel-app/src/app/chapter-generator/chapter-generator.page.ts

@@ -12,6 +12,7 @@ import { AiPolishModalComponent } from '../ai-polish-modal/ai-polish-modal.compo
 import { AiContinueModalComponent } from '../ai-continue-modal/ai-continue-modal.component';
 import { OnInit } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
+import { CloudObject, CloudQuery } from '../lib/ncloud';
 addIcons({ chevronForward });
 
 @Component({
@@ -30,26 +31,59 @@ addIcons({ chevronForward });
     ]
 })
 export class ChapterGeneratorPage implements OnInit {
-  chapters = [
-    { title: 'Chapter 1', content: '' },
-   
-  ];
+  chapters:Array<CloudObject> = [];
   isSideShow: boolean = true;
-  selectedChapterIndex: number | null = null;
+  selectedChapterIndex: number | 0 = 0;
   selectedChapterTitle: string = '';
   selectedChapterContent: string = '';
 
   title: string = '';
   description: string = '';
 
-  constructor(private modalCtrl: ModalController, private route: ActivatedRoute) {  addIcons({ add });}
+  constructor(private modalCtrl: ModalController, private route: ActivatedRoute) {  
+    addIcons({ add });
+  }
 
+  story:CloudObject|undefined
+  async loadStory(){
+    let query = new CloudQuery("Book");
+    query.equalTo("title",this.title);
+    this.story = await query.first()
+    if(!this.story?.id){
+      this.story = new CloudObject("Book");
+      this.story.set({
+        title:this.title,
+        desc:this.description
+      })
+    }
+    console.log(this.story)
+    if(this.story?.id){
+      let queryc = new CloudQuery("BookChapter")
+      queryc.equalTo("story",this.story?.id)
+      let list = await queryc.find();
+      console.log(list)
+      if(list?.length){
+        this.chapters = list;
+      }
+      if(!this.chapters?.length){
+        let citem = new CloudObject("BookChapter")
+        citem.set({
+          title:"新章节",
+          index:1,
+          content:""
+        })
+        this.chapters = [citem];
+      }
+    }
+    console.log(this.chapters)
+  }
   ngOnInit() {
     this.route.queryParams.subscribe(params => {
       this.title = params['title'];
       this.description = params['description'];
       console.log('Received Title:', this.title);
       console.log('Received Description:', this.description);
+      this.loadStory();
     });
   }
   async openAiExpandModal() {
@@ -87,14 +121,15 @@ export class ChapterGeneratorPage implements OnInit {
   }
 
   addChapter() {
-    const newChapter = { title: `Chapter ${this.chapters.length + 1}`, content: '' };
+    let newChapter = new CloudObject("BookChapter");
+    newChapter.set({ title: `新章节 ${this.chapters.length + 1}`, content: '' })
     this.chapters.push(newChapter);
     this.selectChapter(this.chapters.length - 1); // 自动编辑新添加的章节
   }
 
   deleteChapter(index: number) {
     if (index === this.selectedChapterIndex) {
-      this.selectedChapterIndex = null;
+      this.selectedChapterIndex = 0;
       this.selectedChapterTitle = '';
       this.selectedChapterContent = '';
     }
@@ -108,20 +143,30 @@ export class ChapterGeneratorPage implements OnInit {
 
   editChapter(index: number) {
     this.selectedChapterIndex = index;
-    this.selectedChapterTitle = this.chapters[index].title;
-    this.selectedChapterContent = this.chapters[index].content; // 初始化内容为当前章节的内容
+    this.selectedChapterTitle = this.chapters[index]?.get("title");
+    this.selectedChapterContent = this.chapters[index]?.get("content"); // 初始化内容为当前章节的内容
   }
 
-  saveChapter() {
-   
-
-
-    
+  async saveChapter() {
+    await this.story?.save();
+    let query = new CloudQuery("BookChapter");
+    query.equalTo("story",this.story?.id);
+    query.equalTo("index",this.selectedChapterIndex+1)
+    let storyChapter = new CloudObject("BookChapter");
+    storyChapter.set({
+      index:this.selectedChapterIndex+1,
+      title:this.selectedChapterTitle,
+      content:this.selectedChapterContent,
+      story:this.story?.toPointer()
+    })
+    storyChapter.save();
   }
 
   insertTextIntoChapterEnd(text: string) {
     if (this.selectedChapterIndex !== null) {
-      this.chapters[this.selectedChapterIndex].content += text;
+      this.chapters[this.selectedChapterIndex].set({
+        "content":this.chapters[this.selectedChapterIndex].get("content") + text
+      });
       this.selectedChapterContent += text; // 更新显示的内容
     }
   }

+ 6 - 6
novel-app/src/app/home/home.page.html

@@ -77,14 +77,14 @@
       </ion-card-header>
 
       <ion-card-content>
-        <ion-grid *ngIf="stories.length > 0">
+        <ion-grid *ngIf="storyList.length > 0">
           <ion-row>
-            <ion-col size="6" *ngFor="let story of stories">
-              <div class="story-card">
-                <ion-img [src]="story.cover" alt="story cover"></ion-img>
+            <ion-col size="6" *ngFor="let story of storyList">
+              <div class="story-card" (click)="goStory(story)">
+                <ion-img [src]="story.get('cover')" alt=""></ion-img>
                 <div class="story-info">
-                  <h3>{{story.title}}</h3>
-                  <p>{{formatDate(story.createTime)}}</p>
+                  <h3>{{story.get("title")}} {{story.id}}</h3>
+                  <p>{{formatDate(story.createdAt)}}</p>
                 </div>
               </div>
             </ion-col>

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

@@ -3,6 +3,7 @@ import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { IonicModule, PopoverController } from '@ionic/angular';
 import { Router } from '@angular/router';
+import { CloudObject, CloudQuery } from '../lib/ncloud';
 
 
 interface Story {
@@ -21,6 +22,16 @@ interface Story {
 })
 export class HomePage implements AfterViewInit {
   stories: Story[] = [];
+  storyList:Array<CloudObject> = []
+  async loadStoryList(){
+    let query = new CloudQuery("Book")
+    this.storyList = await query.find();
+  }
+  goStory(story:CloudObject){
+    this.router.navigate(['/chapter-generator'], {
+      queryParams: { title: story.get("title"), description: story.get("desc") }
+    });
+  }
   slideWidth: number = 0; // 设置默认值为0
   autoPlayInterval: NodeJS.Timeout | undefined;
   currentIndex: number = 0;
@@ -32,6 +43,7 @@ export class HomePage implements AfterViewInit {
   @ViewChild('bannerContainer', { read: ElementRef }) bannerContainer!: ElementRef;
 
   ngAfterViewInit() {
+    this.loadStoryList();
     this.startAutoPlay();
     this.checkSlideWidth();
   }

+ 307 - 235
novel-app/src/app/lib/ncloud.ts

@@ -1,226 +1,218 @@
 // 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;
-  }
+    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 = `https://dev.fmode.cn/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(`https://dev.fmode.cn/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();
-      // return json || {};
-      const exists = json?.results?.[0] || null;
-      if (exists) {
-          let existsObject = this.dataToObj(exists)
-          return existsObject;
-      }
-      return null
-
-  }
-
-  async find() {
-      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]); // 作用是将对象转换为JSON字符串
-          if(key=="include"){
-              paramStr = this.queryParams[key]?.join(",")
-          }
-          if(key=="where"){
-              paramStr = JSON.stringify(this.queryParams[key]);
-
-          }
-          if(queryStr) {
-              url += `${key}=${paramStr}`;
-          }else{
-              url += `&${key}=${paramStr}`;
-          }
-      })
-
-      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
-      let list = json?.results || []
-      let objList = list.map((item:any)=>this.dataToObj(item))
-      return objList || [];
-  }
-
-  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;
-  }
+    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 = `https://dev.fmode.cn/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 = `https://dev.fmode.cn/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 = `https://dev.fmode.cn/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] || undefined;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return undefined
+    }
+
+    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
 // CloudUser.ts
 export class CloudUser extends CloudObject {
     constructor() {
@@ -243,26 +235,26 @@ export class CloudUser extends CloudObject {
             console.error("用户未登录");
             return null;
         }
-        
-        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;
+        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;
     }
 
     /** 登录 */
     async login(username: string, password: string):Promise<CloudUser|null> {
-        const response = await fetch(`http://dev.fmode.cn:1337/parse/login`, {
+        const response = await fetch(`https://dev.fmode.cn/parse/login`, {
             headers: {
                 "x-parse-application-id": "dev",
                 "Content-Type": "application/json"
@@ -294,7 +286,7 @@ export class CloudUser extends CloudObject {
             return;
         }
 
-        const response = await fetch(`http://dev.fmode.cn:1337/parse/logout`, {
+        const response = await fetch(`https://dev.fmode.cn/parse/logout`, {
             headers: {
                 "x-parse-application-id": "dev",
                 "x-parse-session-token": this.sessionToken
@@ -302,18 +294,26 @@ export class CloudUser extends CloudObject {
             method: "POST"
         });
 
-        const result = await response?.json();
+        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 = {};
-        return true;
     }
 
     /** 注册 */
@@ -324,7 +324,7 @@ export class CloudUser extends CloudObject {
             ...additionalData // 合并额外的用户数据
         };
 
-        const response = await fetch(`http://dev.fmode.cn:1337/parse/users`, {
+        const response = await fetch(`https://dev.fmode.cn/parse/users`, {
             headers: {
                 "x-parse-application-id": "dev",
                 "Content-Type": "application/json"
@@ -340,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
+    }
 }