Bladeren bron

refactor: 优化控制器导入和用户相关功能

- 统一控制器包导入路径
- 优化UserController用户信息更新逻辑
- 优化login.vue登录流程和手机号检查
- 优化parent-index.vue导航逻辑
- 更新需求分析文档状态
User 5 maanden geleden
bovenliggende
commit
7164f6b8a1
20 gewijzigde bestanden met toevoegingen van 322 en 241 verwijderingen
  1. 35 1
      docs/requirements-analysis.md
  2. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/admin/AdminAuthController.java
  3. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/admin/AdminController.java
  4. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/admin/AdminPackageController.java
  5. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/auth/AuthController.java
  6. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/family/BindController.java
  7. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/family/BindInviteController.java
  8. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/family/PointsController.java
  9. 61 1
      zxyj-backend/src/main/java/com/zxyj/controller/family/UserController.java
  10. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/guide/GuideController.java
  11. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/guide/GuideFamilyTaskController.java
  12. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/guide/TeacherController.java
  13. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/guide/TeacherMessageController.java
  14. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/reward/RewardController.java
  15. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/reward/RewardWishlistController.java
  16. 1 1
      zxyj-backend/src/main/java/com/zxyj/controller/task/TaskController.java
  17. 42 17
      zxyj-frontend/pages/index/parent-index.vue
  18. 111 190
      zxyj-frontend/pages/login/login.vue
  19. 54 18
      zxyj-frontend/pages/profile/profile.vue
  20. 5 0
      zxyj-frontend/utils/api.js

+ 35 - 1
docs/requirements-analysis.md

@@ -248,4 +248,38 @@
 5. 指导师审核任务(单个和批量)
 6. 指导师调整/修改/删除任务
 7. 家长发送咨询 → 指导师回复
-8. 权限控制测试(用非 teacher 角色访问)
+8. 权限控制测试(用非 teacher 角色访问)
+
+---
+
+## 七、个人中心(我的)页面功能
+
+### 7.1 菜单项
+
+| 菜单项 | 可见角色 | 说明 |
+|--------|---------|------|
+| 📝 修改个人资料 | 全部 | 跳转 user-edit 页面 |
+| 👶 我的孩子 | 仅家长 | 跳转孩子管理页面 |
+| 📊 积分记录 | 全部 | 跳转积分明细页面 |
+| 🔐 设置/重置密码 | 仅家长和指导师 | 孩子不可见 |
+| 🚪 退出登录 | 全部 | 退出登录 |
+
+### 7.2 密码功能
+
+**设置密码**:
+- 家长和指导师可设置6位数字密码
+- 孩子模式不显示此菜单
+
+**重置密码**:
+- 需要先验证旧密码
+- 再设置新密码
+
+**切换模式验证**:
+- 孩子切换回家长模式时需要验证密码
+- 指导师切换到家长模式时需要验证密码(通过 Web 端)
+
+### 7.3 修改个人资料
+
+- 跳转到 `/pages/user-edit/user-edit` 页面
+- 用户可修改昵称、头像、性别、生日等信息
+- 指导师可修改指导师证书号、证书照片等信息

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/admin/AdminAuthController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.admin;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zxyj.common.Result;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/admin/AdminController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.admin;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/admin/AdminPackageController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.admin;
 
 import com.zxyj.common.Result;
 import com.zxyj.entity.TaskTemplatePackage;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/auth/AuthController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.auth;
 
 import com.zxyj.common.Result;
 import com.zxyj.dto.*;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/family/BindController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.family;
 
 import com.zxyj.common.Result;
 import com.zxyj.dto.BindGuideDTO;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/family/BindInviteController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.family;
 
 import com.zxyj.common.Result;
 import com.zxyj.service.BindInviteService;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/family/PointsController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.family;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zxyj.common.Result;

+ 61 - 1
zxyj-backend/src/main/java/com/zxyj/controller/family/UserController.java

@@ -1,14 +1,21 @@
-package com.zxyj.controller;
+package com.zxyj.controller.family;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zxyj.common.Result;
 import com.zxyj.dto.*;
+import com.zxyj.entity.Child;
+import com.zxyj.entity.Family;
 import com.zxyj.entity.User;
+import com.zxyj.mapper.ChildMapper;
+import com.zxyj.mapper.FamilyMapper;
+import com.zxyj.mapper.UserMapper;
 import com.zxyj.service.UserService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -20,6 +27,15 @@ public class UserController {
     @Resource
     private UserService userService;
 
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private FamilyMapper familyMapper;
+
+    @Resource
+    private ChildMapper childMapper;
+
     private Long getUserId(javax.servlet.http.HttpServletRequest request) {
         // 从请求属性中获取用户ID(由拦截器设置)
         Object userIdObj = request.getAttribute("userId");
@@ -146,4 +162,48 @@ public class UserController {
         boolean success = userService.updateChild(userId, childId, dto);
         return Result.success(success);
     }
+
+    @Operation(summary = "获取家庭成员列表")
+    @GetMapping("/family-members")
+    public Result<Map<String, Object>> getFamilyMembers(javax.servlet.http.HttpServletRequest request) {
+        Long userId = getUserId(request);
+        User user = userMapper.selectById(userId);
+
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        Long familyId = user.getFamilyId();
+        if (familyId == null) {
+            return Result.error("用户未加入家庭");
+        }
+
+        // 获取家庭信息
+        Family family = familyMapper.selectById(familyId);
+        if (family == null) {
+            return Result.error("家庭不存在");
+        }
+
+        // 查询家长列表
+        List<User> parents = userMapper.selectList(
+            new LambdaQueryWrapper<User>()
+                .eq(User::getFamilyId, familyId)
+                .eq(User::getRole, "parent")
+        );
+
+        // 查询孩子列表
+        List<Child> children = childMapper.selectList(
+            new LambdaQueryWrapper<Child>()
+                .eq(Child::getFamilyId, familyId)
+        );
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("familyId", familyId);
+        result.put("familyName", family.getName());
+        result.put("inviteCode", family.getInviteCode());
+        result.put("parents", parents);
+        result.put("children", children);
+
+        return Result.success(result);
+    }
 }

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/guide/GuideController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.guide;
 
 import com.zxyj.common.Result;
 import com.zxyj.dto.BindGuideDTO;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/guide/GuideFamilyTaskController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.guide;
 
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/guide/TeacherController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.guide;
 
 import com.zxyj.common.Result;
 import com.zxyj.dto.*;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/guide/TeacherMessageController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.guide;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zxyj.common.Result;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/reward/RewardController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.reward;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zxyj.common.Result;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/reward/RewardWishlistController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.reward;
 
 import com.zxyj.common.Result;
 import com.zxyj.dto.CreateRewardWishlistDTO;

+ 1 - 1
zxyj-backend/src/main/java/com/zxyj/controller/task/TaskController.java

@@ -1,4 +1,4 @@
-package com.zxyj.controller;
+package com.zxyj.controller.task;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zxyj.common.Result;

+ 42 - 17
zxyj-frontend/pages/index/parent-index.vue

@@ -8,25 +8,26 @@
       </view>
     </view>
 
-    <!-- 家庭孩子列表 -->
+    <!-- 家庭成员列表 -->
     <view class="section">
       <view class="section-title">家庭成员</view>
       <view class="children-list">
         <view 
           class="child-card" 
-          v-for="child in children" 
-          :key="child.id"
-          @click="selectChild(child)"
-          @longpress="switchToChildMode(child)"
+          v-for="member in familyMembers" 
+          :key="member.id"
+          @click="selectMember(member)"
+          @longpress="handleMemberLongPress(member)"
         >
-          <view class="child-avatar">{{ child.nickname ? child.nickname[0] : '?' }}</view>
+          <view class="child-avatar">{{ member.nickname ? member.nickname[0] : '?' }}</view>
           <view class="child-info">
-            <text class="child-name">{{ child.nickname || '孩子' }}</text>
-            <text class="child-points">积分: {{ child.totalPoints }}</text>
+            <text class="child-name">{{ member.nickname || (member.role === 'parent' ? '家长' : '孩子') }}</text>
+            <text class="child-role">{{ member.role === 'parent' ? '家长' : '孩子' }}</text>
+            <text class="child-points" v-if="member.role === 'child'">积分: {{ member.totalPoints || 0 }}</text>
           </view>
-          <view class="child-streak" v-if="child.streakDays > 0">
+          <view class="child-streak" v-if="member.role === 'child' && member.streakDays > 0">
             <text class="fire">🔥</text>
-            <text class="days">{{ child.streakDays }}天</text>
+            <text class="days">{{ member.streakDays }}天</text>
           </view>
         </view>
       </view>
@@ -80,13 +81,14 @@
 </template>
 
 <script>
-import { getChildren, getPendingReviewTasks, approveTask, rejectTask, switchToChild, switchBackToParent } from '../../utils/api.js'
+import { getFamilyMembers, getChildren, getPendingReviewTasks, approveTask, rejectTask, switchToChild, switchBackToParent } from '../../utils/api.js'
 
 export default {
   data() {
     return {
       nickname: '',
-      children: [],
+      familyMembers: [],  // 所有家庭成员(家长+孩子)
+      children: [],      // 孩子列表
       pendingReviewTasks: [],
       selectedChildId: '',
       currentChildId: ''  // 当前切换到的孩子ID
@@ -127,6 +129,15 @@ export default {
         const userInfo = uni.getStorageSync('userInfo')
         this.nickname = userInfo?.nickname || ''
         
+        // 获取家庭所有成员(家长+孩子)
+        try {
+          const membersRes = await getFamilyMembers()
+          this.familyMembers = membersRes.data || []
+        } catch(e) {
+          console.log('获取家庭成员失败,使用孩子列表', e)
+          this.familyMembers = []
+        }
+        
         // 获取孩子列表
         const childrenRes = await getChildren()
         this.children = childrenRes.data || []
@@ -138,12 +149,26 @@ export default {
         console.error('加载数据失败', e)
       }
     },
+    selectMember(member) {
+      if (member.role === 'child') {
+        this.$store.commit('setCurrentChild', member.id)
+        uni.showToast({
+          title: `已选择 ${member.nickname}`,
+          icon: 'none'
+        })
+      }
+    },
+    handleMemberLongPress(member) {
+      // 家长长按进入孩子模式,孩子长按显示信息
+      if (member.role === 'parent') {
+        uni.showToast({ title: '这是家长', icon: 'none' })
+      } else {
+        this.switchToChildMode(member)
+      }
+    },
+    // 保留旧方法以兼容
     selectChild(child) {
-      this.$store.commit('setCurrentChild', child.id)
-      uni.showToast({
-        title: `已选择 ${child.nickname}`,
-        icon: 'none'
-      })
+      this.selectMember({ ...child, role: 'child' })
     },
     // 切换到孩子身份
     async switchToChildMode(child) {

+ 111 - 190
zxyj-frontend/pages/login/login.vue

@@ -16,8 +16,6 @@
           v-model="phone" 
           placeholder="请输入手机号" 
           maxlength="11"
-          @blur="onPhoneBlur"
-          @input="onPhoneInput"
         />
       </view>
       
@@ -41,19 +39,7 @@
         </view>
       </view>
 
-      <button class="login-btn" :loading="loading" @click="phoneLogin">登录</button>
-
-      <!-- 微信一键登录 -->
-      <view class="wechat-section">
-        <button 
-          class="wechat-btn" 
-          open-type="getPhoneNumber" 
-          @getphonenumber="handleWechatPhoneLogin"
-        >
-          <text class="wechat-icon">微信</text>
-          <text>一键登录</text>
-        </button>
-      </view>
+        <button class="login-btn" :loading="loading" @click="phoneLogin">登录</button>
     </view>
 
     <view class="agreement">
@@ -82,14 +68,7 @@ export default {
       countdown: 0,
       agreed: false,
       nickname: '',
-      avatar: '',
-      // 新增字段
-      role: 'parent',  // 角色选择
-      birthday: '',    // 生日
-      gender: '',      // 性别
-      showRoleSelect: true,  // 显示角色选择
-      codeChecked: false,  // 是否已检查过手机号
-      lastCheckedPhone: ''  // 上次检查的手机号
+      avatar: ''
     }
   },
   onLoad() {
@@ -104,7 +83,24 @@ export default {
   },
   methods: {
     handleImageError(e) {
-      console.log('Logo加载失败,使用默认显示')
+      console.log('Logo 加载失败,使用默认显示')
+    },
+    onAgreementChange(e) {
+      this.agreed = e.detail.value.length > 0
+    },
+    showAgreement() {
+      uni.showModal({
+        title: '用户协议',
+        content: '这里是用户协议内容...',
+        showCancel: false
+      })
+    },
+    showPrivacy() {
+      uni.showModal({
+        title: '隐私政策',
+        content: '这里是隐私政策内容...',
+        showCancel: false
+      })
     },
     // 手机号输入完成时检查(11位时触发)
     onPhoneInput() {
@@ -149,43 +145,6 @@ export default {
         showCancel: false
       })
     },
-    // 手机号失焦时检查是否新用户(测试环境)
-    async onPhoneBlur() {
-      if (!this.phone || !/^1[3-9]\d{9}$/.test(this.phone)) {
-        return
-      }
-      
-      // 如果手机号变化了,重置检查状态
-      if (this.lastCheckedPhone !== this.phone) {
-        this.codeChecked = false
-        this.lastCheckedPhone = ''
-      }
-      
-      // 发送验证码后不再重复检查
-      if (this.codeChecked) {
-        return
-      }
-      
-      try {
-        const res = await checkPhone(this.phone)
-        this.codeChecked = true
-        this.lastCheckedPhone = this.phone
-        
-        if (res.data === false) {
-          // 新用户:直接跳转到信息编辑页
-          uni.showToast({ title: '请完善个人信息', icon: 'none' })
-          setTimeout(() => {
-            uni.redirectTo({
-              url: '/pages/user-edit/user-edit?needsRoleSelect=true&phone=' + this.phone
-            })
-          }, 1500)
-        }
-        // 老用户:继续正常流程
-      } catch (e) {
-        // 检查失败,继续正常流程
-        console.error('检查手机号失败', e)
-      }
-    },
     async sendCode() {
       if (!this.phone) {
         uni.showToast({ title: '请输入手机号', icon: 'none' })
@@ -226,138 +185,100 @@ export default {
         return
       }
 
-		this.loading = true
-		try {
-			const res = await phoneLoginApi({
-				phone: this.phone,
-				code: this.code,
-				nickname: this.nickname || this.getNicknameFromWechat(),
-				avatar: this.avatar || this.getAvatarFromWechat(),
-				role: this.role,
-				birthday: this.birthday,
-				gender: this.gender
-			})
-
-			// 保存token和用户信息
-			uni.setStorageSync('token', res.data.token)
-			uni.setStorageSync('userId', res.data.userId)
-			uni.setStorageSync('role', res.data.role)
-			uni.setStorageSync('familyId', res.data.familyId)
-			uni.setStorageSync('userInfo', {
-				nickname: res.data.nickname,
-				userId: res.data.userId
-			})
-
-			uni.showToast({ title: '登录成功', icon: 'success' })
-
-			// 保存用户角色
-			const role = res.data.role || 'parent'
-
-			// 保存用户信息
-			uni.setStorageSync('roles', res.data.roles || role)
-
-			// 根据 isNewUser 决定跳转页面
-			setTimeout(() => {
-				if (res.data.isNewUser) {
-					// 新用户:跳转到信息编辑页面(先选择角色)
-					uni.redirectTo({
-						url: `/pages/user-edit/user-edit?token=${res.data.token}&userId=${res.data.userId}&needsRoleSelect=true`
-					})
-				} else {
-					// 老用户:根据角色进入对应首页
-					this.navigateToHome(role)
-				}
-			}, 1000)
-		} catch (e) {
-			uni.showToast({ title: e.message || '登录失败', icon: 'none' })
-		} finally {
-			this.loading = false
-		}
-    },
-	async handleWechatPhoneLogin(e) {
-			if (e.detail.errMsg === 'getPhoneNumber:ok') {
-				if (!this.agreed) {
-					uni.showToast({ title: '请先同意用户协议', icon: 'none' })
-					return
-				}
-
-				this.loading = true
-				try {
-					// 新版微信小程序获取手机号:使用e.detail.code
-					const phoneCode = e.detail.code
-					if (!phoneCode) {
-						uni.showToast({ title: '获取手机号失败,请重试', icon: 'none' })
-						return
-					}
-
-					// 获取登录code用于获取openid
-					const loginRes = await new Promise((resolve, reject) => {
-						uni.login({
-							provider: 'weixin',
-							success: resolve,
-							fail: reject
-						})
-					})
-
-					// 调用后端微信手机号登录接口
-					const res = await wechatPhoneLogin({
-						code: loginRes.code,        // 用于获取openid
-						phoneCode: phoneCode,       // 用于获取手机号
-						nickname: this.nickname || '用户',
-						avatar: this.avatar || '',
-						role: this.role,
-						birthday: this.birthday,
-						gender: this.gender
-					})
-
-					// 保存token和用户信息
-					uni.setStorageSync('token', res.data.token)
-					uni.setStorageSync('userId', res.data.userId)
-					uni.setStorageSync('role', res.data.role)
-					uni.setStorageSync('familyId', res.data.familyId)
-					// 保存openid,用于后续自动登录
-					if (res.data.openid) {
-						uni.setStorageSync('openid', res.data.openid)
-					}
-					uni.setStorageSync('userInfo', {
-						nickname: res.data.nickname,
-						userId: res.data.userId
-					})
-
-					uni.showToast({ title: '登录成功', icon: 'success' })
-
-					// 保存用户角色
-					const role = res.data.role || 'parent'
-
-					// 根据 isNewUser 决定跳转页面
-					setTimeout(() => {
-						if (res.data.isNewUser) {
-							// 新用户:跳转到信息编辑页面
-							uni.redirectTo({
-								url: `/pages/user-edit/user-edit?token=${res.data.token}&userId=${res.data.userId}`
-							})
-						} else {
-							// 老用户:根据角色进入对应首页
-							this.navigateToHome(role)
-						}
-					}, 1000)
-				} catch (e) {
-					uni.showToast({ title: e.message || '登录失败', icon: 'none' })
-				} finally {
-					this.loading = false
-				}
-			}
-		},
-    navigateToHome(role) {
-      if (role === 'parent') {
-        uni.reLaunch({
-          url: '/pages/index/parent-index'
+      this.loading = true
+      try {
+        const res = await phoneLoginApi({
+          phone: this.phone,
+          code: this.code,
+          nickname: this.nickname || '用户',
+          avatar: this.avatar || ''
         })
-      } else {
-        uni.reLaunch({
-          url: '/pages/index/child-index'
+
+        // 保存 token 和用户信息
+        uni.setStorageSync('token', res.data.token)
+        uni.setStorageSync('userId', res.data.userId)
+        uni.setStorageSync('role', res.data.role)
+        uni.setStorageSync('familyId', res.data.familyId)
+        uni.setStorageSync('userInfo', {
+          nickname: res.data.nickname,
+          userId: res.data.userId
         })
+
+        uni.showToast({ title: '登录成功', icon: 'success' })
+
+        // 直接跳转到首页(根据后端返回的角色自动适配)
+        setTimeout(() => {
+          uni.reLaunch({
+            url: '/pages/index/index'
+          })
+        }, 1000)
+      } catch (e) {
+        uni.showToast({ title: e.message || '登录失败', icon: 'none' })
+      } finally {
+        this.loading = false
       }
+    },
+    async handleWechatPhoneLogin(e) {
+      if (e.detail.errMsg === 'getPhoneNumber:ok') {
+        if (!this.agreed) {
+          uni.showToast({ title: '请先同意用户协议', icon: 'none' })
+          return
+        }
+
+        this.loading = true
+        try {
+          // 新版微信小程序获取手机号:使用 e.detail.code
+          const phoneCode = e.detail.code
+          if (!phoneCode) {
+            uni.showToast({ title: '获取手机号失败,请重试', icon: 'none' })
+            return
+          }
+
+          // 获取登录 code 用于获取 openid
+          const loginRes = await new Promise((resolve, reject) => {
+            uni.login({
+              provider: 'weixin',
+              success: resolve,
+              fail: reject
+            })
+          })
+
+          // 调用后端微信手机号登录接口
+          const res = await wechatPhoneLogin({
+            code: loginRes.code, // 用于获取 openid
+            phoneCode: phoneCode, // 用于获取手机号
+            nickname: this.nickname || '用户',
+            avatar: this.avatar || ''
+          })
+
+          // 保存 token 和用户信息
+          uni.setStorageSync('token', res.data.token)
+          uni.setStorageSync('userId', res.data.userId)
+          uni.setStorageSync('role', res.data.role)
+          uni.setStorageSync('familyId', res.data.familyId)
+          // 保存 openid,用于后续自动登录
+          if (res.data.openid) {
+            uni.setStorageSync('openid', res.data.openid)
+          }
+          uni.setStorageSync('userInfo', {
+            nickname: res.data.nickname,
+            userId: res.data.userId
+          })
+
+          uni.showToast({ title: '登录成功', icon: 'success' })
+
+          // 直接跳转到首页
+          setTimeout(() => {
+            uni.reLaunch({
+              url: '/pages/index/index'
+            })
+          }, 1000)
+        } catch (e) {
+          uni.showToast({ title: e.message || '登录失败', icon: 'none' })
+        } finally {
+          this.loading = false
+        }
+}
     }
   }
 }

+ 54 - 18
zxyj-frontend/pages/profile/profile.vue

@@ -1,15 +1,12 @@
 <template>
   <view class="container">
-    <view class="user-card">
-      <view class="avatar">👤</view>
-      <view class="user-info">
-        <view class="nickname">{{ nickname || '未设置昵称' }}</view>
-        <view class="role">{{ role === 'parent' ? '家长模式' : '孩子模式' }}</view>
-      </view>
-      <button class="btn-switch" @click="switchMode">
-        切换
-      </button>
+  <view class="user-card">
+    <view class="avatar">👤</view>
+    <view class="user-info">
+      <view class="nickname">{{ nickname || '未设置昵称' }}</view>
+      <view class="role">{{ role === 'parent' ? '家长模式' : (role === 'child' ? '孩子模式' : '指导师模式') }}</view>
     </view>
+  </view>
 
 	<view class="menu-list">
 		<!-- 修改个人资料:所有角色可见 -->
@@ -43,19 +40,20 @@
 			<view class="modal-title">{{ hasPassword ? '重置密码' : '设置密码' }}</view>
 			<!-- 重置密码时需要先验证旧密码 -->
 			<view class="form-item" v-if="hasPassword">
-				<input v-model="oldPassword" type="password" placeholder="输入旧密码" maxlength="4" />
+				<input v-model="oldPassword" type="number" password placeholder="输入旧密码" maxlength="6" />
 			</view>
 			<view class="form-item">
-				<input v-model="newPassword" type="password" placeholder="输入新密码" maxlength="4" />
+				<input v-model="newPassword" type="number" password placeholder="输入新密码" maxlength="6" />
 			</view>
 			<view class="form-item">
-				<input v-model="confirmPassword" type="password" placeholder="确认新密码" maxlength="4" />
+				<input v-model="confirmPassword" type="number" password placeholder="确认新密码" maxlength="6" />
 			</view>
 			<view class="modal-btns">
 				<button @click="showPasswordModal = false">取消</button>
 				<button class="btn-primary" @click="savePassword">确定</button>
 			</view>
-</view>
+		</view>
+	</view>
   </view>
 </template>
 
@@ -83,6 +81,24 @@ export default {
     // 加载孩子列表(用于切换模式时判断)
     this.loadChildren()
   },
+  methods: {
+    async loadChildren() {
+      try {
+        const res = await getChildren()
+        this.children = res.data || []
+      } catch (e) {
+        console.error('获取孩子列表失败', e)
+      }
+    },
+    goToEditInfo() {
+      uni.navigateTo({ url: '/pages/user-edit/user-edit' })
+    },
+    goToChildren() {
+      uni.navigateTo({ url: '/pages/profile/children' })
+    },
+    goToPointsLogs() {
+      uni.navigateTo({ url: '/pages/points/points' })
+    },
   methods: {
     async loadChildren() {
       try {
@@ -168,13 +184,33 @@ export default {
         title: `已切换为${newRole === 'parent' ? '家长' : '孩子'}模式`,
         icon: 'success'
       })
-    },
+  },
   onShow() {
     this.nickname = this.$store.state.nickname
     this.role = this.$store.state.role
-    // 检查是否已设置密码(通过检查本地存储或调用API)
+    // 检查是否已设置密码(通过检查本地存储或调用 API)
     this.hasPassword = !!uni.getStorageSync('passwordSet')
+    // 加载孩子列表(用于判断是否有孩子)
+    this.loadChildren()
   },
+  methods: {
+    async loadChildren() {
+      try {
+        const res = await getChildren()
+        this.children = res.data || []
+      } catch (e) {
+        console.error('获取孩子列表失败', e)
+      }
+    },
+    goToEditInfo() {
+      uni.navigateTo({ url: '/pages/user-edit/user-edit' })
+    },
+    goToChildren() {
+      uni.navigateTo({ url: '/pages/profile/children' })
+    },
+    goToPointsLogs() {
+      uni.navigateTo({ url: '/pages/points/points' })
+    },
   methods: {
     switchMode() {
       // 孩子切换到家长需要验证密码
@@ -227,8 +263,8 @@ export default {
       uni.navigateTo({ url: '/pages/points/points' })
     },
     async savePassword() {
-      if (this.newPassword.length !== 4) {
-        uni.showToast({ title: '请输入4位密码', icon: 'none' })
+      if (this.newPassword.length !== 6) {
+        uni.showToast({ title: '请输入6位密码', icon: 'none' })
         return
       }
       if (this.newPassword !== this.confirmPassword) {
@@ -236,7 +272,7 @@ export default {
         return
       }
       // 重置密码需要验证旧密码
-      if (this.hasPassword && this.oldPassword.length !== 4) {
+      if (this.hasPassword && this.oldPassword.length !== 6) {
         uni.showToast({ title: '请输入旧密码', icon: 'none' })
         return
       }

+ 5 - 0
zxyj-frontend/utils/api.js

@@ -137,6 +137,11 @@ export const createFamily = (name) => {
 	return request('/api/user/create-family', 'POST', { name })
 }
 
+// 获取家庭所有成员(家长+孩子)
+export const getFamilyMembers = () => {
+	return request('/api/user/family-members', 'GET')
+}
+
 export const switchMode = (mode) => {
 	return request('/api/user/switch-mode', 'POST', { mode })
 }