Browse Source

fit:login

悦 陈 2 days ago
parent
commit
e2edad261b

+ 6 - 1
myapp/package.json

@@ -61,5 +61,10 @@
     "karma-jasmine-html-reporter": "~2.1.0",
     "typescript": "~5.8.3"
   },
-  "description": "An Ionic project"
+  "description": "An Ionic project",
+  "browserslist": [
+  "last 2 versions",
+  "not dead",
+  "> 0.2%"
+]
 }

+ 106 - 5
myapp/src/app/tab4/tab4.page.html

@@ -5,9 +5,110 @@
 </ion-header>
 
 <ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">tab4</ion-title>
-    </ion-toolbar>
-  </ion-header>
+  <!-- 顶部标题区 -->
+    <div class="header">消息</div>
+    
+    <!-- 按钮功能区 -->
+    <div class="button-area">
+        <button class="action-button active">
+            <span class="icon">👍</span>
+            <span>赞和收藏</span>
+        </button>
+        <button class="action-button">
+            <span class="icon">👥</span>
+            <span>新增关注</span>
+        </button>
+        <button class="action-button">
+            <span class="icon">💬</span>
+            <span>评论和&#64;</span>
+        </button>
+    </div>
+    
+    <!-- 消息卡片区 -->
+    <div class="message-list">
+        <!-- 消息卡片1 - 未读 -->
+        <div class="message-card unread">
+            <img src="https://randomuser.me/api/portraits/women/43.jpg" alt="用户头像" class="avatar">
+            <div class="message-content">
+                <div class="message-header">
+                    <span class="username">张小雨</span>
+                    <span class="time">10:30</span>
+                </div>
+                <p class="message-text">你的照片拍得太棒了!能分享一下拍摄技巧吗?我最近也在学习摄影。</p>
+            </div>
+            <div class="unread-badge"></div>
+        </div>
+        
+        <!-- 消息卡片2 -->
+        <div class="message-card">
+            <img src="https://randomuser.me/api/portraits/men/32.jpg" alt="用户头像" class="avatar">
+            <div class="message-content">
+                <div class="message-header">
+                    <span class="username">王大山</span>
+                    <span class="time">昨天</span>
+                </div>
+                <p class="message-text">感谢你关注我的鸟类观察日记,我会持续更新更多精彩内容!</p>
+            </div>
+        </div>
+        
+        <!-- 消息卡片3 -->
+        <div class="message-card">
+            <img src="https://randomuser.me/api/portraits/women/65.jpg" alt="用户头像" class="avatar">
+            <div class="message-content">
+                <div class="message-header">
+                    <span class="username">李思思</span>
+                    <span class="time">昨天</span>
+                </div>
+                <p class="message-text">你发布的红嘴鸥照片获得了社区精选,恭喜!</p>
+            </div>
+        </div>
+        
+        <!-- 消息卡片4 -->
+        <div class="message-card">
+            <img src="https://randomuser.me/api/portraits/men/75.jpg" alt="用户头像" class="avatar">
+            <div class="message-content">
+                <div class="message-header">
+                    <span class="username">观鸟小助手</span>
+                    <span class="time">周一</span>
+                </div>
+                <p class="message-text">本周观鸟活动报名开始了,点击查看详情并报名参加吧!</p>
+            </div>
+        </div>
+        
+        <!-- 消息卡片5 - 未读 -->
+        <div class="message-card unread">
+            <img src="https://randomuser.me/api/portraits/women/22.jpg" alt="用户头像" class="avatar">
+            <div class="message-content">
+                <div class="message-header">
+                    <span class="username">赵小萌</span>
+                    <span class="time">周一</span>
+                </div>
+                <p class="message-text">&#64;了你 在"城市公园鸟类观察"话题中,快来看看吧!</p>
+            </div>
+            <div class="unread-badge"></div>
+        </div>
+    </div>
+
+    <script>
+        // 简单的交互逻辑
+        document.querySelectorAll('.action-button').forEach(button => {
+            button.addEventListener('click', function() {
+                document.querySelectorAll('.action-button').forEach(btn => {
+                    btn.classList.remove('active');
+                });
+                this.classList.add('active');
+            });
+        });
+        
+        // 点击消息卡片标记为已读
+        document.querySelectorAll('.message-card').forEach(card => {
+            card.addEventListener('click', function() {
+                this.classList.remove('unread');
+                const badge = this.querySelector('.unread-badge');
+                if (badge) {
+                    badge.remove();
+                }
+            });
+        });
+    </script>
 </ion-content>

+ 133 - 0
myapp/src/app/tab4/tab4.page.scss

@@ -0,0 +1,133 @@
+ * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+        }
+        
+        body {
+            background-color: #f5f5f5;
+            color: #333;
+            line-height: 1.5;
+        }
+        
+        /* 顶部标题区 */
+        .header {
+            position: sticky;
+            top: 0;
+            background-color: #fff;
+            padding: 12px 16px;
+            text-align: center;
+            font-size: 18px;
+            font-weight: 600;
+            box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+            z-index: 10;
+        }
+        
+        /* 按钮功能区 */
+        .button-area {
+            display: flex;
+            background-color: #fff;
+            padding: 12px 16px;
+            margin-bottom: 8px;
+            border-bottom: 1px solid #f0f0f0;
+        }
+        
+        .action-button {
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            padding: 8px;
+            background: none;
+            border: none;
+            color: #666;
+            font-size: 14px;
+            cursor: pointer;
+            transition: all 0.2s;
+        }
+        
+        .action-button:hover {
+            color: #1a73e8;
+        }
+        
+        .action-button .icon {
+            font-size: 20px;
+            margin-bottom: 4px;
+        }
+        
+        .action-button.active {
+            color: #1a73e8;
+        }
+        
+        /* 消息卡片区 */
+        .message-list {
+            padding: 0 16px;
+        }
+        
+        .message-card {
+            display: flex;
+            background-color: #fff;
+            padding: 12px;
+            margin-bottom: 8px;
+            border-radius: 8px;
+            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+            transition: transform 0.2s;
+        }
+        
+        .message-card:active {
+            transform: scale(0.98);
+        }
+        
+        .message-card.unread {
+            background-color: #f8f9fa;
+        }
+        
+        .avatar {
+            width: 48px;
+            height: 48px;
+            border-radius: 50%;
+            margin-right: 12px;
+            object-fit: cover;
+            background-color: #e9ecef;
+        }
+        
+        .message-content {
+            flex: 1;
+        }
+        
+        .message-header {
+            display: flex;
+            justify-content: space-between;
+            margin-bottom: 6px;
+        }
+        
+        .username {
+            font-weight: 600;
+            color: #333;
+        }
+        
+        .time {
+            font-size: 12px;
+            color: #999;
+        }
+        
+        .message-text {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            display: -webkit-box;
+            -webkit-line-clamp: 2;
+            line-clamp: 2;
+            -webkit-box-orient: vertical;
+        }
+        
+        /* 未读标记 */
+        .unread-badge {
+            align-self: center;
+            width: 8px;
+            height: 8px;
+            background-color: #1a73e8;
+            border-radius: 50%;
+            margin-left: 8px;
+        }

+ 3 - 1
myapp/src/app/tab5/mine/mine.module.ts

@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 
 import { IonicModule } from '@ionic/angular';
+import { ReactiveFormsModule } from '@angular/forms';
 
 import { MinePageRoutingModule } from './mine-routing.module';
 
@@ -13,7 +14,8 @@ import { MinePage } from './mine.page';
     CommonModule,
     FormsModule,
     IonicModule,
-    MinePageRoutingModule
+    MinePageRoutingModule,
+    ReactiveFormsModule,
   ],
   declarations: [MinePage]
 })

+ 127 - 7
myapp/src/app/tab5/mine/mine.page.html

@@ -10,9 +10,50 @@
       <!-- 已登录状态 -->
       <div class="user-card">
         <ion-avatar class="user-avatar">
-          <img [src]="currentUser?.get('avatar') || 'assets/icon/default-avatar.png'" />
+          <img [src]="currentUser?.get('avatar')?.url || 'assets/icon/default-avatar.png'" />
         </ion-avatar>
         <h2 class="username">{{ currentUser?.get("username") || "未设置用户名" }}</h2>
+        
+        <!-- 用户信息表单 -->
+        <form [formGroup]="profileForm" (ngSubmit)="updateProfile()">
+          <ion-item>
+            <ion-label position="floating">用户名</ion-label>
+            <ion-input 
+              type="text" 
+              formControlName="username"
+              required></ion-input>
+          </ion-item>
+          <ion-note *ngIf="profileForm.get('username')?.invalid && (profileForm.get('username')?.dirty || profileForm.get('username')?.touched)" color="danger">
+            @if(profileForm.get('username')?.errors?.['required']) {
+              <span>用户名不能为空</span>
+            }
+          </ion-note>
+
+          <ion-item>
+            <ion-label position="floating">邮箱</ion-label>
+            <ion-input 
+              type="email" 
+              formControlName="email"></ion-input>
+          </ion-item>
+          <ion-note *ngIf="profileForm.get('email')?.invalid && (profileForm.get('email')?.dirty || profileForm.get('email')?.touched)" color="danger">
+            @if(profileForm.get('email')?.errors?.['email']) {
+              <span>请输入有效的邮箱地址</span>
+            }
+          </ion-note>
+
+          <ion-item>
+            <ion-label position="floating">电话</ion-label>
+            <ion-input 
+              type="tel" 
+              formControlName="phone"></ion-input>
+          </ion-item>
+
+          <ion-button expand="block" type="submit" [disabled]="!profileForm.valid || isLoading">
+            <ion-icon slot="start" name="save-outline"></ion-icon>
+            更新资料
+          </ion-button>
+        </form>
+
         <ion-button expand="block" color="danger" (click)="logout()">
           <ion-icon slot="start" name="log-out-outline"></ion-icon>
           退出登录
@@ -24,13 +65,92 @@
         <ion-avatar class="login-avatar">
           <img src="assets/icon/default-avatar.png" />
         </ion-avatar>
-        <h2 class="welcome-text">欢迎登录</h2>
-        
-        <ion-button expand="block" (click)="login()">
-          <ion-icon slot="start" name="log-in-outline"></ion-icon>
-          使用测试账号登录 (Clym/1234)
+        <h2 class="welcome-text">{{ isLoginMode ? '欢迎登录' : '用户注册' }}</h2>
+
+        <form (ngSubmit)="isLoginMode ? onLogin() : onRegister()">
+          <ion-item>
+            <ion-label position="floating">用户名</ion-label>
+            <ion-input 
+              type="text" 
+              [(ngModel)]="credentials.username" 
+              name="username"
+              required
+              minlength="3"
+              maxlength="20"
+              #usernameInput="ngModel"></ion-input>
+          </ion-item>
+          <ion-note *ngIf="usernameInput.invalid && (usernameInput.dirty || usernameInput.touched)" color="danger">
+            @if(usernameInput.errors?.['required']) {
+              <span>用户名不能为空</span>
+            }
+            @if(usernameInput.errors?.['minlength']) {
+              <span>用户名至少3个字符</span>
+            }
+            @if(usernameInput.errors?.['maxlength']) {
+              <span>用户名最多20个字符</span>
+            }
+          </ion-note>
+
+          <ion-item>
+            <ion-label position="floating">密码</ion-label>
+            <ion-input 
+              type="password" 
+              [(ngModel)]="credentials.password" 
+              name="password"
+              required
+              minlength="6"
+              #passwordInput="ngModel"></ion-input>
+          </ion-item>
+          <ion-note *ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)" color="danger">
+            @if(passwordInput.errors?.['required']) {
+              <span>密码不能为空</span>
+            }
+            @if(passwordInput.errors?.['minlength']) {
+              <span>密码至少6个字符</span>
+            }
+          </ion-note>
+
+          @if(!isLoginMode) {
+            <ion-item>
+              <ion-label position="floating">确认密码</ion-label>
+              <ion-input 
+                type="password" 
+                [(ngModel)]="credentials.confirmPassword" 
+                name="confirmPassword"
+                required
+                #confirmPasswordInput="ngModel"></ion-input>
+            </ion-item>
+            <ion-note *ngIf="(confirmPasswordInput.invalid && (confirmPasswordInput.dirty || confirmPasswordInput.touched)) || 
+                            (credentials.password && credentials.confirmPassword && credentials.password !== credentials.confirmPassword)" 
+                      color="danger">
+              @if(confirmPasswordInput.errors?.['required']) {
+                <span>请确认密码</span>
+              }
+              @if(credentials.password && credentials.confirmPassword && credentials.password !== credentials.confirmPassword) {
+                <span>两次密码不一致</span>
+              }
+            </ion-note>
+          }
+
+          <ion-button 
+            expand="block" 
+            type="submit"
+            [disabled]="isLoading || 
+                       (isLoginMode ? (!credentials.username || !credentials.password) : 
+                                     (!credentials.username || !credentials.password || !credentials.confirmPassword || 
+                                      credentials.password !== credentials.confirmPassword))">
+            <ion-icon slot="start" [name]="isLoginMode ? 'log-in-outline' : 'person-add-outline'"></ion-icon>
+            {{ isLoginMode ? '登录' : '注册' }}
+          </ion-button>
+        </form>
+
+        <ion-button 
+          expand="block" 
+          fill="clear" 
+          (click)="toggleAuthMode()">
+          {{ isLoginMode ? '没有账号?立即注册' : '已有账号?立即登录' }}
         </ion-button>
-        
+
         <div class="divider">
           <span class="divider-line"></span>
           <span class="divider-text">或</span>

+ 12 - 1
myapp/src/app/tab5/mine/mine.page.scss

@@ -31,10 +31,21 @@
 
 .welcome-text {
   font-size: 1.5rem;
-  margin-bottom: 32px;
+  margin-bottom: 24px;
   color: var(--ion-text-color);
 }
 
+ion-item {
+  margin-bottom: 16px;
+}
+
+ion-note {
+  display: block;
+  margin-top: -16px;
+  margin-bottom: 16px;
+  font-size: 0.8rem;
+}
+
 .divider {
   display: flex;
   align-items: center;

+ 220 - 21
myapp/src/app/tab5/mine/mine.page.ts

@@ -1,7 +1,9 @@
 import { Component, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
 import { IonRouterOutlet, ModalController } from '@ionic/angular';
-import { CloudUser } from 'src/lib/ncloud';
+import { CloudQuery, CloudUser } from 'src/lib/ncloud';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { AlertController, LoadingController } from '@ionic/angular';
 
 @Component({
   selector: 'app-mine',
@@ -10,26 +12,223 @@ import { CloudUser } from 'src/lib/ncloud';
 })
 export class MinePage implements OnInit {
 
-  currentUser:CloudUser|undefined
-  constructor(
-    private router:Router,
-    private modalCrtl:ModalController,
-    private routerOutlet:IonRouterOutlet
-  ) { 
-    
-    this.currentUser=new CloudUser()
-  }
-  async login(){
-    let user:any=new CloudUser();
-    user=await this.currentUser?.login("Clym","1234")
-    if(user?.id){
-      this.currentUser=user;
-      this.router.navigate(['/tabs/tab5']);
-    }
-  }
-  async logout(){
-    this.currentUser?.logout();
-    this.currentUser=undefined
+  currentUser: CloudUser | null = null;
+  isLoginMode = true;
+  isLoading = false;
+  profileForm!: FormGroup;
+  
+  credentials = {
+    username: '',
+    password: '',
+    confirmPassword: ''
+  };
+   constructor(
+    private alertCtrl: AlertController,
+    private loadingCtrl: LoadingController,
+    private formBuilder: FormBuilder
+  ) {
+    this.initializeUser();
+    this.createForm();
+  }
+
+  async initializeUser() {
+    this.currentUser = new CloudUser();
+    const user = await this.currentUser.current();
+    if (user?.id) {
+      this.currentUser = user;
+      this.updateFormWithUserData();
+    } else {
+      this.currentUser = null;
+    }
+  }
+
+  createForm() {
+    this.profileForm = this.formBuilder.group({
+      username: ['', Validators.required],
+      email: ['', [Validators.email]],
+      phone: [''],
+      // Add other user fields as needed
+    });
+  }
+
+  updateFormWithUserData() {
+    if (this.currentUser) {
+      this.profileForm.patchValue({
+        username: this.currentUser.get('username'),
+        email: this.currentUser.get('email'),
+        phone: this.currentUser.get('phone')
+      });
+    }
+  }
+
+  async onLogin() {
+    if (!this.credentials.username || !this.credentials.password) {
+      this.showAlert('登录失败', '请输入用户名和密码');
+      return;
+    }
+
+    this.isLoading = true;
+    const loading = await this.loadingCtrl.create({
+      message: '登录中...'
+    });
+    await loading.present();
+
+    try {
+      const user = new CloudUser();
+      const loggedInUser = await user.login(
+        this.credentials.username,
+        this.credentials.password
+      );
+      
+      if (loggedInUser?.id) {
+        this.currentUser = loggedInUser;
+        this.updateFormWithUserData();
+        this.resetCredentials();
+      } else {
+        this.showAlert('登录失败', '用户名或密码不正确');
+      }
+    } catch (error) {
+      const err = error as { message?: string };
+      console.error('登录错误:', err);
+      this.showAlert('登录错误', err.message || '登录过程中发生错误');
+    } finally {
+      this.isLoading = false;
+      await loading.dismiss();
+    }
+  }
+
+  async onRegister() {
+    if (this.credentials.password !== this.credentials.confirmPassword) {
+      this.showAlert('注册失败', '两次输入的密码不一致');
+      return;
+    }
+
+    if (!this.credentials.username || !this.credentials.password) {
+      this.showAlert('注册失败', '请输入用户名和密码');
+      return;
+    }
+
+    this.isLoading = true;
+    const loading = await this.loadingCtrl.create({
+      message: '注册中...'
+    });
+    await loading.present();
+
+    try {
+      // Check if username exists
+      const query = new CloudQuery('_User');
+      query.equalTo('username', this.credentials.username);
+      const existingUser = await query.first();
+      
+      if (existingUser) {
+        this.showAlert('注册失败', '该用户名已被使用');
+        return;
+      }
+
+      // Create new user using the signUp method from CloudUser
+      const newUser = await new CloudUser().signUp(
+        this.credentials.username,
+        this.credentials.password
+      );
+      
+      if (newUser?.id) {
+        this.currentUser = newUser;
+        this.updateFormWithUserData();
+        this.resetCredentials();
+        this.showAlert('注册成功', '您的账号已成功创建');
+      } else {
+        this.showAlert('注册失败', '创建用户时发生错误');
+      }
+    } catch (error) {
+      const err = error as { message?: string };
+      console.error('注册错误:', err);
+      this.showAlert('注册错误', err.message || '注册过程中发生错误');
+    } finally {
+      this.isLoading = false;
+      await loading.dismiss();
+    }
+  }
+
+  async updateProfile() {
+    if (!this.currentUser || !this.profileForm.valid) {
+      return;
+    }
+
+    this.isLoading = true;
+    const loading = await this.loadingCtrl.create({
+      message: '更新中...'
+    });
+    await loading.present();
+
+    try {
+      // Update user data
+      const formData = this.profileForm.value;
+      Object.keys(formData).forEach(key => {
+        if (formData[key] !== undefined && formData[key] !== null) {
+          this.currentUser?.set({ [key]: formData[key] });
+        }
+      });
+
+      // Save changes
+      const updatedUser = await this.currentUser.save();
+      
+      if (updatedUser?.id) {
+        this.showAlert('更新成功', '您的资料已更新');
+      } else {
+        this.showAlert('更新失败', '更新用户资料时发生错误');
+      }
+    } catch (error) {
+      const err = error as { message?: string };
+      console.error('更新错误:', err);
+      this.showAlert('更新错误', err.message || '更新过程中发生错误');
+    } finally {
+      this.isLoading = false;
+      await loading.dismiss();
+    }
+  }
+
+  async logout() {
+    this.isLoading = true;
+    const loading = await this.loadingCtrl.create({
+      message: '登出中...'
+    });
+    await loading.present();
+
+    try {
+      if (this.currentUser) {
+        await this.currentUser.logout();
+        this.currentUser = null;
+        this.profileForm.reset();
+      }
+    } catch (error) {
+      console.error('登出错误:', error);
+      this.showAlert('登出错误', '登出过程中发生错误');
+    } finally {
+      this.isLoading = false;
+      await loading.dismiss();
+    }
+  }
+
+  resetCredentials() {
+    this.credentials = {
+      username: '',
+      password: '',
+      confirmPassword: ''
+    };
+  }
+
+  async showAlert(header: string, message: string) {
+    const alert = await this.alertCtrl.create({
+      header,
+      message,
+      buttons: ['确定']
+    });
+    await alert.present();
+  }
+
+  toggleAuthMode() {
+    this.isLoginMode = !this.isLoginMode;
+    this.resetCredentials();
   }
 
   ngOnInit() {}

+ 1 - 1
myapp/src/app/tab5/tab5.page.html

@@ -11,7 +11,7 @@
         <div class="profile-content">
             <div class="avatar" style="background-image: url('https://picsum.photos/200/200?random=1');"></div>
             <div class="profile-info">
-                <div class="nickname">观鸟达人小王</div>
+                <div class="nickname">0225322</div>
                 <div class="identity">鸟类保护志愿者</div>
                 <div class="signature">用镜头记录每一片羽翼的美丽</div>
             </div>

+ 19 - 0
页面设计.txt

@@ -198,3 +198,22 @@ AI咨询页面设计
 手机版页面,顶部标题,中间按钮功能区,下面为消息卡片
 
 #顶部区域
+中间为标题:消息
+
+#中间按钮区
+从左至右为3个按钮
+分别为
+-赞和收藏
+-新增关注
+-评论和@
+
+#下方消息卡片区
+每个卡片分为左右两个部分
+##左边为发消息的人的头像
+##右侧
+###上方左边
+为发消息的人的姓名
+###上方右边
+发消息的时间
+###下方
+消息内容