فهرست منبع

feat: new user login

5230254 2 ماه پیش
والد
کامیت
ce760bd70a
38فایلهای تغییر یافته به همراه1085 افزوده شده و 13 حذف شده
  1. 9 0
      src/app/app-routing.module.ts
  2. 17 0
      src/app/login/login-routing.module.ts
  3. 17 0
      src/app/login/login.module.ts
  4. 25 0
      src/app/login/login.page.html
  5. 42 0
      src/app/login/login.page.scss
  6. 63 0
      src/app/login/login.page.ts
  7. 17 0
      src/app/register/register-routing.module.ts
  8. 17 0
      src/app/register/register.module.ts
  9. 30 0
      src/app/register/register.page.html
  10. 42 0
      src/app/register/register.page.scss
  11. 61 0
      src/app/register/register.page.ts
  12. 45 11
      src/app/tab3/tab3.page.html
  13. 73 0
      src/app/tab3/tab3.page.scss
  14. 24 2
      src/app/tab3/tab3.page.ts
  15. 2 0
      src/app/tabs/tabs-routing.module.ts
  16. 100 0
      src/modules/user/README.md
  17. 17 0
      src/modules/user/auth.guard.spec.ts
  18. 11 0
      src/modules/user/auth.guard.ts
  19. 17 0
      src/modules/user/edit-info/edit-info-routing.module.ts
  20. 20 0
      src/modules/user/edit-info/edit-info.module.ts
  21. 47 0
      src/modules/user/edit-info/edit-info.page.html
  22. 0 0
      src/modules/user/edit-info/edit-info.page.scss
  23. 17 0
      src/modules/user/edit-info/edit-info.page.spec.ts
  24. 58 0
      src/modules/user/edit-info/edit-info.page.ts
  25. 17 0
      src/modules/user/login/login-routing.module.ts
  26. 20 0
      src/modules/user/login/login.module.ts
  27. 33 0
      src/modules/user/login/login.page.html
  28. 0 0
      src/modules/user/login/login.page.scss
  29. 17 0
      src/modules/user/login/login.page.spec.ts
  30. 93 0
      src/modules/user/login/login.page.ts
  31. 17 0
      src/modules/user/mine/mine-routing.module.ts
  32. 20 0
      src/modules/user/mine/mine.module.ts
  33. 24 0
      src/modules/user/mine/mine.page.html
  34. 0 0
      src/modules/user/mine/mine.page.scss
  35. 17 0
      src/modules/user/mine/mine.page.spec.ts
  36. 28 0
      src/modules/user/mine/mine.page.ts
  37. 14 0
      src/modules/user/user-routing.module.ts
  38. 14 0
      src/modules/user/user.module.ts

+ 9 - 0
src/app/app-routing.module.ts

@@ -10,6 +10,15 @@ const routes: Routes = [
     path: 'detail',
     loadChildren: () => import('./detail-page/detail-page.module').then( m => m.DetailPagePageModule)
   },
+  {
+    path: 'login',
+    loadChildren: () => import('./login/login.module').then(m => m.LoginPageModule)
+  },
+  // 添加:
+  {
+    path: 'user',
+    loadChildren: () => import('../modules/user/user.module').then(m => m.UserModule)
+  }
 ];
 @NgModule({
   imports: [

+ 17 - 0
src/app/login/login-routing.module.ts

@@ -0,0 +1,17 @@
+
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+import { LoginPage } from './login.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: LoginPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class LoginPageRoutingModule { }

+ 17 - 0
src/app/login/login.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { LoginPage } from './login.page'; // 更新路径引用
+import { LoginPageRoutingModule } from './login-routing.module'; // 更新路径引用
+import { IonicModule } from '@ionic/angular';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    LoginPageRoutingModule,
+    IonicModule
+  ],
+  declarations: [LoginPage]
+})
+export class LoginPageModule {}

+ 25 - 0
src/app/login/login.page.html

@@ -0,0 +1,25 @@
+
+    <ion-header>
+      <ion-toolbar>
+        <ion-title>登录</ion-title>
+      </ion-toolbar>
+    </ion-header>
+    
+    <ion-content>
+      <div class="page">
+        <ion-item>
+          <ion-label position="floating">账号</ion-label>
+          <ion-input type="text" [(ngModel)]="username" id="usernameInput"></ion-input>
+        </ion-item>
+    
+        <ion-item>
+          <ion-label position="floating">密码</ion-label>
+          <ion-input type="password" [(ngModel)]="password" id="passwordInput"></ion-input>
+        </ion-item>
+    
+        <div class="button-container">
+          <ion-button (click)="login()" color="primary" class="login-button">登录</ion-button>
+          <ion-button (click)="goToRegister()" color="secondary" class="register-button">注册</ion-button>
+        </div>
+      </div>
+    </ion-content>

+ 42 - 0
src/app/login/login.page.scss

@@ -0,0 +1,42 @@
+
+.page {
+  width: 100%;
+  height: 95vh;
+  background-image: url('https://gd-hbimg.huaban.com/8ef01a8e3e6f705e646a8a78a9f58d15e48352372a1575-3nDXWC');
+  background-position: center bottom;
+  background-size: auto 62%;
+  background-repeat: no-repeat;
+}
+
+ion-content {
+  padding: 20px;
+
+  ion-item {
+    background-color: #e1d1c2;
+    border-radius: 10px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    margin-bottom: 20px;
+
+    ion-label {
+      color: #7c6c5d;
+    }
+  }
+
+  .button-container {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 20px;
+
+    .login-button,
+    .register-button {
+      width: 45%;
+      background-color: #7c6c5d;
+      color: #fff;
+      border-radius: 5px;
+    }
+  }
+}
+
+.error {
+  border: 1px solid red !important;
+}

+ 63 - 0
src/app/login/login.page.ts

@@ -0,0 +1,63 @@
+import { Component, ViewChild, ElementRef } from '@angular/core';
+import { NavController } from '@ionic/angular';
+import { ToastController } from '@ionic/angular';
+
+@Component({
+  selector: 'app-login',
+  templateUrl: 'login.page.html',
+  styleUrls: ['login.page.scss']
+})
+export class LoginPage {
+  username: string = "";
+  password: string = "";
+  registeredUsername: string = "admin";
+  registeredPassword: string = "123456";
+
+  @ViewChild('usernameInput', { static: false })
+  usernameInput!: ElementRef;
+  @ViewChild('passwordInput', { static: false })
+  passwordInput!: ElementRef;
+
+  constructor(private navCtrl: NavController, private toastController: ToastController) {}
+
+  async login() {
+    if (this.username === "" || this.password === "") {
+      const toast = await this.toastController.create({
+        message: '账号和密码不能为空',
+        duration: 2000,
+        color: 'danger'
+      });
+      toast.present();
+      if (this.username === "") {
+        this.usernameInput.nativeElement.classList.add('error');
+      }
+      if (this.password === "") {
+        this.passwordInput.nativeElement.classList.add('error');
+      }
+    } else {
+      if (this.username === this.registeredUsername && this.password === this.registeredPassword) {
+        console.log('登录成功');
+        // 登录成功后跳转到tab3页面
+        this.navCtrl.navigateForward('/tabs/tab3');
+      } else if (this.username !== this.registeredUsername) {
+        const toast = await this.toastController.create({
+          message: '账号不存在',
+          duration: 2000,
+          color: 'danger'
+        });
+        toast.present();
+      } else {
+        const toast = await this.toastController.create({
+          message: '密码错误',
+          duration: 2000,
+          color: 'danger'
+        });
+        toast.present();
+      }
+    }
+  }
+
+  goToRegister() {
+    this.navCtrl.navigateForward('/register');
+  }
+}

+ 17 - 0
src/app/register/register-routing.module.ts

@@ -0,0 +1,17 @@
+
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+import { RegisterPage } from './register.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: RegisterPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class RegisterPageRoutingModule { }

+ 17 - 0
src/app/register/register.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { RegisterPage } from '../register/register.page'; // 更新路径引用
+import { RegisterPageRoutingModule } from '../register/register-routing.module'; // 更新路径引用
+import { IonicModule } from '@ionic/angular';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    RegisterPageRoutingModule,
+    IonicModule
+  ],
+  declarations: [RegisterPage] // 确保正确引入组件
+})
+export class RegisterPageModule {}

+ 30 - 0
src/app/register/register.page.html

@@ -0,0 +1,30 @@
+
+  <ion-header>
+    <ion-toolbar>
+      <ion-title>注册</ion-title>
+    </ion-toolbar>
+  </ion-header>
+  
+  <ion-content>
+    <div class="page">
+      <ion-item>
+        <ion-label position="floating">账号</ion-label>
+        <ion-input type="text" [(ngModel)]="username" id="usernameInput"></ion-input>
+      </ion-item>
+  
+      <ion-item>
+        <ion-label position="floating">密码</ion-label>
+        <ion-input type="password" [(ngModel)]="password" id="passwordInput"></ion-input>
+      </ion-item>
+  
+      <ion-item>
+        <ion-label position="floating">确认密码</ion-label>
+        <ion-input type="password" [(ngModel)]="confirmPassword" id="confirmPasswordInput"></ion-input>
+      </ion-item>
+  
+      <div class="button-container">
+        <ion-button (click)="register()" color="primary" class="register-button">注册</ion-button>
+        <ion-button (click)="goToLogin()" color="secondary" class="return-button">返回</ion-button>
+      </div>
+    </div>
+  </ion-content>

+ 42 - 0
src/app/register/register.page.scss

@@ -0,0 +1,42 @@
+
+.page {
+  width: 100%;
+  height: 95vh;
+  background-image: url('https://gd-hbimg.huaban.com/36c5ce268a52a63190807832e42aca3250e810b98aad5-oPgyAd');
+  background-position: center bottom;
+  background-size: auto 50%;
+  background-repeat: no-repeat;
+}
+
+ion-content {
+  padding: 20px;
+
+  ion-item {
+    background-color: #e1d1c2;
+    border-radius: 10px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    margin-bottom: 20px;
+
+    ion-label {
+      color: #7c6c5d;
+    }
+  }
+
+  .button-container {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 20px;
+
+    .register-button,
+    .return-button {
+      width: 45%;
+      background-color: #7c6c5d;
+      color: #fff;
+      border-radius: 5px;
+    }
+  }
+}
+
+.error {
+  border: 1px solid red !important;
+}

+ 61 - 0
src/app/register/register.page.ts

@@ -0,0 +1,61 @@
+
+import { Component, ViewChild, ElementRef } from '@angular/core';
+import { NavController } from '@ionic/angular';
+import { ToastController } from '@ionic/angular';
+
+@Component({
+  selector: 'app-register',
+  templateUrl: 'register.page.html',
+  styleUrls: ['register.page.scss']
+})
+export class RegisterPage {
+  username: string = "";
+  password: string = "";
+  confirmPassword: string = "";
+
+  @ViewChild('usernameInput', { static: false })
+  usernameInput!: ElementRef;
+  @ViewChild('passwordInput', { static: false })
+  passwordInput!: ElementRef;
+  @ViewChild('confirmPasswordInput', { static: false })
+  confirmPasswordInput!: ElementRef;
+
+  constructor(private navCtrl: NavController, private toastController: ToastController) {}
+
+  async register() {
+    if (this.username === "" || this.password === "" || this.confirmPassword === "") {
+      const toast = await this.toastController.create({
+        message: '账号、密码和确认密码不能为空',
+        duration: 2000,
+        color: 'danger'
+      });
+      toast.present();
+      if (this.username === "") {
+        this.usernameInput.nativeElement.classList.add('error');
+      }
+      if (this.password === "") {
+        this.passwordInput.nativeElement.classList.add('error');
+      }
+      if (this.confirmPassword === "") {
+        this.confirmPasswordInput.nativeElement.classList.add('error');
+      }
+    } else {
+      if (this.password === this.confirmPassword) {
+        // 模拟注册成功,实际情况应该保存到数据库或其他存储介质中
+        console.log('注册成功:', { username: this.username, password: this.password });
+        this.navCtrl.navigateForward('/login');
+      } else {
+        const toast = await this.toastController.create({
+          message: '密码不一致,请重新输入',
+          duration: 2000,
+          color: 'danger'
+        });
+        toast.present();
+      }
+    }
+  }
+
+  goToLogin() {
+    this.navCtrl.navigateForward('/login');
+  }
+}

+ 45 - 11
src/app/tab3/tab3.page.html

@@ -1,17 +1,51 @@
-<ion-header [translucent]="true">
+<ion-header>
   <ion-toolbar>
-    <ion-title>
-      Tab 3
-    </ion-title>
   </ion-toolbar>
 </ion-header>
 
-<ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">Tab 3</ion-title>
-    </ion-toolbar>
-  </ion-header>
+<ion-content class="my-page-content">
+  <ion-card class="user-card">
+    <ion-card-content class="user-content">
+      <ion-icon name="person-circle-outline" class="user-icon"></ion-icon>
+      <ion-label>昵称未填写</ion-label>
+      <div class="user-stats">
+        <div class="user-row">
+          <div class="user-stat">0</div>
+          <div class="user-stat">0</div>
+          <div class="user-stat">0</div>
+          <div class="user-stat">0</div>
+        </div>
+        <div class="user-text">
+          <div>收藏</div>
+          <div>预约</div>
+          <div>点赞</div>
+          <div>浏览记录</div>
+        </div>
+      </div>
+    </ion-card-content>
+  </ion-card>
 
-  <app-explore-container name="Tab 3 page"></app-explore-container>
+  <ion-list class="menu-list">
+   
+    <ion-item (click)="goToMyProfile()" class="menu-item">
+      <ion-icon name="person-circle-outline" slot="start"></ion-icon>
+      <ion-label>我的资料</ion-label>
+    </ion-item>
+    <ion-item (click)="goToMyAppointments()" class="menu-item">
+      <ion-icon name="calendar-outline" slot="start"></ion-icon>
+      <ion-label>我的预约</ion-label>
+    </ion-item>
+    <ion-item (click)="goToMoreServices()" class="menu-item">
+      <ion-icon name="apps-outline" slot="start"></ion-icon>
+      <ion-label>更多服务</ion-label>
+    </ion-item>
+    <ion-item (click)="goToTaskCenter()" class="menu-item">
+      <ion-icon name="clipboard-outline" slot="start"></ion-icon>
+      <ion-label>任务中心</ion-label>
+    </ion-item>
+    <ion-item (click)="goToSystemSettings()" class="menu-item">
+      <ion-icon name="settings-outline" slot="start"></ion-icon>
+      <ion-label>系统设置</ion-label>
+    </ion-item>
+  </ion-list>
 </ion-content>

+ 73 - 0
src/app/tab3/tab3.page.scss

@@ -0,0 +1,73 @@
+ 
+.my-page-content {
+    
+    background-color: #efe7d8;
+    padding: 20px;
+  
+    .user-card {
+      background-color: #f5e5d6;
+      border-radius: 10px;
+      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+      margin-bottom: 20px;
+  
+      .user-content {
+        padding: 20px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+  
+        .user-icon {
+          font-size: 4rem;
+          color: #333;
+        }
+  
+        .user-stats {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          margin-top: 10px;
+  
+          .user-row {
+            display: flex;
+            justify-content: space-around;
+            width: 100%;
+            margin-bottom: 5px;
+  
+            .user-stat {
+              // font-weight: bold;
+              margin-right: 50.5px;
+            }
+          }
+  
+          .user-text {
+            display: flex;
+            justify-content: space-around;
+            align-items: center;
+            width: 100%;
+            margin-top: 5px;
+  
+            div {
+              font-weight: bold;
+              margin-right: 30px; // 调整文本右移距离
+            }
+          }
+        }
+      }
+    }
+  
+    .menu-list {
+    
+      background-color: #f5e5d6;
+      border-radius: 10px;
+      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  
+      .menu-item {
+        
+        --padding-start: 20px;
+        font-family: '宋体', serif;
+        &:hover {
+          box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+        }
+      }
+    }
+  }

+ 24 - 2
src/app/tab3/tab3.page.ts

@@ -1,5 +1,5 @@
 import { Component } from '@angular/core';
-
+import { NavController } from '@ionic/angular';
 @Component({
   selector: 'app-tab3',
   templateUrl: 'tab3.page.html',
@@ -7,6 +7,28 @@ import { Component } from '@angular/core';
 })
 export class Tab3Page {
 
-  constructor() {}
+  
+  constructor(private navCtrl: NavController) {}
+
+  goToMyProfile() {
+    this.navCtrl.navigateForward('/my-profile'); // 导航到“我的资料”页面
+  }
+
+  goToMyAppointments() {
+    this.navCtrl.navigateForward('/my-appointments'); // 导航到“我的预约”页面
+  }
 
+  goToMoreServices() {
+    this.navCtrl.navigateForward('/more-services'); // 导航到“更多服务”页面
+  }
+
+  goToTaskCenter() {
+    this.navCtrl.navigateForward('/task-center'); // 导航到“任务中心”页面
+  }
+
+  goToSystemSettings() {
+    this.navCtrl.navigateForward('/system-settings'); // 导航到“系统设置”页面
+  }
+  
 }
+

+ 2 - 0
src/app/tabs/tabs-routing.module.ts

@@ -1,6 +1,7 @@
 import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
 import { TabsPage } from './tabs.page';
+import { authGuard } from 'src/modules/user/auth.guard';
 
 const routes: Routes = [
   {
@@ -17,6 +18,7 @@ const routes: Routes = [
       },
       {
         path: 'tab3',
+        canActivate:[authGuard],
         loadChildren: () => import('../tab3/tab3.module').then(m => m.Tab3PageModule)
       },
       {

+ 100 - 0
src/modules/user/README.md

@@ -0,0 +1,100 @@
+# 用户模块
+
+# 功能结构
+- 用户模块
+
+- 模块内功能
+    - 页面
+        - 登录/注册
+            - 用户的注册及登录
+                - 忘记密码
+        - 资料编辑
+            - 文字
+            - 图片编辑
+        - 我的
+            - 登陆状态
+                - 登录
+                    - 会员卡片
+                - 未登录
+                    - 占位卡片
+
+- 模块外功能
+    - 限制访问
+        - 路由守卫
+    - 数据关联
+        - 读取当前用户数据
+            - localStorage
+                - 本地存储
+        - 由XXX用户创建的
+
+
+
+# 安装依赖
+``` bash
+npm i -S parse
+npm i -D @types/parse
+```
+配置 根目录/tsconfig.json
+``` json
+  "compilerOptions": {
+    "allowSyntheticDefaultImports": true,
+  }
+```
+
+# 设置微服务地址+参数
+- app.component.ts 根组件顶部增加
+``` ts
+// 引用Parse JS SDK
+import Parse from "parse";
+Parse.initialize("dev"); // 设置applicationId
+Parse.serverURL = "http://web2023.fmode.cn:9999/parse"; // 设置serverURL
+```
+
+# 复制user整个模块目录
+- 创建/src/modules目录
+- 将案例项目/src/modules/user目录,复制到自己项目/src/modules/user
+
+# 配置全局user路由
+src\app\app-routing.module.ts
+``` ts
+const routes: Routes = [
+  // 添加:
+  {
+    path: 'user',
+    loadChildren: () => import('../modules/user/user.module').then(m => m.UserModule)
+  }
+];
+```
+
+# 配置单页Tabs我的路由
+- src\app\tabs\tabs-routing.module.ts
+``` ts
+// 新增在tab3后面
+    {
+        path: 'mine',
+        loadChildren: () => import('../../modules/user/mine/mine.module').then(m => m.MinePageModule)
+    },
+```
+- src\app\tabs\tabs.page.html
+``` html
+<ion-tab-button tab="mine" href="/tabs/mine">
+      <ion-icon aria-hidden="true" name="person"></ion-icon>
+      <ion-label>我的</ion-label>
+</ion-tab-button>
+```
+
+# 路由守卫 AuthGuard用法
+
+``` ts
+// 引用路由守卫
+import { authGuard } from 'src/modules/user/auth.guard';
+
+
+
+{
+        path: 'tab2',
+        // 此处增加路由守卫
+        canActivate:[authGuard],
+        loadChildren: () => import('../../modules/contact/contact-list/contact-list.module').then(mod => mod.ContactListPageModule)
+    },
+```

+ 17 - 0
src/modules/user/auth.guard.spec.ts

@@ -0,0 +1,17 @@
+import { TestBed } from '@angular/core/testing';
+import { CanActivateFn } from '@angular/router';
+
+import { authGuard } from './auth.guard';
+
+describe('authGuard', () => {
+  const executeGuard: CanActivateFn = (...guardParameters) => 
+      TestBed.runInInjectionContext(() => authGuard(...guardParameters));
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+  });
+
+  it('should be created', () => {
+    expect(executeGuard).toBeTruthy();
+  });
+});

+ 11 - 0
src/modules/user/auth.guard.ts

@@ -0,0 +1,11 @@
+import { CanActivateFn } from '@angular/router';
+import Parse from "parse";
+export const authGuard: CanActivateFn = (route, state) => {
+  let user = Parse.User.current()
+  if(user?.id){
+    return true
+  }else{
+    location.href = "/user/login";
+    return false;
+  }
+};

+ 17 - 0
src/modules/user/edit-info/edit-info-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { EditInfoPage } from './edit-info.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: EditInfoPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class EditInfoPageRoutingModule {}

+ 20 - 0
src/modules/user/edit-info/edit-info.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { EditInfoPageRoutingModule } from './edit-info-routing.module';
+
+import { EditInfoPage } from './edit-info.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    EditInfoPageRoutingModule
+  ],
+  declarations: [EditInfoPage]
+})
+export class EditInfoPageModule {}

+ 47 - 0
src/modules/user/edit-info/edit-info.page.html

@@ -0,0 +1,47 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>资料编辑</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">资料编辑</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>{{currentUser?.get('username')}} - {{currentUser?.id}}</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list>
+        <ion-item>
+          <ion-input label="姓名" type="text" [(ngModel)]="userInfo.name"></ion-input>
+        </ion-item>
+        <ion-item>
+          <ion-input label="手机" type="tel" [(ngModel)]="userInfo.mobile"></ion-input>
+        </ion-item>
+        <ion-item>
+          <ion-select label="性别" [(ngModel)]="userInfo.gender">
+            <ion-select-option value="男">男</ion-select-option>
+            <ion-select-option value="女">女</ion-select-option>
+          </ion-select>
+        </ion-item>
+        <ion-item>
+          <ion-label>生日</ion-label>
+          <ion-datetime-button datetime="birthday"></ion-datetime-button>
+          <ion-modal [keepContentsMounted]="true">
+            <ng-template>
+              <ion-datetime id="birthday" displayFormat="MM/DD/YYYY" [(ngModel)]="userInfo.birthday"></ion-datetime>
+            </ng-template>
+          </ion-modal>
+        </ion-item>
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+
+  <ion-button expand="block" (click)="save()">保存</ion-button>
+  <ion-button expand="block" (click)="cancel()">取消</ion-button>
+</ion-content>

+ 0 - 0
src/modules/user/edit-info/edit-info.page.scss


+ 17 - 0
src/modules/user/edit-info/edit-info.page.spec.ts

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

+ 58 - 0
src/modules/user/edit-info/edit-info.page.ts

@@ -0,0 +1,58 @@
+import { Component, OnInit } from '@angular/core';
+import { NavController } from '@ionic/angular';
+import * as Parse from 'parse';
+
+@Component({
+  selector: 'app-edit-info',
+  templateUrl: './edit-info.page.html',
+  styleUrls: ['./edit-info.page.scss'],
+})
+export class EditInfoPage implements OnInit {
+
+  userInfo: any = {
+    name: '',
+    mobile: '',
+    gender: '',
+    birthday: ''
+  };
+  currentUser:Parse.User|undefined
+  constructor(private navController: NavController) {}
+
+  ngOnInit() {
+    this.currentUser = Parse.User.current();
+    if (this.currentUser) {
+      // 修改uesrInfo赋值逻辑,仅加载被编辑的字段属性值
+      let json = this.currentUser.toJSON();
+      for (const key in json) {
+        if (this.userInfo.hasOwnProperty(key)) {
+          this.userInfo[key] = json[key]
+        }
+      }
+    }
+    console.log(this.userInfo)
+  }
+
+  save() {
+    this.currentUser = Parse.User.current();
+    if (this.currentUser) {
+      console.log(this.userInfo)
+      this.userInfo.birthday = this.userInfo.birthday || new Date()
+      this.userInfo.birthday = new Date(this.userInfo.birthday);
+      for (const key in this.userInfo) {
+        if (this.userInfo.hasOwnProperty(key)) {
+          this.currentUser.set(key, this.userInfo[key]);
+        }
+      }
+      this.currentUser.save().then(() => {
+        this.navController.back();
+      }).catch((error) => {
+        console.error('Error saving user data: ', error);
+      });
+    }
+  }
+
+  cancel() {
+    this.navController.back();
+  }
+
+}

+ 17 - 0
src/modules/user/login/login-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { LoginPage } from './login.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: LoginPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class LoginPageRoutingModule {}

+ 20 - 0
src/modules/user/login/login.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { LoginPageRoutingModule } from './login-routing.module';
+
+import { LoginPage } from './login.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    LoginPageRoutingModule
+  ],
+  declarations: [LoginPage]
+})
+export class LoginPageModule {}

+ 33 - 0
src/modules/user/login/login.page.html

@@ -0,0 +1,33 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>登录/注册</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>登录/注册</ion-card-title>
+    </ion-card-header>
+  
+    <ion-card-content>
+
+      <ion-list [inset]="true">
+        <ion-item>
+          <ion-input [(ngModel)]="username" label="账号" placeholder="请输入用户名"></ion-input>
+        </ion-item>
+        <ion-item>
+          <ion-input [(ngModel)]="password" label="密码" type="password" placeholder="请输入密码"></ion-input>
+        </ion-item>
+      </ion-list>
+     
+    </ion-card-content>
+  
+    <ion-button (click)="login()" fill="clear">登录</ion-button>
+    <ion-button (click)="register()" fill="clear">注册</ion-button>
+  </ion-card>
+
+  <!-- 新增路由返回逻辑,执行back函数 -->
+  <ion-button expand="block" (click)="back()">返回</ion-button>
+</ion-content>

+ 0 - 0
src/modules/user/login/login.page.scss


+ 17 - 0
src/modules/user/login/login.page.spec.ts

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

+ 93 - 0
src/modules/user/login/login.page.ts

@@ -0,0 +1,93 @@
+import { Component, OnInit } from '@angular/core';
+import { AlertController, NavController } from '@ionic/angular';
+import * as Parse from "parse"
+// 引用Router服务
+@Component({
+  selector: 'app-login',
+  templateUrl: './login.page.html',
+  styleUrls: ['./login.page.scss'],
+})
+export class LoginPage implements OnInit {
+
+  username:string = ""
+  password:string = ""
+  constructor(
+    // 新增:Router服务,用于路由跳转
+    private navCtrl:NavController,
+    private alertController:AlertController
+  ) { }
+
+  ngOnInit() {
+  }
+
+  async login(){
+    let user
+    try {
+      user = await Parse.User.logIn(this.username,this.password)
+    } catch (error:any) {
+      let message:string = ""
+      // 新增提示词详情,根据Parse.User.login方法返回的不同英文提示词,增加对应的中文内容转换
+      if(error?.message.indexOf("is required")>-1){
+        message = "必须输入账号或邮箱"
+      }
+      if(error?.message.indexOf("Invalid username")>-1){
+        message = "账号或密码错误,请检查"
+      }
+      this.presentAlert({
+        header:"登录失败",
+        subHeader:"状态码:"+error.code,
+        message:message || error.message
+      })
+    }
+    console.log(user)
+    if(user?.id){
+      this.navCtrl.navigateRoot("/tabs/tab3")
+    }
+  }
+  async register(){
+    let user = new Parse.User()
+    user.set("username",this.username)
+    user.set("password",this.password)
+    try {
+        let result = await user.signUp();
+        console.log(result)
+        if(result?.id){
+          this.navCtrl.back()
+        }
+        // Hooray! Let them use the app now.
+    } catch (error:any) {
+        // 新增提示词详情,根据Parse.User.signUp方法返回的不同英文提示词,增加对应的中文内容转换
+        let message:string = ""
+        if(error?.message.indexOf("already exists")>-1){
+          message = "该账号已存在请修改后重试"
+        }
+        if(error?.message.indexOf("empty")>-1){
+          message = "账号不能为空请输入后重试"
+        }
+        this.presentAlert({
+          header:"注册失败",
+          subHeader:"状态码:"+error.code,
+          message:message || error.message
+        })
+    }
+  }
+
+  async presentAlert(options:{header:string,subHeader:string,message:string}) {
+    const alert = await this.alertController.create({
+      header: options?.header,
+      subHeader: options?.subHeader,
+      message: options?.message,
+      buttons: ['好的'],
+    });
+
+    await alert.present();
+  }
+
+  /**
+   * 返回上级页面函数
+   * @desc
+   */
+  back(){
+    this.navCtrl.back()
+  }
+}

+ 17 - 0
src/modules/user/mine/mine-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { MinePage } from './mine.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: MinePage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class MinePageRoutingModule {}

+ 20 - 0
src/modules/user/mine/mine.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { MinePageRoutingModule } from './mine-routing.module';
+
+import { MinePage } from './mine.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    MinePageRoutingModule
+  ],
+  declarations: [MinePage]
+})
+export class MinePageModule {}

+ 24 - 0
src/modules/user/mine/mine.page.html

@@ -0,0 +1,24 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>我的</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+
+  <ion-card>
+    <img alt="" src="https://ionicframework.com/docs/img/demos/card-media.png" />
+    <ion-card-header>
+      <ion-card-title>{{user?.get("username") || '未登录'}}</ion-card-title>
+      <ion-card-subtitle *ngIf="!user?.id">请您登陆后继续使用</ion-card-subtitle>
+      <ion-card-subtitle *ngIf="user?.id">{{user?.get("name")}}-{{user?.get("gender")}}</ion-card-subtitle>
+    </ion-card-header>
+ 
+    <!-- 新增:根据用户状态,显示登录/登出按钮,执行跳转或登出函数 -->
+    <ion-button *ngIf="!user?.id" fill="clear" routerLink="/user/login">登录</ion-button>
+    <ion-button *ngIf="user?.id" fill="clear" routerLink="/user/edit/info">编辑资料</ion-button>
+    <ion-button *ngIf="user?.id" fill="clear" (click)="logout()">登出</ion-button>
+  </ion-card>
+
+
+</ion-content>

+ 0 - 0
src/modules/user/mine/mine.page.scss


+ 17 - 0
src/modules/user/mine/mine.page.spec.ts

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

+ 28 - 0
src/modules/user/mine/mine.page.ts

@@ -0,0 +1,28 @@
+import { Component, OnInit } from '@angular/core';
+// 由于Parse本身是js库,在ts中加载需要通过 * as Parse转换一下
+import Parse from "parse"
+@Component({
+  selector: 'app-mine',
+  templateUrl: './mine.page.html',
+  styleUrls: ['./mine.page.scss'],
+})
+export class MinePage implements OnInit {
+
+  constructor() {
+   
+  }
+
+  // 由于Parse.User.current()是随着localStorage变化的属性
+  // 为了避免首次复制后用户状态变化,页面不同步,通过get方法实现实时获取
+  user:Parse.User|undefined
+  async ngOnInit() {
+      this.user = await Parse.User.current()
+      setInterval(async ()=>{
+      this.user = await Parse.User.current()
+    },1000)
+  }
+  logout(){
+    Parse.User.logOut();
+  }
+
+}

+ 14 - 0
src/modules/user/user-routing.module.ts

@@ -0,0 +1,14 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+const routes: Routes = [
+    {path: 'login', loadChildren: () => import('./login/login.module').then(mod => mod.LoginPageModule)},
+    {path: 'mine', loadChildren: () => import('./mine/mine.module').then(mod => mod.MinePageModule)},
+    {path: 'edit/info', loadChildren: () => import('./edit-info/edit-info.module').then(mod => mod.EditInfoPageModule)},
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class UserRoutingModule { }

+ 14 - 0
src/modules/user/user.module.ts

@@ -0,0 +1,14 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { UserRoutingModule } from './user-routing.module';
+
+
+@NgModule({
+  declarations: [],
+  imports: [
+    CommonModule,
+    UserRoutingModule
+  ]
+})
+export class UserModule { }