Przeglądaj źródła

feat: device manage with list

未来全栈 2 dni temu
rodzic
commit
b49ccbc6ef

+ 11 - 11
industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.html

@@ -1,26 +1,26 @@
+<!-- comp-device-item.html 更新 -->
 @if(type=="list"){
     <tr>
         <td>{{device.did}}</td>
         <td>{{device.name}}</td>
         <td>{{device.type}}</td>
         <td>{{device.location}}</td>
-        <td><span class="status-badge running">{{device.status}}</span></td>
-        <td><div class="health-meter"><div class="health-fill" style="width: {{device.health}}%;"></div><span>{{device.health}}%</span></div></td>
+        <td><span class="status-badge {{getStatusClass(device.status)}}">{{device.status}}</span></td>
+        <td><div class="health-meter"><div class="health-fill" [style.width]="device.health + '%'"></div><span>{{device.health}}%</span></div></td>
         <td>{{device.lastMaintenance | date:'yyyy-MM-dd'}}</td>
         <td>
-            <button class="icon-btn"><i class="fa fa-eye" routerLink="/device"></i></button>
-            <button class="icon-btn"><i class="fa fa-edit"></i></button>
-            <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
+            <button class="icon-btn"><i class="fa fa-eye" routerLink="/device/{{device.id}}"></i></button>
+            <button class="icon-btn" (click)="edit.emit(device)"><i class="fa fa-edit"></i></button>
+            <button class="icon-btn danger" (click)="delete.emit(device)"><i class="fa fa-trash"></i></button>
         </td>
     </tr>
 }
 
 @if(type=="card"){
-
   <div class="device-card">
     <div class="card-header">
       <h2>{{device.did}}</h2>
-      <span class="status-badge running">{{device.status}}</span>
+      <span class="status-badge {{getStatusClass(device.status)}}">{{device.status}}</span>
     </div>
     <div class="card-body">
       <div class="card-row">
@@ -45,9 +45,9 @@
       </div>
     </div>
     <div class="card-actions">
-      <button class="icon-btn"><i class="fa fa-eye" routerLink="/device"></i></button>
-      <button class="icon-btn"><i class="fa fa-edit"></i></button>
-      <button class="icon-btn danger"><i class="fa fa-trash"></i></button>
+      <button class="icon-btn"><i class="fa fa-eye" routerLink="/device/{{device.id}}"></i></button>
+      <button class="icon-btn" (click)="edit.emit(device)"><i class="fa fa-edit"></i></button>
+      <button class="icon-btn danger" (click)="delete.emit(device)"><i class="fa fa-trash"></i></button>
     </div>
   </div>
-}
+}

+ 6 - 5
industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.scss

@@ -1,9 +1,10 @@
 
-.device-container {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
-  gap: 20px;
-  padding: 20px;
+:host{
+  display:contents;
+  // tr{
+  //   display:table-row;
+
+  // }
 }
 
 .device-card {

+ 19 - 7
industry-monitor-web/src/app/pages/device-management/comp-device-item/comp-device-item.ts

@@ -1,16 +1,28 @@
+// comp-device-item.ts 更新
 import { CommonModule, DatePipe } from '@angular/common';
-import { Component, Input } from '@angular/core';
+import { Component, Input, Output, EventEmitter } from '@angular/core';
 import { RouterModule } from '@angular/router';
 
 @Component({
+  standalone: true,
   selector: 'app-comp-device-item',
-  imports: [CommonModule,RouterModule],
+  imports: [CommonModule, RouterModule, DatePipe],
   templateUrl: './comp-device-item.html',
   styleUrl: './comp-device-item.scss'
 })
 export class CompDeviceItem {
-  @Input()
-  device:any
-  @Input()
-  type:string = "list" // list card
-}
+  @Input() device: any;
+  @Input() type: string = "list";
+  @Output() edit = new EventEmitter<any>();
+  @Output() delete = new EventEmitter<any>();
+
+  getStatusClass(status: string) {
+    switch (status) {
+      case '运行中': return 'running';
+      case '待机': return 'idle';
+      case '维护中': return 'maintenance';
+      case '已停止': return 'stopped';
+      default: return '';
+    }
+  }
+}

+ 95 - 24
industry-monitor-web/src/app/pages/device-management/device-management.component.html

@@ -1,10 +1,9 @@
-
 <!-- device-management.component.html -->
 <div class="management-container">
   <div class="management-header">
     <h2><i class="fa fa-toolbox"></i> 设备管理</h2>
     <div class="controls">
-      <button class="btn primary"><i class="fa fa-plus"></i> 添加设备</button>
+      <button class="btn primary" (click)="showAddModal()"><i class="fa fa-plus"></i> 添加设备</button>
     </div>
   </div>
 
@@ -13,7 +12,7 @@
     <div class="filter-controls">
       <div class="filter-group">
         <label>设备类型:</label>
-        <select>
+        <select [(ngModel)]="filter.type">
           <option>全部</option>
           <option>CNC加工中心</option>
           <option>注塑机</option>
@@ -23,7 +22,7 @@
       </div>
       <div class="filter-group">
         <label>设备状态:</label>
-        <select>
+        <select [(ngModel)]="filter.status">
           <option>全部</option>
           <option>运行中</option>
           <option>待机</option>
@@ -33,37 +32,109 @@
       </div>
       <div class="filter-group">
         <label>所在位置:</label>
-        <select>
+        <select [(ngModel)]="filter.location">
           <option>全部车间</option>
           <option>车间A</option>
           <option>车间B</option>
           <option>车间C</option>
         </select>
       </div>
-      <button class="btn"><i class="fa fa-search"></i> 搜索</button>
+      <button class="btn" (click)="applyFilter()"><i class="fa fa-search"></i> 搜索</button>
+      <button class="btn" (click)="resetFilter()"><i class="fa fa-refresh"></i> 重置</button>
     </div>
   </div>
 
   <div class="device-table-card">
-    <h3><i class="fa fa-list"></i> 设备列表 <button (click)="listType='list'">列表</button> <button (click)="listType='card'">卡片</button></h3>
-    <table class="device-table">
-      <thead>
-        <tr>
-          <th>设备ID</th>
-          <th>设备名称</th>
-          <th>类型</th>
-          <th>位置</th>
-          <th>状态</th>
-          <th>健康指数</th>
-          <th>最后维护</th>
-          <th>操作</th>
-        </tr>
-      </thead>
-      <tbody>
-        @for(device of deviceList;track device){
+    <h3><i class="fa fa-list"></i> 设备列表 
+      <button (click)="listType='list'" [class.active]="listType === 'list'">列表</button> 
+      <button (click)="listType='card'" [class.active]="listType === 'card'">卡片</button>
+    </h3>
+    
+    @if (listType === 'list') {
+      <table class="device-table">
+        <thead>
+          <tr>
+            <th>设备ID</th>
+            <th>设备名称</th>
+            <th>类型</th>
+            <th>位置</th>
+            <th>状态</th>
+            <th>健康指数</th>
+            <th>最后维护</th>
+            <th>操作</th>
+          </tr>
+        </thead>
+        <tbody>
+          @for (device of deviceList; track device.id) {
+            <app-comp-device-item [type]="listType" [device]="device"></app-comp-device-item>
+          }
+        </tbody>
+      </table>
+    }
+    
+    @if (listType === 'card') {
+      <div class="device-grid">
+        @for (device of deviceList; track device.id) {
           <app-comp-device-item [type]="listType" [device]="device"></app-comp-device-item>
         }
-      </tbody>
-    </table>
+      </div>
+    }
   </div>
+
+  <!-- 添加/编辑设备模态框 -->
+  @if (isAddModalVisible) {
+    <div class="modal-overlay">
+      <div class="modal">
+        <div class="modal-header">
+          <h3>{{ editingDevice ? '编辑设备' : '添加新设备' }}</h3>
+          <button class="close-btn" (click)="isAddModalVisible = false">&times;</button>
+        </div>
+        <div class="modal-body">
+          <div class="form-group">
+            <label>设备名称</label>
+            <input type="text" [(ngModel)]="newDevice.name" placeholder="请输入设备名称">
+          </div>
+          <div class="form-group">
+            <label>设备类型</label>
+            <select [(ngModel)]="newDevice.type">
+              <option>CNC加工中心</option>
+              <option>注塑机</option>
+              <option>装配机器人</option>
+              <option>检测设备</option>
+            </select>
+          </div>
+          <div class="form-group">
+            <label>所在位置</label>
+            <select [(ngModel)]="newDevice.location">
+              <option>车间A/产线1</option>
+              <option>车间A/产线2</option>
+              <option>车间A/产线3</option>
+              <option>车间B/产线1</option>
+              <option>车间B/产线2</option>
+              <option>车间C/产线1</option>
+              <option>车间C/产线2</option>
+              <option>车间C/产线3</option>
+            </select>
+          </div>
+          <div class="form-group">
+            <label>设备状态</label>
+            <select [(ngModel)]="newDevice.status">
+              <option>运行中</option>
+              <option>待机</option>
+              <option>维护中</option>
+              <option>已停止</option>
+            </select>
+          </div>
+          <div class="form-group">
+            <label>健康指数</label>
+            <input type="number" [(ngModel)]="newDevice.health" min="0" max="100" placeholder="0-100">
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button class="btn" (click)="isAddModalVisible = false">取消</button>
+          <button class="btn primary" (click)="saveDevice()">保存</button>
+        </div>
+      </div>
+    </div>
+  }
 </div>

+ 136 - 55
industry-monitor-web/src/app/pages/device-management/device-management.component.ts

@@ -1,7 +1,10 @@
-import { Component, ViewEncapsulation } from '@angular/core';
+// device-management.component.ts
+import { Component, ViewEncapsulation, OnInit } from '@angular/core';
 import { DatePipe } from '@angular/common';
 import { RouterModule } from '@angular/router';
 import { CompDeviceItem } from './comp-device-item/comp-device-item';
+import { FormsModule } from '@angular/forms';
+import { CloudObject, CloudQuery } from '../../../lib/ncloud';
 
 @Component({
   standalone: true,
@@ -10,66 +13,144 @@ import { CompDeviceItem } from './comp-device-item/comp-device-item';
   imports: [
     DatePipe,
     RouterModule,
-    CompDeviceItem
+    CompDeviceItem,
+    FormsModule
   ],
   templateUrl: './device-management.component.html',
   encapsulation: ViewEncapsulation.None
 })
-export class DeviceManagementComponent {
-  listType:string = "list"
-  deviceList:Array<any> = [
-  {
-    "did": "DEV-2023-001",
-    "name": "CNC 铣床 #01",
-    "type": "CNC加工中心",
-    "location": "车间A/产线3",
-    "status": "运行中",
-    "health": 89,
-    "lastMaintenance": new Date()
-    },
-  {
-    "did": "DEV-2023-002",
-    "name": "注塑机 #05",
-    "type": "注塑机",
-    "location": "车间B/产线1",
-    "status": "待机",
-    "health": 76,
-    "lastMaintenance": new Date()
-    },
-  {
-    "did": "DEV-2023-003",
-    "name": "装配机器人 #03",
-    "type": "装配机器人",
-    "location": "车间C/产线2",
-    "status": "维护中",
-    "health": 65,
-    "lastMaintenance": new Date()
-    },
-  {
-    "did": "DEV-2023-004",
-    "name": "质检仪 #02",
-    "type": "检测设备",
-    "location": "车间A/产线1",
-    "status": "运行中",
-    "health": 92,
-    "lastMaintenance": new Date()
-    },
-  {
-    "did": "DEV-2023-005",
-    "name": "包装机 #07",
-    "type": "包装设备",
-    "location": "车间C/产线3",
-    "status": "已停止",
-    "health": 58,
-    "lastMaintenance": new Date()
-    }
-]
-currentTime: Date = new Date();
-   ngOnInit(): void {
+export class DeviceManagementComponent implements OnInit {
+  listType: string = "list";
+  deviceList: Array<any> = [];
+  currentTime: Date = new Date();
+  
+  // 筛选条件
+  filter = {
+    type: '全部',
+    status: '全部',
+    location: '全部车间'
+  };
+  
+  // 添加/编辑设备
+  editingDevice: any = null;
+  isAddModalVisible = false;
+  newDevice = {
+    name: '',
+    type: 'CNC加工中心',
+    location: '车间A/产线1',
+    status: '运行中',
+    health: 90
+  };
+
+  constructor() {}
+
+  async ngOnInit() {
     // 更新时间,每秒更新一次
     setInterval(() => {
       this.currentTime = new Date();
     }, 1000);
 
-   }
-}
+    await this.loadDevices();
+  }
+
+  async loadDevices() {
+    const query = new CloudQuery("Device");
+    
+    // 添加筛选条件
+    if (this.filter.type !== '全部') {
+      query.equalTo("type", this.filter.type);
+    }
+    if (this.filter.status !== '全部') {
+      query.equalTo("status", this.filter.status);
+    }
+    if (this.filter.location !== '全部车间') {
+      query.equalTo("location", this.filter.location);
+    }
+    
+    const devices = await query.find();
+    this.deviceList = devices.map((device:any) => ({
+      id: device.id,
+      did: device.get("did"),
+      name: device.get("name"),
+      type: device.get("type"),
+      location: device.get("location"),
+      status: device.get("status"),
+      health: device.get("health"),
+      lastMaintenance: new Date(device.get("lastMaintenance")?.iso || new Date())
+    }));
+  }
+
+  async applyFilter() {
+    await this.loadDevices();
+  }
+
+  async resetFilter() {
+    this.filter = {
+      type: '全部',
+      status: '全部',
+      location: '全部车间'
+    };
+    await this.loadDevices();
+  }
+
+  showAddModal() {
+    this.isAddModalVisible = true;
+    this.editingDevice = null;
+    this.newDevice = {
+      name: '',
+      type: 'CNC加工中心',
+      location: '车间A/产线1',
+      status: '运行中',
+      health: 90
+    };
+  }
+
+  showEditModal(device: any) {
+    this.isAddModalVisible = true;
+    this.editingDevice = device;
+    this.newDevice = {
+      name: device.name,
+      type: device.type,
+      location: device.location,
+      status: device.status,
+      health: device.health
+    };
+  }
+
+  async saveDevice() {
+    const deviceObj = this.editingDevice 
+      ? new CloudObject("Device") 
+      : new CloudObject("Device");
+    
+    if (this.editingDevice) {
+      deviceObj.id = this.editingDevice.id;
+    } else {
+      // 为新设备生成ID
+      const now = new Date();
+      const did = `DEV-${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}-${Math.floor(1000 + Math.random() * 9000)}`;
+      deviceObj.set({ did });
+    }
+    
+    deviceObj.set({
+      name: this.newDevice.name,
+      type: this.newDevice.type,
+      location: this.newDevice.location,
+      status: this.newDevice.status,
+      health: this.newDevice.health,
+      lastMaintenance: new Date().toISOString()
+    });
+    
+    await deviceObj.save();
+    this.isAddModalVisible = false;
+    await this.loadDevices();
+  }
+
+  async deleteDevice(device: any) {
+    if (confirm(`确定要删除设备 ${device.name} 吗?`)) {
+      const deviceObj = new CloudObject("Device");
+      deviceObj.id = device.id;
+      await deviceObj.destroy();
+      await this.loadDevices();
+    }
+  }
+}

+ 148 - 1
industry-monitor-web/src/app/pages/device-management/device-management.css

@@ -179,4 +179,151 @@
 .icon-btn.danger:hover {
   background: #e74c3c;
   color: white;
-}
+}
+
+
+.device-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 20px;
+}
+
+button.active {
+  background-color: #3498db;
+  color: white;
+}
+
+/* 设备状态标签样式 */
+.status-badge {
+  display: inline-block;
+  padding: 4px 10px;
+  border-radius: 4px;
+  font-size: 13px;
+  font-weight: 500;
+}
+
+.status-badge.running {
+  background: #e8f6f0;
+  color: #27ae60;
+  border: 1px solid #27ae60;
+}
+
+.status-badge.idle {
+  background: #fef5e7;
+  color: #f39c12;
+  border: 1px solid #f39c12;
+}
+
+.status-badge.maintenance {
+  background: #ebf5fb;
+  color: #3498db;
+  border: 1px solid #3498db;
+}
+
+.status-badge.stopped {
+  background: #f5f7fa;
+  color: #7f8c8d;
+  border: 1px solid #bdc3c7;
+}
+/* device-management.component.css 新增部分 */
+:host {
+  display: block;
+  width: 100%;
+  padding: 15px;
+}
+
+.management-container {
+  max-width: 1400px;
+  margin: 0 auto;
+  padding: 20px;
+  background: white;
+  border-radius: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+  position: relative;
+}
+
+.device-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 20px;
+  margin-top: 20px;
+}
+
+button.active {
+  background-color: #3498db;
+  color: white;
+}
+
+/* 模态框样式 */
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.modal {
+  background: white;
+  border-radius: 8px;
+  width: 500px;
+  max-width: 90%;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.modal-header {
+  padding: 15px 20px;
+  border-bottom: 1px solid #eee;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.modal-header h3 {
+  margin: 0;
+}
+
+.close-btn {
+  background: none;
+  border: none;
+  font-size: 24px;
+  cursor: pointer;
+  color: #7f8c8d;
+}
+
+.modal-body {
+  padding: 20px;
+}
+
+.form-group {
+  margin-bottom: 15px;
+}
+
+.form-group label {
+  display: block;
+  margin-bottom: 5px;
+  font-weight: 500;
+}
+
+.form-group input,
+.form-group select {
+  width: 100%;
+  padding: 8px 12px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+}
+
+.modal-footer {
+  padding: 15px 20px;
+  border-top: 1px solid #eee;
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+/* 其他原有样式保持不变... */

+ 63 - 0
industry-monitor-web/src/app/pages/device-management/device.model.ts

@@ -0,0 +1,63 @@
+import { CloudObject } from "../../../lib/ncloud";
+
+export class Device extends CloudObject {
+  constructor() {
+    super('Device');
+  }
+
+  get did(): string {
+    return this.get('did');
+  }
+
+  set did(value: string) {
+    this.set({ did: value }); // 修改为使用对象方式
+  }
+
+  get name(): string {
+    return this.get('name');
+  }
+
+  set name(value: string) {
+    this.set({ name: value }); // 修改为使用对象方式
+  }
+
+  get type(): string {
+    return this.get('type');
+  }
+
+  set type(value: string) {
+    this.set({ type: value }); // 修改为使用对象方式
+  }
+
+  get location(): string {
+    return this.get('location');
+  }
+
+  set location(value: string) {
+    this.set({ location: value }); // 修改为使用对象方式
+  }
+
+  get status(): string {
+    return this.get('status');
+  }
+
+  set status(value: string) {
+    this.set({ status: value }); // 修改为使用对象方式
+  }
+
+  get health(): number {
+    return this.get('health');
+  }
+
+  set health(value: number) {
+    this.set({ health: value }); // 修改为使用对象方式
+  }
+
+  get lastMaintenance(): Date {
+    return new Date(this.get('lastMaintenance'));
+  }
+
+  set lastMaintenance(value: Date) {
+    this.set({ lastMaintenance: value.toISOString() }); // 修改为使用对象方式
+  }
+}

+ 431 - 0
industry-monitor-web/src/lib/ncloud.ts

@@ -0,0 +1,431 @@
+// CloudObject.ts
+
+let serverURL = `https://dev.fmode.cn/parse`;
+if (location.protocol == "http:") {
+    serverURL = `http://dev.fmode.cn:1337/parse`;
+}
+
+export class CloudObject {
+    className: string;
+    id: string | undefined = undefined;
+    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 = serverURL + `/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(serverURL + `/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 = undefined;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    queryParams: Record<string, any> = { where: {} };
+
+    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 = serverURL + `/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 = serverURL + `/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 = serverURL + `/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}&limit=1`;
+        }
+
+        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);
+        Object.keys(exists).forEach(key => {
+            if (exists[key]?.__type == "Object") {
+                exists[key] = this.dataToObj(exists[key])
+            }
+        })
+        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(serverURL + `/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(serverURL + `/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(serverURL + `/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 = undefined;
+        this.sessionToken = null;
+        this.data = {};
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(serverURL + `/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 = serverURL + `/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
+    }
+}