소스 검색

feat: community psql connection

未来全栈 3 달 전
부모
커밋
d5ce7043db

+ 68 - 67
AIart-app/src/app/art-detail/art-detail.component.html

@@ -13,87 +13,88 @@
 </ion-header>
 
 <ion-content>
-  @if (artwork) {
-  <div class="artwork-container">
-    <!-- 作品图片 -->
-    <div class="image-section">
-      <img [src]="artwork.image" [alt]="artwork.title" class="main-image">
-    </div>
-
-    <!-- 作品信息 -->
-    <div class="info-section">
-      <div class="artwork-header">
-        <h1>{{artwork.title}}</h1>
-        <div class="category-tag">{{artwork.category}}</div>
+  <div *ngIf="artwork; else errorTemplate">
+    <div class="artwork-container">
+      <!-- 作品图片 -->
+      <div class="image-section">
+        <img [src]="artwork.fileUrl" [alt]="artwork.title" class="main-image">
       </div>
 
-      <div class="user-info">
-        <div class="user">
-          <img [src]="artwork.avatarUrl" [alt]="artwork.userName" class="avatar">
-          <div class="user-details">
-            <span class="username">{{artwork.userName}}</span>
-            <span class="date">{{artwork.date}}</span>
+      <!-- 作品信息 -->
+      <div class="info-section">
+        <div class="artwork-header">
+          <h1>{{artwork.title}}</h1>
+          <div class="category-tag">{{artwork.category}}</div>
+        </div>
+
+        <div class="user-info">
+          <div class="user">
+            <img [src]="artwork.avatarUrl" [alt]="artwork.userName" class="avatar">
+            <div class="user-details">
+              <span class="username">{{artwork.userName}}</span>
+              <span class="date">{{artwork.updatedAt|date:'yyyy-MM-dd'}}</span>
+            </div>
           </div>
         </div>
-      </div>
 
-      <p class="description">{{artwork.description}}</p>
+        <p class="description">{{artwork.description}}</p>
 
-      <!-- 交互按钮 -->
-      <div class="action-buttons">
-        <ion-button fill="clear" (click)="toggleLike()">
-          <ion-icon [name]="isLiked ? 'heart' : 'heart-outline'" [color]="isLiked ? 'danger' : 'medium'"></ion-icon>
-          <span>{{artwork.likes}}</span>
-        </ion-button>
-        <ion-button fill="clear" (click)="toggleSave()">
-          <ion-icon [name]="isSaved ? 'bookmark' : 'bookmark-outline'"></ion-icon>
-          <span>收藏</span>
-        </ion-button>
+        <!-- 交互按钮 -->
+        <div class="action-buttons">
+          <ion-button fill="clear" (click)="toggleLike()">
+            <ion-icon [name]="isLiked ? 'heart' : 'heart-outline'" [color]="isLiked ? 'danger' : 'medium'"></ion-icon>
+            <span>{{artwork.likesCount}}</span>
+          </ion-button>
+          <ion-button fill="clear" (click)="toggleSave()">
+            <ion-icon [name]="isSaved ? 'bookmark' : 'bookmark-outline'"></ion-icon>
+            <span>收藏</span>
+          </ion-button>
+        </div>
       </div>
-    </div>
 
-    <!-- 评论区域 -->
-    <div class="comments-section">
-      <h2>评论 ({{artwork.comments?.length || 0}})</h2>
+      <!-- 评论区域 -->
+      <div class="comments-section">
+        <h2>评论 ({{comments.length}})</h2>
 
-      <!-- 评论输入框 -->
-      <div class="comment-input">
-        <ion-textarea [(ngModel)]="commentText" placeholder="写下你的评论..." rows="3"></ion-textarea>
-        <ion-button expand="block" (click)="submitComment()">发表评论</ion-button>
-      </div>
+        <!-- 评论输入框 -->
+        <div class="comment-input">
+          <ion-textarea [(ngModel)]="commentText" placeholder="写下你的评论..." rows="3"></ion-textarea>
+          <ion-button expand="block" (click)="submitComment()">发表评论</ion-button>
+        </div>
 
-      <!-- 评论列表 -->
-      <div class="comments-list">
-        @for(comment of artwork.comments; track comment.id) {
-        <div class="comment-item">
-          <div class="comment-header">
-            <img [src]="comment.avatarUrl" [alt]="comment.userName" class="comment-avatar">
-            <div class="comment-info">
-              <span class="comment-username">{{comment.userName}}</span>
-              <span class="comment-date">{{comment.date}}</span>
+        <!-- 评论列表 -->
+        <div class="comments-list">
+          <div *ngFor="let comment of comments; trackBy: trackByCommentId" class="comment-item">
+            <div class="comment-header">
+              <img [src]="comment.avatarUrl" [alt]="comment.userName" class="comment-avatar">
+              <div class="comment-info">
+                <span class="comment-username">{{comment.userName}}</span>
+                <span class="comment-date">{{comment.updatedAt | date:'yyyy-MM-dd'}}</span>
+              </div>
+            </div>
+            <p class="comment-content">{{comment.description}}</p>
+            <div class="comment-actions">
+              <ion-button fill="clear" size="small">
+                <ion-icon name="heart-outline"></ion-icon>
+                <span>{{comment.likesCount}}</span>
+              </ion-button>
+              <ion-button fill="clear" size="small">
+                <!--<ion-icon name="chatbubble-outline"></ion-icon>-->
+                <!--<span>回复</span>-->
+              </ion-button>
             </div>
-          </div>
-          <p class="comment-content">{{comment.content}}</p>
-          <div class="comment-actions">
-            <ion-button fill="clear" size="small">
-              <ion-icon name="heart-outline"></ion-icon>
-              <span>{{comment.likes}}</span>
-            </ion-button>
-            <ion-button fill="clear" size="small">
-              <ion-icon name="chatbubble-outline"></ion-icon>
-              <span>回复</span>
-            </ion-button>
           </div>
         </div>
-        }
       </div>
     </div>
   </div>
-  } @else {
-  <div class="error-message">
-    <ion-icon name="alert-circle-outline"></ion-icon>
-    <p>作品信息加载失败</p>
-    <ion-button (click)="goBack()">返回</ion-button>
-  </div>
-  }
+
+  <!-- 错误处理 -->
+  <ng-template #errorTemplate>
+    <div class="error-message">
+      <ion-icon name="alert-circle-outline"></ion-icon>
+      <p>作品信息加载失败</p>
+      <ion-button (click)="goBack()">返回</ion-button>
+    </div>
+  </ng-template>
 </ion-content>

+ 125 - 11
AIart-app/src/app/art-detail/art-detail.component.ts

@@ -3,28 +3,30 @@ import { ActivatedRoute, Router } from '@angular/router';
 import { IonicModule } from '@ionic/angular';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
+import { CloudQuery, CloudObject, Pointer } from '../../lib/community_ncloud'; // 确保路径正确
 
-interface Artwork {
-  id: string;
-  image: string;
+interface Work {
+  WorkId: string; // 修改为 WorkId
   title: string;
   description: string;
+  fileUrl: string;
   avatarUrl: string;
   userName: string;
-  date: string;
-  likes: number;
+  updatedAt: string;
+  likesCount: number;
   category: string;
-  comments?: Comment[];
+  comments: string[];
 }
 
+
 interface Comment {
-  id: string;
+  CommentId: string;
   userId: string;
   userName: string;
   avatarUrl: string;
-  content: string;
-  date: string;
-  likes: number;
+  description: string;
+  updatedAt: string;
+  likesCount: number;
 }
 
 @Component({
@@ -34,6 +36,117 @@ interface Comment {
   standalone: true,
   imports: [IonicModule, CommonModule, FormsModule]
 })
+export class ArtDetailComponent implements OnInit {
+  artwork?: Work;
+  comments: Comment[] = [];
+  isLiked = false;
+  isSaved = false;
+  commentText = '';
+
+  constructor(
+    private route: ActivatedRoute,
+    private router: Router
+  ) {
+    const navigation = this.router.getCurrentNavigation();
+    if (navigation?.extras.state) {
+      this.artwork = navigation.extras.state['artwork'] as Work;
+    }
+  }
+
+  ngOnInit() {
+    if (this.artwork) {
+      this.loadComments();
+    }
+  }
+
+  // 加载评论数据
+  async loadComments() {
+    try {
+      // 使用 map() 生成每个评论ID的查询,并通过 Promise.all 并行执行这些查询
+      const commentQueries = this.artwork?.comments.map(commentId => {
+        const query = new CloudQuery('Comment');
+        query.equalTo('CommentId', commentId); // 查询对应 CommentId 的评论
+        return query.find();
+      });
+
+      if (commentQueries) {
+        const results = await Promise.all(commentQueries); // 等待所有评论查询结果
+        // 将查询结果转换为 Comment 对象并更新 comments 数组
+        this.comments = results.flat().map((commentObj: CloudObject) => {
+          const data = commentObj.data as Comment;
+          return {
+            CommentId: data.CommentId,
+            userId: data.userId,
+            userName: data.userName,
+            avatarUrl: data.avatarUrl,
+            description: data.description,
+            updatedAt: data.updatedAt,
+            likesCount: data.likesCount
+          };
+        });
+      }
+    } catch (error) {
+      console.error('加载评论数据失败:', error);
+    }
+  }
+
+  toggleLike() {
+    this.isLiked = !this.isLiked;
+    if (this.artwork) {
+      this.artwork.likesCount += this.isLiked ? 1 : -1;
+    }
+  }
+
+  toggleSave() {
+    this.isSaved = !this.isSaved;
+    this.showToast(this.isSaved ? '已收藏' : '已取消收藏');
+  }
+
+  async submitComment() {
+    if (!this.commentText.trim()) {
+      this.showToast('请输入评论内容');
+      return;
+    }
+    // 这里添加提交评论的逻辑
+    this.showToast('评论已提交');
+    this.commentText = '';
+  }
+
+  private async showToast(message: string) {
+    const toast = document.createElement('ion-toast');
+    toast.message = message;
+    toast.duration = 2000;
+    toast.position = 'top';
+    document.body.appendChild(toast);
+    await toast.present();
+  }
+
+  goBack() {
+    this.router.navigate(['/tabs/tab3']);
+  }
+  async shareArtwork() {
+    if (navigator.share) {
+      try {
+        await navigator.share({
+          title: this.artwork?.title,
+          text: this.artwork?.description,
+          url: window.location.href
+        });
+      } catch (error) {
+        this.showToast('分享失败,请重试');
+      }
+    } else {
+      await navigator.clipboard.writeText(window.location.href);
+      this.showToast('链接已复制到剪贴板');
+    }
+  }
+
+  trackByCommentId(index: number, comment: Comment) {
+    return comment.CommentId;
+  }
+
+}
+/*
 export class ArtDetailComponent implements OnInit {
   artwork?: Artwork;
   isLiked = false;
@@ -81,7 +194,7 @@ export class ArtDetailComponent implements OnInit {
   toggleLike() {
     this.isLiked = !this.isLiked;
     if (this.artwork) {
-      this.artwork.likes += this.isLiked ? 1 : -1;
+      this.artwork.likesCount += this.isLiked ? 1 : -1;
     }
   }
 
@@ -130,3 +243,4 @@ export class ArtDetailComponent implements OnInit {
     this.router.navigate(['/tabs/tab3']);
   }
 }
+*/

+ 7 - 6
AIart-app/src/app/tab3/tab3.page.html

@@ -44,9 +44,9 @@
   </div>
   <!-- 内容网格 -->
   <div class="content-grid">
-    @for(artwork of artworks; track artwork.id) {
-    <div class="content-card" (click)="goToDetail(artwork.id)" style="cursor: pointer;">
-      <img [src]="artwork.image" [alt]="artwork.title" class="card-image">
+    <div class="content-card" (click)="goToDetail(artwork.WorkId)" style="cursor: pointer;"
+      *ngFor="let artwork of artworks; 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>
@@ -55,18 +55,19 @@
             <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.date}}</span>
+              <span class="date">{{artwork.updatedAt| date:'MM-dd'}}</span>
             </div>
           </div>
           <div class="likes">
             <ion-icon name="heart-outline"></ion-icon>
-            <span>{{artwork.likes}}</span>
+            <span>{{artwork.likesCount}}</span>
           </div>
         </div>
       </div>
     </div>
-    }
   </div>
+
+
 </ion-content>
 
 <!-- 添加侧边菜单 -->

+ 89 - 2
AIart-app/src/app/tab3/tab3.page.ts

@@ -1,7 +1,21 @@
-import { Component } from '@angular/core';
+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'; // 确保路径正确
+
+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',
@@ -15,6 +29,78 @@ import { CommonModule } from '@angular/common';
     IonMenu, IonList, IonItem, IonMenuButton
   ],
 })
+
+export class Tab3Page implements OnInit {
+  artworks: Work[] = [];  // 存储所有的帖子信息
+
+  constructor(private router: Router) { }
+
+  ngOnInit() {
+    this.loadWorks();  // 加载帖子数据
+    this.red_underline('发现');
+  }
+
+  // 加载所有帖子数据
+  async loadWorks() {
+    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);
+    }
+  }
+
+  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');
+    }
+  }
+
+  // 点击跳转到具体的帖子详情页面
+  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);
+    }
+  }
+  // 使用 trackByFn 来提高性能
+  trackByFn(index: number, item: any) {
+    return item.WorkId;  // 使用 WorkId 作为唯一标识符
+  }
+}
+
+/*
 export class Tab3Page {
   artworks = [
     {
@@ -65,6 +151,7 @@ export class Tab3Page {
 
   ngOnInit() {
     this.red_underline('发现');
+    
   }
   constructor(private router: Router) { }
   red_underline(text: string) {
@@ -95,6 +182,6 @@ export class Tab3Page {
       console.warn('Artwork not found for id:', artId);
     }
   }
+    */
 
 
-}

BIN
AIart-app/src/assets/img/user1.png


BIN
AIart-app/src/assets/img/user10.png


BIN
AIart-app/src/assets/img/user11.png


BIN
AIart-app/src/assets/img/user12.png


BIN
AIart-app/src/assets/img/user13.png


BIN
AIart-app/src/assets/img/user14.png


BIN
AIart-app/src/assets/img/user2.png


BIN
AIart-app/src/assets/img/user3.png


BIN
AIart-app/src/assets/img/user4.png


BIN
AIart-app/src/assets/img/user5.png


BIN
AIart-app/src/assets/img/user6.png


BIN
AIart-app/src/assets/img/user7.png


BIN
AIart-app/src/assets/img/user8.png


BIN
AIart-app/src/assets/img/user9.png


+ 592 - 0
AIart-app/src/lib/community_ncloud.ts

@@ -0,0 +1,592 @@
+/*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 };
+    }
+
+    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;
+    }
+}

+ 1 - 1
AIart-prod/communitySharing.md

@@ -341,7 +341,7 @@ ExperienceShare "1" -- "*" ExperienceComment : contains
             "userName":"make",
             "description": "记录用户的评论",
             "WorkId":"w1", //适当冗余,链接work表
-            "fileUrl": "https://example.com/artwork/abstract_painting.jpg",//用户头像图片路径
+            "avatarUrl": "https://example.com/artwork/abstract_painting.jpg",//用户头像图片路径
             "likesCount": 15,//喜欢的数量
             "createdAt": "2024-01-15T10:00:00Z",
             "updatedAt": "2024-01-15T10:00:00Z"

+ 233 - 7
Alart-server/migration/data.js

@@ -4984,12 +4984,238 @@ module.exports.OptionList = [
         "isSelected": false
     }
 
+];
 
+module.exports.WorkList = [
+    {
+        "WorkId": "w1",
+        "userId": "aiart",
+        "userName": "星空艺术家",
+        "title": "梵高-星空高清画作",
+        "description": "致敬梵高 #艺术品 #星空",
+        "category": ["艺术品", "星空"],
+        "fileUrl": "assets/img/xingkong.png",
+        "avatarUrl": "assets/img/book1.png",
+        "likesCount": 1687,
+        "commentsCount": 3,
+        "comments": ["c1", "c2", "c3"],
+        "createdAt": "2024-01-15T10:00:00Z",
+        "updatedAt": "2024-01-15T10:00:00Z"
+    },
+    {
+        "WorkId": "w2",
+        "userId": "artlover",
+        "userName": "艺术鉴赏家",
+        "title": "梵高《向日葵》",
+        "description": "经典中的经典,笔触很牛!",
+        "category": ["艺术", "花卉"],
+        "fileUrl": "assets/img/xiangrikui.png",
+        "avatarUrl": "assets/img/book2.png",
+        "likesCount": 2341,
+        "commentsCount": 2,
+        "comments": ["c4", "c5"],
+        "createdAt": "2024-01-20T12:00:00Z",
+        "updatedAt": "2024-01-20T12:00:00Z"
+    },
+    {
+        "WorkId": "w3",
+        "userId": "artfan",
+        "userName": "艺术探索者",
+        "title": "梵高自画像",
+        "description": "梵高生平自画像之其中🔟副👩‍🎨 #艺术欣赏 #美",
+        "category": ["艺术欣赏", "自画像"],
+        "fileUrl": "assets/img/fangao.png",
+        "avatarUrl": "assets/img/book3.png",
+        "likesCount": 1892,
+        "commentsCount": 4,
+        "comments": ["c6", "c7", "c8", "c9"],
+        "createdAt": "2024-01-10T09:00:00Z",
+        "updatedAt": "2024-01-10T09:00:00Z"
+    },
+    {
+        "WorkId": "w4",
+        "userId": "landscapeartist",
+        "userName": "风景画师",
+        "title": "阿尔小镇",
+        "description": "走吧,梵高,去你最爱的阿尔看看,阿尔小镇",
+        "category": ["风景", "小镇"],
+        "fileUrl": "assets/img/cunzhuang.png",
+        "avatarUrl": "assets/img/book4.png",
+        "likesCount": 2156,
+        "commentsCount": 5,
+        "comments": ["c10", "c11", "c12", "c13", "c14"],
+        "createdAt": "2024-01-05T08:00:00Z",
+        "updatedAt": "2024-01-05T08:00:00Z"
+    }
+];
 
-
-
-
-
-
-
-];
+module.exports.CommentList = [
+    {
+        "objectId": "artwork1",
+        "CommentId": "c1",
+        "userId": "user1",
+        "userName": "make",
+        "description": "太喜欢这幅画了!梵高的星空太震撼!",
+        "WorkId": "w1",
+        "avatarUrl": "assets/img/user1.png",
+        "likesCount": 10,
+        "createdAt": "2024-01-15T11:00:00Z",
+        "updatedAt": "2024-01-15T11:00:00Z"
+    },
+    {
+        "objectId": "artwork1",
+        "CommentId": "c2",
+        "userId": "user2",
+        "userName": "lucy",
+        "description": "真的很有艺术感,这种画面让我想起了夜晚的美丽。",
+        "WorkId": "w1",
+        "avatarUrl": "assets/img/user2.png",
+        "likesCount": 5,
+        "createdAt": "2024-01-15T12:00:00Z",
+        "updatedAt": "2024-01-15T12:00:00Z"
+    },
+    {
+        "objectId": "artwork1",
+        "CommentId": "c3",
+        "userId": "user3",
+        "userName": "john",
+        "description": "这幅画的细节太完美了,梵高的作品永远是经典。",
+        "WorkId": "w1",
+        "avatarUrl": "assets/img/user3.png",
+        "likesCount": 15,
+        "createdAt": "2024-01-15T13:00:00Z",
+        "updatedAt": "2024-01-15T13:00:00Z"
+    },
+    {
+        "objectId": "artwork2",
+        "CommentId": "c4",
+        "userId": "user4",
+        "userName": "anna",
+        "description": "非常棒的作品,向日葵的色彩十分鲜艳。",
+        "WorkId": "w2",
+        "avatarUrl": "assets/img/user4.png",
+        "likesCount": 7,
+        "createdAt": "2024-01-20T13:00:00Z",
+        "updatedAt": "2024-01-20T13:00:00Z"
+    },
+    {
+        "objectId": "artwork2",
+        "CommentId": "c5",
+        "userId": "user5",
+        "userName": "lily",
+        "description": "梵高的作品真的很有感染力,每次看都感动。",
+        "WorkId": "w2",
+        "avatarUrl": "assets/img/user5.png",
+        "likesCount": 3,
+        "createdAt": "2024-01-20T14:00:00Z",
+        "updatedAt": "2024-01-20T14:00:00Z"
+    },
+    {
+        "objectId": "artwork3",
+        "CommentId": "c6",
+        "userId": "user6",
+        "userName": "james",
+        "description": "这幅自画像看起来很有深度,充满情感。",
+        "WorkId": "w3",
+        "avatarUrl": "assets/img/user6.png",
+        "likesCount": 9,
+        "createdAt": "2024-01-10T10:00:00Z",
+        "updatedAt": "2024-01-10T10:00:00Z"
+    },
+    {
+        "objectId": "artwork3",
+        "CommentId": "c7",
+        "userId": "user7",
+        "userName": "susan",
+        "description": "非常喜欢这幅自画像,给人一种很强的情感冲击。",
+        "WorkId": "w3",
+        "avatarUrl": "assets/img/user7.png",
+        "likesCount": 6,
+        "createdAt": "2024-01-10T11:00:00Z",
+        "updatedAt": "2024-01-10T11:00:00Z"
+    },
+    {
+        "objectId": "artwork3",
+        "CommentId": "c8",
+        "userId": "user8",
+        "userName": "max",
+        "description": "非常具有代表性的梵高自画像,富有个性。",
+        "WorkId": "w3",
+        "avatarUrl": "assets/img/user8.png",
+        "likesCount": 13,
+        "createdAt": "2024-01-10T12:00:00Z",
+        "updatedAt": "2024-01-10T12:00:00Z"
+    },
+    {
+        "objectId": "artwork3",
+        "CommentId": "c9",
+        "userId": "user9",
+        "userName": "kate",
+        "description": "这幅画让我想起了梵高的艺术生涯,非常有震撼力。",
+        "WorkId": "w3",
+        "avatarUrl": "assets/img/user9.png",
+        "likesCount": 8,
+        "createdAt": "2024-01-10T13:00:00Z",
+        "updatedAt": "2024-01-10T13:00:00Z"
+    },
+    {
+        "objectId": "artwork4",
+        "CommentId": "c10",
+        "userId": "user10",
+        "userName": "alice",
+        "description": "这幅画让我感觉自己置身于阿尔的美丽小镇,太棒了!",
+        "WorkId": "w4",
+        "avatarUrl": "assets/img/user10.png",
+        "likesCount": 20,
+        "createdAt": "2024-01-05T09:00:00Z",
+        "updatedAt": "2024-01-05T09:00:00Z"
+    },
+    {
+        "objectId": "artwork4",
+        "CommentId": "c11",
+        "userId": "user11",
+        "userName": "bob",
+        "description": "阿尔小镇的风景真是太美了,画面充满了宁静感。",
+        "WorkId": "w4",
+        "avatarUrl": "assets/img/user11.png",
+        "likesCount": 15,
+        "createdAt": "2024-01-05T09:30:00Z",
+        "updatedAt": "2024-01-05T09:30:00Z"
+    },
+    {
+        "objectId": "artwork4",
+        "CommentId": "c12",
+        "userId": "user12",
+        "userName": "charlie",
+        "description": "这幅画太有故事感了,我仿佛能感受到梵高的心情。",
+        "WorkId": "w4",
+        "avatarUrl": "assets/img/user12.png",
+        "likesCount": 10,
+        "createdAt": "2024-01-05T10:00:00Z",
+        "updatedAt": "2024-01-05T10:00:00Z"
+    },
+    {
+        "objectId": "artwork4",
+        "CommentId": "c13",
+        "userId": "user13",
+        "userName": "david",
+        "description": "阿尔小镇的画面清新自然,看得让我感到放松。",
+        "WorkId": "w4",
+        "avatarUrl": "assets/img/user13.png",
+        "likesCount": 18,
+        "createdAt": "2024-01-05T10:30:00Z",
+        "updatedAt": "2024-01-05T10:30:00Z"
+    },
+    {
+        "objectId": "artwork4",
+        "CommentId": "c14",
+        "userId": "user14",
+        "userName": "eva",
+        "description": "每次看这幅画,都会想到我曾经去过的那个小镇,怀念。",
+        "WorkId": "w4",
+        "avatarUrl": "assets/img/user14.png",
+        "likesCount": 22,
+        "createdAt": "2024-01-05T11:00:00Z",
+        "updatedAt": "2024-01-05T11:00:00Z"
+    }
+]

+ 84 - 4
Alart-server/migration/import-data.js

@@ -170,7 +170,7 @@ displayQuestionnaireDetails("jbg6EYXk3G");
 */
 
 const { CloudQuery, CloudObject } = require("../lib/ncloud");
-const { QuestionnaireResultList, QuestionnaireList, QuestionList, OptionList, UserInterestProfileList } = require("./data");
+const { QuestionnaireResultList, QuestionnaireList, QuestionList, OptionList, UserInterestProfileList, WorkList, CommentList } = require("./data");
 
 importData();
 
@@ -179,15 +179,21 @@ let DataMap = {
     Questionnaire: {},
     Question: {},
     Option: {},
-    UserInterestProfile: {}
+    UserInterestProfile: {},
+    Work: {},
+    Comment: {}
 };
 
+
+
+
 async function importData() {
     // 确保 UserInterestProfileList 已经加载
     if (!UserInterestProfileList || UserInterestProfileList.length === 0) {
         console.log("UserInterestProfileList 数据为空或未定义!");
         return;
     }
+    /*
 
     // 导入问卷结果数据
     let questionnaireResultList = QuestionnaireResultList;
@@ -222,7 +228,21 @@ async function importData() {
     for (let index = 0; index < userInterestProfileList.length; index++) {
         let profile = userInterestProfileList[index];
         profile = await importObject("UserInterestProfile", profile);
+    }*/
+
+    // 导入Work数据
+    let workList = WorkList;  // 假设 WorkList 是包含工作数据的数组
+    for (let index = 0; index < workList.length; index++) {
+        let work = workList[index];
+        work = await importObject("Work", work);
     }
+    /*
+    // 导入Comment数据
+    let commentList = CommentList;  // 假设 CommentList 是包含评论数据的数组
+    for (let index = 0; index < commentList.length; index++) {
+        let comment = commentList[index];
+        comment = await importObject("Comment", comment);
+    }*/
 
     // console.log(DataMap);
 }
@@ -241,7 +261,7 @@ async function importObject(className, data) {
     // 导入前批量处理 Array 类型数据,进行重定向
     Object.keys(data)?.forEach(key => {
         let field = data[key];
-        let fieldSrcId = field?.objectId || field?.OptionId || field?.QuestionId || field?.UserInterestProfileId;
+        let fieldSrcId = field?.objectId || field?.OptionId || field?.QuestionId || field?.UserInterestProfileId || field?.workId || field?.commentId;;
 
         if (fieldSrcId) { // 是 Pointer 类型的字段
             if (key === "userIds" || key === "questionnaireIds" || key === "questionIds") {
@@ -297,6 +317,7 @@ async function displayQuestionnaireDetails(questionnaireId) {
     console.log(`问卷状态: ${questionnaire.get('status')}`);
     console.log("问题列表:");
 
+
     // 获取问题 IDs(数组字段)
     let questionIds = questionnaire.get('questions');
 
@@ -341,6 +362,65 @@ async function displayQuestionnaireDetails(questionnaireId) {
         console.log(""); // 添加空行以便于阅读
     }
 }
+// 展示帖子以及其评论的函数
+async function displayWorkDetails(workId) {
+    // 查询帖子数据
+    let workQuery = new CloudQuery("Work");
+    workQuery.equalTo("WorkId", workId);  // 使用 WorkId 查询
+    let work = await workQuery.first();
+
+    // 检查帖子是否存在
+    if (!work) {
+        console.log("帖子未找到,无法展示详情。");
+        return;
+    }
+
+    console.log(`帖子标题: ${work.get('title')}`);
+    console.log(`帖子描述: ${work.get('description')}`);
+    console.log(`点赞数: ${work.get('likesCount')}`);
+    console.log(`评论数: ${work.get('commentsCount')}`);
+    console.log("评论列表:");
+
+    // 获取评论 IDs(数组字段)
+    let commentIds = work.get('comments');
+
+    // 查询所有相关评论
+    let commentQuery = new CloudQuery("Comment");
+    commentQuery.containedIn("CommentId", commentIds);  // 查询 CommentId 在评论 ID 列表中的评论
+    let comments = await commentQuery.find();
+
+    // 检查是否有评论
+    if (comments.length === 0) {
+        console.log("当前帖子没有评论。");
+        return;
+    }
+
+    // 输出评论
+    for (let comment of comments) {
+        // 打印评论对象,检查结构
+        console.log(comment);  // 打印出完整的评论对象
+
+        // 如果是 CloudObject,使用 get 方法
+        if (comment instanceof CloudObject) {
+            console.log(`  评论人: ${comment.get('userName')}`);
+            console.log(`  评论内容: ${comment.get('description')}`);
+            console.log(`  评论点赞数: ${comment.get('likesCount')}`);
+            console.log(`  评论时间: ${comment.get('createdAt')}`);
+        } else {
+            // 否则,直接使用属性
+            console.log(`  评论人: ${comment.userName}`);
+            console.log(`  评论内容: ${comment.description}`);
+            console.log(`  评论点赞数: ${comment.likesCount}`);
+            console.log(`  评论时间: ${comment.createdAt}`);
+        }
+        console.log(""); // 添加空行以便于阅读
+    }
+}
+
+
+
+// 示例调用
+//displayQuestionnaireDetails("q1");
 
 // 示例调用
-displayQuestionnaireDetails("test_q1");
+displayWorkDetails("w4");