CuddleNan 1 hete
szülő
commit
f28459b1b6

+ 103 - 60
myapp/src/app/tab3/tab3.page.html

@@ -1,73 +1,116 @@
-<!-- tab3.page.html -->
 <ion-header [translucent]="true">
-  <ion-toolbar class="header-bar">
-    <ion-title class="app-title">我的</ion-title>
-    <ion-icon name="leaf" slot="end" class="header-icon"></ion-icon>
+  <ion-toolbar color="success">
+    <ion-title>我的空间</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="handleAction('settings')">
+        <ion-icon slot="icon-only" name="settings"></ion-icon>
+      </ion-button>
+    </ion-buttons>
   </ion-toolbar>
 </ion-header>
 
-<ion-content [fullscreen]="true" class="profile-content">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">我的空间</ion-title>
-    </ion-toolbar>
-  </ion-header>
+<ion-content [fullscreen]="true" class="ion-padding">
+  @if(currentUser?.id){
+    <!-- 已登录状态 -->
+    <div class="profile-container">
+      <!-- 用户信息卡片 -->
+      <ion-card class="user-card">
+        <ion-card-header>
+          <div class="avatar-container">
+            <ion-avatar class="glow-avatar">
+              <img src="assets/avatar.png" alt="用户头像">
+            </ion-avatar>
+            <ion-badge color="warning" class="vip-badge">VIP3</ion-badge>
+          </div>
+          <ion-card-title class="username">{{currentUser?.get('username') || '美食探险家'}}</ion-card-title>
+          <ion-card-subtitle class="user-id">ID: {{currentUser?.id || 'foodie_9527'}}</ion-card-subtitle>
+        </ion-card-header>
+        
+        <ion-card-content>
+          <ion-grid class="stats-grid">
+            <ion-row>
+              <ion-col size="6">
+                <ion-button expand="block" fill="clear" (click)="goToRecords()" class="stat-button">
+                  <div class="stat-content">
+                    <ion-icon name="time" color="success" class="stat-icon"></ion-icon>
+                    <h3>156</h3>
+                    <p>历史浏览</p>
+                  </div>
+                </ion-button>
+              </ion-col>
+              <ion-col size="6">
+                <ion-button expand="block" fill="clear" (click)="goToCollections()" class="stat-button">
+                  <div class="stat-content">
+                    <ion-icon name="heart" color="danger" class="stat-icon"></ion-icon>
+                    <h3>89</h3>
+                    <p>我的收藏</p>
+                  </div>
+                </ion-button>
+              </ion-col>
+            </ion-row>
+          </ion-grid>
+        </ion-card-content>
+      </ion-card>
 
-  <!-- 用户信息 -->
-  <div class="user-section">
-    <div class="avatar-container">
-      <ion-avatar class="glow-avatar">
-        <img src="assets/avatar.png" alt="用户头像">
-      </ion-avatar>
-      <ion-badge color="success" class="id-badge">VIP3</ion-badge>
-    </div>
-    <div class="user-info">
-      <h2 class="username">美食探险家</h2>
-      <p class="user-id">ID: foodie_9527</p>
-    </div>
-  </div>
+      <!-- 功能菜单 -->
+      <ion-list lines="full" class="menu-list">
+        <ion-item button detail (click)="edit()">
+          <ion-icon name="create" slot="start" color="primary"></ion-icon>
+          <ion-label>编辑资料</ion-label>
+          <ion-note slot="end">完善信息</ion-note>
+        </ion-item>
+        
+        <ion-item button detail (click)="handleAction('feedback')">
+          <ion-icon name="chatbubbles" slot="start" color="success"></ion-icon>
+          <ion-label>意见反馈</ion-label>
+          <ion-note slot="end">及时沟通</ion-note>
+        </ion-item>
 
-  <!-- 数据卡片,使用普通 div 代替 ion-grid 和 ion-col -->
-  <div class="stats-grid">
-    <div class="stat-card history" style="display: inline-block; width: 48%;" (click)="goToRecords()">
-      <ion-icon name="time" class="stat-icon"></ion-icon>
-      <div class="stat-content" >
-        <h3>156</h3>
-        <p>历史浏览</p>
-      </div>
-    </div>
-    <div class="stat-card favorite" style="display: inline-block; width: 48%;" (click)="goToCollections()">
-      <ion-icon name="heart" class="stat-icon"></ion-icon>
-      <div class="stat-content">
-        <h3>89</h3>
-        <p>我的收藏</p>
-      </div>
-    </div>
-  </div>
-
-  <!-- 功能菜单 -->
-  <ion-list lines="none" class="menu-list">
-    <ion-item class="menu-item" button detail (click)="handleAction('feedback')">
-      <ion-icon name="chatbubble-ellipses" slot="start" color="success"></ion-icon>
-      <ion-label>意见反馈</ion-label>
-      <ion-note>及时沟通</ion-note>
-    </ion-item>
+        <ion-item button detail (click)="handleAction('privacy')">
+          <ion-icon name="shield-checkmark" slot="start" color="warning"></ion-icon>
+          <ion-label>隐私政策</ion-label>
+          <ion-note slot="end">v2.1</ion-note>
+        </ion-item>
 
-    <ion-item class="menu-item" button detail (click)="handleAction('privacy')">
-      <ion-icon name="shield-checkmark" slot="start" color="success"></ion-icon>
-      <ion-label>隐私政策</ion-label>
-      <ion-note>v2.1</ion-note>
-    </ion-item>
+        <ion-item button detail (click)="handleAction('agreement')">
+          <ion-icon name="document-text" slot="start" color="tertiary"></ion-icon>
+          <ion-label>用户协议</ion-label>
+          <ion-note slot="end">2024版</ion-note>
+        </ion-item>
+      </ion-list>
 
-    <ion-item class="menu-item" button detail (click)="handleAction('agreement')">
-      <ion-icon name="document-text" slot="start" color="success"></ion-icon>
-      <ion-label>用户协议</ion-label>
-      <ion-note>2024版</ion-note>
-    </ion-item>
-  </ion-list>
+      <!-- 退出按钮 -->
+      <ion-button expand="block" color="danger" fill="outline" (click)="logout()" class="logout-button">
+        <ion-icon name="log-out" slot="start"></ion-icon>
+        退出登录
+      </ion-button>
+    </div>
+  } @else {
+    <!-- 未登录状态 -->
+    <div class="login-container">
+      <ion-card>
+        <ion-card-header>
+          <ion-card-title>欢迎来到美食助手</ion-card-title>
+          <ion-card-subtitle>登录以享受完整功能</ion-card-subtitle>
+        </ion-card-header>
+        
+        <ion-card-content>
+          <ion-button expand="block" color="success" (click)="login()">
+            <ion-icon name="log-in" slot="start"></ion-icon>
+            立即登录
+          </ion-button>
+          
+          <ion-button expand="block" fill="outline" color="medium" (click)="login()">
+            <ion-icon name="person-add" slot="start"></ion-icon>
+            快速体验
+          </ion-button>
+        </ion-card-content>
+      </ion-card>
+    </div>
+  }
 
   <!-- 版本信息 -->
-  <div class="version-section">
+  <div class="version-info">
     <ion-text color="medium">当前版本 2.1.0</ion-text>
     <ion-text color="medium">© 2024 美食助手</ion-text>
   </div>

+ 122 - 0
myapp/src/app/tab3/tab3.page.scss

@@ -1,4 +1,126 @@
 /* tab3.page.scss */
+.profile-container {
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+.user-card {
+  text-align: center;
+  margin-bottom: 20px;
+  border-radius: 15px;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+  
+  .avatar-container {
+    position: relative;
+    margin: 0 auto 15px;
+    width: fit-content;
+  }
+  
+  .glow-avatar {
+    width: 100px;
+    height: 100px;
+    margin: 0 auto;
+    border: 3px solid var(--ion-color-success);
+    box-shadow: 0 0 15px rgba(var(--ion-color-success-rgb), 0.3);
+  }
+  
+  .vip-badge {
+    position: absolute;
+    bottom: -5px;
+    right: -5px;
+    font-size: 12px;
+  }
+  
+  .username {
+    font-weight: bold;
+    margin-top: 10px;
+  }
+  
+  .user-id {
+    color: var(--ion-color-medium);
+    font-size: 14px;
+  }
+}
+
+.stats-grid {
+  margin: 20px 0;
+  
+  .stat-button {
+    height: 100%;
+    
+    .stat-content {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      
+      .stat-icon {
+        font-size: 24px;
+        margin-bottom: 5px;
+      }
+      
+      h3 {
+        margin: 5px 0;
+        font-size: 18px;
+        font-weight: bold;
+      }
+      
+      p {
+        margin: 0;
+        font-size: 14px;
+        color: var(--ion-color-medium);
+      }
+    }
+  }
+}
+
+.menu-list {
+  background: transparent;
+  margin-bottom: 20px;
+  
+  ion-item {
+    --border-color: rgba(var(--ion-color-medium-rgb), 0.1);
+    --padding-start: 10px;
+    --padding-end: 10px;
+    border-radius: 10px;
+    margin-bottom: 8px;
+    
+    ion-icon {
+      font-size: 20px;
+    }
+  }
+}
+
+.logout-button {
+  margin: 20px 0;
+}
+
+.login-container {
+  max-width: 500px;
+  margin: 50px auto 0;
+  
+  ion-card {
+    border-radius: 15px;
+    
+    ion-card-header {
+      text-align: center;
+    }
+    
+    ion-button {
+      margin-bottom: 10px;
+    }
+  }
+}
+
+.version-info {
+  text-align: center;
+  margin-top: 30px;
+  font-size: 12px;
+  
+  ion-text {
+    display: block;
+    margin-bottom: 5px;
+  }
+}
 :host {
     --primary-green: #2ecc71;
     --secondary-green: #27ae60;

+ 90 - 2
myapp/src/app/tab3/tab3.page.ts

@@ -1,16 +1,86 @@
 // tab3.page.ts
 import { Component } from '@angular/core';
-import { NavController } from '@ionic/angular';
+import { ModalController, NavController } from '@ionic/angular';
+import { ModalUserLoginComponent } from 'fmode-ng';
+import { CloudApi, CloudUser } from 'src/lib/ncloud';
+import { 
+  IonHeader, 
+  IonToolbar, 
+  IonTitle, 
+  IonContent, 
+  IonButton, 
+  IonIcon, 
+  IonCard, 
+  IonCardHeader, 
+  IonCardTitle, 
+  IonCardSubtitle, 
+  IonCardContent,
+  IonGrid,
+  IonRow,
+  IonCol,
+  IonList,
+  IonItem,
+  IonLabel,
+  IonNote,
+  IonBadge,
+  IonAvatar,
+  IonText,
+  IonButtons
+} from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { 
+  settings, 
+  time, 
+  heart, 
+  create, 
+  chatbubbles, 
+  shieldCheckmark, 
+  documentText, 
+  logIn, 
+  logOut, 
+  personAdd 
+} from 'ionicons/icons';
 
 @Component({
   selector: 'app-tab3',
   templateUrl: 'tab3.page.html',
   styleUrls: ['tab3.page.scss'],
   standalone: false,
+  // imports: [
+  //   IonHeader, 
+  //   IonToolbar, 
+  //   IonTitle, 
+  //   IonContent, 
+  //   IonButton, 
+  //   IonIcon, 
+  //   IonCard, 
+  //   IonCardHeader, 
+  //   IonCardTitle, 
+  //   IonCardSubtitle, 
+  //   IonCardContent,
+  //   IonGrid,
+  //   IonRow,
+  //   IonCol,
+  //   IonList,
+  //   IonItem,
+  //   IonLabel,
+  //   IonNote,
+  //   IonBadge,
+  //   IonAvatar,
+  //   IonText,
+  //   IonButtons
+  // ]
 })
 export class Tab3Page {
+  currentUser: CloudUser | undefined;
 
-  constructor(private navCtrl: NavController) { }
+  constructor(private navCtrl: NavController, private modalCtrl: ModalController) { 
+    this.currentUser = new CloudUser();
+    addIcons({
+      settings, time, heart, create, chatbubbles, 
+      shieldCheckmark, documentText, logIn, logOut, personAdd
+    });
+  }
 
   handleAction(type: string) {
     switch(type) {
@@ -25,6 +95,24 @@ export class Tab3Page {
         break;
     }
   }
+  async login() {
+    let user : any = new CloudUser();
+     user = await this.currentUser?.login("abctest","1234")
+      if(user?.id){
+        this.currentUser = user;
+      }
+    }
+  logout() {
+   this.currentUser?.logout();
+   this.currentUser = undefined;
+  }
+  async edit(){
+    const modal = await this.modalCtrl.create({
+      component:ModalUserLoginComponent,
+    });
+    modal.present();
+    const {data,role} = await modal.onWillDismiss();
+  }
   goToRecords() {
     this.navCtrl.navigateForward(["tabs","tab3","page-records"]);
     console.log('Navigating to page-records');

+ 4 - 0
myapp/src/lib/user/README.md

@@ -0,0 +1,4 @@
+# 用户逻辑讲解
+
+# 用户登录逻辑
+- ncloud.ts > CloudUser

+ 66 - 0
myapp/src/lib/user/modal-user-edit/modal-user-edit.component.html

@@ -0,0 +1,66 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-title>编辑资料</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="cancel()">
+        <ion-icon name="close-outline" slot="icon-only"></ion-icon>
+      </ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <ion-list>
+    <ion-item>
+      <ion-icon name="person-outline" slot="start"></ion-icon>
+      <ion-input [value]="userData['username']" (ionChange)="userDataChange('username',$event)" label="用户名"
+        label-placement="floating" placeholder="请输入用户名">
+      </ion-input>
+    </ion-item>
+
+    <ion-item>
+      <ion-icon name="id-card-outline" slot="start"></ion-icon>
+      <ion-input [value]="userData['realname']" (ionChange)="userDataChange('realname',$event)" label="真实姓名"
+        label-placement="floating" placeholder="请输入真实姓名">
+      </ion-input>
+    </ion-item>
+
+    <ion-item>
+      <ion-icon name="calendar-outline" slot="start"></ion-icon>
+      <ion-input type="number" [value]="userData['age']" (ionChange)="userDataChange('age',$event)" label="年龄"
+        label-placement="floating" placeholder="请输入年龄">
+      </ion-input>
+    </ion-item>
+
+    <ion-item>
+      <ion-icon name="transgender-outline" slot="start"></ion-icon>
+      <ion-label>性别</ion-label>
+      <ion-segment [value]="userData['gender']" (ionChange)="userDataChange('gender', $event)">
+        <ion-segment-button value="男">
+          <ion-label>男</ion-label>
+        </ion-segment-button>
+        <ion-segment-button value="女">
+          <ion-label>女</ion-label>
+        </ion-segment-button>
+      </ion-segment>
+    </ion-item>
+
+    <ion-item>
+      <ion-icon name="image-outline" slot="start"></ion-icon>
+      <ion-input [value]="userData['avatar']" (ionChange)="userDataChange('avatar',$event)" label="头像URL"
+        label-placement="floating" placeholder="请输入头像URL地址">
+      </ion-input>
+    </ion-item>
+  </ion-list>
+
+  <div class="action-buttons">
+    <ion-button expand="block" (click)="save()" color="primary" shape="round">
+      <ion-icon name="save-outline" slot="start"></ion-icon>
+      保存
+    </ion-button>
+    <ion-button expand="block" (click)="cancel()" color="medium" fill="outline" shape="round">
+      <ion-icon name="close-outline" slot="start"></ion-icon>
+      取消
+    </ion-button>
+  </div>
+</ion-content>

+ 27 - 0
myapp/src/lib/user/modal-user-edit/modal-user-edit.component.scss

@@ -0,0 +1,27 @@
+/* modal-user-edit.component.scss */
+ion-content {
+    --padding-bottom: 80px;
+}
+
+.action-buttons {
+    margin-top: 32px;
+
+    ion-button {
+        margin-bottom: 16px;
+    }
+}
+
+ion-segment {
+    width: 100%;
+    max-width: 200px;
+    margin-left: auto;
+}
+
+ion-item {
+    --padding-start: 0;
+
+    ion-icon {
+        margin-right: 16px;
+        color: var(--ion-color-medium);
+    }
+}

+ 22 - 0
myapp/src/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
myapp/src/lib/user/modal-user-edit/modal-user-edit.component.ts

@@ -0,0 +1,65 @@
+import { Component, OnInit } from '@angular/core';
+import {
+  IonHeader, IonToolbar, IonTitle, IonContent,
+  IonButton, IonInput, IonItem, IonList, IonIcon,
+  IonSegment, IonSegmentButton, IonLabel, IonButtons
+} from '@ionic/angular/standalone';
+import { ModalController } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+import { addIcons } from 'ionicons';
+import {
+  closeOutline, saveOutline, personOutline,
+  idCardOutline, calendarOutline, transgenderOutline,
+  imageOutline
+} from 'ionicons/icons';
+
+@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,
+    IonButton, IonInput, IonItem, IonList, IonIcon,
+    IonSegment, IonSegmentButton, IonLabel, IonButtons
+  ],
+})
+export class ModalUserEditComponent implements OnInit {
+  currentUser: CloudUser | undefined;
+  userData: any = {};
+
+  constructor(private modalCtrl: ModalController) {
+    this.currentUser = new CloudUser();
+    this.userData = { ...this.currentUser.data };
+    addIcons({
+      closeOutline, saveOutline, personOutline,
+      idCardOutline, calendarOutline, transgenderOutline,
+      imageOutline
+    });
+  }
+
+  userDataChange(key: string, ev: any) {
+    let value = ev?.detail?.value;
+    if (value) {
+      this.userData[key] = value;
+    }
+  }
+
+  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");
+  }
+}

+ 36 - 0
myapp/src/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
myapp/src/lib/user/modal-user-login/modal-user-login.component.scss


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

+ 92 - 0
myapp/src/lib/user/modal-user-login/modal-user-login.component.ts

@@ -0,0 +1,92 @@
+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
+}

+ 62 - 0
myapp/src/lib/user/page-mine/page-mine.component.html

@@ -0,0 +1,62 @@
+<ion-content [fullscreen]="true" class="ion-padding">
+  <ion-header [translucent]="true">
+    <ion-toolbar color="primary">
+      <ion-title>我的资料</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+  <div class="profile-section">
+    @if(currentUser?.id){
+    <!-- 已登录状态 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-avatar class="profile-avatar">
+          <img [src]="currentUser?.get('avatar') || 'assets/icon/avatar-default.png'" alt="用户头像" />
+        </ion-avatar>
+        <ion-card-title class="ion-text-center">
+          {{currentUser?.get("username")}}
+        </ion-card-title>
+        <ion-card-subtitle class="ion-text-center">
+          {{currentUser?.get('realname') || '未设置姓名'}}
+        </ion-card-subtitle>
+      </ion-card-header>
+
+      <ion-card-content>
+        <ion-list lines="none">
+          <ion-item>
+            <ion-icon name="person-outline" slot="start"></ion-icon>
+            <ion-label>性别</ion-label>
+            <ion-note slot="end">{{currentUser?.get('gender') || '未设置'}}</ion-note>
+          </ion-item>
+          <ion-item>
+            <ion-icon name="calendar-outline" slot="start"></ion-icon>
+            <ion-label>年龄</ion-label>
+            <ion-note slot="end">{{currentUser?.get('age') || '未设置'}}</ion-note>
+          </ion-item>
+        </ion-list>
+
+        <ion-button expand="block" (click)="edit()" color="primary">
+          <ion-icon name="create-outline" slot="start"></ion-icon>
+          编辑资料
+        </ion-button>
+        <ion-button expand="block" (click)="logout()" color="danger" fill="outline">
+          <ion-icon name="log-out-outline" slot="start"></ion-icon>
+          登出
+        </ion-button>
+      </ion-card-content>
+    </ion-card>
+    }
+    @if(!currentUser?.id){
+    <!-- 未登录状态 -->
+    <div class="login-prompt">
+      <ion-icon name="person-circle-outline" class="login-icon"></ion-icon>
+      <h2>您还未登录</h2>
+      <p>登录后可以保存您的个人资料</p>
+      <ion-button expand="block" (click)="login()" color="primary">
+        <ion-icon name="log-in-outline" slot="start"></ion-icon>
+        登录用户 abc 1234
+      </ion-button>
+    </div>
+    }
+  </div>
+</ion-content>

+ 36 - 0
myapp/src/lib/user/page-mine/page-mine.component.scss

@@ -0,0 +1,36 @@
+/* page-mine.component.scss */
+.profile-section {
+    max-width: 600px;
+    margin: 0 auto;
+}
+
+.profile-avatar {
+    width: 100px;
+    height: 100px;
+    margin: 0 auto 16px;
+}
+
+.login-prompt {
+    text-align: center;
+    padding: 40px 20px;
+
+    .login-icon {
+        font-size: 80px;
+        color: var(--ion-color-medium);
+        margin-bottom: 20px;
+    }
+
+    h2 {
+        font-size: 1.5rem;
+        margin-bottom: 8px;
+    }
+
+    p {
+        color: var(--ion-color-medium);
+        margin-bottom: 30px;
+    }
+}
+
+ion-button {
+    margin-top: 16px;
+}

+ 22 - 0
myapp/src/lib/user/page-mine/page-mine.component.spec.ts

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

+ 61 - 0
myapp/src/lib/user/page-mine/page-mine.component.ts

@@ -0,0 +1,61 @@
+import { Component, OnInit } from '@angular/core';
+import { CloudUser } from 'src/lib/ncloud';
+import { ModalUserEditComponent } from '../modal-user-edit/modal-user-edit.component';
+import { ModalController } from "@ionic/angular/standalone";
+import {
+  IonContent, IonHeader, IonTitle, IonToolbar, IonCard,
+  IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent,
+  IonButton, IonAvatar, IonList, IonItem, IonLabel, IonNote, IonIcon
+} from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import {
+  personOutline, calendarOutline, createOutline,
+  logOutOutline, logInOutline, personCircleOutline
+} from 'ionicons/icons';
+
+@Component({
+  selector: 'app-page-mine',
+  templateUrl: './page-mine.component.html',
+  styleUrls: ['./page-mine.component.scss'],
+  standalone: true,
+  imports: [
+    IonContent, IonHeader, IonTitle, IonToolbar, IonCard,
+    IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent,
+    IonButton, IonAvatar, IonList, IonItem, IonLabel, IonNote, IonIcon
+  ]
+})
+export class PageMineComponent implements OnInit {
+  currentUser: CloudUser | undefined;
+
+  constructor(private modalCtrl: ModalController) {
+    this.currentUser = new CloudUser();
+    addIcons({
+      personOutline, calendarOutline, createOutline,
+      logOutOutline, logInOutline, personCircleOutline
+    });
+  }
+
+  async edit() {
+    const modal = await this.modalCtrl.create({
+      component: ModalUserEditComponent,
+    });
+    modal.present();
+
+    const { data, role } = await modal.onWillDismiss();
+  }
+
+  async login() {
+    let user: any = new CloudUser();
+    user = await user?.login("abctest", "1234");
+    if (user?.id) {
+      this.currentUser = user;
+    }
+  }
+
+  logout() {
+    this.currentUser?.logout();
+    this.currentUser = undefined;
+  }
+
+  ngOnInit() { }
+}

+ 240 - 0
myapp/src/lib/user/token-guard/token.guard.ts

@@ -0,0 +1,240 @@
+/**
+@desc
+请您帮我设计一个ionic/angular项目的TokenGuard路由守卫,检查localStorage是否有token值。若不存在,通过dom构建ui交互提示用户填写token(不使用modal和angular逻辑)。若存在token或填写后,则调用接口:
+curl -X GET \
+  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
+  -H "X-Parse-Session-Token: r:pnktnjyb996sj4p156gjtp4im" \
+  https://YOUR.PARSE-SERVER.HERE/parse/users/me 
+可用fetch请求验证token是否正常。
+若不正常,提示错误请重新填写直到填写了有效token。
+若正常则返回true。
+接口地址为https://server.fmode.cn/parse 应用id为 ncloudmaster。
+
+@example 路由守卫使用
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+import { TokenGuard } from './guards/token.guard';
+
+const routes: Routes = [
+  {
+    path: 'protected',
+    loadChildren: () => import('./protected/protected.module').then(m => m.ProtectedPageModule),
+    canActivate: [TokenGuard]
+  },
+  // 其他路由...
+];
+
+@NgModule({
+  imports: [RouterModule.forRoot(routes)],
+  exports: [RouterModule]
+})
+export class AppRoutingModule {}
+
+ */
+
+// src/app/guards/token.guard.ts
+import { Injectable } from '@angular/core';
+import { CanActivate, Router } from '@angular/router';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class TokenGuard implements CanActivate {
+  private readonly PARSE_SERVER_URL = 'https://server.fmode.cn/parse';
+  private readonly APPLICATION_ID = 'ncloudmaster';
+
+  constructor(
+    private http: HttpClient,
+    private router: Router
+  ) {}
+
+  async canActivate(): Promise<boolean> {
+    let token = localStorage.getItem('token');
+
+    if (!token) {
+      token = await this.showTokenPrompt();
+      if (!token) {
+        this.router.navigate(['/login']);
+        return false;
+      }
+    }
+
+    const isValid = await this.validateToken(token);
+    if (!isValid) {
+      localStorage.removeItem('token');
+      return this.canActivate();
+    }
+
+    localStorage.setItem('token', token);
+    return true;
+  }
+
+  private async validateToken(token: string): Promise<boolean> {
+    const headers = new HttpHeaders({
+      'X-Parse-Application-Id': this.APPLICATION_ID,
+      'X-Parse-Session-Token': token
+    });
+
+    try {
+      const response: any = await this.http.get(
+        `${this.PARSE_SERVER_URL}/users/me`,
+        { headers }
+      ).toPromise();
+      return !!response?.objectId;
+    } catch (error) {
+      return false;
+    }
+  }
+
+  private showTokenPrompt(): Promise<string | null> {
+    return new Promise((resolve) => {
+      // 创建遮罩层
+      const overlay = document.createElement('div');
+      overlay.style.position = 'fixed';
+      overlay.style.top = '0';
+      overlay.style.left = '0';
+      overlay.style.width = '100%';
+      overlay.style.height = '100%';
+      overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
+      overlay.style.display = 'flex';
+      overlay.style.justifyContent = 'center';
+      overlay.style.alignItems = 'center';
+      overlay.style.zIndex = '1000';
+
+      // 创建对话框
+      const dialog = document.createElement('div');
+      dialog.style.backgroundColor = 'white';
+      dialog.style.padding = '20px';
+      dialog.style.borderRadius = '8px';
+      dialog.style.width = '80%';
+      dialog.style.maxWidth = '400px';
+
+      // 创建标题
+      const title = document.createElement('h3');
+      title.textContent = '请输入 Token';
+      title.style.marginTop = '0';
+      title.style.color = "black";
+      dialog.appendChild(title);
+
+      // 创建描述
+      // 使用以下指令获取token
+      
+      const descEl:HTMLElement = document.createElement("div");
+      descEl.innerHTML = `获取token方法:<br>
+      1.登录<a href="https://ai.fmode.cn" target="_blank">https://ai.fmode.cn</a><br>
+      2.按F12进入调试——打开控制台Console<br>
+      3.输入指令:<br>
+      <span style="color:blue;">JSON.parse(localStorage.getItem("Parse/ncloudmaster/currentUser"))?.sessionToken</span><br>
+      4.复制字符串内容,形如:<br>
+      <span style="color:red">r:xxxxxxxxxxxxx</span>
+      `
+      descEl.style.color = "black"
+      dialog.appendChild(descEl);
+
+      // 创建错误消息容器
+      const errorMsg = document.createElement('div');
+      errorMsg.style.color = 'red';
+      errorMsg.style.minHeight = '20px';
+      errorMsg.style.margin = '10px 0';
+      dialog.appendChild(errorMsg);
+
+      // 创建输入框
+      const input = document.createElement('input');
+      input.type = 'text';
+      input.placeholder = '请输入您的 Token';
+      input.style.width = '100%';
+      input.style.padding = '10px';
+      input.style.marginBottom = '15px';
+      input.style.boxSizing = 'border-box';
+      dialog.appendChild(input);
+
+      // 创建按钮容器
+      const buttonContainer = document.createElement('div');
+      buttonContainer.style.display = 'flex';
+      buttonContainer.style.justifyContent = 'flex-end';
+      buttonContainer.style.gap = '10px';
+
+      // 创建提交按钮
+      const submitBtn = document.createElement('button');
+      submitBtn.textContent = '提交';
+      submitBtn.style.padding = '8px 16px';
+      submitBtn.style.backgroundColor = '#3880ff';
+      submitBtn.style.color = 'white';
+      submitBtn.style.border = 'none';
+      submitBtn.style.borderRadius = '4px';
+      submitBtn.disabled = true;
+
+      // 创建取消按钮
+      const cancelBtn = document.createElement('button');
+      cancelBtn.textContent = '取消';
+      cancelBtn.style.padding = '8px 16px';
+      cancelBtn.style.backgroundColor = '#eb445a';
+      cancelBtn.style.color = 'white';
+      cancelBtn.style.border = 'none';
+      cancelBtn.style.borderRadius = '4px';
+
+      // 添加按钮到容器
+      buttonContainer.appendChild(cancelBtn);
+      buttonContainer.appendChild(submitBtn);
+      dialog.appendChild(buttonContainer);
+
+      // 添加到遮罩层
+      overlay.appendChild(dialog);
+      document.body.appendChild(overlay);
+
+      // 自动聚焦输入框
+      input.focus();
+
+      // 输入验证
+      input.addEventListener('input', () => {
+        submitBtn.disabled = !input.value.trim();
+      });
+
+      // 取消按钮事件
+      const cleanup = () => {
+        document.body.removeChild(overlay);
+      };
+
+      cancelBtn.addEventListener('click', () => {
+        cleanup();
+        resolve(null);
+      });
+
+      // 提交按钮事件
+      submitBtn.addEventListener('click', async () => {
+        const token = input.value.trim();
+        if (!token) return;
+
+        const isValid = await this.validateToken(token);
+        if (isValid) {
+          cleanup();
+          resolve(token);
+        } else {
+          errorMsg.textContent = 'Token 无效,请重新输入';
+          input.value = '';
+          submitBtn.disabled = true;
+          input.focus();
+        }
+      });
+
+      // 回车键提交
+      input.addEventListener('keypress', async (e) => {
+        if (e.key === 'Enter' && input.value.trim()) {
+          const token = input.value.trim();
+          const isValid = await this.validateToken(token);
+          
+          if (isValid) {
+            cleanup();
+            resolve(token);
+          } else {
+            errorMsg.textContent = 'Token 无效,请重新输入';
+            input.value = '';
+            submitBtn.disabled = true;
+            input.focus();
+          }
+        }
+      });
+    });
+  }
+}