yi hai 1 ano
pai
achega
885adf9b95

+ 28 - 91
FilmDraw-app/src/app/tab4/tab4.page.html

@@ -5,103 +5,40 @@
   </ion-header>
   
   <ion-content [fullscreen]="true">
-    <!-- <ion-header collapse="condense">
-      <ion-toolbar>
-        <ion-title size="large">Tab 4</ion-title>
-      </ion-toolbar>
-    </ion-header>
-  
-    <app-explore-container name="Tab 4 page"></app-explore-container> -->
 
       <!-- 用户信息区 -->
   <section>
-    <ion-card>
-      <ion-card-header>
-        <ion-card-title>用户信息</ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
-        <ion-item>
-          <ion-avatar slot="start">
-            <img src="assets/user-avatar.png" alt="用户头像">
-          </ion-avatar>
-          <ion-label>
-            <h2>{{ username }}</h2>
-          </ion-label>
-          <ion-button slot="end" (click)="editProfile()">
-            <ion-icon name="create"></ion-icon>
-            编辑资料
-          </ion-button>
-        </ion-item>
-      </ion-card-content>
-    </ion-card>
-  </section>
-
-  <!-- 我的收藏区 -->
-  <section>
-    <ion-card>
-      <ion-card-header>
-        <ion-card-title>我的收藏</ion-card-title>
+<!-- 用户登录状态 -->
+<ion-card>
+  <!-- 未登录 -->
+   @if(!currentUser?.id){
+     <ion-card-header>
+       <ion-card-title>请登录</ion-card-title>
+       <ion-card-subtitle>暂无信息</ion-card-subtitle>
       </ion-card-header>
-      <ion-card-content>
-        <ion-list>
-          <ion-item *ngFor="let movie of favoriteMovies">
-            <ion-thumbnail slot="start">
-              <img [src]="movie.coverImage" alt="{{ movie.title }} 封面">
-            </ion-thumbnail>
-            <ion-label>
-              <h2>{{ movie.title }}</h2>
-              <p>评分: {{ movie.rating }}</p>
-              <p>{{ movie.description }}</p>
-            </ion-label>
-          </ion-item>
-        </ion-list>
-      </ion-card-content>
-    </ion-card>
-  </section>
+    }
+      <!-- 未登录 -->
+   @if(currentUser?.id){
+    <ion-card-header>
+      <ion-card-title>{{currentUser?.get("username")}} {{currentUser?.get("realname")}}</ion-card-title>
+      <ion-card-subtitle>性别:{{currentUser?.get("gender")||"-"}} 年龄:{{currentUser?.get("age")||"-"}}</ion-card-subtitle>
+    </ion-card-header>
+    }
+    <ion-card-content>
+    @if(!currentUser?.id){
+      <ion-button expand="block" (click)="login()">登录</ion-button>
+      <ion-button expand="block" (click)="signup()">注册</ion-button>
+      
+    }
+   @if(currentUser?.id){
+    <ion-button expand="block" (click)="editUser()">编辑资料</ion-button>
+    <ion-button expand="block" (click)="logout()" color="light">登出</ion-button>
+  }
+  </ion-card-content>
+</ion-card>
 
-  <!-- 我的讨论区 -->
-  <section>
-    <ion-card>
-      <ion-card-header>
-        <ion-card-title>我的讨论</ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
-        <ion-list>
-          <ion-item *ngFor="let post of userPosts">
-            <ion-label>
-              <h2>{{ post.title }}</h2>
-              <p>时间: {{ post.date }}</p>
-              <p>评论数: {{ post.comments }}</p>
-            </ion-label>
-          </ion-item>
-        </ion-list>
-      </ion-card-content>
-    </ion-card>
   </section>
 
-  <!-- 设置区 -->
-  <section>
-    <ion-card>
-      <ion-card-header>
-        <ion-card-title>设置</ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
-        <ion-list>
-          <ion-item button (click)="navigateTo('profile-settings')">
-            <ion-icon slot="start" name="person"></ion-icon>
-            <ion-label>个人信息设置</ion-label>
-          </ion-item>
-          <ion-item button (click)="navigateTo('privacy-settings')">
-            <ion-icon slot="start" name="lock"></ion-icon>
-            <ion-label>隐私设置</ion-label>
-          </ion-item>
-          <ion-item button (click)="navigateTo('notification-settings')">
-            <ion-icon slot="start" name="notifications"></ion-icon>
-            <ion-label>通知设置</ion-label>
-          </ion-item>
-        </ion-list>
-      </ion-card-content>
-    </ion-card>
-  </section>
+  
   </ion-content>
   

+ 65 - 1
FilmDraw-app/src/app/tab4/tab4.page.scss

@@ -69,4 +69,68 @@ ion-title {
   
   ion-list {
     padding: 0; // 去掉列表内边距
-  }
+  }
+  ion-avatar {
+    width: 50px; /* 或者你需要的任何尺寸 */
+     height: 50px; /* 保持宽高一致,避免变形 */
+    }
+    
+    ion-avatar img {
+     width: 100%; /* 让图片填满avatar */
+    height: auto; /* 保持图片比例 */
+    }
+    
+    ion-header {
+      background: #f8f9fa; // 设置头部背景色
+     
+    }
+    
+      ion-item{
+        border-radius: 8px; // 设置列表项圆角
+        margin: 5px 0; // 添加列表项的上下外边距
+      }
+  
+    ion-content{
+       background: #f8f9fa; // 设置背景色
+    }
+        
+    ion-col {
+      text-align: center;   /* 确保列内的内容居中(但这里可能由 .ion-text-center 类已经处理) */
+      .avatar-container {
+        display: flex;
+        flex-direction: column; /* 垂直排列子元素 */
+        align-items: center;    /* 垂直居中容器内的内容(对于图像来说可能不是必需的,但对于整体布局有帮助) */
+        margin-top: 20px;       /* 可选:为容器添加一些顶部间距 */
+  
+        ion-avatar {
+          width: 100px;         /* 设置头像的宽度 */
+          height: 100px;        /* 设置头像的高度,与宽度相同以实现圆形 */
+          border-radius: 50%;   /* 将头像设置为圆形 */
+          overflow: hidden;     /* 确保图像不会溢出圆形框架 */
+  
+          img {
+            width: 100%;        /* 让图像填满头像容器 */
+            height: auto; /* 保持图片比例 */
+            object-fit: cover;  /* 确保图像内容适应圆形框架,而不失真 */
+          }
+       }  
+  
+    .username {
+      font-size: 40px;
+      font-weight: bold;
+      margin-top: 10px;
+    }
+       
+      }
+  }
+      ion-col {
+        ion-item {
+          background-color: #ffffff;
+          margin: 2px 0;
+          padding: 2px;
+          box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
+          text-align: left;
+          font-size: 15px;
+          width: 100%;
+        }
+      }

+ 27 - 61
FilmDraw-app/src/app/tab4/tab4.page.ts

@@ -1,76 +1,42 @@
 import { Component } from '@angular/core';
-import { 
-  IonHeader, IonToolbar, IonTitle, IonContent,
-  IonCard, IonCardHeader, IonCardTitle, IonCardContent,
-  IonList, IonItem, IonLabel, IonAvatar, IonButton, IonIcon, IonThumbnail, 
-} from '@ionic/angular/standalone';
-import { ExploreContainerComponent } from '../explore-container/explore-container.component';
-import { CommonModule } from '@angular/common';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
 
-interface Movie {
-  title: string;
-  rating: string;
-  description: string;
-  coverImage: string;
-}
-
-interface Post {
-  title: string;
-  date: string;
-  comments: number;
-}
 @Component({
   selector: 'app-tab4',
   templateUrl: 'tab4.page.html',
   styleUrls: ['tab4.page.scss'],
   standalone: true,
-  imports: [
-    CommonModule,
-    IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,
-    IonCard, IonCardHeader, IonCardTitle, IonCardContent,
-    IonList, IonItem, IonLabel, IonAvatar, IonButton, IonIcon, IonThumbnail,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle
   ],
 })
 export class Tab4Page {
-  username: string = '用户A'; // 用户名
-
-  favoriteMovies: Movie[] = [
-    {
-      title: '某某剧名1',
-      rating: '9.0',
-      description: '这是一部精彩的剧集,讲述了...',
-      coverImage: 'assets/movie1.jpg',
-    },
-    {
-      title: '某某剧名2',
-      rating: '8.5',
-      description: '这部剧集以其独特的视角吸引了观众...',
-      coverImage: 'assets/movie2.jpg',
-    },
-    // 其他收藏的影视剧...
-  ];
+  currentUser:CloudUser|undefined
+  constructor(private modalCtrl:ModalController) {
+    this.currentUser = new CloudUser();
+  }
+  async login(){
+    // 弹出登录窗口
+    let user = await openUserLoginModal(this.modalCtrl);
+    if(user?.id){
+      this.currentUser = user
+    }
+  }
+  async signup(){
+    // 弹出注册窗口
+    let user = await openUserLoginModal(this.modalCtrl,"signup");
+    if(user?.id){
+      this.currentUser = user
+    }
+  }
+  logout(){
+    this.currentUser?.logout();
+  }
 
-  userPosts: Post[] = [
-    {
-      title: '对《某某剧名》的看法',
-      date: '2024-11-20',
-      comments: 5,
-    },
-    {
-      title: '最新剧集讨论',
-      date: '2024-11-18',
-      comments: 2,
-    },
-    // 其他用户发帖...
-  ];
+  editUser(){
 
-  editProfile() {
-    // 编辑资料逻辑
-    console.log('编辑资料');
   }
 
-  navigateTo(setting: string) {
-    // 导航到设置页面逻辑
-    console.log(`导航到 ${setting}`);
-  }
 }

+ 132 - 0
FilmDraw-app/src/lib/ncloud.ts

@@ -1,3 +1,5 @@
+
+//CloudObject.ts
 export class CloudObject {
     className: string;
     id: string | null = null;
@@ -190,4 +192,134 @@ export class CloudQuery {
         existsObject.updatedAt = exists.updatedAt;
         return existsObject;
     }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("Users"); // 用户类在Parse中是"Users"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/Users")
+        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;
+    }
 }

+ 39 - 0
FilmDraw-app/src/lib/user/modal-user-login/modal-user-login.component.html

@@ -0,0 +1,39 @@
+
+<!-- 用户登录状态 -->
+<ion-card>
+  <ion-card-header>
+    <ion-card-title>
+     <ion-segment [value]="type" (ionChange)="typeChange($event)">
+       <ion-segment-button value="login">
+         <ion-label>登录</ion-label>
+       </ion-segment-button>
+       <ion-segment-button value="signup">
+         <ion-label>注册</ion-label>
+       </ion-segment-button>
+     </ion-segment>
+    </ion-card-title>
+    <ion-card-subtitle>请输入账号密码</ion-card-subtitle>
+   </ion-card-header>
+ <ion-card-content>
+
+   <ion-item>
+     <ion-input [value]="username" (ionChange)="usernameChange($event)" label="账号" placeholder="请您输入账号/手机号"></ion-input>
+   </ion-item>
+   <ion-item>
+     <ion-input [value]="password" (ionChange)="passwordChange($event)" label="密码" type="password" value="password"></ion-input>
+   </ion-item>
+   @if(type=="signup"){
+     <ion-item>
+      <ion-input [value]="password2" (ionChange)="password2Change($event)" label="请再次输入密码" type="password" value="password"></ion-input>
+     </ion-item>
+   }
+ 
+   @if(type=="login"){
+     <ion-button expand="block" (click)="login()">登录</ion-button>
+   }
+   @if(type=="signup"){
+     <ion-button expand="block" (click)="signup()">注册</ion-button>
+   }
+
+</ion-card-content>
+</ion-card>

+ 0 - 0
FilmDraw-app/src/lib/user/modal-user-login/modal-user-login.component.scss


+ 22 - 0
FilmDraw-app/src/lib/user/modal-user-login/modal-user-login.component.spec.ts

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

+ 93 - 0
FilmDraw-app/src/lib/user/modal-user-login/modal-user-login.component.ts

@@ -0,0 +1,93 @@
+import { Input, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonInput, IonItem, IonSegment, IonSegmentButton, IonLabel } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-modal-user-login',
+  templateUrl: './modal-user-login.component.html',
+  styleUrls: ['./modal-user-login.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
+    IonInput,IonItem,
+    IonSegment,IonSegmentButton,IonLabel
+  ],
+})
+export class ModalUserLoginComponent  implements OnInit {
+  @Input()
+  type:"login"|"signup" = "login"
+  typeChange(ev:any){
+    this.type = ev?.detail?.value || ev?.value || 'login'
+  }
+  username:string = ""
+  usernameChange(ev:any){
+    console.log(ev)
+    this.username = ev?.detail?.value
+  }
+  password:string = ""
+  passwordChange(ev:any){
+    this.password = ev?.detail?.value
+  }
+  password2:string = ""
+  password2Change(ev:any){
+    this.password2 = ev?.detail?.value
+  }
+  constructor(private modalCtrl:ModalController) { }
+
+  ngOnInit() {}
+
+  async login(){
+    if(!this.username || !this.password){
+      console.log("请输入完整")
+      return
+    }
+    let user:any = new CloudUser();
+    user = await user.login(this.username,this.password);
+    if(user?.id){
+       this.modalCtrl.dismiss(user,"confirm")
+    }else{
+      console.log("登录失败")
+    }
+  }
+
+  async signup(){
+    if(!this.username || !this.password || !this.password2){
+      console.log("请输入完整")
+      return
+    }
+    if(this.password!=this.password2){
+      console.log("两次密码不符,请修改")
+      return
+    }
+
+    let user:any = new CloudUser();
+    
+    user = await user.signUp(this.username,this.password);
+    if(user){
+      this.type = "login"
+      console.log("注册成功请登录")
+    }
+  }
+
+}
+
+
+export async function openUserLoginModal(modalCtrl:ModalController,type:"login"|"signup"="login"):Promise<CloudUser|null>{
+  const modal = await modalCtrl.create({
+    component: ModalUserLoginComponent,
+    componentProps:{
+      type:type
+    },
+    breakpoints:[0.5,0.7],
+    initialBreakpoint:0.5
+  });
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+  return null
+}