Просмотр исходного кода

Merge branch 'master' of http://git.fmode.cn:3000/lzy/S202226701025

lzy 1 год назад
Родитель
Сommit
25f6daa5db
30 измененных файлов с 1021 добавлено и 239 удалено
  1. 2 2
      FilmDraw-app/src/app/app.routes.ts
  2. 21 0
      FilmDraw-app/src/app/homepage/feedback/feedback.page.html
  3. 8 0
      FilmDraw-app/src/app/homepage/feedback/feedback.page.scss
  4. 62 0
      FilmDraw-app/src/app/homepage/feedback/feedback.page.ts
  5. 5 5
      FilmDraw-app/src/app/homepage/feedback/feedback.spec.ts
  6. 0 13
      FilmDraw-app/src/app/homepage/inform/inform.page.html
  7. 0 20
      FilmDraw-app/src/app/homepage/inform/inform.page.ts
  8. 35 20
      FilmDraw-app/src/app/tab1/tab1.page.html
  9. 72 66
      FilmDraw-app/src/app/tab1/tab1.page.scss
  10. 47 12
      FilmDraw-app/src/app/tab1/tab1.page.ts
  11. 6 19
      FilmDraw-app/src/app/tab4/tab4.page.html
  12. 6 10
      FilmDraw-app/src/app/tab4/tab4.page.ts
  13. BIN
      FilmDraw-app/src/assets/img/default.png
  14. BIN
      FilmDraw-app/src/assets/img/posttext.jpg
  15. BIN
      FilmDraw-app/src/assets/img/random.png
  16. 177 17
      FilmDraw-app/src/lib/ncloud.ts
  17. 36 0
      FilmDraw-app/src/lib/public/post-comment/post-comment.component.html
  18. 59 0
      FilmDraw-app/src/lib/public/post-comment/post-comment.component.scss
  19. 22 0
      FilmDraw-app/src/lib/public/post-comment/post-comment.component.spec.ts
  20. 106 0
      FilmDraw-app/src/lib/public/post-comment/post-comment.component.ts
  21. 7 3
      FilmDraw-app/src/lib/public/post-publisher/post-publisher.component.html
  22. 15 2
      FilmDraw-app/src/lib/public/post-publisher/post-publisher.component.scss
  23. 18 3
      FilmDraw-app/src/lib/public/post-publisher/post-publisher.component.ts
  24. 4 0
      FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/comp-uploader-hwobs.component.html
  25. 0 0
      FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/comp-uploader-hwobs.component.scss
  26. 22 0
      FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/comp-uploader-hwobs.component.spec.ts
  27. 66 0
      FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/comp-uploader-hwobs.component.ts
  28. 166 0
      FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/hwobs.service.ts
  29. 3 0
      FilmDraw-app/src/lib/user/modal-user-edit/modal-user-edit.component.html
  30. 56 47
      FilmDraw-server/migration/import-data.js

+ 2 - 2
FilmDraw-app/src/app/app.routes.ts

@@ -14,8 +14,8 @@ export const routes: Routes = [
     loadComponent: () => import('./homepage/like/like.page').then( m => m.LikePage)
   },
   {
-    path: 'inform',
-    loadComponent: () => import('./homepage/inform/inform.page').then( m => m.InformPage)
+    path: 'feedback',
+    loadComponent: () => import('./homepage/feedback/feedback.page').then( m => m.FeedbackPage)
   },
   {
     path: 'privacy',

+ 21 - 0
FilmDraw-app/src/app/homepage/feedback/feedback.page.html

@@ -0,0 +1,21 @@
+<ion-header >
+  <ion-toolbar>
+    <ion-title>反馈</ion-title>
+  <ion-buttons slot="end">
+    <ion-button (click)="closeFeedback()">
+      <ion-icon name="close"></ion-icon>
+    </ion-button>
+  </ion-buttons>
+</ion-toolbar>
+</ion-header>
+
+
+  <ion-content>
+    <ion-item>
+      <ion-label position="floating">反馈内容</ion-label>
+      <ion-textarea  [value]="FeedbackData['feedback']"  (ionChange)="FeedbackDataChange('feedback',$event)"  [autoGrow]="true">
+        </ion-textarea>
+    </ion-item>
+    <ion-button class="submit" expand="block" (click)="saveFeedback()">提交反馈</ion-button>
+  </ion-content>
+

+ 8 - 0
FilmDraw-app/src/app/homepage/feedback/feedback.page.scss

@@ -0,0 +1,8 @@
+ion-textarea{
+
+    min-height: 200px;
+}
+.submit{
+
+    --background:#036a99;;
+}

+ 62 - 0
FilmDraw-app/src/app/homepage/feedback/feedback.page.ts

@@ -0,0 +1,62 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { IonIcon,IonContent, IonHeader, IonTitle, IonToolbar,NavController, IonButton, IonTextarea, IonItem, IonLabel, 
+  IonButtons, ToastController } from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { close } from 'ionicons/icons';
+import { CloudFeedback, CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-feedback',
+  templateUrl: './feedback.page.html',
+  styleUrls: ['./feedback.page.scss'],
+  standalone: true,
+  imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule,
+    IonIcon,IonButton,IonTextarea,IonItem,IonLabel,FormsModule,IonButtons,
+  ]
+})
+export class FeedbackPage implements OnInit {
+
+currentUser:CloudUser
+  FeedbackData:any={
+ feedback: '', // 初始化 feedback 字段
+  };
+
+
+constructor(private navCtrl:NavController,private toastController: ToastController){
+    this.currentUser=new CloudUser();
+    addIcons({close})
+  }
+
+  ngOnInit() {
+  }
+  
+  closeFeedback() {
+    this.navCtrl.back();
+  }
+
+  FeedbackDataChange(key:string,env:any){
+    this.FeedbackData[key] = env.detail.value; // 更新 postData 对象
+  }
+    async saveFeedback(){
+    let feedback=new CloudFeedback();
+    feedback.setFeedbackData(this.FeedbackData, this.currentUser.id); // 设置帖子数据和用户ID
+
+    try {
+      await feedback.save(); // 保存反馈
+      this.FeedbackData['feedback'] = ''; // 清空 feedback
+       // 创建并显示提示
+    const toast = await this.toastController.create({
+      message: '反馈已提交!',
+      duration: 2000, // 持续时间(毫秒)
+      position: 'middle', // 显示位置
+    });
+    toast.present(); // 显示提示
+    } catch (error) {
+      console.error('保存反馈失败:', error);
+    }
+    }
+
+
+}

+ 5 - 5
FilmDraw-app/src/app/homepage/inform/inform.page.spec.ts → FilmDraw-app/src/app/homepage/feedback/feedback.spec.ts

@@ -1,12 +1,12 @@
 import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { InformPage } from './inform.page';
+import { FeedbackPage } from './feedback.page';
 
-describe('InformPage', () => {
-  let component: InformPage;
-  let fixture: ComponentFixture<InformPage>;
+describe('FeedbackPage', () => {
+  let component: FeedbackPage;
+  let fixture: ComponentFixture<FeedbackPage>;
 
   beforeEach(() => {
-    fixture = TestBed.createComponent(InformPage);
+    fixture = TestBed.createComponent(FeedbackPage);
     component = fixture.componentInstance;
     fixture.detectChanges();
   });

+ 0 - 13
FilmDraw-app/src/app/homepage/inform/inform.page.html

@@ -1,13 +0,0 @@
-<ion-header [translucent]="true">
-  <ion-toolbar>
-    <ion-title>Inform</ion-title>
-  </ion-toolbar>
-</ion-header>
-
-<ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">Inform</ion-title>
-    </ion-toolbar>
-  </ion-header>
-</ion-content>

+ 0 - 20
FilmDraw-app/src/app/homepage/inform/inform.page.ts

@@ -1,20 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-import { IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';
-
-@Component({
-  selector: 'app-inform',
-  templateUrl: './inform.page.html',
-  styleUrls: ['./inform.page.scss'],
-  standalone: true,
-  imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule]
-})
-export class InformPage implements OnInit {
-
-  constructor() { }
-
-  ngOnInit() {
-  }
-
-}

+ 35 - 20
FilmDraw-app/src/app/tab1/tab1.page.html

@@ -6,7 +6,7 @@
       </ion-avatar>
       <ion-searchbar slot="end" expand="with-icon"></ion-searchbar>
       <ion-buttons slot="end">
-        <ion-button (click)="openPublishModal()">
+        <ion-button fill="clear" (click)="openPublishModal()">
             发布+ 
         </ion-button>
       </ion-buttons>
@@ -29,8 +29,8 @@
        <ion-list>
         <ion-item *ngFor="let topic of filmpostList">
          <ion-label>
-          <h2>{{post?.get('title')}}</h2>
-          <p>参与人数:10086 👤| 热度:10086🔥 </p>
+          <h2>{{topic?.get('label')}}</h2>
+          <p>参与人数:1 👤| 热度:10086🔥 </p>
          </ion-label>
         </ion-item>
        </ion-list>
@@ -49,7 +49,7 @@
         <ion-item *ngFor="let post of filmpostList">
          <ion-label>
           <h2>{{post?.get('title')}} </h2>
-          <p>作者: | 评论数: | 热度: </p>
+          <p>作者:{{post?.get('author')?.username}} | 评论数: {{filmpostList.length}}| 热度:</p>
          </ion-label>
         </ion-item>
        </ion-list>
@@ -61,26 +61,41 @@
  
 
 
-    <ion-list>
-      <ion-item *ngFor="let post of filmpostList" class="post-item">
-        <ion-avatar slot="start">
-          <img src="/assets/img/1.png" alt="User Avatar" />
+  <ion-list>
+    <input type="hidden" [(ngModel)]="selectedPostId">
+    <ion-item *ngFor="let post of filmpostList" class="post-item">
+      <div>
+      <div class="avatar-container">
+        <ion-avatar class="avatar">
+          <img [src]="post?.get('author')?.avatar||avatarUrl" alt="User Avatar" />
         </ion-avatar>
-        <ion-label>
-          <h2>{{post?.get('title')}} </h2>
+          <div class="user-info">
+            <ion-label class="username">  {{post?.get('author')?.username}} </ion-label>
+            </div>
+      </div>
+
+    <div class="content">
+      <ion-label>
+          <h1 class="title">{{post?.get('title')}} </h1>
           <h3 class="topic"></h3>
-          <p class="post-content" [class.collapsed]="post?.get('content').length  > 100">
-          {{ post?.get('content').length > 100 ? (post?.get('content') | slice:0:100) + '...' : post?.get('content') }}
+        <div class="post-content-wrapper">
+            <p class="post-content" [class.collapsed]="post?.get('content').length > 200" (click)="toggleContent(post)">
+            {{ showFullContent(post) }}
           </p>
-          <p class="post-content" >
-            {{post?.get('content')}}
+            <p *ngIf="post?.get('content').length > 200" class="view-more" (click)="toggleContent(post)">
+            {{ isContentCollapsed(post) ? '查看更多' : '收起' }}
           </p>
-          <div class="post-actions">
+        </div>
+            <img *ngIf="post?.get('avatar')!='undefined'" class="post-avatar"  src="{{post?.get('avatar')}}" />
+        <div class="post-actions">
             <ion-button fill="clear" (click)="likePost()">👍 </ion-button>
-            <ion-button fill="clear" (click)="commentPost()">💬</ion-button>
-          </div>
-        </ion-label>
-      </ion-item>
-    </ion-list>
+          <ion-button fill="clear" (click)="commentPost(post?.id!)">💬</ion-button>
+        </div>
+      </ion-label>
+    </div>  
+
+</div>
+    </ion-item>
+  </ion-list>
   </ion-content>
 

+ 72 - 66
FilmDraw-app/src/app/tab1/tab1.page.scss

@@ -18,21 +18,16 @@
       height: 50px; // 设置头像高度
     }
 
-  ion-button {
-    --background:#036a99; // 设置按钮背景色
-    --color: white; // 设置按钮文字颜色
-    width:50px;
-    height: 30px;
-    border-radius: 10px;
-  }
-
 
 
+//话题、讨论区
 .content-container {
   display: flex;
   justify-content: space-between; /* 在主轴上均匀分配空间 */
   padding: 1px; /* 内边距 */
   background: white;
+  align-items: center;
+  padding: 15px;
 }
 
 ion-segment {
@@ -65,7 +60,7 @@ ion-card {
 // }
 
 .scrollable-content {
-  height: 150px; /* 设置最大高度,可以根据需要调整 */
+  height: 150px; /*高度 */
   overflow-y: auto; /* 添加垂直滚动条 */
 }
 
@@ -90,6 +85,7 @@ ion-label p {
   font-size: 0.9em; /* 字体大小 */
 }
 
+//帖子
 .post-item {
   margin-bottom: 16px;
 }
@@ -102,71 +98,81 @@ ion-label p {
 .post-content {
   margin: 8px 0;
   transition: max-height 0.3s ease;
+  // max-height: 1000px; /* 设置最大高度,可以根据需要调整 */
+  overflow-y: auto; /* 添加垂直滚动条 */
+}
+
+.post-author-avatar{
+  display: flex;
+  align-items: center;
+  padding: 15px;
+}
+
+.avatar-container {
+  display: flex;
+  align-items: center;
+  padding: 15px;
+}
+
+
+.avatar {
+  width: 50px;
+  height: 50px;
+  border-radius: 50%;
+  overflow: hidden;
+  margin-right: 15px;
+}
+
+.user-info {
+  flex-grow: 1;
 }
 
+.username {
+  font-size: 20px;
+  font-weight: bold;
+  text-decoration: none;
+  color: #000000;
+}
+
+.content{
+padding-left:20px;
+
+}
+.title{
+font-size:18px;
+font-weight: bold;
+
+}
+
+.post-avatar{
+  /* 基本样式 */
+max-width: 70%; /* 确保照片不会超过其父容器的宽度 */
+height: auto;    /* 保持照片的宽高比 */
+border-radius: 8px; /* 可选:给照片添加圆角 */
+box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 可选:给照片添加阴影 */
+margin-bottom: 20px; /* 可选:给照片下方添加一些间距 */
+display: block; /* 确保照片是块级元素,以便应用margin等属性 */
+margin-left: left; /* 水平居中(与margin-right: auto;一起使用) */ 
+margin-right: auto;
+}
+
+
 .collapsed {
-  max-height: 40px; // 控制折叠后的高度
   overflow: hidden;
+  overflow-y: auto; /* 添加垂直滚动条 */
 }
 
 .post-actions {
   display: flex;
-  justify-content: space-between;
   margin-top: 8px;
 }
 
-// ion-header {
-//     background-color: #ffffff; // 设置头部背景色
-//     color: white; // 设置头部文字颜色
-//   }
-  
-//   ion-card {
-//     margin: 10px; // 设置卡片之间的间距
-//     border-radius: 10px; // 设置卡片圆角
-//     box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); // 添加阴影效果
-//   }
-  
-//   ion-card-header {
-//     background-color: #d8e5fa; // 设置头部背景色
-//     color: white; // 设置头部文字颜色
-//   }
-  
-//   ion-card-title {
-//     font-size: 1.2em; // 设置卡片标题字体大小
-//     font-weight: bold; // 设置卡片标题字体加粗
-//   }
-  
-//   ion-item {
-//     --ion-item-background: transparent; // 设置列表项背景透明
-//   }
-  
-//   ion-label {
-//     color: #333; // 设置标签文字颜色
-//   }
-  
-//   ion-button {
-//     --background: #3880ff; // 设置按钮背景色
-//     --color: white; // 设置按钮文字颜色
-//     margin-top: 10px; // 设置按钮与上方内容的间距
-//     border-radius: 5px; // 设置按钮圆角
-//   }
-  
-
-  
-//   h2 {
-//     font-size: 1em; // 设置二级标题字体大小
-//     margin: 0; // 去掉默认外边距
-//   }
-  
-//   p {
-//     font-size: 0.9em; // 设置段落字体大小
-//     color: #666; // 设置段落文字颜色
-//   }
-  
-//   ion-list {
-//     padding: 0; // 去掉列表内边距
-//   }
-  
-//   ion-card-content {
-//     padding: 10px; // 设置卡片内容内边距
-//   }
+ion-button {
+  --background:#036a99; // 设置按钮背景色
+  --color: white; // 设置按钮文字颜色
+  width:50px;
+  height: 30px;
+  border-radius: 10px;
+  float: left;
+}
+

+ 47 - 12
FilmDraw-app/src/app/tab1/tab1.page.ts

@@ -10,6 +10,8 @@ import { CommonModule } from '@angular/common';
 import { openPostPublisherModal } from 'src/lib/public/post-publisher/post-publisher.component';
 import { CloudObject, CloudPost, CloudQuery, CloudUser } from 'src/lib/ncloud';
 import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
+import { openPostCommentModal } from 'src/lib/public/post-comment/post-comment.component';
+import { FormsModule } from '@angular/forms';
 
 @Component({
   selector: 'app-tab1',
@@ -20,7 +22,7 @@ import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-log
     CommonModule,
     IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,
  
-    IonButton, IonButtons, IonIcon,IonSegment,
+    IonButton, IonButtons, IonIcon,IonSegment,FormsModule,
 
     IonCard, IonCardHeader, IonCardTitle, IonCardContent,
     IonList, IonItem, IonLabel, IonAvatar, IonInput, IonTextarea,IonSearchbar
@@ -30,7 +32,10 @@ import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-log
 export class Tab1Page {
   currentUser:CloudUser|undefined
   post:CloudPost|undefined
-
+  filmpostList: Array<CloudObject> = []; // 创建用于数据列表存储的属性
+  private contentStates: { [key: string]: boolean } = {}; // 存储帖子内容展开/收起状态
+  selectedPostId: string | undefined; // 新增属性,用于存储当前选中帖子的 ID
+  avatarUrl: string = 'assets/img/default.png'; // 默认头像路径
 
   constructor(private modalCtrl:ModalController){
     this.currentUser=new CloudUser();
@@ -43,39 +48,69 @@ export class Tab1Page {
       this.loadFilmPostList()
     }
   
-    // 创建用于数据列表存储的属性
-    filmpostList:Array<CloudObject> = []
+
+   
 
     // 查询并加载列表的函数
     async loadFilmPostList(){
-      let query = new CloudQuery("FilmPost");
-      this.filmpostList = await query.find()
+    let query = new CloudQuery("FilmPost"); 
+    query.include('author');
+    this.filmpostList = await query.find();
+    console.log(this.filmpostList);
+      // 初始化内容状态
+    this.filmpostList.forEach(post => {
+      this.contentStates[post?.id?.toString() || ''] = true; 
+    });
     }
+    
+    
 
 async openPublishModal(){
 
   if(!this.currentUser?.id){
-    openUserLoginModal(this.modalCtrl);
+    await  openUserLoginModal(this.modalCtrl);
   }
 if(this.currentUser?.id){
-  openPostPublisherModal(this.modalCtrl);
+  await  openPostPublisherModal(this.modalCtrl);
 }
 
+}
+ // 切换帖子内容展开/收起状态的函数
+ toggleContent(post: CloudObject) {
+  const postId = post?.id?.toString() || '';
+  this.contentStates[postId] = !this.contentStates[postId];
 }
 
-  likePost() {
-   
+  // 显示帖子完整内容的函数
+  showFullContent(post: CloudObject): string {
+    const content = post?.get('content') as string;
+    const postId = post.id?.toString() || '';
+    return this.contentStates[postId] ? `${content.slice(0, 200)}` :content ;
+  }
+ 
+  // 判断帖子内容是否已折叠的函数
+  isContentCollapsed(post: CloudObject): boolean {
+    const postId = post.id?.toString() || '';
+    return this.contentStates[postId];
+  }
+ 
+
+ async likePost() {
+
   }
 
-  commentPost() {
+ async commentPost(postId: string) {
     // 评论逻辑
+this.selectedPostId = postId; // 更新选中帖子的 ID
+await openPostCommentModal(this.modalCtrl, this.selectedPostId);
+  
   }
   onSearch(event: any) {
         const searchTerm = event.target.value; // 获取用户输入的搜索内容
         console.log('搜索内容:', searchTerm);
         // 在这里添加搜索逻辑,例如过滤列表或导航到搜索结果页面
       }
-  
+
 
 
   

+ 6 - 19
FilmDraw-app/src/app/tab4/tab4.page.html

@@ -29,7 +29,7 @@
     <div class="profile-container">
       <div class="avatar-container">
         <ion-avatar class="avatar">
-          <img [src]="currentUser?.get('avatar') || avatarUrl" alt="avatar">
+          <img [src]="currentUser?.get('avatar') || avatarUrl1" alt="avatar">
         </ion-avatar>
         <div class="user-info">
           <ion-label class="username">{{ currentUser?.get("username") }}</ion-label>
@@ -56,6 +56,7 @@
           </ion-item>
         </ion-col>
       </ion-row>
+      
       <ion-row>
         <ion-col>
           <ion-item>
@@ -64,6 +65,7 @@
           </ion-item>
         </ion-col>
       </ion-row>
+
       <ion-row>
         <ion-col>
           <ion-item>
@@ -72,26 +74,11 @@
         </ion-item>
         </ion-col>
       </ion-row>
+      
       <ion-row>
         <ion-col>
           <ion-item>
-            <ion-label (click)="goToPrivacy()">隐私与安全设置</ion-label>
-            <ion-reorder name="lock-closed-outline" ></ion-reorder>
-          </ion-item>
-        </ion-col>
-      </ion-row>
-      <ion-row>
-        <ion-col>
-          <ion-item>
-            <ion-label (click)="goToSetting()">个性化偏好设置</ion-label>
-            <ion-reorder name="settings" ></ion-reorder>
-          </ion-item>
-        </ion-col>
-      </ion-row>
-      <ion-row>
-        <ion-col>
-          <ion-item>
-            <ion-label (click)="goToInform()">通知与消息管理</ion-label>
+            <ion-label (click)="goToFeedback()">反馈</ion-label>
             <ion-reorder  name="notifications-outline"></ion-reorder>
           </ion-item>
         </ion-col>
@@ -99,7 +86,7 @@
       <ion-row>
         <ion-col>
           <ion-item>
-            <ion-label (click)="goToHelp()">设置与帮助</ion-label>
+            <ion-label (click)="goToHelp()">帮助</ion-label>
             <ion-reorder name="help-circle" ></ion-reorder>
           </ion-item>
         </ion-col>

+ 6 - 10
FilmDraw-app/src/app/tab4/tab4.page.ts

@@ -29,12 +29,10 @@ import { openUserEditModal } from 'src/lib/user/modal-user-edit/modal-user-edit.
   ],
 })
 export class Tab4Page {
-  userInfo: any = {
-    username: 'yi',
-    // 其他用户信息字段...
-  };
+ 
   public isDisabled = true;
-  avatarUrl: string = 'assets/img/1.png'; // 默认头像路径
+  avatarUrl: string = 'assets/img/default.png'; // 默认头像路径
+  avatarUrl1: string = 'assets/img/random.png'; // 默认头像路径
  
   currentUser:CloudUser|undefined
   constructor(private modalCtrl:ModalController, private navCtrl: NavController) {
@@ -78,15 +76,13 @@ export class Tab4Page {
   goToMyposts() {
     this.navCtrl.navigateForward('/myposts');
   }
-  goToInform() {
-    this.navCtrl.navigateForward('/inform');
+  goToFeedback() {
+    this.navCtrl.navigateForward('/feedback');
   }
   goToPrivacy() {
     this.navCtrl.navigateForward('/privacy');
   }
-  goToSetting() {
- 
-  }
+
 
 
 

BIN
FilmDraw-app/src/assets/img/default.png


BIN
FilmDraw-app/src/assets/img/posttext.jpg


BIN
FilmDraw-app/src/assets/img/random.png


+ 177 - 17
FilmDraw-app/src/lib/ncloud.ts

@@ -87,29 +87,37 @@ export class CloudQuery {
     constructor(className: string) {
         this.className = className;
     }
-
+    include(...fields: string[]) {
+        this.whereOptions["include"] = fields;
+    }
     greaterThan(key: string, value: any) {
-        if (!this.whereOptions[key]) this.whereOptions[key] = {};
-        this.whereOptions[key]["$gt"] = value;
+        if (!this.whereOptions["where"]) this.whereOptions["where"] = {};
+        if (!this.whereOptions["where"][key]) this.whereOptions["where"][key] = {};
+        this.whereOptions["where"][key]["$gt"] = value;
     }
 
     greaterThanAndEqualTo(key: string, value: any) {
-        if (!this.whereOptions[key]) this.whereOptions[key] = {};
-        this.whereOptions[key]["$gte"] = value;
+        if (!this.whereOptions["where"]) this.whereOptions["where"] = {};
+        if (!this.whereOptions["where"][key]) this.whereOptions["where"][key] = {};
+        this.whereOptions["where"][key]["$gte"] = value;
     }
 
     lessThan(key: string, value: any) {
-        if (!this.whereOptions[key]) this.whereOptions[key] = {};
-        this.whereOptions[key]["$lt"] = value;
+        if (!this.whereOptions["where"]) this.whereOptions["where"] = {};
+        if (!this.whereOptions["where"][key]) this.whereOptions["where"][key] = {};
+        this.whereOptions["where"][key]["$lt"] = value;
     }
 
     lessThanAndEqualTo(key: string, value: any) {
-        if (!this.whereOptions[key]) this.whereOptions[key] = {};
-        this.whereOptions[key]["$lte"] = value;
+        if (!this.whereOptions["where"]) this.whereOptions["where"] = {};
+        if (!this.whereOptions["where"][key]) this.whereOptions["where"][key] = {};
+        this.whereOptions["where"][key]["$lte"] = value;
     }
 
     equalTo(key: string, value: any) {
-        this.whereOptions[key] = value;
+        if (!this.whereOptions["where"]) this.whereOptions["where"] = {};
+        this.whereOptions["where"][key] = value;
+        return this;
     }
 
     async get(id: string) {
@@ -130,13 +138,23 @@ export class CloudQuery {
         return json || {};
     }
 
-    async find(){
+    async find(): Promise<Array<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=${whereStr}`;
-        }
+        // 构建查询字符串
+        let queryStr = '';
+        Object.keys(this.whereOptions).forEach(key => {
+            let paramStr = JSON.stringify(this.whereOptions[key]);
+            if (key === "include") {
+                paramStr = this.whereOptions[key]?.join(","); // 将数组转换为逗号分隔字符串
+            }
+            // 添加查询参数到 URL
+            if (queryStr) {
+                url += `&${key}=${paramStr}`;
+            } else {
+                url += `${key}=${paramStr}`;
+            }
+        });
 
         const response = await fetch(url, {
             headers: {
@@ -374,19 +392,39 @@ export class CloudUser extends CloudObject {
 }
 
 //CloudPost.ts
+interface PostData {
+    title: string; // 标题属性
+    content: string; // 内容属性
+    [key: string]: any; // 其他任意属性
+}
+
 export class CloudPost extends CloudObject {
     constructor() {
         super('FilmPost'); // 调用父类构造函数,指定类名
     }
 
     // 设置帖子数据,包括发布者信息
-    setPostData(postData: Record<string, any>, userId: string|null) {
+    setPostData(postData: PostData, userId: string | null) {
+        // 确保 title 和 content 是必需的
+        if (!postData.title || !postData.content) {
+            throw new Error('Both title and content are required.');
+        }
+
         this.set(postData); // 设置帖子内容
         this.data["author"] = { "__type": "Pointer", "className": "_User", "objectId": userId }; // 设置发布者信息
     }
 
+    // 点赞帖子
+    async likePost() {
+           }
+
     // 更新帖子数据
-    async updatePost(postData: Record<string, any>) {
+    async updatePost(postData: PostData) {
+        // 确保 title 和 content 是必需的
+        if (!postData.title || !postData.content) {
+            throw new Error('Both title and content are required.');
+        }
+
         this.set(postData); // 更新帖子内容
         return await this.save(); // 保存更新
     }
@@ -396,3 +434,125 @@ export class CloudPost extends CloudObject {
         return await this.destroy(); // 调用父类的 destroy 方法
     }
 }
+
+
+// CloudComment.ts
+interface CommentData {
+    content: string; // 评论内容
+    postId: string; // 关联的帖子 ID
+    [key: string]: any; // 其他任意属性
+}
+
+export class CloudComment extends CloudObject {
+    constructor() {
+        super('FilmPostComment'); // 调用父类构造函数,指定类名
+    }
+        // 设置评论数据,包括发布者和关联的帖子
+        setCommentData(CommentData: CommentData, userId: string | null) {
+            // 确保 title 和 content 是必需的
+            if (!CommentData.postId || !CommentData.content) {
+                throw new Error('Both post and content are required.');
+            }
+    
+            this.set(CommentData); // 设置帖子内容
+            this.data["user"] = { "__type": "Pointer", "className": "_User", "objectId": userId }; // 设置发布者信息
+            this.data["post"] = { "__type": "Pointer", "className": "FilmPost", "objectId": CommentData.postId }; // 设置关联的帖子信息
+        }
+    
+
+   // 创建评论
+   async createComment(commentData: CommentData, userId: string | null) {
+    this.setCommentData(commentData, userId);
+    try {
+        const savedComment = await this.save();
+        return savedComment; // 返回保存的评论
+    } catch (error) {
+        console.error("Error creating comment:", error);
+        throw error; // 重新抛出错误
+    }
+}
+
+// 更新帖子评论计数
+ async updatePostCommentCount(postId: string, increment: number) {
+    const postQuery = new CloudQuery('FilmPost');
+    const post = await postQuery.get(postId);
+    if (!post) {
+        console.error(`Post with ID ${postId} not found.`);
+        throw new Error(`Post with ID ${postId} not found.`);
+    }
+    // 确保 post.data 存在 commentCount 属性
+    if (typeof post.data["commentCount"] === 'undefined') {
+        console.warn(`commentCount property is undefined for postId: ${postId}. Initializing to 0.`);
+        post.data["commentCount"] = 0; // 初始化为 0
+    }
+    
+    post.data["commentCount"] = Math.max((post.data["commentCount"] || 0) + increment, 0); // 更新评论计数
+    const result = await post.save(); // 保存更新后的帖子
+    console.log("Post updated successfully:", result);
+}
+
+// 更新评论
+async updateComment(commentData: CommentData) {
+    if (!this.id) {
+        throw new Error('Comment ID is required for updating.');
+    }
+
+    this.set(commentData); // 更新评论内容
+    return await this.save(); // 保存更新
+}
+
+// 删除评论
+async deleteComment() {
+    if (!this.id) {
+        throw new Error('Comment ID is required for deletion.');
+    }
+
+    // 获取关联的帖子并更新评论计数
+    return await this.destroy(); // 调用父类的 destroy 方法
+}
+
+// 查询评论
+async findComments(postId: string) {
+
+}
+}
+
+
+//CloudFeedback.ts
+interface FeedbackData {
+    feedback: string; // 内容属性
+    [key: string]: any; // 其他任意属性
+}
+
+export class CloudFeedback extends CloudObject {
+    constructor() {
+        super('FilmFeedback'); // 调用父类构造函数,指定类名
+    }
+
+    // 设置反馈数据,包括发布者信息
+    setFeedbackData(feedbackData: FeedbackData, userId: string | null) {
+        // 确保 feedback 是必需的
+        if (!feedbackData.feedback) {
+            throw new Error('feedback is required.');
+        }
+
+        this.set(feedbackData); // 设置帖子内容
+        this.data["user"] = { "__type": "Pointer", "className": "_User", "objectId": userId }; // 设置发布者信息
+    }
+
+    // 更新帖子数据
+    async updateFeedback(feedbackData: FeedbackData) {
+        // 确保 feedback 是必需的
+         if (!feedbackData.feedback) {
+            throw new Error('feedback is required.');
+        }
+
+        this.set(feedbackData); // 更新反馈内容
+        return await this.save(); // 保存更新
+    }
+
+    // 删除帖子
+    async deleteFeedback() {
+        return await this.destroy(); // 调用父类的 destroy 方法
+    }
+}

+ 36 - 0
FilmDraw-app/src/lib/public/post-comment/post-comment.component.html

@@ -0,0 +1,36 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-title>评论</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="comment-section">
+  <ion-list>
+    <div *ngFor="let comment of commentList">
+      <div>
+      <div class="avatar-container">
+
+          <ion-avatar class="avatar">
+          <img [src]="comment?.get('user')?.avatar || avatarUrl" alt="avatar">
+          </ion-avatar>
+            <div class="user-info">
+          <ion-label class="username">{{ comment?.get('user')?.username ||'unkown user'}}</ion-label>
+        </div>
+        </div>
+        <div class="content">
+        <p>{{ comment?.get('content') }}</p>
+        </div>
+  
+  </div> 
+
+</div>
+  </ion-list>
+</ion-content> 
+
+<div class="send-comment">
+    <ion-item>
+      <ion-input [value]="commentData['content']" (ionChange)="commentDataChange('content',$event)"  placeholder="写下你的评论..."></ion-input>
+      <ion-button (click)="sendComment()" fill="clear">发送</ion-button>
+    </ion-item>
+  
+  </div>

+ 59 - 0
FilmDraw-app/src/lib/public/post-comment/post-comment.component.scss

@@ -0,0 +1,59 @@
+.comment-section {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+  }
+
+  .avatar-container {
+    display: flex;
+    align-items: center;
+    padding: 15px;
+  }
+  
+  .avatar {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    overflow: hidden;
+    margin-right: 5px;
+  }
+
+  .user-info {
+    flex-grow: 1;
+  }
+
+  .username {
+    font-size: 15px;
+    font-weight: bold;
+    text-decoration: none;
+    color: #000000;
+  } 
+
+  .content{
+    padding-left:20px;
+    
+    }
+
+ 
+  ion-list {
+    flex: 1; /* 使评论列表占据剩余空间 */
+    overflow-y: auto; /* 允许滚动 */
+  } 
+  .send-comment {
+    padding: 16px; /* 添加一些内边距 */
+    background-color: #f9f9f9; /* 可选,设置背景颜色 */
+  }
+  
+  ion-item {
+    margin: 5px 0; /* 评论项的间距 */
+  }
+  
+  h2 {
+    margin: 0;
+    font-size: 1.2em; /* 用户名字体大小 */
+  }
+  
+  p {
+    margin: 0;
+    font-size: 1em; /* 评论内容字体大小 */
+  }

+ 22 - 0
FilmDraw-app/src/lib/public/post-comment/post-comment.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { PostCommentComponent } from './post-comment.component';
+
+describe('PostCommentComponent', () => {
+  let component: PostCommentComponent;
+  let fixture: ComponentFixture<PostCommentComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [PostCommentComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(PostCommentComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 106 - 0
FilmDraw-app/src/lib/public/post-comment/post-comment.component.ts

@@ -0,0 +1,106 @@
+import { NgFor } from '@angular/common';
+import { Component, Input, OnInit } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IonList,IonButton, IonButtons, IonContent, IonHeader, IonInput, IonItem, IonLabel, IonTitle, IonToolbar, ModalController, IonToast, ToastController, IonAvatar } from '@ionic/angular/standalone';
+import { CloudComment, CloudQuery, CloudUser } from 'src/lib/ncloud';
+
+
+@Component({
+  selector: 'app-post-comment',
+  templateUrl: './post-comment.component.html',
+  styleUrls: ['./post-comment.component.scss'],
+  standalone: true,
+   imports: [
+     IonContent,IonList,IonItem,NgFor,IonItem,IonLabel,IonInput,IonButton,
+     IonHeader,IonToolbar,IonButtons,IonTitle,FormsModule,IonToast,IonAvatar,
+     ]
+})
+export class PostCommentComponent  implements OnInit {
+  commentList: any[] = []; // 存储评论
+  @Input() postId!: string; // 接收帖子 ID
+  currentUser: CloudUser; // 当前用户实例
+  avatarUrl: string = 'assets/img/default.png'; // 默认头像路径
+
+  constructor( private toastController: ToastController) {
+      this.currentUser = new CloudUser(); // 初始化当前用户
+  }
+
+  ngOnInit() {
+      this.loadComments(); // 页面加载时获取评论
+  }
+
+  // 加载评论
+  async loadComments() {
+      let commentQuery = new CloudQuery('FilmPostComment');
+      let comments=commentQuery.equalTo('post', { "__type": "Pointer", "className": "FilmPost", "objectId": this.postId }).find();
+      try {
+          this.commentList = await comments;
+      } catch (error) {
+          console.error("Error loading commentList:", error);
+      }
+  }
+
+
+
+  commentData:any={
+
+      }
+      commentDataChange(key:string,env:any){
+        this.commentData[key] = env.detail.value; // 更新 commentData 对象
+       console.log(this.commentData);
+      }
+ 
+  // 发送评论
+  async sendComment() {
+      if (!this.commentData['content']) {
+          console.error('评论内容不能为空');
+          return; // 如果评论内容为空,直接返回
+      }
+      this.commentData['postId']=this.postId;
+      let comment = new CloudComment();
+    try{
+      // 创建评论
+      await comment.createComment(this.commentData, this.currentUser.id);
+     
+      this.commentData['content'] = ''; // 清空输入框
+       // 创建并显示提示
+       const toast = await this.toastController.create({
+        message: '评论发送成功!',
+        duration: 2000, // 持续时间(毫秒)
+        position: 'middle', // 显示位置
+      });
+      toast.present(); // 显示提示
+      await comment.updatePostCommentCount(this.postId,1);
+      await this.loadComments(); // 重新加载评论
+      }catch (error) {
+        console.error('评论失败:', error);
+        
+      }
+
+  }
+
+
+}
+
+export async function openPostCommentModal(modalCtrl: ModalController, id: string | null | undefined):Promise<CloudUser|null>{
+if(!id) {
+   console.error('postId is null or undefined');
+   return null;
+ }
+
+ const modal = await modalCtrl.create({
+   component: PostCommentComponent,
+   componentProps: { postId: id }, // 传递 postId 到 PostCommentComponent
+   breakpoints: [0.5, 1.0],
+   initialBreakpoint: 1.0});
+  
+
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+  return null
+}

+ 7 - 3
FilmDraw-app/src/lib/public/post-publisher/post-publisher.component.html

@@ -12,9 +12,13 @@
   <ion-item>
     <ion-input [value]="postData['title']" (ionChange)="postDataChange('title',$event)" title="标题" placeholder="请输入的标题"></ion-input>
   </ion-item>
-   <ion-item>
-     <ion-input [value]="postData['content']" (ionChange)="postDataChange('content',$event)" content="内容" placeholder="请输入你要分享的内容"></ion-input>
-   </ion-item>
+  <ion-item>
+    <div class="scrollable-content">
+    <ion-textarea   
+    [value]="postData['content']"  (ionChange)="postDataChange('content',$event)"  placeholder="请输入你想输入的内容" [autoGrow]="true">
+  </ion-textarea>
+</div>
+  </ion-item>
    <ion-item>
     <ion-input [value]="postData['label']" (ionChange)="postDataChange('label',$event)" label="话题" placeholder="请输入你讨论的话题"></ion-input>
   </ion-item>

+ 15 - 2
FilmDraw-app/src/lib/public/post-publisher/post-publisher.component.scss

@@ -1,3 +1,16 @@
 ion-button{
-    --background: #036a99;;
-}
+    --background: #036a99;
+}
+.title{
+    font-size: large;
+}
+ion-textarea{
+    min-height: 200px;
+    width: 100%;
+
+}
+.scrollable-content {
+    max-height: 450px; /*高度 */
+    overflow-y: auto; /* 添加垂直滚动条 */
+  }
+  

+ 18 - 3
FilmDraw-app/src/lib/public/post-publisher/post-publisher.component.ts

@@ -1,7 +1,9 @@
 import { Component, OnInit } from '@angular/core';
 import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, 
   IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonInput, IonItem, 
-  IonSegment, IonSegmentButton, IonLabel } from '@ionic/angular/standalone';
+  IonSegment, IonSegmentButton, IonLabel, 
+  IonTextarea,
+  ToastController} from '@ionic/angular/standalone';
 import { CloudPost, CloudUser } from 'src/lib/ncloud';
 @Component({
   selector: 'app-post-publisher',
@@ -11,7 +13,7 @@ import { CloudPost, CloudUser } from 'src/lib/ncloud';
   imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
     IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
     IonInput,IonItem,
-    IonSegment,IonSegmentButton,IonLabel
+    IonSegment,IonSegmentButton,IonLabel,IonTextarea,
   ],
 })
 export class PostPublisherComponent  implements OnInit {
@@ -27,7 +29,7 @@ export class PostPublisherComponent  implements OnInit {
   }
 
   currentUser:CloudUser
-  constructor(private modalCtrl:ModalController){
+  constructor(private modalCtrl:ModalController,private toastController:ToastController){
     this.currentUser=new CloudUser();
 
   }
@@ -40,8 +42,21 @@ export class PostPublisherComponent  implements OnInit {
     try {
       await post.save(); // 保存帖子
       this.modalCtrl.dismiss(); // 保存成功后关闭模态框
+       // 创建并显示提示
+      const toast = await this.toastController.create({
+      message: '帖子已发送!',
+      duration: 2000, // 持续时间(毫秒)
+      position: 'bottom', // 显示位置
+    });
+    toast.present(); // 显示提示
     } catch (error) {
       console.error('保存帖子失败:', error);
+      const toast = await this.toastController.create({
+        message: '保存帖子失败!',
+        duration: 2000, // 持续时间(毫秒)
+        position: 'bottom', // 显示位置
+      });
+      toast.present(); // 显示提示
     }
 
   }

+ 4 - 0
FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/comp-uploader-hwobs.component.html

@@ -0,0 +1,4 @@
+<!-- 文件选择器 Input -->
+<h1>请选择文件:</h1>
+<input type="file" multiple (change)="onFileChange($event)"/>
+<button (click)="upload()">上传</button>

+ 0 - 0
FilmDraw-app/src/app/homepage/inform/inform.page.scss → FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/comp-uploader-hwobs.component.scss


+ 22 - 0
FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/comp-uploader-hwobs.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { CompUploaderHwobsComponent } from './comp-uploader-hwobs.component';
+
+describe('CompUploaderHwobsComponent', () => {
+  let component: CompUploaderHwobsComponent;
+  let fixture: ComponentFixture<CompUploaderHwobsComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [CompUploaderHwobsComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(CompUploaderHwobsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 66 - 0
FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/comp-uploader-hwobs.component.ts

@@ -0,0 +1,66 @@
+import { Component, OnInit,Input,Output,EventEmitter } from '@angular/core';
+import { HwobsProvider } from '../hwobs.service';
+
+@Component({
+  selector: 'comp-uploader-hwobs',
+  templateUrl: './comp-uploader-hwobs.component.html',
+  styleUrls: ['./comp-uploader-hwobs.component.scss'],
+  standalone: true,
+})
+export class CompUploaderHwobsComponent  implements OnInit {
+
+  @Input() url:string = "";
+  @Output() onUrlChange:EventEmitter<string> = new EventEmitter<string>()
+
+  uploader:HwobsProvider|undefined
+  constructor() { }
+
+  ngOnInit() {
+    this.uploader = new HwobsProvider({
+      bucketName:"nova-cloud",
+      prefix:"dev/jxnu/storage/",
+      host:"https://app.fmode.cn/",
+      access_key_id:"XSUWJSVMZNHLWFAINRZ1",
+      secret_access_key:"P4TyfwfDovVNqz08tI1IXoLWXyEOSTKJRVlsGcV6"
+    });
+  }
+
+  file:File|undefined
+  fileData:any = ""
+  fileList:File[] = []
+
+  async upload(){
+    let filename = this.file?.name;
+    let dateStr = `${new Date().getFullYear()}${new Date().getMonth()+1}${new Date().getDate()}`;
+    let hourStr = `${new Date().getHours()}${new Date().getMinutes()+1}${new Date().getSeconds()}`;
+    let key = `${dateStr}/${hourStr}-${filename}`;
+    // let key = `storage/${filename}`
+    if(this.file){
+      let attachment = await this.uploader?.uploadFile(this.file,key);
+      console.log(attachment);
+      this.url = attachment?.get("url");
+      this.onUrlChange.emit(this.url);
+    }
+  }
+  /**
+   * 文件选择器 选择文件触发事件
+   * @param event 
+   */
+  async onFileChange(event:any){
+    console.log(event)
+    // 将选择的文件列表,赋值给fileList
+    this.fileList = event?.target?.files;
+    // 默认将第一个文件,显示在展示区域
+    this.setFile(event?.target?.files?.[0]);
+  }
+
+  /**
+   * 设置展示区域文件
+   * @param file 
+   */
+  async setFile(file:any){
+    // 将文件设置为展示区域文件
+    this.file = file
+  }
+
+}

+ 166 - 0
FilmDraw-app/src/lib/srorage/comp-uploader-hwobs/hwobs.service.ts

@@ -0,0 +1,166 @@
+import { Injectable } from '@angular/core';
+
+// @ts-ignore
+import ObsClient from "esdk-obs-browserjs"
+
+import Parse from "parse";
+
+/**
+ * HwobsDir 华为OBS目录接口
+ * @public
+ */
+export interface HwobsDir{
+  Prefix:string  // "storage/2023/"
+
+}
+
+/**
+ * HwobsDir 华为OBS文件接口
+ * @public
+ */
+export interface HwobsFile{
+  ETag: "\"f0ec968fe51ab48348307e06476122eb\""
+  Key:string  //"storage/3mkf41033623275.png"
+  LastModified:string //"2023-11-08T12:03:13.008Z"
+  Owner:object // {ID: '09971a1979800fb60fbbc00ada51f7e0'}
+  Size:string //"25839"
+  StorageClass:string //"STANDARD"
+}
+
+/**
+ * HwobsProvider 华为OBS文件服务
+ * @public
+ */
+export class HwobsProvider {
+  obsClient:ObsClient
+  bucketName:string
+  host:string
+  globalPrefix:string = ""
+  constructor(options:{
+    host:string
+    bucketName:string
+    access_key_id:string
+    secret_access_key:string
+    prefix?:string
+    server?:string
+  }) {
+      this.globalPrefix = options.prefix || ""
+      this.host = options?.host
+      this.bucketName = options?.bucketName
+      this.obsClient = new ObsClient({
+        access_key_id: options.access_key_id,       
+        secret_access_key: options.secret_access_key,
+        // 这里以华南-广州为例,其他地区请按实际情况填写
+        server: options?.server||'https://obs.cn-south-1.myhuaweicloud.com'
+    });
+  }
+
+  /**
+   * 目录及检索相关函数
+   */
+  listDir(prefix:any):Promise<{
+    dirs:Array<HwobsDir>,
+    files:Array<HwobsFile>
+  }>{
+    return new Promise((resolve,reject)=>{
+        this.obsClient.listObjects({
+          Bucket : this.bucketName,
+          Prefix : prefix,
+          Delimiter: '/'
+        }, (err:any, result:any) => {
+          if(err){              
+            console.error('Error-->' + err);   
+            reject(err)    
+          }else{              
+            console.log('Status-->' + result.CommonMsg.Status);
+            console.log(result)
+                    if(result.CommonMsg.Status < 300 && result.InterfaceResult){
+                          for(var j in result.InterfaceResult.Contents){
+                                    console.log('Contents[' + j +  ']:');
+                                    console.log('Key-->' + result.InterfaceResult.Contents[j]['Key']);
+                                    console.log('Owner[ID]-->' + result.InterfaceResult.Contents[j]['Owner']['ID']);
+                            }
+                    }       
+                    let dirs:HwobsDir[] = result.InterfaceResult.CommonPrefixes
+                    let files:HwobsFile[] = result.InterfaceResult.Contents
+                    resolve({dirs:dirs,files:files})     
+          }
+        });
+      })
+
+  }
+
+  /**
+   * 文件上传相关函数
+   * @param file 
+   * @param key 
+   * @returns 
+   */
+  async uploadFile(file:File,key:string):Promise<Parse.Object>{
+    console.log(this.globalPrefix,key)
+    // key 文件上传后的全部路径
+    // /storage/<公司账套>/<应用名称>/年月日/<文件名>.<文件后缀>
+    // /storage/web2023/<学号>/年月日/<文件名>.<文件后缀>
+    let attach = await this.checkFileExists(file);
+    if(attach?.id) return attach
+      return new Promise((resolve,reject)=>{
+        this.obsClient.putObject({
+              Bucket : this.bucketName,
+              Key : this.globalPrefix+key,
+              SourceFile : file
+        },  async (err:any, result:any) => {
+              if(err){
+                    console.error('Error-->' + err);
+                    reject(err)
+              }else{
+                console.log('Status-->' + result.CommonMsg.Status);
+                let attach = await this.saveAttachment(file,this.globalPrefix+key)
+                resolve(attach)
+              }
+        });
+      })
+  }
+  Attachment = Parse.Object.extend("Attachment")
+
+  async checkFileExists(file:any):Promise<Parse.Object>{
+    let hash = await this.getFileHash(file)
+    // 文件HASH查重,避免重复上传
+    let attach:Parse.Object
+    let query = new Parse.Query("Attachment")
+    query.equalTo("hash",hash);
+    query.equalTo("size",file.size);
+    let exists:any = await query.first();
+    if(!exists?.id) exists = new this.Attachment()
+    attach = exists
+    return attach
+  }
+  async saveAttachment(file:File,key:string){
+    console.log("saveAttachment",key)
+    let hash = await this.getFileHash(file)
+    let attach = await this.checkFileExists(file)
+    attach.set("name",file.name)
+    attach.set("size",file.size)
+    attach.set("mime",file.type)
+    attach.set("url",this.host + key)
+    attach.set("hash",hash)
+    attach = await attach.save()
+    return attach
+  }
+
+  async getFileHash(file:File) {
+    return new Promise((resolve, reject) => {
+      const reader = new FileReader();
+      reader.onload = async (event:any) => {
+        const buffer = event.target.result;
+        const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
+        const hashArray = Array.from(new Uint8Array(hashBuffer));
+        const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
+        resolve(hashHex);
+      };
+      reader.onerror = (event:any) => {
+        reject(event.target.error);
+      };
+      reader.readAsArrayBuffer(file);
+    });
+  }
+}

+ 3 - 0
FilmDraw-app/src/lib/user/modal-user-edit/modal-user-edit.component.html

@@ -23,6 +23,9 @@
      <ion-item>
       <ion-input [value]="userData['bio']" (ionChange)="userDataChange('bio',$event)" label="个人简介" placeholder="个人简介"></ion-input>
      </ion-item>
+     <ion-item>
+      <ion-input [value]="userData['imagery']" (ionChange)="userDataChange('imagery',$event)" label="个人偏好" placeholder="个人偏好"></ion-input>
+     </ion-item>
 
    <ion-button expand="block" (click)="save()">保存</ion-button>
    <ion-button expand="block" (click)="cancel()">取消</ion-button>

+ 56 - 47
FilmDraw-server/migration/import-data.js

@@ -1,58 +1,67 @@
-const { CloudQuery, CloudObject } = require("../lib/ncloud");
-const { FilmUserList } = require("./data.js");
-inportFilmUser()
-
-DataMap = {
-   FilmUser:{}
-}
+// const { CloudQuery, CloudObject } = require("../lib/ncloud");
+// const { FilmUserList } = require("./data.js");
+// inportFilmUser()
 
-async function inportFilmUser(){
-    // 导入用户数据
-    let filmuserList =FilmUserList
-    for (let index = 0; index <filmuserList.length; index++) {
-        let filmuser =filmuserList[index];
-       filmuser = await importObject("FilmUser",filmuser)
-    }
-    console.log(DataMap["FilmUser"])
-}
+// DataMap = {
+//    FilmUser:{}
+// }
 
-async function importObject(className,data){
-
-    // 查重 username 数据源列表中的objectId并非数据库生成的唯一ID,因此需要有一个username字段进行记录,并查重
-    let query = new CloudQuery(className)
-    let username = data.objectId
-    query.equalTo("username",username)
-    let importObj = await query.first()
-    console.log(importObj)
-
-    // 导入
-    // 导入前批量处理Pointer类型数据,进行重定向
-    Object.keys(data)?.forEach(key=>{
-        let field = data[key]
-        let username = field?.objectId
-        if(username){ // 是数组字段
-            if(key=="depart"){
-                data[key] = DataMap?.["Department"]?.[username]?.toPointer();
-            }
-        }
-    })
-    // 若未添加,则创建新对象并保存
-    if(!importObj?.id){
-        importObj = new CloudObject(className)
-    }
-
-      // 保存或更新数据
-      data.username = username;
-      importObj.set(data);
-      importObj = await importObj.save();
-      DataMap[className][username] = importObj
-}
+// async function inportFilmUser(){
+//     // 导入用户数据
+//     let filmuserList =FilmUserList
+//     for (let index = 0; index <filmuserList.length; index++) {
+//         let filmuser =filmuserList[index];
+//        filmuser = await importObject("FilmUser",filmuser)
+//     }
+//     console.log(DataMap["FilmUser"])
+// }
 
+// async function importObject(className,data){
+
+//     // 查重 username 数据源列表中的objectId并非数据库生成的唯一ID,因此需要有一个username字段进行记录,并查重
+//     let query = new CloudQuery(className)
+//     let username = data.objectId
+//     query.equalTo("username",username)
+//     let importObj = await query.first()
+//     console.log(importObj)
+
+//     // 导入
+//     // 导入前批量处理Pointer类型数据,进行重定向
+//     Object.keys(data)?.forEach(key=>{
+//         let field = data[key]
+//         let username = field?.objectId
+//         if(username){ // 是数组字段
+//             if(key=="depart"){
+//                 data[key] = DataMap?.["Department"]?.[username]?.toPointer();
+//             }
+//         }
+//     })
+//     // 若未添加,则创建新对象并保存
+//     if(!importObj?.id){
+//         importObj = new CloudObject(className)
+//     }
+
+//       // 保存或更新数据
+//       data.username = username;
+//       importObj.set(data);
+//       importObj = await importObj.save();
+//       DataMap[className][username] = importObj
+// }
 
 
 
+//测试get能否输出数组
+const { CloudQuery, CloudObject } = require("../lib/ncloud");
 
+async function test(){
+    let post =new CloudQuery("FilmPost");
+    let postList=Array("FilmPost")
+    postList[0]=post.get('zWMthTPtXY');
+   console.log( post.get('zWMthTPtXY'));
+   console.log(postList);
 
+}
+test();