Parcourir la source

登录与注册 编译有误

15949524127 il y a 1 an
Parent
commit
65dd792a3c

+ 77 - 0
src/app/tab4/tab4.page.html

@@ -0,0 +1,77 @@
+<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-card>
+    <!-- 未登录 -->
+     @if(!currentUser?.id){
+        <ion-content>
+          <ion-card class="login-card">
+              <ion-card-header>
+                  <ion-card-title>请登录</ion-card-title>
+                  <ion-card-subtitle>暂无信息</ion-card-subtitle>
+                  <ion-card-content>欢迎来到“智养人生”!在这里,我们为您提供科学、全面的健康管理方案,帮助您实现身心的和谐与平衡。无论是饮食、运动还是心理调适,我们的专业团队将为您量身定制个性化的健康计划。让我们一起探索智慧养生的奥秘,开启健康新生活!登录后,您将踏上智养之旅,发现更好的自己。欢迎您的加入,智养人生,从此刻开始!</ion-card-content>
+              </ion-card-header>
+              <div class="image-container">
+                <img src="https://app.fmode.cn/dev/jxnu/202226701019/logo.png" alt="健康主题图片" class="responsive-image">
+            </div>
+          </ion-card>
+      </ion-content>
+      }
+        <!-- 已登录 -->
+     @if(currentUser?.id){
+      <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>
+  @if(currentUser?.id){
+    <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" (click)="goToAvatar()" color="success">前往生成</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>
+
+    <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>

+ 125 - 0
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
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();
+  });
+});

+ 72 - 0
src/app/tab4/tab4.page.ts

@@ -0,0 +1,72 @@
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController } from '@ionic/angular/standalone';
+
+
+import { Router } from '@angular/router';
+import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
+import { CloudUser } from 'src/lib/ncloud';
+import { openUserEditModal } from 'src/lib/user/modal-user-edit/modal-user-edit.component';
+
+@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,
+  ],
+})
+export class Tab4Page {
+  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;
+}
+}

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

@@ -21,6 +21,11 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../tab3/tab3.page').then((m) => m.Tab3Page),
       },
+      {
+        path: 'tab4',
+        loadComponent: () =>
+          import('../tab4/tab4.page').then((m) => m.Tab4Page),
+      },
       {
         path: '',
         redirectTo: '/tabs/tab1',

+ 389 - 0
src/lib/ncloud.ts

@@ -0,0 +1,389 @@
+// 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", "ACL"].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 || {};
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+
+    }
+
+    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]); // 作用是将对象转换为JSON字符串
+            if(key=="include"){
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if(key=="where"){
+                paramStr = JSON.stringify(this.queryParams[key]);
+
+            }
+            if(queryStr) {
+                url += `${key}=${paramStr}`;
+            }else{
+                url += `&${key}=${paramStr}`;
+            }
+        })
+
+        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
+        let list = json?.results || []
+        let objList = list.map((item:any)=>this.dataToObj(item))
+        return objList || [];
+    }
+
+    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;
+    }
+}

+ 29 - 0
src/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
src/lib/user/modal-user-edit/modal-user-edit.component.scss


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

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

@@ -0,0 +1,66 @@
+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-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
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
src/lib/user/modal-user-login/modal-user-login.component.scss


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

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

@@ -0,0 +1,96 @@
+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) {
+    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
+}

+ 3 - 0
tsconfig.json

@@ -3,6 +3,9 @@
   "compileOnSave": false,
   "compilerOptions": {
     "baseUrl": "./src",
+    "paths": {
+      "src/*": ["*"]
+    },
     "outDir": "./dist/out-tsc",
     "forceConsistentCasingInFileNames": true,
     "strict": true,