Ver Fonte

Merge branch 'master' of http://git.fmode.cn:3000/0225273/APPmy

0225273 há 4 meses atrás
pai
commit
ca2bc91110
35 ficheiros alterados com 687 adições e 52 exclusões
  1. BIN
      asset/img/customer-avatar.jpg
  2. 89 3
      package-lock.json
  3. 2 0
      package.json
  4. 4 4
      src/app/app-routing.module.ts
  5. 1 0
      src/app/app.module.ts
  6. 8 7
      src/app/feedback/feedback.page.html
  7. 3 3
      src/app/feedback/feedback.page.ts
  8. 23 16
      src/app/login/login.page.html
  9. 80 2
      src/app/login/login.page.ts
  10. 8 7
      src/app/tab2/tab2.page.html
  11. 20 3
      src/app/tab2/tab2.page.scss
  12. 6 6
      src/app/tab3/tab3.page.html
  13. 20 0
      src/app/tab3/tab3.page.scss
  14. 3 1
      src/app/tab4/tab4.page.ts
  15. BIN
      src/assets/img/a.png
  16. BIN
      src/assets/img/b.png
  17. 0 0
      src/assets/img/support-avatar.png
  18. 100 0
      src/modules/user/README.md
  19. 17 0
      src/modules/user/auth.guard.spec.ts
  20. 10 0
      src/modules/user/auth.guard.ts
  21. 17 0
      src/modules/user/edit-info/edit-info-routing.module.ts
  22. 20 0
      src/modules/user/edit-info/edit-info.module.ts
  23. 47 0
      src/modules/user/edit-info/edit-info.page.html
  24. 0 0
      src/modules/user/edit-info/edit-info.page.scss
  25. 17 0
      src/modules/user/edit-info/edit-info.page.spec.ts
  26. 58 0
      src/modules/user/edit-info/edit-info.page.ts
  27. 17 0
      src/modules/user/mine/mine-routing.module.ts
  28. 20 0
      src/modules/user/mine/mine.module.ts
  29. 24 0
      src/modules/user/mine/mine.page.html
  30. 0 0
      src/modules/user/mine/mine.page.scss
  31. 17 0
      src/modules/user/mine/mine.page.spec.ts
  32. 28 0
      src/modules/user/mine/mine.page.ts
  33. 14 0
      src/modules/user/user-routing.module.ts
  34. 14 0
      src/modules/user/user.module.ts
  35. BIN
      src/modules/user/用户模块.xmind

BIN
asset/img/customer-avatar.jpg


+ 89 - 3
package-lock.json

@@ -23,6 +23,7 @@
         "@capacitor/status-bar": "6.0.0",
         "@ionic/angular": "^8.0.0",
         "ionicons": "^7.0.0",
+        "parse": "^5.2.0",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
         "zone.js": "~0.14.2"
@@ -40,6 +41,7 @@
         "@capacitor/cli": "6.1.0",
         "@ionic/angular-toolkit": "^11.0.1",
         "@types/jasmine": "~5.1.0",
+        "@types/parse": "^3.0.9",
         "@typescript-eslint/eslint-plugin": "^6.0.0",
         "@typescript-eslint/parser": "^6.0.0",
         "eslint": "^8.57.0",
@@ -2522,6 +2524,18 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@babel/runtime-corejs3": {
+      "version": "7.24.6",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime-corejs3/-/runtime-corejs3-7.24.6.tgz",
+      "integrity": "sha512-tbC3o8uHK9xMgMsvUm9qGqxVpbv6yborMBLbDteHIc7JDNHsTV0vDMQ5j1O1NXvO+BDELtL9KgoWYaUVIVGt8w==",
+      "dependencies": {
+        "core-js-pure": "^3.30.2",
+        "regenerator-runtime": "^0.14.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
     "node_modules/@babel/template": {
       "version": "7.24.7",
       "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.24.7.tgz",
@@ -5287,6 +5301,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/parse": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmmirror.com/@types/parse/-/parse-3.0.9.tgz",
+      "integrity": "sha512-DGTHygc7krgmNAK8h42giwmAofCd9uv2++RD+zw6OmWI7AEnlTYZwEuWsx22SA2CSMQrZW8P2INHLpQbnQFUng==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/qs": {
       "version": "6.9.15",
       "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.15.tgz",
@@ -7369,6 +7392,16 @@
         "url": "https://opencollective.com/core-js"
       }
     },
+    "node_modules/core-js-pure": {
+      "version": "3.37.1",
+      "resolved": "https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.37.1.tgz",
+      "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==",
+      "hasInstallScript": true,
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
     "node_modules/core-util-is": {
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -7513,6 +7546,12 @@
         "node": ">= 8"
       }
     },
+    "node_modules/crypto-js": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
+      "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+      "optional": true
+    },
     "node_modules/css-loader": {
       "version": "7.1.1",
       "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-7.1.1.tgz",
@@ -10091,6 +10130,11 @@
         "postcss": "^8.1.0"
       }
     },
+    "node_modules/idb-keyval": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/idb-keyval/-/idb-keyval-6.2.1.tgz",
+      "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg=="
+    },
     "node_modules/ieee754": {
       "version": "1.2.1",
       "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
@@ -13382,6 +13426,25 @@
         "node": ">=6"
       }
     },
+    "node_modules/parse": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmmirror.com/parse/-/parse-5.2.0.tgz",
+      "integrity": "sha512-FoD3kcLAQCw/2J1984sl3GUBzbHE2tA9mUcyw/EBWZ46WVZTzV+kjnA5tttXyzN4uodt21wSluzjbGnyLqreKw==",
+      "dependencies": {
+        "@babel/runtime-corejs3": "7.24.6",
+        "idb-keyval": "6.2.1",
+        "react-native-crypto-js": "1.0.0",
+        "uuid": "10.0.0",
+        "ws": "8.17.1",
+        "xmlhttprequest": "1.8.0"
+      },
+      "engines": {
+        "node": ">=18 <21"
+      },
+      "optionalDependencies": {
+        "crypto-js": "4.2.0"
+      }
+    },
     "node_modules/parse-imports": {
       "version": "2.1.1",
       "resolved": "https://registry.npmmirror.com/parse-imports/-/parse-imports-2.1.1.tgz",
@@ -13434,6 +13497,18 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/parse/node_modules/uuid": {
+      "version": "10.0.0",
+      "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz",
+      "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/parse5": {
       "version": "7.1.2",
       "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.1.2.tgz",
@@ -14075,6 +14150,11 @@
       "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
       "dev": true
     },
+    "node_modules/react-native-crypto-js": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/react-native-crypto-js/-/react-native-crypto-js-1.0.0.tgz",
+      "integrity": "sha512-FNbLuG/HAdapQoybeZSoes1PWdOj0w242gb+e1R0hicf3Gyj/Mf8M9NaED2AnXVOX01b2FXomwUiw1xP1K+8sA=="
+    },
     "node_modules/readable-stream": {
       "version": "3.6.2",
       "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -14140,8 +14220,7 @@
     "node_modules/regenerator-runtime": {
       "version": "0.14.1",
       "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
-      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
-      "dev": true
+      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
     },
     "node_modules/regenerator-transform": {
       "version": "0.15.2",
@@ -17326,7 +17405,6 @@
       "version": "8.17.1",
       "resolved": "https://registry.npmmirror.com/ws/-/ws-8.17.1.tgz",
       "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
-      "dev": true,
       "engines": {
         "node": ">=10.0.0"
       },
@@ -17374,6 +17452,14 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/xmlhttprequest": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmmirror.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
+      "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",

+ 2 - 0
package.json

@@ -28,6 +28,7 @@
     "@capacitor/status-bar": "6.0.0",
     "@ionic/angular": "^8.0.0",
     "ionicons": "^7.0.0",
+    "parse": "^5.2.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.14.2"
@@ -45,6 +46,7 @@
     "@capacitor/cli": "6.1.0",
     "@ionic/angular-toolkit": "^11.0.1",
     "@types/jasmine": "~5.1.0",
+    "@types/parse": "^3.0.9",
     "@typescript-eslint/eslint-plugin": "^6.0.0",
     "@typescript-eslint/parser": "^6.0.0",
     "eslint": "^8.57.0",

+ 4 - 4
src/app/app-routing.module.ts

@@ -10,10 +10,6 @@ const routes: Routes = [
     path: 'tab4',
     loadChildren: () => import('./tab4/tab4.module').then( m => m.Tab4PageModule)
   },
-  {
-    path: 'login',
-    loadChildren: () => import('./login/login.module').then( m => m.LoginPageModule)
-  },
   {
     path: 'feedback',
     loadChildren: () => import('./feedback/feedback.module').then( m => m.FeedbackPageModule)
@@ -21,6 +17,10 @@ const routes: Routes = [
   {
     path: 'settings',
     loadChildren: () => import('./settings/settings.module').then( m => m.SettingsPageModule)
+  },
+  {
+    path: 'login',
+    loadChildren: () => import('./login/login.module').then( m => m.LoginPageModule)
   }
 
 

+ 1 - 0
src/app/app.module.ts

@@ -9,6 +9,7 @@ import { AppComponent } from './app.component';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 
+
 @NgModule({
   declarations: [AppComponent],
   imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,CommonModule,FormsModule],

+ 8 - 7
src/app/feedback/feedback.page.html

@@ -9,13 +9,14 @@
 
 <ion-content>
   <ion-list lines="none">
-  <ion-item *ngFor="let message of chatMessages" [class.sender]="message.sender === 'customer'">
-    <ion-avatar slot="start">
-      <ion-img [src]="'D:\workspace\APPmy\asset\img' + message.avatar"></ion-img>
-    </ion-avatar>
-    <ion-label>{{ message.text }}</ion-label>
-  </ion-item>
-</ion-list>
+    <ion-item *ngFor="let message of chatMessages" [class.sender]="message.sender === 'customer'">
+      <ion-avatar slot="start">
+        <ion-img [src]="message.avatar"></ion-img>
+      </ion-avatar>
+      <ion-label>{{ message.text }}</ion-label>
+    </ion-item>
+  </ion-list>
+  
 
 
   <ion-footer>

+ 3 - 3
src/app/feedback/feedback.page.ts

@@ -15,14 +15,14 @@ export class FeedbackPage implements OnInit {
   ngOnInit() {
     // 模拟一些聊天消息
     this.chatMessages = [
-      { text: '你好,请问有什么可以帮助您的吗?', sender: 'customer', avatar: 'customer-avatar.jpg' },
-      { text: '您好,我想咨询一下关于产品的信息。', sender: 'support', avatar: 'support-avatar.png' }
+      { text: '你好,请问有什么可以帮助您的吗?', sender: 'customer', avatar: 'https://img-bsy.txrpic.com/preview/Element/00/00/89/11/E-891182-2418FE26A.png?imageMogr2/quality/90/thumbnail/320x%3E' },
+      { text: '您好,我想咨询一下关于产品的信息。', sender: 'support', avatar: 'https://tse3-mm.cn.bing.net/th/id/OIP-C.c43kVbudRWN3-pJYtmfugAAAAA?rs=1&pid=ImgDetMain' }
     ];
   }
 
   sendMessage() {
     if (this.newMessage.trim() !== '') {
-      this.chatMessages.push({ text: this.newMessage, sender: 'customer', avatar: 'customer-avatar.jpg' });
+      this.chatMessages.push({ text: this.newMessage, sender: 'customer', avatar: 'https://tse3-mm.cn.bing.net/th/id/OIP-C.c43kVbudRWN3-pJYtmfugAAAAA?rs=1&pid=ImgDetMain' });
       // 在这里可以添加客服回复的逻辑
       this.newMessage = '';
     }

+ 23 - 16
src/app/login/login.page.html

@@ -1,26 +1,33 @@
 <ion-header [translucent]="true">
   <ion-toolbar>
-    <ion-buttons slot="start">
-      <ion-back-button defaultHref="/tabs/tab4"></ion-back-button>
-    </ion-buttons>
-    <ion-title style="font-size:30px">登录</ion-title>
+    <ion-title>登录/注册</ion-title>
   </ion-toolbar>
 </ion-header>
 
-<ion-content>
+<ion-content [fullscreen]="true">
+
   <ion-card>
+    <ion-card-header>
+      <ion-card-title>登录/注册</ion-card-title>
+    </ion-card-header>
+  
     <ion-card-content>
-      <ion-item>
-        <ion-label position="floating">用户名</ion-label>
-        <ion-input type="text"></ion-input>
-      </ion-item>
-      <ion-item>
-        <ion-label position="floating">密码</ion-label>
-        <ion-input type="password"></ion-input>
-      </ion-item>
+
+      <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>
-</ion-content>
-
-
 
+  <!-- 新增路由返回逻辑,执行back函数 -->
+  <ion-button expand="block" (click)="back()">返回</ion-button>
+</ion-content>

+ 80 - 2
src/app/login/login.page.ts

@@ -1,5 +1,7 @@
 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',
@@ -7,9 +9,85 @@ import { Component, OnInit } from '@angular/core';
 })
 export class LoginPage implements OnInit {
 
-  constructor() { }
+  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.back()
+    }
+  }
+  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()
+  }
 }

+ 8 - 7
src/app/tab2/tab2.page.html

@@ -28,8 +28,8 @@
   <div class="details-container">
     <!-- 装修公司详情页 -->
     <div *ngIf="selectedSegment === 'company'">
-      <!-- 装修公司详情页 -->
-      <ion-card *ngFor="let company of companies">
+      <div *ngFor="let company of companies">
+      <ion-card class="Company-card">
         <ion-card-header><ion-card-title> {{ company.name }}</ion-card-title>
       </ion-card-header>
         <ion-card-content>
@@ -46,7 +46,7 @@
             <ion-text>{{ company.description }}</ion-text>
           </ion-item>
           <!-- 公司服务 -->
-          <ion-card class="concept-card">
+          <ion-card class="company-card">
             <ion-card-header>
               公司服务:
             </ion-card-header>
@@ -55,7 +55,7 @@
             </ion-card-content>
           </ion-card>
           <!-- 公司优势 -->
-          <ion-card class="concept-card">
+          <ion-card class="company-card">
             <ion-card-header>
               公司优势:
             </ion-card-header>
@@ -64,12 +64,12 @@
             </ion-card-content>
           </ion-card>
           <!-- 公司案例 -->
-          <ion-card class="concept-card">
+          <ion-card class="company-card">
             <ion-card-header>
               公司案例:
             </ion-card-header>
             <ion-card-content>
-              <ion-text>{{ company.cases }}</ion-text>
+              <ion-text [innerHTML]="formatHtmlContent(company.cases)"></ion-text>
             </ion-card-content>
           </ion-card>
           <!-- 底部按钮 -->
@@ -80,11 +80,12 @@
         </ion-card-content>
       </ion-card>
     </div>
+    </div>
 
     <!-- 设计机构详情页 -->
     <div *ngIf="selectedSegment === 'design'">
     <div *ngFor="let designCompany of designCompanies">
-      <ion-card class="company-card">
+      <ion-card class="Angency-card">
         <ion-card-header>
           <ion-card-title>{{ designCompany.name }}</ion-card-title>
           <ion-card-subtitle>星级评分: {{ designCompany.starRating }} | 评价数量: {{ designCompany.reviewCount }}</ion-card-subtitle>

+ 20 - 3
src/app/tab2/tab2.page.scss

@@ -1,3 +1,20 @@
-.details-container {
-    padding: 20px;
-  }
+
+  .company-card ion-card-header {
+    color: rgb(52, 160, 255); /* 将文字颜色设置为黑色 */
+  }
+  
+  .company-card ion-card-header{
+    font-size: 1.4em; /* 增大字号至1.2em(根据需要调整大小) */
+  }
+  .company-card {
+    background-color: #e0ffd2; /* 设置ion-card的背景颜色为灰色(根据需要调整颜色) */
+    color: rgb(0, 0, 0); /* 设置文本颜色为黑色 */
+  }
+  .Company-card {
+    background-color: #f9ffdb; /* 设置ion-card的背景颜色为灰色(根据需要调整颜色) */
+    color: rgb(0, 0, 0); /* 设置文本颜色为黑色 */
+  }
+  .info-item {
+  background-color: #f9ffdb; /* 设置info-item的背景颜色为灰色(根据需要调整颜色) */
+  color: rgb(0, 0, 0); /* 设置文本颜色为黑色 */
+}

+ 6 - 6
src/app/tab3/tab3.page.html

@@ -12,13 +12,13 @@
 
 <ion-content>
   <ion-searchbar placeholder="输入关键字搜索"></ion-searchbar>
-  <ion-card>
-    <ion-card-header>
+  <ion-card class="card">
+    <ion-card-header >
       <ion-card-title>最新推荐</ion-card-title>
     </ion-card-header>
-    <ion-card-content>
-      <ion-avatar slot="start">
-        <ion-img src="https://wx4.sinaimg.cn/mw690/006GBbhsgy1hqa92rfo7tj30qo0qo76t.jpg"></ion-img>
+    <ion-card-content >
+      <ion-avatar>
+        <ion-img src="assets/img/a.png"></ion-img>
       </ion-avatar>
       <ion-label>用户名</ion-label><br>
       <ion-label>标签</ion-label><br>
@@ -48,7 +48,7 @@
    <div *ngIf="activeTab === 'explore'" >
     <ion-item>
       <ion-avatar slot="start">
-        <ion-img src="https://ww2.sinaimg.cn/mw690/007QvzfIly1hraz8xoasnj30u00u0wh6.jpg"></ion-img>
+        <ion-img src="assets/img/b.png"></ion-img>
       </ion-avatar>
       <ion-label>
         <h2>用户名</h2>

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

@@ -10,3 +10,23 @@ ion-card-title {
 ion-avatar {
   margin-right: 10px;
 }
+.card {
+  width: 365px;
+  height: 250px;
+  background-color: #4158D0;
+  background-image: linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%);
+  border-radius: 8px;
+  color: white;
+  overflow: hidden;
+  position: relative;
+  transform-style: preserve-3d;
+  perspective: 1000px;
+  transition: all 0.5s cubic-bezier(0.23, 1, 0.320, 1);
+  cursor: pointer;
+}
+
+
+
+
+
+

+ 3 - 1
src/app/tab4/tab4.page.ts

@@ -1,5 +1,6 @@
 import { Component, OnInit } from '@angular/core';
 import { NavController } from '@ionic/angular';
+import { Router } from '@angular/router';
 
 @Component({
   selector: 'app-tab4',
@@ -8,7 +9,7 @@ import { NavController } from '@ionic/angular';
 })
 export class Tab4Page implements OnInit {
 
-  constructor(private navCtrl: NavController) {}//this.selectedContent = '发布';
+  constructor(private navCtrl: NavController,private router: Router) {}//this.selectedContent = '发布';
   selectedContent: string = '发布';
 
   publishedWorks: any[] = [
@@ -67,6 +68,7 @@ export class Tab4Page implements OnInit {
 
   openSettingsPage() {
     // 打开设置页面
+    this.router.navigate(['/settings']);
   }
 
   openLoginPage() {

BIN
src/assets/img/a.png


BIN
src/assets/img/b.png


+ 0 - 0
asset/img/support-avatar.png → src/assets/img/support-avatar.png


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

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

@@ -0,0 +1,10 @@
+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{
+    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/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('../../app/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 { }

BIN
src/modules/user/用户模块.xmind