For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 实现三种邀请分享卡片(邀请孩子/邀请家人/邀请规划师),接收方通过微信分享卡片进入小程序,可自动完成注册/登录/绑定。
架构: 后端新增 invite_card 表 + InviteCardController/InviteCardService,负责邀请码生成和接收验证;前端在三个入口页面添加分享按钮,登录页处理邀请参数自动登录。
Tech Stack: Spring Boot 2.7.18 + MyBatis-Plus, uni-app Vue 2, 微信小程序原生分享
| 文件 | 操作 | 职责 |
|---|---|---|
zxyj-backend/.../entity/InviteCard.java |
新建 | 邀请码实体 |
zxyj-backend/.../mapper/InviteCardMapper.java |
新建 | MyBatis Mapper |
zxyj-backend/.../service/InviteCardService.java |
新建 | 生成/验证/使用邀请码 |
zxyj-backend/.../controller/InviteCardController.java |
新建 | 3个 REST 端点 |
zxyj-backend/.../config/DatabaseInitializer.java |
修改 | 建 invite_card 表 |
zxyj-frontend/utils/api.js |
修改 | 新增 3 个 API 函数 |
zxyj-frontend/pages/profile/children.vue |
修改 | 每个孩子加「邀请」按钮 + share |
zxyj-frontend/pages/profile/profile.vue |
修改 | 加「邀请家人」按钮 + share |
zxyj-frontend/pages/growth/index.vue |
修改 | 加「邀请规划师」按钮 + share |
zxyj-frontend/pages/login/login.vue |
修改 | 处理 invite_code 自动登录 |
前提: 了解项目 MyBatis-Plus 实体/Mapper 写法:见 entity/User.java、mapper/UserMapper.java。
Files:
zxyj-backend/src/main/java/com/zxyj/entity/InviteCard.javazxyj-backend/src/main/java/com/zxyj/mapper/InviteCardMapper.javazxyj-backend/src/main/java/com/zxyj/config/DatabaseInitializer.java(加建表语句)Test: 启动后端验证建表成功
[ ] Step 1: 创建 InviteCard 实体
package com.zxyj.entity;
import com.baomidou.mybatisplus.annotation.*;
import java.time.LocalDateTime;
@TableName("invite_card")
public class InviteCard {
@TableId(type = IdType.AUTO)
private Long id;
private String type; // child/family/guide
private String code; // 唯一邀请码
private Long refId; // 关联ID (childId/familyId)
private Long creatorId; // 创建人
private LocalDateTime expiresAt;
private Integer used; // 0-未使用 1-已使用
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// getters/setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
public Long getRefId() { return refId; }
public void setRefId(Long refId) { this.refId = refId; }
public Long getCreatorId() { return creatorId; }
public void setCreatorId(Long creatorId) { this.creatorId = creatorId; }
public LocalDateTime getExpiresAt() { return expiresAt; }
public void setExpiresAt(LocalDateTime expiresAt) { this.expiresAt = expiresAt; }
public Integer getUsed() { return used; }
public void setUsed(Integer used) { this.used = used; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}
[ ] Step 2: 创建 InviteCardMapper
package com.zxyj.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zxyj.entity.InviteCard;
public interface InviteCardMapper extends BaseMapper<InviteCard> {
}
[ ] Step 3: DatabaseInitializer 中添加建表语句
找到 DatabaseInitializer.java 中的 SQL 执行部分,在最后加一条 CREATE TABLE IF NOT EXISTS:
CREATE TABLE IF NOT EXISTS invite_card (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
type VARCHAR(20) NOT NULL COMMENT 'child/family/guide',
code VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请码',
ref_id BIGINT NOT NULL COMMENT '关联ID(childId/familyId)',
creator_id BIGINT NOT NULL COMMENT '创建人',
expires_at DATETIME NOT NULL,
used TINYINT DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_code (code),
INDEX idx_ref_type (ref_id, type)
) COMMENT='邀请卡片';
[ ] Step 4: 编译并启动验证
cd zxyj-backend && mvn clean compile spring-boot:run
确认启动日志无报错,表自动创建。
[ ] Step 5: Commit
git add zxyj-backend/src/main/java/com/zxyj/entity/InviteCard.java
git add zxyj-backend/src/main/java/com/zxyj/mapper/InviteCardMapper.java
git add zxyj-backend/src/main/java/com/zxyj/config/DatabaseInitializer.java
git commit -m "feat: add invite_card table and entity"
Files:
zxyj-backend/src/main/java/com/zxyj/service/InviteCardService.javaTest: 编译验证
[ ] Step 1: 创建 InviteCardService
package com.zxyj.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zxyj.common.Result;
import com.zxyj.entity.*;
import com.zxyj.mapper.*;
import com.zxyj.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
@Service
public class InviteCardService {
@Resource
private InviteCardMapper inviteCardMapper;
@Resource
private UserMapper userMapper;
@Resource
private UserService userService;
@Resource
private ChildMapper childMapper;
@Resource
private FamilyMapper familyMapper;
/**
* 生成邀请码
* @param type child/family/guide
* @param refId 关联ID
* @param creatorId 创建人ID
*/
public InviteCard generate(String type, Long refId, Long creatorId) {
// 使旧的未使用邀请码失效
invalidateOldCodes(type, refId);
InviteCard card = new InviteCard();
card.setType(type);
card.setCode(generateCode(type));
card.setRefId(refId);
card.setCreatorId(creatorId);
card.setExpiresAt(LocalDateTime.now().plusHours(24));
card.setUsed(0);
card.setCreatedAt(LocalDateTime.now());
card.setUpdatedAt(LocalDateTime.now());
inviteCardMapper.insert(card);
return card;
}
private void invalidateOldCodes(String type, Long refId) {
List<InviteCard> oldCards = inviteCardMapper.selectList(
new LambdaQueryWrapper<InviteCard>()
.eq(InviteCard::getType, type)
.eq(InviteCard::getRefId, refId)
.eq(InviteCard::getUsed, 0)
);
for (InviteCard card : oldCards) {
card.setUsed(1);
card.setUpdatedAt(LocalDateTime.now());
inviteCardMapper.updateById(card);
}
}
private String generateCode(String type) {
String prefix;
switch (type) {
case "child": prefix = "CHILD_"; break;
case "family": prefix = "FAMILY_"; break;
case "guide": prefix = "GUIDE_"; break;
default: prefix = "INV_"; break;
}
String random = UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase();
return prefix + random;
}
/**
* 验证邀请码并返回关联信息
*/
public Result<Map<String, Object>> verify(String code) {
InviteCard card = inviteCardMapper.selectOne(
new LambdaQueryWrapper<InviteCard>().eq(InviteCard::getCode, code)
);
if (card == null) {
return Result.error("邀请码无效");
}
if (card.getUsed() == 1) {
return Result.error("邀请码已使用");
}
if (card.getExpiresAt().isBefore(LocalDateTime.now())) {
return Result.error("邀请码已过期");
}
Map<String, Object> result = new HashMap<>();
result.put("type", card.getType());
result.put("refId", card.getRefId());
if ("child".equals(card.getType())) {
Child child = childMapper.selectById(card.getRefId());
if (child != null) {
result.put("nickname", child.getNickname());
Family family = familyMapper.selectById(child.getFamilyId());
if (family != null) {
result.put("familyName", family.getName());
}
}
} else if ("family".equals(card.getType())) {
Family family = familyMapper.selectById(card.getRefId());
if (family != null) {
result.put("familyName", family.getName());
}
}
return Result.success(result);
}
/**
* 接受邀请 - 核心逻辑
* @param code 邀请码
* @param phone 手机号
* @return 登录结果(token, userId, role, familyId)
*/
public Result<Map<String, Object>> accept(String code, String phone) {
InviteCard card = inviteCardMapper.selectOne(
new LambdaQueryWrapper<InviteCard>().eq(InviteCard::getCode, code)
);
if (card == null) return Result.error("邀请码无效");
if (card.getUsed() == 1) return Result.error("邀请码已使用");
if (card.getExpiresAt().isBefore(LocalDateTime.now())) return Result.error("邀请码已过期");
if ("child".equals(card.getType())) {
return acceptChild(card, phone);
} else if ("family".equals(card.getType())) {
return acceptFamily(card, phone);
} else if ("guide".equals(card.getType())) {
return acceptGuide(card, phone);
}
return Result.error("未知邀请类型");
}
private Result<Map<String, Object>> acceptChild(InviteCard card, String phone) {
Child child = childMapper.selectById(card.getRefId());
if (child == null) return Result.error("孩子信息不存在");
if (child.getPhone() == null || !child.getPhone().equals(phone)) {
return Result.error("手机号不匹配,请联系家长确认");
}
// 检查孩子是否已有账号
User existingUser = userMapper.selectOne(
new LambdaQueryWrapper<User>().eq(User::getPhone, phone)
);
User user;
if (existingUser != null) {
user = existingUser;
} else {
// 自动注册
user = new User();
user.setPhone(phone);
user.setNickname(child.getNickname() != null ? child.getNickname() : "孩子");
user.setRole("child");
user.setFamilyId(child.getFamilyId());
user.setAvatar(child.getAvatar());
userMapper.insert(user);
}
// 标记邀请码已使用
card.setUsed(1);
card.setUpdatedAt(LocalDateTime.now());
inviteCardMapper.updateById(card);
Map<String, Object> result = new HashMap<>();
result.put("token", userService.generateToken(user));
result.put("userId", user.getId());
result.put("role", "child");
result.put("familyId", user.getFamilyId());
return Result.success(result);
}
private Result<Map<String, Object>> acceptFamily(InviteCard card, String phone) {
Family family = familyMapper.selectById(card.getRefId());
if (family == null) return Result.error("家庭不存在");
// 检查手机号是否已注册
User existingUser = userMapper.selectOne(
new LambdaQueryWrapper<User>().eq(User::getPhone, phone)
);
User user;
if (existingUser != null) {
user = existingUser;
// 更新家庭ID
user.setFamilyId(card.getRefId());
userMapper.updateById(user);
} else {
user = new User();
user.setPhone(phone);
user.setNickname("家人");
user.setRole("parent");
user.setFamilyId(card.getRefId());
userMapper.insert(user);
}
card.setUsed(1);
card.setUpdatedAt(LocalDateTime.now());
inviteCardMapper.updateById(card);
Map<String, Object> result = new HashMap<>();
result.put("token", userService.generateToken(user));
result.put("userId", user.getId());
result.put("role", user.getRole());
result.put("familyId", user.getFamilyId());
return Result.success(result);
}
private Result<Map<String, Object>> acceptGuide(InviteCard card, String phone) {
// 规划师邀请只返回家庭信息,让用户自己去规划师注册页走完整流程
Family family = familyMapper.selectById(card.getRefId());
if (family == null) return Result.error("家庭不存在");
card.setUsed(1);
card.setUpdatedAt(LocalDateTime.now());
inviteCardMapper.updateById(card);
Map<String, Object> result = new HashMap<>();
result.put("familyId", card.getRefId());
result.put("familyName", family.getName());
result.put("type", "guide");
return Result.success(result);
}
}
[ ] Step 2: 编译验证
cd zxyj-backend && mvn clean compile
确认无编译错误。
[ ] Step 3: Commit
git add zxyj-backend/src/main/java/com/zxyj/service/InviteCardService.java
git commit -m "feat: add InviteCardService with generate/verify/accept logic"
Files:
zxyj-backend/src/main/java/com/zxyj/controller/guide/InviteCardController.javaTest: 启动后端 + curl 测试
[ ] Step 1: 创建 InviteCardController
package com.zxyj.controller.guide;
import com.zxyj.common.Result;
import com.zxyj.entity.*;
import com.zxyj.mapper.*;
import com.zxyj.service.InviteCardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Tag(name = "邀请卡片", description = "家庭成员/规划师邀请分享")
@RestController
@RequestMapping("/api/invite-card")
public class InviteCardController {
@Resource
private InviteCardService inviteCardService;
@Resource
private ChildMapper childMapper;
@Resource
private FamilyMapper familyMapper;
@Resource
private UserMapper userMapper;
@PostMapping("/generate")
@Operation(summary = "生成邀请码")
public Result<Map<String, Object>> generate(@RequestBody Map<String, Object> params,
HttpServletRequest request) {
Long userId = (Long) request.getAttribute("userId");
String type = (String) params.get("type");
Object refIdObj = params.get("refId");
if (type == null || refIdObj == null) {
return Result.error("缺少必要参数: type/refId");
}
Long refId = Long.valueOf(refIdObj.toString());
// 验证用户对 refId 的归属权
if ("child".equals(type)) {
Child child = childMapper.selectById(refId);
if (child == null) return Result.error("孩子不存在");
User user = userMapper.selectById(userId);
if (user == null || !user.getFamilyId().equals(child.getFamilyId())) {
return Result.error("无权生成该孩子的邀请");
}
} else if ("family".equals(type) || "guide".equals(type)) {
Family family = familyMapper.selectById(refId);
if (family == null) return Result.error("家庭不存在");
User user = userMapper.selectById(userId);
if (user == null || !user.getFamilyId().equals(family.getId())) {
return Result.error("无权生成该家庭的邀请");
}
}
InviteCard card = inviteCardService.generate(type, refId, userId);
Map<String, Object> result = new HashMap<>();
result.put("code", card.getCode());
result.put("expiresAt", card.getExpiresAt().toString());
return Result.success(result);
}
@PostMapping("/verify")
@Operation(summary = "验证邀请码")
public Result<Map<String, Object>> verify(@RequestBody Map<String, Object> params) {
String code = (String) params.get("code");
if (code == null) return Result.error("缺少邀请码");
return inviteCardService.verify(code);
}
@PostMapping("/accept")
@Operation(summary = "接受邀请(自动注册/登录)")
public Result<Map<String, Object>> accept(@RequestBody Map<String, Object> params) {
String code = (String) params.get("code");
String phone = (String) params.get("phone");
if (code == null || phone == null) {
return Result.error("缺少必要参数: code/phone");
}
return inviteCardService.accept(code, phone);
}
}
[ ] Step 2: 检查 UserService.generateToken 方法是否存在
搜索 userService.generateToken 或 UserService 中是否有生成 token 的方法。如果不存在,需要实现一个简单的 JWT token 生成(复用项目中已有的 JwtUtil):
// 在 InviteCardService 中注入 JwtUtil
@Resource
private com.zxyj.config.JwtUtil jwtUtil;
// 然后 generateToken 方法:
private String generateToken(User user) {
return jwtUtil.generateToken(user.getId(), user.getPhone(), user.getRole());
}
如果项目中已有 JwtUtil,确认其 generateToken 的方法签名一致。
[ ] Step 3: 启动后端测试
cd zxyj-backend && mvn spring-boot:run
在另一个终端测试:
# 生成邀请码(需要先获取 token)
# 测试 child 类型
curl -X POST http://localhost:8080/api/invite-card/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"type":"child","refId":1}'
# 验证邀请码
curl -X POST http://localhost:8080/api/invite-card/verify \
-H "Content-Type: application/json" \
-d '{"code":"CHILD_XXXXXX"}'
确认返回正常。
[ ] Step 4: Commit
git add zxyj-backend/src/main/java/com/zxyj/controller/guide/InviteCardController.java
git commit -m "feat: add InviteCardController with generate/verify/accept endpoints"
Files:
Modify: zxyj-frontend/utils/api.js
[ ] Step 1: 新增 API 函数
在 utils/api.js 末尾添加:
// 邀请卡片
export const generateInviteCard = (type, refId) => {
return request('/api/invite-card/generate', 'POST', { type, refId })
}
export const verifyInviteCard = (code) => {
return request('/api/invite-card/verify', 'POST', { code })
}
export const acceptInviteCard = (code, phone) => {
return request('/api/invite-card/accept', 'POST', { code, phone })
}
[ ] Step 2: Commit
git add zxyj-frontend/utils/api.js
git commit -m "feat: add invite card API functions"
Files:
Modify: zxyj-frontend/pages/profile/children.vue
[ ] Step 1: 在模板每个孩子项加「邀请」按钮
找到 <view class="child-actions">,在编辑后加邀请按钮:
<view class="child-actions">
<text class="btn-invite" @click="inviteChild(child)">邀请</text>
<text class="btn-edit" @click="editChild(child)">编辑</text>
</view>
在 data() 中添加:
data() {
return {
children: [],
shareData: null // 用于 onShareAppMessage
}
}
在 onLoad 中定义 onShareAppMessage:
onLoad() {
// 必须在页面级别定义 onShareAppMessage,不能在 methods 里
},
onShareAppMessage() {
if (this.shareData) {
return {
title: this.shareData.title,
path: this.shareData.path,
imageUrl: '/static/invite-card.png'
}
}
return { title: '知行益家', path: '/pages/login/login' }
}
在 methods 中添加:
async inviteChild(child) {
try {
const { generateInviteCard } = require('../../utils/api.js')
const res = await generateInviteCard('child', child.id)
const code = res.data.code
// 保存分享数据
this.shareData = {
title: `邀请 ${child.nickname || '孩子'} 加入家庭`,
path: `/pages/login/login?invite_code=${code}`
}
// 触发微信原生分享
// 注意:需要 button open-type="share" 才能触发
uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
} catch (e) {
uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
}
}
由于微信分享必须通过 button open-type="share" 触发(不能通过 @click 手动调),需要改模板:
<view class="child-actions">
<button class="btn-invite-btn" open-type="share" @click="prepareInvite(child)">邀请</button>
<text class="btn-edit" @click="editChild(child)">编辑</text>
</view>
方法分离:
async prepareInvite(child) {
try {
const { generateInviteCard } = require('../../utils/api.js')
const res = await generateInviteCard('child', child.id)
const code = res.data.code
this.shareData = {
title: `邀请 ${child.nickname || '孩子'} 加入家庭`,
path: `/pages/login/login?invite_code=${code}`
}
this._waitingShare = true
} catch (e) {
uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
}
}
在 <style> 末尾添加:
.btn-invite-btn {
background: #3B82F6;
color: #fff;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 8rpx;
margin-right: 10rpx;
line-height: 1.8;
}
[ ] Step 6: Commit
git add zxyj-frontend/pages/profile/children.vue
git commit -m "feat: add invite button for each child in children management"
Files:
Modify: zxyj-frontend/pages/profile/profile.vue
[ ] Step 1: 添加 invite 相关数据和 onShareAppMessage
在 data() 中加:
data() {
return {
// ... existing data
shareData: null
}
}
在页面添加 onShareAppMessage(如果已存在则追加逻辑):
onShareAppMessage() {
if (this.shareData) {
return {
title: this.shareData.title,
path: this.shareData.path,
imageUrl: '/static/invite-card.png'
}
}
}
在菜单列表中找到合适位置,比如「积分记录」之后,添加:
<view class="menu-item" @click="prepareFamilyInvite" v-if="role === 'parent'">
<text>👨👩👧👦 邀请家人</text>
<text class="arrow">›</text>
</view>
由于分享需要 button 触发,改为:
<button class="menu-item-btn" open-type="share" @click="prepareFamilyInvite" v-if="role === 'parent'">
<text>👨👩👧👦 邀请家人</text>
<text class="arrow">›</text>
</button>
在 methods 中添加:
async prepareFamilyInvite() {
try {
const userInfo = uni.getStorageSync('userInfo')
const familyId = uni.getStorageSync('familyId')
if (!familyId) {
uni.showToast({ title: '您暂未加入家庭', icon: 'none' })
return
}
const { generateInviteCard } = require('../../utils/api.js')
const res = await generateInviteCard('family', familyId)
const code = res.data.code
this.shareData = {
title: `${userInfo.nickname || '家人'} 邀请你加入家庭`,
path: `/pages/login/login?invite_code=${code}`
}
uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
} catch (e) {
uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
}
}
[ ] Step 5: 添加按钮样式
.menu-item-btn {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 28rpx 24rpx;
border: none;
background: transparent;
font-size: 28rpx;
color: #333;
border-bottom: 1rpx solid #f0f0f0;
}
.menu-item-btn::after { border: none; }
[ ] Step 6: Commit
git add zxyj-frontend/pages/profile/profile.vue
git commit -m "feat: add invite family button in profile page"
Files:
Modify: zxyj-frontend/pages/growth/index.vue
[ ] Step 1: 添加分享数据和 onShareAppMessage
在 data() 中添加:
data() {
return {
// ... existing data
shareData: null
}
}
添加 onShareAppMessage 生命周期:
onShareAppMessage() {
if (this.shareData) {
return {
title: this.shareData.title,
path: this.shareData.path,
imageUrl: '/static/invite-card.png'
}
}
}
在 <view class="footer-btn"> 区域添加(或在 children-list 区域):
<view class="footer-btn" v-if="!selectedChild">
<button class="btn-invite-guide" open-type="share" @click="prepareGuideInvite">
邀请成长规划师
</button>
</view>
在 methods 中添加:
async prepareGuideInvite() {
try {
const familyId = uni.getStorageSync('familyId')
if (!familyId) {
uni.showToast({ title: '您暂未加入家庭', icon: 'none' })
return
}
const userInfo = uni.getStorageSync('userInfo')
const { generateInviteCard } = require('../../utils/api.js')
const res = await generateInviteCard('guide', familyId)
const code = res.data.code
this.shareData = {
title: `${userInfo.nickname || '家长'} 邀请你成为成长规划师`,
path: `/pages/login/login?invite_code=${code}`
}
uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
} catch (e) {
uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
}
}
[ ] Step 4: 添加样式
.btn-invite-guide {
background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff;
border-radius: 50rpx;
padding: 20rpx 0;
font-size: 28rpx;
margin-top: 20rpx;
width: 100%;
}
[ ] Step 5: Commit
git add zxyj-frontend/pages/growth/index.vue
git commit -m "feat: add invite guide button in growth page"
Files:
Modify: zxyj-frontend/pages/login/login.vue
[ ] Step 1: onLoad 增加 invite_code 捕获
找到 onLoad 方法,修改为支持 invite_code 参数:
onLoad(options) {
// 处理邀请码
if (options.invite_code) {
uni.setStorageSync('inviteCode', options.invite_code)
console.log('检测到邀请码:', options.invite_code)
}
// 兼容旧版参数名
if (options.inviteCode && !options.invite_code) {
uni.setStorageSync('inviteCode', options.inviteCode)
}
}
找到 handleWechatPhoneLogin 方法,在 wechatPhoneLogin 调用之前,检测是否有 inviteCode:
查看 handleWechatPhoneLogin 的逻辑。在当前流程中,微信登录拿到手机号后直接调后端 wechatPhoneLogin。需要在手机号获取成功后,如果存在邀请码,先调 acceptInviteCard 而不是普通登录。
修改 handleWechatPhoneLogin 方法:
async handleWechatPhoneLogin(e) {
if (e.detail.errMsg === 'getPhoneNumber:ok') {
// ... 现有手机号验证逻辑 ...
this.loading = true
try {
const inputPhone = this.phone
// 检查是否有邀请码
const inviteCode = uni.getStorageSync('inviteCode')
if (inviteCode) {
// 走邀请码自动注册/登录流程
const { acceptInviteCard } = require('../../utils/api.js')
const res = await acceptInviteCard(inviteCode, inputPhone)
// 保存登录信息
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('userId', res.data.userId)
uni.setStorageSync('role', res.data.role)
uni.setStorageSync('currentRole', res.data.role)
if (res.data.familyId) {
uni.setStorageSync('familyId', res.data.familyId)
}
// 清除邀请码
uni.removeStorageSync('inviteCode')
uni.showToast({ title: '加入成功', icon: 'success' })
setTimeout(() => {
this.navigateToHome(res.data.role)
}, 1000)
return
}
// 没有邀请码,走普通登录逻辑
// ... 现有登录逻辑 ...
注意:需要判断微信登录返回时 phone 字段的获取方式。当前微信登录的测试环境中,用户输入的 inputPhone 直接作为手机号传递。
在 phoneLogin 方法中,检测邀请码:
async phoneLogin() {
// ... 现有验证逻辑 ...
this.loading = true
try {
// 检查是否有邀请码
const inviteCode = uni.getStorageSync('inviteCode')
if (inviteCode) {
const { acceptInviteCard } = require('../../utils/api.js')
const res = await acceptInviteCard(inviteCode, this.phone)
// 保存登录信息
uni.setStorageSync('token', res.data.token)
uni.setStorageSync('userId', res.data.userId)
uni.setStorageSync('role', res.data.role)
uni.setStorageSync('currentRole', res.data.role)
if (res.data.familyId) {
uni.setStorageSync('familyId', res.data.familyId)
}
uni.removeStorageSync('inviteCode')
uni.showToast({ title: '加入成功', icon: 'success' })
setTimeout(() => {
this.navigateToHome(res.data.role)
}, 1000)
return
}
// 没有邀请码,走普通登录
const res = await phoneLoginApi({...})
// ... 现有逻辑 ...
[ ] Step 4: Commit
git add zxyj-frontend/pages/login/login.vue
git commit -m "feat: handle invite_code auto-login in login page"
InviteCard 字段名(type/refId/code/used)在所有任务中保持一致button open-type="share" 触发,已在 children.vue 和 profile.vue 中正确处理getPhoneNumber 解密在测试环境走 inputPhone 明文传递,已在 login.vue 中兼容