Pārlūkot izejas kodu

feat(novel-app): 添加用户登录

18460000105 3 mēneši atpakaļ
vecāks
revīzija
5fe992dc23

+ 94 - 90
novel-app/src/app/app.routes.ts

@@ -1,93 +1,97 @@
-import { HttpClientModule } from '@angular/common/http';
-import { NgModule } from '@angular/core';
-import { PreloadAllModules, RouteReuseStrategy, RouterModule, Routes } from '@angular/router';
-import { IonicRouteStrategy } from '@ionic/angular';
-
-export const routes: Routes = [
-  {
-    path: '',
-    loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
-  },
-  {
-    path: 'story-generator',
-    loadComponent: () => import('./story-generator/story-generator.page').then(m => m.StoryGeneratorPage)
-  },
-  {
-    path: 'toolbox',
-    loadComponent: () => import('./toolbox/toolbox.page').then(m => m.ToolboxPage)
-  },
-  {
-    path: '',
-    loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
-  },
-  {
-    path: 'register',
-    loadComponent: () => import('./register/register.page').then(m => m.RegisterPage)
-  },
-  {
-    path: 'login',
-    loadComponent: () => import('./login/login.page').then(m => m.LoginPage)
-  },
-  {
-    path: 'short-generator',
-    loadComponent: () => import('./short-generator/short-generator.page').then(m => m.ShortGeneratorPage)
-  },
-  {
-    path: 'character',
-    loadComponent: () => import('./character/character.page').then(m => m.CharacterPage)
-  },
-  {
-    path: 'character-creator',
-    loadComponent: () => import('./character-creator/character-creator.page').then(m => m.CharacterCreatorPage)
-  },
-  {
-    path: 'chapter-generator',
-    loadComponent: () => import('./chapter-generator/chapter-generator.page').then(m => m.ChapterGeneratorPage)
-  },
-  {
-    path: 'character-detail/:id',
-    loadComponent: () => import('./character-detail/character-detail.component').then(m => m.CharacterDetailComponent)
-  },
-  {
-    path: 'tab1',
-    loadComponent: () => import('./tab1/tab1.page').then(m => m.Tab1Page)
-  },
-  {
-    path: 'world-setup',
-    loadComponent: () => import('./world-setup/world-setup.page').then(m => m.WorldSetupPage)
-  },
-  {
-    path: 'character-generator',
-    loadComponent: () => import('./character-generator/character-generator.page').then(m => m.CharacterGeneratorPage)
-  },
-  {
-    path: 'name-generator',
-    loadComponent: () => import('./name-generator/name-generator.page').then(m => m.NameGeneratorPage)
-  },
-  {
-    path: 'agent-create',
-    loadComponent: () => import('./agent-create/agent-create.page').then( m => m.AgentCreatePage)
-  },
-  {
-    path: 'atest',
-    loadComponent: () => import('./atest/atest.page').then( m => m.AtestPage)
+import { HttpClientModule } from '@angular/common/http';
+import { NgModule } from '@angular/core';
+import { PreloadAllModules, RouteReuseStrategy, RouterModule, Routes } from '@angular/router';
+import { IonicRouteStrategy } from '@ionic/angular';
+
+export const routes: Routes = [
+  {
+    path: '',
+    loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
+  },
+  {
+    path: 'story-generator',
+    loadComponent: () => import('./story-generator/story-generator.page').then(m => m.StoryGeneratorPage)
+  },
+  {
+    path: 'toolbox',
+    loadComponent: () => import('./toolbox/toolbox.page').then(m => m.ToolboxPage)
+  },
+  {
+    path: '',
+    loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
+  },
+  {
+    path: 'register',
+    loadComponent: () => import('./register/register.page').then(m => m.RegisterPage)
+  },
+  {
+    path: 'login',
+    loadComponent: () => import('./login/login.page').then(m => m.LoginPage)
+  },
+  {
+    path: 'short-generator',
+    loadComponent: () => import('./short-generator/short-generator.page').then(m => m.ShortGeneratorPage)
+  },
+  {
+    path: 'character',
+    loadComponent: () => import('./character/character.page').then(m => m.CharacterPage)
+  },
+  {
+    path: 'character-creator',
+    loadComponent: () => import('./character-creator/character-creator.page').then(m => m.CharacterCreatorPage)
+  },
+  {
+    path: 'chapter-generator',
+    loadComponent: () => import('./chapter-generator/chapter-generator.page').then(m => m.ChapterGeneratorPage)
+  },
+  {
+    path: 'character-detail/:id',
+    loadComponent: () => import('./character-detail/character-detail.component').then(m => m.CharacterDetailComponent)
+  },
+  {
+    path: 'tab1',
+    loadComponent: () => import('./tab1/tab1.page').then(m => m.Tab1Page)
+  },
+  {
+    path: 'world-setup',
+    loadComponent: () => import('./world-setup/world-setup.page').then(m => m.WorldSetupPage)
+  },
+  {
+    path: 'character-generator',
+    loadComponent: () => import('./character-generator/character-generator.page').then(m => m.CharacterGeneratorPage)
+  },
+  {
+    path: 'name-generator',
+    loadComponent: () => import('./name-generator/name-generator.page').then(m => m.NameGeneratorPage)
+  },
+  {
+    path: 'agent-create',
+    loadComponent: () => import('./agent-create/agent-create.page').then( m => m.AgentCreatePage)
+  },
+  {
+    path: 'atest',
+    loadComponent: () => import('./atest/atest.page').then( m => m.AtestPage)
+  },
  {
+    path: 'tab4',
+    loadComponent: () => import('./tab4/tab4.page').then( m => m.Tab4Page)
   }
 
-
-
-
-
-];
-
-
-
-
-
-@NgModule({
-  imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }), HttpClientModule],
-  exports: [RouterModule],
-  providers: [
-    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
-  ],
-})
+
+
+
+
+
+];
+
+
+
+
+
+@NgModule({
+  imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }), HttpClientModule],
+  exports: [RouterModule],
+  providers: [
+    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
+  ],
+})
 export class AppRoutingModule { }

+ 123 - 165
novel-app/src/app/lib/ncloud.ts

@@ -219,171 +219,129 @@ export class CloudQuery {
   }
 }
 
+// CloudUser.ts
 // 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;
-  }
+    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;
+        }
+        
+        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;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        return this;
+    }
 }

+ 29 - 0
novel-app/src/app/lib/user/modal-user-edit/modal-user-edit.component.html

@@ -0,0 +1,29 @@
+<!-- 用户登录状态 -->
+<ion-card>
+  <ion-card-header>
+    <ion-card-title>
+      用户名:{{currentUser?.get("username")}}
+    </ion-card-title>
+    <ion-card-subtitle>请输入您的详细资料</ion-card-subtitle>
+   </ion-card-header>
+ <ion-card-content>
+
+   <ion-item>
+     <ion-input [value]="userData['realname']" (ionChange)="userDataChange('realname',$event)" label="姓名" placeholder="请您输入真实姓名"></ion-input>
+   </ion-item>
+   <ion-item>
+     <ion-input type="number" [value]="userData['age']" (ionChange)="userDataChange('age',$event)" label="年龄" placeholder="请您输入年龄"></ion-input>
+    </ion-item>
+  <ion-item>
+     <ion-input [value]="userData['gender']" (ionChange)="userDataChange('gender',$event)" label="性别" placeholder="请您输入男/女"></ion-input>
+    </ion-item>
+    <ion-item>
+      <ion-input [value]="userData['avatar']" (ionChange)="userDataChange('avatar',$event)" label="头像" placeholder="请您输入头像地址(地址错误则会显示默认头像)"></ion-input>
+     </ion-item>
+
+   <ion-button expand="block" (click)="save()">保存</ion-button>
+   <ion-button expand="block" (click)="cancel()">取消</ion-button>
+ 
+
+</ion-card-content>
+</ion-card>

+ 0 - 0
novel-app/src/app/lib/user/modal-user-edit/modal-user-edit.component.scss


+ 22 - 0
novel-app/src/app/lib/user/modal-user-edit/modal-user-edit.component.spec.ts

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

+ 65 - 0
novel-app/src/app/lib/user/modal-user-edit/modal-user-edit.component.ts

@@ -0,0 +1,65 @@
+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 '../../ncloud';
+
+@Component({
+  selector: 'app-modal-user-edit',
+  templateUrl: './modal-user-edit.component.html',
+  styleUrls: ['./modal-user-edit.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
+    IonInput,IonItem,
+    IonSegment,IonSegmentButton,IonLabel
+  ],
+})
+export class ModalUserEditComponent  implements OnInit {
+
+  currentUser:CloudUser|undefined
+  userData:any = {}
+  userDataChange(key:string,ev:any){
+    let value = ev?.detail?.value
+    if(value){
+      this.userData[key] = value
+    }
+  }
+  constructor(private modalCtrl:ModalController) { 
+    this.currentUser = new CloudUser();
+    this.userData = this.currentUser.data;
+  }
+
+  ngOnInit() {}
+
+  async save(){
+    Object.keys(this.userData).forEach(key=>{
+      if(key=="age"){
+        this.userData[key] = Number(this.userData[key])
+      }
+    })
+
+    this.currentUser?.set(this.userData)
+    await this.currentUser?.save()
+    this.modalCtrl.dismiss(this.currentUser,"confirm")
+  }
+  cancel(){
+    this.modalCtrl.dismiss(null,"cancel")
+
+  }
+}
+
+export async function openUserEditModal(modalCtrl:ModalController):Promise<CloudUser|null>{
+  const modal = await modalCtrl.create({
+    component: ModalUserEditComponent,
+    breakpoints:[0.7,1.0],
+    initialBreakpoint:0.7
+  });
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+  return null
+}

+ 36 - 0
novel-app/src/app/lib/user/modal-user-login/modal-user-login.component.html

@@ -0,0 +1,36 @@
+<!-- 用户登录状态 -->
+<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
novel-app/src/app/lib/user/modal-user-login/modal-user-login.component.scss


+ 22 - 0
novel-app/src/app/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();
+  });
+});

+ 95 - 0
novel-app/src/app/lib/user/modal-user-login/modal-user-login.component.ts

@@ -0,0 +1,95 @@
+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 '../../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) {
+    console.log(this.type)
+   }
+
+  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") // 
+       console.log("登录成功")
+    }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
+}

+ 42 - 0
novel-app/src/app/tab4/tab4.page.html

@@ -0,0 +1,42 @@
+<ion-header [translucent]="true">
+  <ion-toolbar class="custom-toolbar">
+    <ion-title class="custom-title">
+      我的
+    </ion-title>
+  </ion-toolbar>
+</ion-header>
+<ion-content [fullscreen]="true">
+
+  <ion-refresher slot="fixed" (ionRefresh)="handleRefresh($event)">
+    <ion-refresher-content></ion-refresher-content>
+  </ion-refresher>
+ 
+  <ion-card>
+       
+      <ion-card-header class="card-header">
+        <img [src]="currentUser?.get('avatar')" onerror="this.src='https://app.fmode.cn/dev/jxnu/202226701019/头像示例.png';" alt="图片加载失败" class="avatar" />
+        <div class="user-info">
+            <ion-card-title>账号:{{currentUser?.get("username")}}</ion-card-title>
+            <ion-card-subtitle>
+                姓名: {{currentUser?.get("realname") || "-"}} 
+                性别: {{currentUser?.get("gender") || "-"}} 
+                年龄: {{currentUser?.get("age") || "-"}}
+            </ion-card-subtitle>
+        </div>
+    </ion-card-header>
+      
+      <ion-card-content>
+      @if(!currentUser?.id){
+        <ion-button expand="block" (click)="signup()" color="success">注册</ion-button>
+        <ion-button expand="block" (click)="login()" color="success">登录</ion-button>
+      }
+     @if(currentUser?.id){
+      <ion-button expand="block" (click)="editUser()" color="success">编辑资料</ion-button>
+      <ion-button expand="block" (click)="logout()" color="medium">登出</ion-button>
+    }
+    </ion-card-content>
+  </ion-card>
+
+
+
+</ion-content>

+ 125 - 0
novel-app/src/app/tab4/tab4.page.scss

@@ -0,0 +1,125 @@
+.custom-toolbar {
+  --background: rgba(255, 255, 255, 0.8); /* 使工具栏背景透明 */
+  display: flex; /* 使用 Flexbox 布局 */
+  justify-content: center; /* 水平居中 */
+  align-items: center; /* 垂直居中 */
+  padding: 0; /* 去掉默认内边距 */
+}
+
+.custom-title {
+  font-size: 1.3em; /* 字体大小 */
+  font-weight: bold; /* 加粗 */
+  color: #000000; 
+  text-align: center; /* 文字居中对齐 */
+  margin: 0; /* 去掉默认外边距 */
+  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); /* 添加文字阴影效果 */
+  /* 添加其他美化效果 */
+  font-family: "微软雅黑"; /* 自定义字体 */
+}
+
+ion-card {
+  background-color: #e0f7fa; /* 浅蓝色背景,给人以清新和健康的感觉 */
+  border-radius: 10px; /* 圆角边框 */
+  padding: 20px; /* 内边距 */
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* 轻微的阴影效果 */
+}
+
+ion-card-title {
+  font-size: 1.5em; /* 标题字体大小 */
+  font-weight: bold; /* 加粗 */
+  color: #00796b; /* 深绿色字体,象征健康 */
+  margin: 0; /* 去掉默认的外边距 */
+}
+
+ion-card-subtitle {
+  font-size: 1.2em; /* 副标题字体大小 */
+  color: #004d40; /* 更深的绿色字体 */
+  margin-top: 5px; /* 顶部外边距 */
+}
+
+ion-card:hover {
+  background-color: #b2ebf2; /* 悬停时的背景色变化 */
+  transition: background-color 0.3s; /* 背景色变化的过渡效果 */
+}
+.memo-card {
+  background-color: #e0f7fa; /* 浅蓝色背景,给人以清新和健康的感觉 */
+  border-radius: 10px; /* 圆角边框 */
+  padding: 20px; /* 内边距 */
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* 轻微的阴影效果 */
+}
+
+.memo-title {
+  font-size: 1.8em; /* 标题字体大小 */
+  font-weight: bold; /* 加粗 */
+  color: #00796b; /* 深绿色字体,象征健康 */
+  margin: 15px 0; /* 顶部和底部外边距 */
+}
+
+.memo-description {
+  font-size: 1.1em; /* 描述字体大小 */
+  color: #004d40; /* 更深的绿色字体 */
+  margin-bottom: 20px; /* 底部外边距 */
+}
+
+.tag-list {
+  list-style-type: none; /* 去掉默认的列表样式 */
+  padding: 0; /* 去掉内边距 */
+}
+
+.tag-item {
+  background-color: #b2ebf2; /* 标签背景色 */
+  color: #00796b; /* 标签字体颜色 */
+  border-radius: 5px; /* 标签圆角 */
+  padding: 10px; /* 标签内边距 */
+  margin: 5px 0; /* 标签外边距 */
+  transition: background-color 0.3s; /* 背景色变化的过渡效果 */
+  cursor: pointer; /* 鼠标悬停时显示为可点击 */
+}
+
+.tag-item:hover {
+  background-color: #80deea; /* 悬停时的背景色变化 */
+}
+.card-header {
+  display: flex; /* 使用 Flexbox 布局 */
+  align-items: center; /* 垂直居中对齐 */
+  padding: 10px; /* 内边距 */
+}
+
+.avatar {
+  width: 50px; /* 头像宽度 */
+  height: 50px; /* 头像高度 */
+  border-radius: 50%; /* 圆形头像 */
+  margin-right: 15px; /* 头像与文本之间的间距 */
+  object-fit: cover; /* 确保图片覆盖区域并保持比例 */
+}
+
+.user-info {
+  flex: 1; /* 使用户信息部分占据剩余空间 */
+}
+
+ion-content {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 68vh; /* 使内容区域高度为视口高度 */
+}
+
+.login-card {
+  width: 94%; /* 可以根据需要调整宽度 */
+  max-width: 400px; /* 设置最大宽度以避免过宽 */
+  text-align: center; /* 文本居中 */
+  padding: 10px; /* 添加内边距 */
+}
+
+.image-container {
+  width: 100%; /* 图片容器宽度100% */
+  display: flex; /* 使用flex布局 */
+  justify-content: center; /* 水平居中 */
+  margin-top: 10px; /* 上方间距 */
+}
+
+.responsive-image {
+  max-width: 80%; /* 最大宽度为容器宽度 */
+  height: auto; /* 高度自动 */
+  border-radius: 8px; /* 可选:添加圆角效果 */
+}

+ 18 - 0
novel-app/src/app/tab4/tab4.page.spec.ts

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

+ 80 - 0
novel-app/src/app/tab4/tab4.page.ts

@@ -0,0 +1,80 @@
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonRefresher, IonRefresherContent } from '@ionic/angular/standalone';
+import { CloudUser } from '../lib/ncloud';
+import { openUserEditModal } from '../lib/user/modal-user-edit/modal-user-edit.component';
+import { openUserLoginModal } from '../lib/user/modal-user-login/modal-user-login.component';
+
+import { Router } from '@angular/router';
+
+@Component({
+  selector: 'app-tab4',
+  templateUrl: 'tab4.page.html',
+  styleUrls: ['tab4.page.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
+    IonRefresher,IonRefresherContent
+  ],
+})
+export class Tab4Page {
+  handleRefresh(event:any) {
+    setTimeout(() => {
+      // Any calls to load data go here
+      this.currentUser = new CloudUser();
+      event.target.complete();
+    }, 2000);
+  }
+
+  goToCollection(){
+    console.log("goToCollection");
+  }
+
+  goToAvatar(){
+    console.log(['route'])
+    this.router.navigate(['/tabs/picture'])
+  }
+
+  currentUser:CloudUser|undefined
+  constructor(
+    private router: Router,
+    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();
+  }
+
+  editUser(){
+    openUserEditModal(this.modalCtrl)
+  }
+
+  editTags:Array<String>=[]
+   async setTagsValue(ev:any){
+    let currentUser = new CloudUser();
+    let userPrompt = ``
+    if(!currentUser?.id){
+      console.log("用户未登录,请登录后重试");
+      let user = await openUserLoginModal(this.modalCtrl);
+      if(!user?.id){
+        return
+      }
+      currentUser = user;
+    }
+  //console.log("setTagsValue",ev);
+  this.editTags=ev;
+}
+}