课堂监控项目策划书 1 سال پیش
والد
کامیت
25322df8fa
6فایلهای تغییر یافته به همراه1358 افزوده شده و 6 حذف شده
  1. 6 6
      src/app/tab1/tab1.page.ts
  2. 145 0
      src/app/test6/tab3.page.html
  3. 398 0
      src/app/test6/tab3.page.scss
  4. 18 0
      src/app/test6/tab3.page.spec.ts
  5. 185 0
      src/app/test6/tab3.page.ts
  6. 606 0
      src/lib/community_ncloud.ts

+ 6 - 6
src/app/tab1/tab1.page.ts

@@ -22,12 +22,12 @@ export class Tab1Page {
   * 轮播图
   */
   images = [
-    'https://picsum.photos/800/400?random=19',
-    'https://picsum.photos/800/400?random=18',
-    'https://picsum.photos/800/400?random=17',
-    'https://picsum.photos/800/400?random=15',
-    'https://picsum.photos/800/400?random=13',
-    'https://picsum.photos/800/400?random=14',
+    'https://img1.baidu.com/it/u=6935186,2834387782&fm=253&fmt=auto&app=138&f=JPEG?w=760&h=380',
+    'https://img2.baidu.com/it/u=2478719336,2042051460&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=399',
+    'https://img2.baidu.com/it/u=1682351053,4026021439&fm=253&fmt=auto&app=138&f=JPEG?w=759&h=388',
+    'https://img0.baidu.com/it/u=1243244788,1615322067&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=400',
+    'https://img2.baidu.com/it/u=2769090116,1910828136&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=400',
+    'https://img0.baidu.com/it/u=3479829541,3394557712&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=400',
   ];
   currentSlide = 0;
   intervalId: any;

+ 145 - 0
src/app/test6/tab3.page.html

@@ -0,0 +1,145 @@
+<ion-header [translucent]="true" class="ion-no-border">
+    <ion-toolbar>
+      <div class="header-container" [class.search-active]="isSearchActive">
+        <!-- 菜单按钮,只有在搜索未激活时显示 -->
+        <ion-menu-button *ngIf="!isSearchActive" class="menu-icon"></ion-menu-button>
+  
+        <!-- 导航项,只有在搜索未激活时显示 -->
+        <div *ngIf="!isSearchActive" class="nav-links">
+          <span class="nav-item" (click)="red_underline('关注')" data-id="关注">关注</span>
+          <span class="nav-item" (click)="red_underline('发现')" data-id="发现">发现</span>
+          <span class="nav-item" (click)="red_underline('附近')" data-id="附近">附近</span>
+        </div>
+  
+        <!-- 搜索图标,点击时切换搜索框 -->
+        <div *ngIf="!isSearchActive">
+          <ion-icon name="search-outline" class="search-icon" (click)="onSearchIconClick()"></ion-icon>
+        </div>
+  
+        <!-- 搜索框,只有在搜索激活时显示 -->
+        <!-- 搜索框 -->
+        <div class="search-bar-container" *ngIf="isSearchActive">
+          <ion-searchbar [(ngModel)]="searchQuery" (ionInput)="onSearchInput()" placeholder="搜索你感兴趣的内容..."
+            class="custom-searchbar" showClearButton="always" inputmode="search" enterkeyhint="search">
+          </ion-searchbar>
+          <button class="close-btn" (click)="onCloseSearch()">
+            取消
+          </button>
+        </div>
+      </div>
+    </ion-toolbar>
+  </ion-header>
+  
+  
+  <ion-content [fullscreen]="true">
+    <!-- 分类导航 -->
+    <div class="category-nav">
+      <ion-toolbar>
+        <ion-tabs>
+          <ion-tab-bar slot="top">
+            <ion-tab-button tab="home">
+              <ion-icon name="home-outline" slot="start"></ion-icon>
+              <ion-label>推荐</ion-label>
+            </ion-tab-button>
+            <ion-tab-button tab="psychology">
+              <ion-icon name="film-outline" slot="start"></ion-icon>
+              <ion-label>视频</ion-label>
+            </ion-tab-button>
+            <ion-tab-button tab="course">
+              <ion-icon name="brush-outline" slot="start"></ion-icon>
+              <ion-label>绘画</ion-label>
+            </ion-tab-button>
+            <ion-tab-button tab="test">
+              <ion-icon name="shirt-outline" slot="start"></ion-icon>
+              <ion-label>穿搭</ion-label>
+            </ion-tab-button>
+            <ion-tab-button tab="ebook">
+              <ion-icon name="dance-outline" slot="start"></ion-icon>
+              <ion-label>舞蹈</ion-label>
+            </ion-tab-button>
+          </ion-tab-bar>
+        </ion-tabs>
+      </ion-toolbar>
+    </div>
+    <!-- 内容网格 -->
+    <div class="content-grid">
+      <div class="content-card" (click)="goToDetail(artwork.WorkId)" style="cursor: pointer;"
+        *ngFor="let artwork of displayedArtworks;trackBy: trackByFn">
+        <img [src]="artwork.fileUrl" [alt]="artwork.title" class="card-image">
+        <div class="card-content">
+          <div class="title">{{artwork.title}}</div>
+          <div class="description">{{artwork.description}}</div>
+          <div class="user-info">
+            <div class="user">
+              <img [src]="artwork.avatarUrl" [alt]="artwork.userName" class="avatar">
+              <div class="info">
+                <span class="name" style="font-size: 12px;">{{artwork.userName}}</span>
+                <span class="date">{{artwork.updatedAt| date:'MM-dd'}}</span>
+              </div>
+            </div>
+            <div class="likes">
+              <ion-icon name="heart-outline"></ion-icon>
+              <span>{{artwork.likesCount}}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  
+    <!-- 添加搜索结果为空时的提示 -->
+    @if(isSearchActive && searchQuery && displayedArtworks.length === 0) {
+    <div class="no-results">
+      <ion-icon name="search-outline"></ion-icon>
+      <h3>未找到相关内容</h3>
+      <p>换个关键词试试看</p>
+    </div>
+    }
+  
+  </ion-content>
+  
+  <!-- 添加侧边菜��� -->
+  <ion-menu contentId="main-content" side="start">
+    <ion-content class="menu-content">
+      <div class="menu-header">
+        <h2>个人中心</h2>
+      </div>
+  
+      <ion-list lines="none">
+        <ion-item button detail>
+          <ion-icon name="create-outline" slot="start"></ion-icon>
+          <ion-label>创作中心</ion-label>
+        </ion-item>
+  
+        <ion-item button detail>
+          <ion-icon name="document-outline" slot="start"></ion-icon>
+          <ion-label>我的草稿</ion-label>
+        </ion-item>
+  
+        <ion-item button detail>
+          <ion-icon name="chatbubble-outline" slot="start"></ion-icon>
+          <ion-label>我的评论</ion-label>
+        </ion-item>
+  
+        <ion-item button detail>
+          <ion-icon name="heart-outline" slot="start"></ion-icon>
+          <ion-label>我的收藏</ion-label>
+        </ion-item>
+  
+        <ion-item button detail>
+          <ion-icon name="time-outline" slot="start"></ion-icon>
+          <ion-label>浏览记录</ion-label>
+        </ion-item>
+  
+        <ion-item button detail (click)="goToUploadFile()">
+          <ion-icon name="add-outline" slot="start"></ion-icon>
+          <ion-label>上传文件</ion-label>
+        </ion-item>
+  
+      </ion-list>
+    </ion-content>
+  </ion-menu>
+  
+  <!-- 主内容区域添加 id -->
+  <div class="ion-page" id="main-content">
+    <!-- 其他内容持不变 -->
+  </div>

+ 398 - 0
src/app/test6/tab3.page.scss

@@ -0,0 +1,398 @@
+:host {
+    --page-padding: 16px;
+    --primary-color: #b64d24;
+    --card-border-radius: 12px;
+    --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+#main-content {
+    pointer-events: none !important;
+}
+
+// 头部导航样式
+.header-container {
+    padding: 8px var(--page-padding);
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    position: relative;
+    width: 100%;
+    height: 50px;
+    transition: all 0.3s ease;
+    background: white;
+
+    .menu-icon {
+        font-size: 24px;
+        color: #333;
+        padding: 8px;
+        border-radius: 50%;
+        cursor: pointer;
+        transition: background-color 0.3s;
+
+        &:hover {
+            background-color: rgba(0, 0, 0, 0.05);
+        }
+    }
+
+    .nav-links {
+        display: flex;
+        gap: 32px;
+
+        .nav-item {
+            font-size: 15px;
+            color: #666;
+            padding: 6px 2px;
+            cursor: pointer;
+            position: relative;
+            transition: color 0.3s;
+
+            &[underline="true"] {
+                color: var(--primary-color);
+                font-weight: 500;
+
+                &::after {
+                    content: '';
+                    position: absolute;
+                    bottom: 0;
+                    left: 0;
+                    width: 100%;
+                    height: 3px;
+                    background: var(--primary-color);
+                    border-radius: 2px;
+                }
+            }
+        }
+    }
+
+    .search-icon {
+        font-size: 22px;
+        color: #666;
+        padding: 8px;
+        //height: 50px;
+        border-radius: 50%;
+        cursor: pointer;
+        transition: all 0.3s;
+        margin-top: 5px;
+
+        &:hover {
+            color: var(--primary-color);
+            background-color: rgba(182, 77, 36, 0.1);
+        }
+    }
+
+    /* 搜索框容器样式 */
+    .search-bar-container {
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 0;
+        height: 56px;
+        display: flex;
+        align-items: center;
+        padding: 0 16px;
+        animation: slideIn 0.3s ease-out;
+        background: white;
+        z-index: 100;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+
+        .custom-searchbar {
+            margin-top: 5px;
+        }
+
+        .close-btn {
+            background: transparent;
+            border: none;
+            color: var(--primary-color);
+            font-size: 16px;
+            font-weight: 500;
+            padding: 8px 16px;
+            margin-left: 12px;
+            height: 42px;
+            border-radius: 12px;
+            transition: all 0.2s ease;
+            min-width: 64px;
+            margin-bottom: 5px;
+
+            &:active {
+                background: rgba(182, 77, 36, 0.1);
+                transform: scale(0.98);
+            }
+        }
+    }
+}
+
+// 添加搜索框动画
+@keyframes slideIn {
+    from {
+        opacity: 0;
+        transform: translateY(-8px);
+    }
+
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+// 添加搜索结果为空时的提示样式
+.no-results {
+    text-align: center;
+    padding: 48px 24px;
+    color: #666;
+
+    ion-icon {
+        font-size: 56px;
+        color: #ddd;
+        margin-bottom: 16px;
+        opacity: 0.8;
+    }
+
+    h3 {
+        font-size: 18px;
+        margin: 12px 0;
+        color: #333;
+        font-weight: 500;
+    }
+
+    p {
+        font-size: 15px;
+        color: #999;
+        margin: 0;
+        line-height: 1.5;
+    }
+}
+
+// 分类导航样式
+.category-nav {
+    background: white;
+    border-bottom: 1px solid #f5f5f5;
+    margin-bottom: 8px;
+
+    ion-tab-bar {
+        --background: transparent;
+        --border-color: transparent;
+        padding: 0 var(--page-padding);
+
+        ion-tab-button {
+            --color: #666;
+            --color-selected: var(--primary-color);
+            --padding-top: 8px;
+            --padding-bottom: 8px;
+
+            ion-label {
+                font-size: 14px;
+                font-weight: 500;
+            }
+
+            &.tab-selected::after {
+                content: '';
+                position: absolute;
+                bottom: 0;
+                left: 50%;
+                transform: translateX(-50%);
+                width: 20px;
+                height: 3px;
+                background: var(--primary-color);
+                border-radius: 1.5px;
+            }
+        }
+    }
+}
+
+// 内容网格样式
+.content-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+    padding: 12px;
+
+    .content-card {
+        position: relative;
+        cursor: pointer;
+        transition: transform 0.2s ease;
+
+        &:hover {
+            transform: translateY(-2px);
+        }
+
+        // 确保所有子��素都以正确传递点击事件
+        * {
+            pointer-events: none;
+        }
+
+        background: white;
+        border-radius: var(--card-border-radius);
+        box-shadow: var(--card-shadow);
+        overflow: hidden;
+
+        .card-image {
+            width: 100%;
+            height: 200px;
+            object-fit: cover;
+        }
+
+        .card-content {
+            padding: 12px;
+
+            .title {
+                font-size: 14px;
+                color: #333;
+                margin-bottom: 5px;
+                line-height: 1.4;
+                display: -webkit-box;
+                -webkit-line-clamp: 2;
+                line-clamp: 2;
+                -webkit-box-orient: vertical;
+                overflow: hidden;
+            }
+
+            .description {
+                font-size: 12px;
+                color: #666;
+                margin-bottom: 5px;
+                line-height: 1.4;
+                display: -webkit-box;
+                -webkit-line-clamp: 2;
+                line-clamp: 2;
+                -webkit-box-orient: vertical;
+                overflow: hidden;
+            }
+
+            .user-info {
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+
+                .user {
+                    display: flex;
+                    align-items: center;
+                    gap: 8px;
+
+                    .avatar {
+                        width: 28px;
+                        height: 28px;
+                        border-radius: 50%;
+                        object-fit: cover;
+                    }
+
+                    .info {
+                        display: flex;
+                        flex-direction: column;
+
+                        .name {
+                            font-size: 13px;
+                            color: #333;
+                            font-weight: 500;
+                        }
+
+                        .date {
+                            font-size: 12px;
+                            color: #999;
+                        }
+                    }
+                }
+
+                .likes {
+                    display: flex;
+                    align-items: center;
+                    gap: 4px;
+                    color: #666;
+                    font-size: 13px;
+
+                    ion-icon {
+                        font-size: 16px;
+                        color: #999;
+                        transition: all 0.3s;
+                        cursor: pointer;
+
+                        &:hover {
+                            color: #ff4b4b;
+                            transform: scale(1.1);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+// 侧边菜单样式
+ion-menu {
+    --width: 280px;
+    --background: white;
+
+    .menu-content {
+        --background: white;
+
+        .menu-header {
+            padding: 24px 20px;
+            background: linear-gradient(135deg, var(--primary-color), #ff6b4a);
+
+            h2 {
+                color: white;
+                margin: 0;
+                font-size: 20px;
+                font-weight: 600;
+            }
+        }
+
+        ion-list {
+            padding: 12px 0;
+
+            ion-item {
+                --padding-start: 20px;
+                --padding-end: 16px;
+                --min-height: 50px;
+                --background: transparent;
+                --background-hover: rgba(182, 77, 36, 0.05);
+                --ripple-color: rgba(182, 77, 36, 0.1);
+
+                ion-icon {
+                    color: #666;
+                    font-size: 20px;
+                    margin-right: 12px;
+                }
+
+                ion-label {
+                    font-size: 15px;
+                    font-weight: 500;
+                    color: #333;
+                }
+
+                &:hover {
+                    ion-icon {
+                        color: var(--primary-color);
+                    }
+
+                    ion-label {
+                        color: var(--primary-color);
+                    }
+                }
+            }
+        }
+    }
+}
+
+// 修改菜单按钮样式
+.menu-icon {
+    font-size: 24px;
+    padding: 8px;
+    border-radius: 50%;
+    cursor: pointer;
+    color: #333;
+    transition: background-color 0.3s;
+
+    &:hover {
+        background-color: rgba(0, 0, 0, 0.05);
+    }
+}
+
+// 添加搜索时的遮罩效果
+.search-active {
+
+    .content-grid,
+    .category-nav {
+        opacity: 0.5;
+        transition: opacity 0.3s ease;
+    }
+}

+ 18 - 0
src/app/test6/tab3.page.spec.ts

@@ -0,0 +1,18 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Tab3Page } from './tab3.page';
+
+describe('Tab3Page', () => {
+  let component: Tab3Page;
+  let fixture: ComponentFixture<Tab3Page>;
+
+  beforeEach(async () => {
+    fixture = TestBed.createComponent(Tab3Page);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 185 - 0
src/app/test6/tab3.page.ts

@@ -0,0 +1,185 @@
+import { Component, OnInit } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonSearchbar, IonIcon, IonTabButton, IonTabs, IonTabBar, IonLabel, IonMenu, IonList, IonItem, IonMenuButton } from '@ionic/angular/standalone';
+import { Router } from '@angular/router';
+import { CommonModule } from '@angular/common';
+import { CloudQuery, CloudObject, Pointer } from '../../lib/community_ncloud'; // 确保路径正确
+import { FormsModule } from '@angular/forms';  // 导入 FormsModule
+
+interface Work {
+  WorkId: string; // 修改为 WorkId
+  title: string;
+  description: string;
+  fileUrl: string;
+  avatarUrl: string;
+  userName: string;
+  updatedAt: string;
+  likesCount: number;
+  category: string;
+  comments: string[];
+}
+
+@Component({
+  selector: 'app-tab3',
+  templateUrl: 'tab3.page.html',
+  styleUrls: ['tab3.page.scss'],
+  standalone: true,
+  imports: [
+    CommonModule,
+    IonHeader, IonToolbar, IonTitle, IonContent,
+    IonSearchbar, IonIcon, IonTabButton, IonTabs, IonTabBar, IonLabel,
+    IonMenu, IonList, IonItem, IonMenuButton, FormsModule,  // 添加 FormsModule
+  ],
+})
+
+export class Tab3Page implements OnInit {
+  artworks: Work[] = [];  // 存储所有的帖子信息
+  displayedArtworks: Work[] = []; // 存储当前显示的帖子
+  isSearchActive = false; // 控制搜索框是否显示
+  searchQuery = ''; // 搜索框的输入内容
+  constructor(private router: Router) { }
+  ngOnInit() {
+    this.loadWorks();  // 加载帖子数据
+    this.red_underline('发现');
+  }
+
+  // 加载所有帖子数据
+  async loadWorks() {
+    try {
+      const workQuery = new CloudQuery('Work');
+      const workObjs = await workQuery.find();
+
+      // 初始化 artworks 和 displayedArtworks
+      this.artworks = workObjs.map((workObj: CloudObject) => {
+        const workData = workObj.data as Work;
+
+        return {
+          ...workData,
+          objectId: String(workObj.id),
+          imageUrl: `${workData.fileUrl}`, // 图片路径
+        };
+      });
+
+      // 初始显示所有帖子
+      this.displayedArtworks = [...this.artworks];
+    } catch (error) {
+      console.error('加载帖子数据失败:', error);
+    }
+  }
+  red_underline(text: string) {
+    const prevUnderlined = document.querySelectorAll('span[underline="true"]') as NodeListOf<HTMLElement>;
+    prevUnderlined.forEach((el: HTMLElement) => {
+      el.style.textDecoration = 'none';
+      el.removeAttribute('underline');
+    });
+    const target = document.querySelector(`span[data-id="${text}"]`) as HTMLElement;
+    if (target) {
+      target.setAttribute('underline', 'true');
+    }
+  }
+
+  // 点击搜索图像时触发
+  onSearchIconClick() {
+    this.isSearchActive = true;
+    // 添加动画完成后再聚焦
+    setTimeout(() => {
+      const searchbar = document.querySelector('ion-searchbar');
+      searchbar?.setFocus();
+    }, 300);
+  }
+
+  // 关闭搜索框
+  onCloseSearch() {
+    // 添加渐隐动画
+    const searchContainer = document.querySelector('.search-bar-container');
+    if (searchContainer) {
+      searchContainer.classList.add('fade-out');
+      setTimeout(() => {
+        this.isSearchActive = false;
+        this.searchQuery = '';
+        this.displayedArtworks = [...this.artworks];
+      }, 200);
+    }
+  }
+  onSearchInput() {
+    if (!this.searchQuery.trim()) {
+      this.displayedArtworks = [...this.artworks];
+      return;
+    }
+
+    this.filterPosts(this.searchQuery.trim());
+  }
+
+  // 根据搜索关键字过滤帖子
+  filterPosts(query: string) {
+    const searchTerms = query.toLowerCase().split(' ').filter(term => term.length > 0);
+
+    this.displayedArtworks = this.artworks.filter(artwork => {
+      const title = artwork.title.toLowerCase();
+      const description = artwork.description.toLowerCase();
+
+      return searchTerms.every(term =>
+        title.includes(term) || description.includes(term)
+      );
+    });
+  }
+
+  // 根据传入的 Work[] 数组加载帖子数据
+  async loadWorkByContent(workContents: Work[]) {
+    try {
+      // 初始化查询
+      const workQuery = new CloudQuery('Work');
+
+
+      // 查询数据库中的帖子
+      const workObjs = await workQuery.find();
+
+      // 处理查询到的帖子数据
+      this.artworks = workObjs.map((workObj: CloudObject) => {
+        const workData = workObj.data as Work;
+
+        console.log(workObj.data);
+        console.log(workData.updatedAt);
+        console.log(workData.fileUrl);
+
+        // 返回构造好的帖子数据
+        return {
+          ...workData,
+          objectId: String(workObj.id),
+          imageUrl: `${workData.fileUrl}`, // 这里确保你的路径正确
+        };
+      });
+    } catch (error) {
+      console.error('加载帖子数据失败:', error);
+    }
+  }
+
+
+  // 点击跳转到具体的帖子详情页面
+  goToDetail(artId: string) {
+    console.log('Work ID:', artId);
+    const artwork = this.artworks.find(art => art.WorkId === artId);
+
+    if (artwork) {
+      this.router.navigate(['/tabs/art-detail', artId], {
+        state: { artwork }
+      }).then(() => {
+        console.log('Navigation successful');
+      }).catch(err => {
+        console.error('Navigation failed:', err);
+      });
+    } else {
+      console.warn('Artwork not found for id:', artId);
+    }
+  }
+
+
+  // 跳转到上传文件页面
+  goToUploadFile() {
+    console.log("跳转上传文件的页面")
+    this.router.navigate(['/tabs/upload-file']);  // 跳转到 upload-file 页面
+  }
+  // 使用 trackByFn 来提高性能
+  trackByFn(index: number, item: any) {
+    return item.WorkId;  // 使用 WorkId 作为唯一标识符
+  }
+}

+ 606 - 0
src/lib/community_ncloud.ts

@@ -0,0 +1,606 @@
+/*export interface Pointer {
+    __type: string;
+    className: string;
+    objectId?: string; // 这里保持 objectId 作为可选字段
+    //QuestionnaireId?: string; // 自定义主键
+    //QuestionId?: string; // 自定义主键
+    //OptionId?: string; // 自定义主键
+    //_UserId?: string; // 自定义主键
+}
+
+export class CloudObject {
+    id: string | null = null; // 数据库生成的 objectId
+    className: string;
+    createdAt: string | null = null;
+    updatedAt: string | null = null;
+    data: Record<string, any> = {};
+
+    // 新增的自定义主键
+    QuestionnaireId?: string;
+    QuestionId?: string;
+    OptionId?: string;
+    _UserId?: string;
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer(): Pointer {
+        // 使用自定义主键生成指针
+        let pointer: Pointer = { "__type": "Pointer", "className": this.className };
+
+        // 根据类名设置相应的 ID
+        switch (this.className) {
+            case "Questionnaire":
+                pointer.objectId = this.QuestionnaireId || "";
+                break;
+            case "Question":
+                pointer.objectId = this.QuestionId || "";
+                break;
+            case "Option":
+                pointer.objectId = this.OptionId || "";
+                break;
+            case "_User":
+                pointer.objectId = this._UserId || "";
+                break;
+            default:
+                pointer.objectId = this.id || ""; // 保持 objectId 作为后备
+        }
+
+        return pointer;
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt", "ACL"].includes(key)) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save(): Promise<this> {
+        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(): Promise<boolean> {
+        if (!this.id) return false;
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+        const result = await response.json();
+        if (result) {
+            this.id = null;
+        }
+        return true;
+    }
+}
+
+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;
+        return this;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$gte"] = value;
+        return this;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$lt"] = value;
+        return this;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$lte"] = value;
+        return this;
+    }
+
+    equalTo(key: string, value: any) {
+        this.whereOptions[key] = value;
+        return this;
+    }
+
+    async get(id: string): Promise<Record<string, any>> {
+        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}`;
+        const response = await fetch(url, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+        const json = await response.json();
+        return json || {};
+    }
+
+    async find(): Promise<CloudObject[]> {
+        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=${encodeURIComponent(whereStr)}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+        const json = await response.json();
+        return json?.results.map((item: any) => {
+            const cloudObject = new CloudObject(this.className);
+            cloudObject.set(item);
+            cloudObject.id = item.objectId;
+            cloudObject.createdAt = item.createdAt;
+            cloudObject.updatedAt = item.updatedAt;
+            return cloudObject;
+        }) || [];
+    }
+
+    async first(): Promise<CloudObject | null> {
+        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=${encodeURIComponent(whereStr)}&limit=1`;
+        } else {
+            url += `limit=1`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+        const json = await response.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            const cloudObject = new CloudObject(this.className);
+            cloudObject.set(exists);
+            cloudObject.id = exists.objectId;
+            cloudObject.createdAt = exists.createdAt;
+            cloudObject.updatedAt = exists.updatedAt;
+            return cloudObject;
+        }
+        return null;
+    }
+}*/
+// CloudObject.ts
+export interface Pointer {
+    __type: string;
+    className: string;
+    objectId?: string; // 这里保持 objectId 作为可选字段
+}
+export class CloudObject {
+    id: string | null = null;
+    className: string;
+    data: Record<string, any> = {};
+    createdAt: string | null = null;
+    updatedAt: string | null = null;
+
+    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"].includes(key)) {
+                if (key === "updatedAt") {
+                    this.updatedAt = json[key];  // 特别处理 updatedAt 字段
+                }
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save(): Promise<CloudObject> {
+        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,
+            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(): Promise<boolean> {
+        if (!this.id) return false;
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            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) {
+        this.whereOptions[key] = { "$gt": value };
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        this.whereOptions[key] = { "$gte": value };
+    }
+
+    lessThan(key: string, value: any) {
+        this.whereOptions[key] = { "$lt": value };
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        this.whereOptions[key] = { "$lte": value };
+    }
+
+    equalTo(key: string, value: any) {
+        this.whereOptions[key] = value;
+    }
+
+    containedIn(key: string, valueArray: any[]) {
+        this.whereOptions[key] = { "$in": valueArray };
+    }
+
+    /**
+     * 实现 startsWith 方法
+     * @param key 要匹配的字段
+     * @param prefix 匹配的前缀字符串
+     */
+    startsWith(key: string, prefix: string) {
+        if (typeof prefix !== 'string') {
+            throw new Error('The prefix must be a string.');
+        }
+
+        // 添加 $regex 条件进行前缀匹配
+        this.whereOptions[key] = { "$regex": `^${prefix}` };
+    }
+
+    async get(id: string): Promise<Record<string, any>> {
+        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"
+            },
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response.json();
+        return json || {};
+    }
+
+    async find(): Promise<CloudObject[]> {
+        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=${encodeURIComponent(whereStr)}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2BDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response.json();
+        return json?.results.map((item: any) => {
+            const cloudObject = new CloudObject(this.className);
+            cloudObject.set(item);
+            cloudObject.id = item.objectId;
+            // 提取 updatedAt 字段并存储在其他地方(例如,直接存入数据或作为额外字段返回)
+            const updatedAt = item.updatedAt; // 提取 updatedAt 字段
+            cloudObject.data['updatedAt'] = updatedAt; // 或者将其放在一个独立的变量中,取决于需求
+            return cloudObject;
+        }) || [];
+    }
+
+    async first(): Promise<CloudObject | null> {
+        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=${encodeURIComponent(whereStr)}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            const existsObject = new CloudObject(this.className);
+            existsObject.set(exists);
+            existsObject.id = exists.objectId;
+            existsObject.createdAt = exists.createdAt;
+            existsObject.updatedAt = exists.updatedAt;
+            return existsObject;
+        }
+        return null;
+    }
+}
+// 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"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return false;
+        }
+
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = null;
+        this.sessionToken = null;
+        this.data = {};
+        return true;
+    }
+
+    /** 注册 */
+    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;
+    }
+}