Browse Source

add:user-login&logout

csdn1233 3 months ago
parent
commit
a247eb3fb7

+ 5 - 2
AIart-app/src/app/app.component.ts

@@ -1,12 +1,15 @@
 import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
 import { IonApp, IonRouterOutlet } from '@ionic/angular/standalone';
 
+
 @Component({
   selector: 'app-root',
   templateUrl: 'app.component.html',
   standalone: true,
-  imports: [IonApp, IonRouterOutlet],
+  imports: [IonApp, IonRouterOutlet, FormsModule],
 })
 export class AppComponent {
-  constructor() {}
+  constructor() { }
+
 }

+ 5 - 1
AIart-app/src/app/interest-search/interest-search.component.html

@@ -1,5 +1,8 @@
 <ion-header [translucent]="true">
   <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button default-href="/tabs/tab1" style="color:black;"></ion-back-button>
+    </ion-buttons>
     <ion-title style="font-family: 'Courier New', Courier, monospace;">
       <span style="font-weight: bold;">调查问卷</span>
     </ion-title>
@@ -66,6 +69,7 @@
   </ion-list>
   <div style="display: flex;justify-content: space-between;align-items: center;margin: 0 15px 0 15px;">
     <ion-button style="width: 45%;">保存</ion-button>
-    <ion-button style="width: 45%;">提交</ion-button>
+    <ion-button id="yes/no" style="width: 45%;">提交</ion-button>
+    <ion-alert trigger="yes/no" header="是否确定提交" [buttons]="alertButtons"></ion-alert>
   </div>
 </ion-content>

+ 3 - 2
AIart-app/src/app/interest-search/interest-search.component.ts

@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core';
-import { IonTextarea, IonCheckbox, IonList, IonButton, IonContent, IonHeader, IonInput, IonTitle, IonToolbar, IonItem, IonLabel, IonRadioGroup, IonRadio, IonDatetimeButton, IonDatetime, IonModal } from '@ionic/angular/standalone';
+import { IonTextarea, IonCheckbox, IonList, IonButton, IonContent, IonHeader, IonInput, IonTitle, IonToolbar, IonItem, IonLabel, IonRadioGroup, IonRadio, IonDatetimeButton, IonDatetime, IonModal, IonAlert, IonBackButton, IonButtons } from '@ionic/angular/standalone';
 
 @Component({
   selector: 'app-interest-search',
@@ -8,7 +8,7 @@ import { IonTextarea, IonCheckbox, IonList, IonButton, IonContent, IonHeader, Io
   standalone: true,
   imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonTextarea, IonInput,
     IonList, IonItem, IonLabel, IonCheckbox, IonRadioGroup, IonRadio, IonDatetimeButton,
-    IonDatetime, IonModal
+    IonDatetime, IonModal, IonAlert, IonBackButton, IonButtons,
   ],
 })
 export class InterestSearchComponent implements OnInit {
@@ -16,6 +16,7 @@ export class InterestSearchComponent implements OnInit {
   constructor() {
   }
 
+  alertButtons = ['确定'];
   ngOnInit() { }
 
 }

+ 3 - 2
AIart-app/src/app/tab5/tab5.page.html

@@ -26,8 +26,9 @@
           </div>
         </div>
       </div>
-      <div style="display: flex;align-items: center;margin-right: 10px;">
-        <ion-icon name="chevron-forward-outline"></ion-icon>
+      <div style="display: flex;align-items: center;margin-right: 20px;">
+        <ion-icon name="chevron-forward-outline" class="rounded-rectangle" (click)="goUserLogin()"
+          style="width: 25px;height: 25px;"></ion-icon>
       </div>
     </div>
     <div style="display: flex;align-items: center;justify-content: center;gap: 40px;

+ 5 - 1
AIart-app/src/app/tab5/tab5.page.ts

@@ -1,6 +1,7 @@
 import { Component } from '@angular/core';
 import { IonHeader, IonToolbar, IonTitle, IonContent, IonSearchbar, IonIcon } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
+import { Router } from '@angular/router';
 
 @Component({
   selector: 'app-tab5',
@@ -11,5 +12,8 @@ import { ExploreContainerComponent } from '../explore-container/explore-containe
     IonSearchbar, IonIcon],
 })
 export class tab5Page {
-  constructor() { }
+  constructor(private router: Router) { }
+  goUserLogin() {
+    this.router.navigate(['/tabs/user-login'])
+  }
 }

+ 5 - 0
AIart-app/src/app/tabs/tabs.routes.ts

@@ -51,6 +51,11 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../interest-search/interest-search.component').then((m) => m.InterestSearchComponent),
       },
+      {
+        path: 'user-login',
+        loadComponent: () =>
+          import('../user-login/user-login.component').then((m) => m.UserLoginComponent),
+      },
       {
         path: '',
         redirectTo: '/tabs/tab1',

+ 68 - 0
AIart-app/src/app/user-login/user-login.component.html

@@ -0,0 +1,68 @@
+<ion-header [translucent]="true">
+  <ion-toolbar class="custom-toolbar">
+    <ion-buttons slot="start">
+      <ion-back-button default-href="/tabs/tab5" style="color:black;"></ion-back-button>
+    </ion-buttons>
+    @if(!currentUser?.id){
+    <ion-title class="custom-title">
+      用户登录注册
+    </ion-title>
+    }
+    @if(currentUser?.id){
+    <ion-title class="custom-title">
+      个人信息
+    </ion-title>
+    }
+  </ion-toolbar>
+</ion-header>
+<ion-content [fullscreen]="true" style="padding-top: 3px;">
+
+  <!-- 用户登录状态 -->
+  <ion-card style="height: 90%;width: 90%;">
+    <!-- 未登录 -->
+    @if(!currentUser?.id){
+    <ion-card-header>
+      <ion-card-title>请登录</ion-card-title>
+      <ion-card-subtitle>暂无信息</ion-card-subtitle>
+    </ion-card-header>
+    }
+    <!-- 已登录 -->
+    @if(currentUser?.id){
+    <ion-card-header class="card-header">
+      <img [src]="currentUser?.get('avatar')|| '../../assets/image/doctor7.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>
+  @if(currentUser?.id){
+  <ion-card class="memo-card">
+    <h2 class="memo-title">健康备忘录</h2>
+    <p class="memo-description">写下您问诊的医生名或者心动的科普知识,便于您下次查找(点击标签可删除)</p>
+    <edit-tag (onTagChange)="setTagsValue($event)"></edit-tag>
+
+    <h2 class="memo-title">收藏夹</h2>
+    <ul class="tag-list">
+      @for(tag of editTags; track tag;){
+      <li class="tag-item">{{tag}}</li>
+      }
+    </ul>
+  </ion-card>
+  }
+</ion-content>

+ 14 - 0
AIart-app/src/app/user-login/user-login.component.scss

@@ -0,0 +1,14 @@
+.custom-toolbar {
+    --background: rgba(255, 255, 255, 0.8);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 0;
+}
+
+.custom-title {
+    font-size: 17px;
+    color: #000000;
+    text-align: center;
+    margin: 0;
+}

+ 22 - 0
AIart-app/src/app/user-login/user-login.component.spec.ts

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

+ 64 - 0
AIart-app/src/app/user-login/user-login.component.ts

@@ -0,0 +1,64 @@
+import { Component, OnInit } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonButtons, IonBackButton } from '@ionic/angular/standalone';
+import { EditTagComponent } from '../edit-tag/edit-tag.component';
+import { CloudUser } from 'src/lib/ncloud';
+import { openUserLoginModal } from 'src/lib/user/model-user-login/model-user-login.component';
+import { openUserEditModal } from 'src/lib/user/model-user-edit/model-user-edit.component';
+
+@Component({
+  selector: 'app-user-login',
+  templateUrl: './user-login.component.html',
+  styleUrls: ['./user-login.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent,
+    IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle,
+    EditTagComponent, IonButtons, IonBackButton,
+  ],
+})
+export class UserLoginComponent implements OnInit {
+  ngOnInit(): void {
+
+  }
+  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();
+  }
+
+  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;
+  }
+
+}

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


+ 379 - 0
AIart-app/src/lib/ncloud.ts

@@ -0,0 +1,379 @@
+
+// CloudObject.ts
+export class CloudObject {
+    className: string;
+    id: string | null = null;
+    createdAt: any;
+    updatedAt: any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = null;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    queryParams: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    include(...fileds: string[]) {
+        this.queryParams["include"] = fileds;
+    }
+    greaterThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        this.queryParams["where"][key] = value;
+    }
+
+    async get(id: string) {
+        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        return json || {};
+    }
+
+    async find() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        let queryStr = ``
+        Object.keys(this.queryParams).forEach(key => {
+            let paramStr = JSON.stringify(this.queryParams[key]);
+            if (key == "include") {
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if (queryStr) {
+                url += `${key}=${paramStr}`;
+            } else {
+                url += `&${key}=${paramStr}`;
+            }
+        })
+        // if (Object.keys(this.queryParams["where"]).length) {
+
+        // }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item: any) => this.dataToObj(item))
+        return objList || [];
+    }
+
+
+    async first() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists: any): CloudObject {
+        let existsObject = new CloudObject(this.className);
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}
+
+// 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;
+    }
+}

+ 33 - 0
AIart-app/src/lib/user/model-user-edit/model-user-edit.component.html

@@ -0,0 +1,33 @@
+<!-- 用户登录状态 -->
+<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
AIart-app/src/lib/user/model-user-edit/model-user-edit.component.scss


+ 22 - 0
AIart-app/src/lib/user/model-user-edit/model-user-edit.component.spec.ts

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

+ 63 - 0
AIart-app/src/lib/user/model-user-edit/model-user-edit.component.ts

@@ -0,0 +1,63 @@
+import { Component, OnInit } from '@angular/core';
+import { IonButton, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonContent, IonHeader, IonInput, IonItem, IonLabel, IonSegment, IonSegmentButton, IonTitle, IonToolbar, ModalController } from '@ionic/angular/standalone';
+import { CloudUser } from '../../ncloud';
+
+@Component({
+  selector: 'model-user-edit',
+  templateUrl: './model-user-edit.component.html',
+  styleUrls: ['./model-user-edit.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent,
+    IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, IonInput, IonItem,
+    IonSegment, IonSegmentButton, IonLabel,
+  ],
+})
+export class ModelUserEditComponent 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: ModelUserEditComponent,
+    breakpoints: [0.7, 1.0],
+    initialBreakpoint: 0.7
+  });
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+  return null
+}

+ 39 - 0
AIart-app/src/lib/user/model-user-login/model-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
AIart-app/src/lib/user/model-user-login/model-user-login.component.scss


+ 22 - 0
AIart-app/src/lib/user/model-user-login/model-user-login.component.spec.ts

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

+ 91 - 0
AIart-app/src/lib/user/model-user-login/model-user-login.component.ts

@@ -0,0 +1,91 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { IonButton, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonContent, IonHeader, IonInput, IonItem, IonLabel, IonSegment, IonSegmentButton, IonTitle, IonToolbar, ModalController } from '@ionic/angular/standalone';
+import { CloudUser } from '../../ncloud';
+import { ModalUserLoginComponent } from 'fmode-ng';
+
+@Component({
+  selector: 'model-user-login',
+  templateUrl: './model-user-login.component.html',
+  styleUrls: ['./model-user-login.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent,
+    IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, IonInput, IonItem,
+    IonSegment, IonSegmentButton, IonLabel,
+  ],
+})
+export class ModelUserLoginComponent 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: ModelUserLoginComponent,
+    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
+}