feat: 新增微信小程序登录功能及第三方账号绑定表

This commit is contained in:
Ray.Hao
2026-03-05 07:45:01 +08:00
parent 9d117ce884
commit 27a8f0e6a5
40 changed files with 1643 additions and 164 deletions

View File

@@ -1,6 +1,7 @@
package com.youlai.boot.auth.controller;
import com.youlai.boot.auth.model.vo.CaptchaVO;
import com.youlai.boot.auth.model.vo.WechatLoginResult;
import com.youlai.boot.auth.model.dto.LoginRequest;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.core.web.Result;
@@ -67,6 +68,39 @@ public class AuthController {
return Result.success();
}
@Operation(summary = "微信小程序登录(个人小程序)")
@PostMapping("/wechat-miniapp/login")
@Log(value = "微信小程序登录", module = LogModuleEnum.LOGIN)
public Result<WechatLoginResult> loginByWechatMini(
@Parameter(description = "微信登录code", example = "xxx") @RequestParam String code
) {
WechatLoginResult result = authService.loginByWechatMini(code);
return Result.success(result);
}
@Operation(summary = "微信小程序一键登录(企业小程序)")
@PostMapping("/wechat-miniapp/phone-login")
@Log(value = "微信小程序一键登录", module = LogModuleEnum.LOGIN)
public Result<AuthenticationToken> loginByWechatMiniWithPhone(
@Parameter(description = "微信登录code", example = "xxx") @RequestParam String loginCode,
@Parameter(description = "手机号授权code", example = "xxx") @RequestParam String phoneCode
) {
AuthenticationToken result = authService.wechatMiniLoginWithPhone(loginCode, phoneCode);
return Result.success(result);
}
@Operation(summary = "微信小程序绑定手机号")
@PostMapping("/wechat-miniapp/bind-mobile")
@Log(value = "微信小程序绑定手机号", module = LogModuleEnum.LOGIN)
public Result<AuthenticationToken> bindMobileForWechatMini(
@Parameter(description = "微信openid") @RequestParam String openid,
@Parameter(description = "手机号", example = "18812345678") @RequestParam String mobile,
@Parameter(description = "短信验证码", example = "1234") @RequestParam String code
) {
AuthenticationToken result = authService.bindMobileForWechatMini(openid, mobile, code);
return Result.success(result);
}
@Operation(summary = "退出登录")
@DeleteMapping("/logout")
@Log(value = "退出登录", module = LogModuleEnum.LOGIN)

View File

@@ -0,0 +1,60 @@
package com.youlai.boot.auth.model.vo;
import com.youlai.boot.security.model.AuthenticationToken;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 微信小程序登录结果
*/
@Data
@Schema(description = "微信小程序登录结果")
public class WechatLoginResult {
@Schema(description = "是否新用户")
private Boolean isNewUser;
@Schema(description = "是否需要绑定手机号")
private Boolean needBindMobile;
@Schema(description = "微信openid绑定手机号时需要")
private String openid;
@Schema(description = "访问令牌")
private String accessToken;
@Schema(description = "刷新令牌")
private String refreshToken;
@Schema(description = "令牌类型")
private String tokenType;
@Schema(description = "过期时间(秒)")
private Long expiresIn;
/**
* 创建需要绑定手机号的结果
*/
public static WechatLoginResult needBindMobile(String openid) {
WechatLoginResult result = new WechatLoginResult();
result.setIsNewUser(true);
result.setNeedBindMobile(true);
result.setOpenid(openid);
return result;
}
/**
* 创建登录成功的结果
*/
public static WechatLoginResult success(AuthenticationToken token) {
WechatLoginResult result = new WechatLoginResult();
result.setIsNewUser(false);
result.setNeedBindMobile(false);
result.setAccessToken(token.getAccessToken());
result.setRefreshToken(token.getRefreshToken());
result.setTokenType(token.getTokenType());
result.setExpiresIn(token.getExpiresIn());
return result;
}
}

View File

@@ -1,6 +1,7 @@
package com.youlai.boot.auth.service;
import com.youlai.boot.auth.model.vo.CaptchaVO;
import com.youlai.boot.auth.model.vo.WechatLoginResult;
import com.youlai.boot.security.model.AuthenticationToken;
/**
@@ -55,4 +56,31 @@ public interface AuthService {
* @return 登录结果
*/
AuthenticationToken loginBySms(String mobile, String code);
/**
* 微信小程序登录(个人小程序)
*
* @param code 微信登录code
* @return 登录结果
*/
WechatLoginResult loginByWechatMini(String code);
/**
* 微信小程序一键登录(企业小程序)
*
* @param loginCode 微信登录code
* @param phoneCode 手机号授权code
* @return 登录结果
*/
AuthenticationToken wechatMiniLoginWithPhone(String loginCode, String phoneCode);
/**
* 微信小程序绑定手机号
*
* @param openid 微信openid
* @param mobile 手机号
* @param smsCode 短信验证码
* @return 登录结果
*/
AuthenticationToken bindMobileForWechatMini(String openid, String mobile, String smsCode);
}

View File

@@ -1,21 +1,30 @@
package com.youlai.boot.auth.service.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.auth.model.vo.CaptchaVO;
import com.youlai.boot.auth.model.vo.WechatLoginResult;
import com.youlai.boot.auth.service.AuthService;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.enums.CaptchaTypeEnum;
import com.youlai.boot.config.property.CaptchaProperties;
import com.youlai.boot.support.sms.enums.SmsTypeEnum;
import com.youlai.boot.support.sms.service.SmsService;
import com.youlai.boot.security.exception.NeedBindMobileException;
import com.youlai.boot.security.model.AuthenticationToken;
import com.youlai.boot.security.model.SmsAuthenticationToken;
import com.youlai.boot.security.model.SysUserDetails;
import com.youlai.boot.security.model.WechatMiniAuthenticationToken;
import com.youlai.boot.security.token.TokenManager;
import com.youlai.boot.security.util.SecurityUtils;
import com.youlai.boot.support.sms.enums.SmsTypeEnum;
import com.youlai.boot.support.sms.service.SmsService;
import com.youlai.boot.system.enums.SocialPlatformEnum;
import com.youlai.boot.system.model.entity.User;
import com.youlai.boot.system.service.UserSocialService;
import com.youlai.boot.system.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
@@ -24,8 +33,10 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.awt.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -50,6 +61,9 @@ public class AuthServiceImpl implements AuthService {
private final SmsService smsService;
private final RedisTemplate<String, Object> redisTemplate;
private final UserSocialService userSocialService;
private final UserService userService;
private final WxMaService wxMaService;
/**
* 用户名密码登录
@@ -198,4 +212,150 @@ public class AuthServiceImpl implements AuthService {
return tokenManager.refreshToken(refreshToken);
}
/**
* 微信小程序登录(个人小程序)
*/
@Override
public WechatLoginResult loginByWechatMini(String code) {
WechatMiniAuthenticationToken token = new WechatMiniAuthenticationToken(code);
try {
Authentication authentication = authenticationManager.authenticate(token);
AuthenticationToken authToken = tokenManager.generateToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return WechatLoginResult.success(authToken);
} catch (NeedBindMobileException e) {
return WechatLoginResult.needBindMobile(e.getOpenid());
}
}
/**
* 微信小程序一键登录(企业小程序)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AuthenticationToken wechatMiniLoginWithPhone(String loginCode, String phoneCode) {
// 1. 用 loginCode 换取 openid
cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult session;
try {
session = wxMaService.jsCode2SessionInfo(loginCode);
} catch (Exception e) {
log.error("微信小程序一键登录失败获取openid异常loginCode={}", loginCode, e);
throw new IllegalArgumentException("微信登录失败:" + e.getMessage());
}
String openid = session.getOpenid();
// 2. 用 phoneCode 换取手机号
cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo phoneInfo;
try {
phoneInfo = wxMaService.getUserService().getPhoneNoInfo(phoneCode);
} catch (Exception e) {
log.error("微信小程序一键登录失败获取手机号异常phoneCode={}", phoneCode, e);
throw new IllegalArgumentException("获取手机号失败:" + e.getMessage());
}
String mobile = phoneInfo.getPhoneNumber();
log.info("微信小程序一键登录openid={}, mobile={}", openid, mobile);
// 3. 查询或创建用户
User user = userService.lambdaQuery()
.eq(User::getMobile, mobile)
.one();
if (user == null) {
user = new User();
user.setMobile(mobile);
user.setUsername("wx_" + IdUtil.fastSimpleUUID().substring(0, 8));
user.setNickname(phoneInfo.getNickName() != null ? phoneInfo.getNickName() : "微信用户");
user.setAvatar(phoneInfo.getAvatarUrl());
user.setStatus(1);
user.setIsDeleted(0);
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userService.save(user);
log.info("微信小程序一键登录创建新用户mobile={}, userId={}", mobile, user.getId());
}
// 4. 绑定 openid
userSocialService.bindOrUpdate(
user.getId(),
SocialPlatformEnum.WECHAT_MINI,
openid,
session.getUnionid(),
user.getNickname(),
user.getAvatar(),
session.getSessionKey()
);
// 5. 生成 token
SysUserDetails userDetails = new SysUserDetails(userService.getAuthInfoByMobile(mobile));
AuthenticationToken authToken = tokenManager.generateToken(userDetails);
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
);
log.info("微信小程序一键登录成功mobile={}, openid={}", mobile, openid);
return authToken;
}
/**
* 微信小程序绑定手机号
*/
@Override
@Transactional(rollbackFor = Exception.class)
public AuthenticationToken bindMobileForWechatMini(String openid, String mobile, String smsCode) {
// 1. 验证短信验证码
String cacheKey = StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile);
String cachedCode = (String) redisTemplate.opsForValue().get(cacheKey);
if (!StrUtil.equals(smsCode, cachedCode)) {
throw new IllegalArgumentException("验证码错误");
}
// 删除验证码
redisTemplate.delete(cacheKey);
// 2. 查询或创建用户
User user = userService.lambdaQuery()
.eq(User::getMobile, mobile)
.one();
if (user == null) {
// 创建新用户
user = new User();
user.setMobile(mobile);
user.setUsername("wx_" + IdUtil.fastSimpleUUID().substring(0, 8));
user.setNickname("微信用户");
user.setStatus(1);
user.setIsDeleted(0);
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userService.save(user);
log.info("微信小程序绑定手机号创建新用户mobile={}, userId={}", mobile, user.getId());
}
// 3. 绑定第三方账号
userSocialService.bindOrUpdate(
user.getId(),
SocialPlatformEnum.WECHAT_MINI,
openid,
null,
null,
null,
null
);
// 4. 生成token
SysUserDetails userDetails = new SysUserDetails(userService.getAuthInfoByMobile(mobile));
AuthenticationToken authToken = tokenManager.generateToken(userDetails);
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
);
log.info("微信小程序绑定手机号成功mobile={}, openid={}", mobile, openid);
return authToken;
}
}

View File

@@ -1,5 +1,6 @@
package com.youlai.boot.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.ArrayUtil;
import com.youlai.boot.config.property.SecurityProperties;
@@ -9,9 +10,11 @@ import com.youlai.boot.security.filter.TokenAuthenticationFilter;
import com.youlai.boot.security.handler.MyAccessDeniedHandler;
import com.youlai.boot.security.handler.MyAuthenticationEntryPoint;
import com.youlai.boot.security.provider.SmsAuthenticationProvider;
import com.youlai.boot.security.provider.WechatMiniAuthenticationProvider;
import com.youlai.boot.security.token.TokenManager;
import com.youlai.boot.security.service.SysUserDetailsService;
import com.youlai.boot.system.service.ConfigService;
import com.youlai.boot.system.service.UserSocialService;
import com.youlai.boot.system.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
@@ -54,6 +57,9 @@ public class SecurityConfig {
private final ConfigService configService;
private final SecurityProperties securityProperties;
private final WxMaService wxMaService;
private final UserSocialService userSocialService;
/**
* 配置安全过滤链 SecurityFilterChain
*/
@@ -128,17 +134,27 @@ public class SecurityConfig {
return new SmsAuthenticationProvider(userService, redisTemplate);
}
/**
* 微信小程序认证 Provider
*/
@Bean
public WechatMiniAuthenticationProvider wechatMiniAuthenticationProvider() {
return new WechatMiniAuthenticationProvider(wxMaService, userSocialService);
}
/**
* 认证管理器
*/
@Bean
public AuthenticationManager authenticationManager(
DaoAuthenticationProvider daoAuthenticationProvider,
SmsAuthenticationProvider smsAuthenticationProvider
SmsAuthenticationProvider smsAuthenticationProvider,
WechatMiniAuthenticationProvider wechatMiniAuthenticationProvider
) {
return new ProviderManager(
daoAuthenticationProvider,
smsAuthenticationProvider
smsAuthenticationProvider,
wechatMiniAuthenticationProvider
);
}
}

View File

@@ -0,0 +1,31 @@
package com.youlai.boot.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 微信小程序配置
*/
@Configuration
@EnableConfigurationProperties(WxMaProperties.class)
public class WxMaConfiguration {
@Bean
public WxMaService wxMaService(WxMaProperties properties) {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
config.setAppid(properties.getAppid());
config.setSecret(properties.getSecret());
config.setToken(properties.getToken());
config.setAesKey(properties.getAesKey());
config.setMsgDataFormat(properties.getMsgDataFormat());
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(config);
return service;
}
}

View File

@@ -0,0 +1,38 @@
package com.youlai.boot.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 微信小程序配置属性
*/
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {
/**
* 小程序appid
*/
private String appid;
/**
* 小程序Secret
*/
private String secret;
/**
* 小程序token
*/
private String token;
/**
* 小程序EncodingAESKey
*/
private String aesKey;
/**
* 消息格式
*/
private String msgDataFormat;
}

View File

@@ -0,0 +1,28 @@
package com.youlai.boot.security.exception;
import org.springframework.security.core.AuthenticationException;
/**
* 需要绑定手机号异常
*/
public class NeedBindMobileException extends AuthenticationException {
private final String openid;
private final String sessionKey;
public NeedBindMobileException(String openid, String sessionKey) {
super("需要绑定手机号");
this.openid = openid;
this.sessionKey = sessionKey;
}
public String getOpenid() {
return openid;
}
public String getSessionKey() {
return sessionKey;
}
}

View File

@@ -0,0 +1,73 @@
package com.youlai.boot.security.model;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import java.io.Serial;
import java.util.Collection;
/**
* 微信小程序认证 Token
*/
public class WechatMiniAuthenticationToken extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = 622L;
/**
* 认证信息
* 未认证时微信code
* 已认证时SysUserDetails 用户详情
*/
private final Object principal;
/**
* 凭证信息
* 未认证时null
* 已认证时null
*/
private final Object credentials;
/**
* 创建未认证的 Token
*
* @param code 微信小程序code
*/
public WechatMiniAuthenticationToken(String code) {
super(AuthorityUtils.NO_AUTHORITIES);
this.principal = code;
this.credentials = null;
setAuthenticated(false);
}
/**
* 创建已认证的 Token
*
* @param principal 用户详情SysUserDetails
* @param authorities 授权信息
*/
public WechatMiniAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = null;
super.setAuthenticated(true);
}
/**
* 创建已认证的 Token静态工厂方法
*/
public static WechatMiniAuthenticationToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
return new WechatMiniAuthenticationToken(principal, authorities);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
}

View File

@@ -0,0 +1,93 @@
package com.youlai.boot.security.provider;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.util.ObjectUtil;
import com.youlai.boot.security.exception.NeedBindMobileException;
import com.youlai.boot.security.model.SysUserDetails;
import com.youlai.boot.security.model.UserAuthInfo;
import com.youlai.boot.security.model.WechatMiniAuthenticationToken;
import com.youlai.boot.system.enums.SocialPlatformEnum;
import com.youlai.boot.system.model.entity.UserSocial;
import com.youlai.boot.system.service.UserSocialService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 微信小程序认证 Provider
*/
@Slf4j
@RequiredArgsConstructor
public class WechatMiniAuthenticationProvider implements AuthenticationProvider {
private final WxMaService wxMaService;
private final UserSocialService userSocialService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String code = (String) authentication.getPrincipal();
if (code == null || code.isEmpty()) {
log.warn("微信小程序登录失败code为空");
throw new IllegalArgumentException("code不能为空");
}
try {
// 1. 用 code 换取 openid
WxMaJscode2SessionResult session = wxMaService.jsCode2SessionInfo(code);
String openid = session.getOpenid();
String sessionKey = session.getSessionKey();
log.info("微信小程序登录openid={}", openid);
// 2. 根据 openid 查询绑定信息
UserSocial userSocial = userSocialService.getByPlatformAndOpenid(SocialPlatformEnum.WECHAT_MINI, openid);
if (userSocial == null) {
// 未绑定,抛出异常提示需要绑定手机号
log.info("微信小程序登录用户未绑定手机号openid={}", openid);
throw new NeedBindMobileException(openid, sessionKey);
}
// 3. 获取用户认证信息
UserAuthInfo userAuthInfo = userSocialService.getAuthInfoByOpenid(SocialPlatformEnum.WECHAT_MINI, openid);
if (userAuthInfo == null) {
log.warn("微信小程序登录失败用户不存在openid={}", openid);
throw new UsernameNotFoundException("用户不存在");
}
// 4. 检查用户状态
if (ObjectUtil.notEqual(userAuthInfo.getStatus(), 1)) {
log.warn("微信小程序登录失败用户已禁用username={}", userAuthInfo.getUsername());
throw new DisabledException("用户已被禁用");
}
// 5. 更新 session_key
userSocialService.updateSessionKey(userSocial.getId(), sessionKey);
// 6. 构建已认证 Token
SysUserDetails userDetails = new SysUserDetails(userAuthInfo);
log.info("微信小程序登录成功username={}, openid={}", userAuthInfo.getUsername(), openid);
return WechatMiniAuthenticationToken.authenticated(userDetails, userDetails.getAuthorities());
} catch (WxErrorException e) {
log.error("微信小程序登录失败调用微信接口异常code={}", code, e);
throw new IllegalArgumentException("微信登录失败:" + e.getMessage());
}
}
@Override
public boolean supports(Class<?> authentication) {
return WechatMiniAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@@ -0,0 +1,30 @@
package com.youlai.boot.system.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.youlai.boot.common.base.IBaseEnum;
import lombok.Getter;
/**
* 第三方登录平台类型枚举
*/
@Getter
public enum SocialPlatformEnum implements IBaseEnum<String> {
WECHAT_MINI("WECHAT_MINI", "微信小程序"),
WECHAT_MP("WECHAT_MP", "微信公众号"),
WECHAT_OPEN("WECHAT_OPEN", "微信开放平台"),
ALIPAY("ALIPAY", "支付宝"),
QQ("QQ", "QQ"),
APPLE("APPLE", "Apple ID");
@EnumValue
private final String value;
private final String label;
SocialPlatformEnum(String value, String label) {
this.value = value;
this.label = label;
}
}

View File

@@ -0,0 +1,22 @@
package com.youlai.boot.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.boot.security.model.UserAuthInfo;
import com.youlai.boot.system.model.entity.UserSocial;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户第三方账号绑定持久层
*/
@Mapper
public interface UserSocialMapper extends BaseMapper<UserSocial> {
/**
* 根据用户ID获取认证信息
*
* @param userId 用户ID
* @return 认证信息
*/
UserAuthInfo getAuthInfoByUserId(Long userId);
}

View File

@@ -0,0 +1,74 @@
package com.youlai.boot.system.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.youlai.boot.common.base.BaseEntity;
import com.youlai.boot.system.enums.SocialPlatformEnum;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/**
* 用户第三方账号绑定实体
*/
@TableName("sys_user_social")
@Getter
@Setter
public class UserSocial {
/**
* 主键ID
*/
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 平台类型
*/
private SocialPlatformEnum platform;
/**
* 平台openid
*/
private String openid;
/**
* 微信unionid
*/
private String unionid;
/**
* 第三方昵称
*/
private String nickname;
/**
* 第三方头像URL
*/
private String avatar;
/**
* 微信session_key
*/
private String sessionKey;
/**
* 是否已验证(1-已验证 0-未验证)
*/
private Integer verified;
/**
* 绑定时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,70 @@
package com.youlai.boot.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.boot.security.model.UserAuthInfo;
import com.youlai.boot.system.enums.SocialPlatformEnum;
import com.youlai.boot.system.model.entity.UserSocial;
/**
* 用户第三方账号绑定业务接口
*/
public interface UserSocialService extends IService<UserSocial> {
/**
* 根据平台和openid查询绑定信息
*
* @param platform 平台类型
* @param openid openid
* @return 绑定信息
*/
UserSocial getByPlatformAndOpenid(SocialPlatformEnum platform, String openid);
/**
* 根据unionid查询绑定信息
*
* @param unionid unionid
* @return 绑定信息
*/
UserSocial getByUnionid(String unionid);
/**
* 绑定或更新第三方账号
*
* @param userId 用户ID
* @param platform 平台类型
* @param openid openid
* @param unionid unionid
* @param nickname 昵称
* @param avatar 头像
* @param sessionKey session_key
* @return 绑定信息
*/
UserSocial bindOrUpdate(Long userId, SocialPlatformEnum platform, String openid, String unionid, String nickname, String avatar, String sessionKey);
/**
* 解绑第三方账号
*
* @param userId 用户ID
* @param platform 平台类型
* @return 是否成功
*/
boolean unbind(Long userId, SocialPlatformEnum platform);
/**
* 根据openid获取用户认证信息
*
* @param platform 平台类型
* @param openid openid
* @return 用户认证信息
*/
UserAuthInfo getAuthInfoByOpenid(SocialPlatformEnum platform, String openid);
/**
* 更新session_key
*
* @param id 绑定记录ID
* @param sessionKey session_key
*/
void updateSessionKey(Long id, String sessionKey);
}

View File

@@ -0,0 +1,107 @@
package com.youlai.boot.system.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.security.model.UserAuthInfo;
import com.youlai.boot.system.enums.SocialPlatformEnum;
import com.youlai.boot.system.mapper.UserSocialMapper;
import com.youlai.boot.system.model.entity.UserSocial;
import com.youlai.boot.system.service.UserSocialService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
/**
* 用户第三方账号绑定业务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserSocialServiceImpl extends ServiceImpl<UserSocialMapper, UserSocial> implements UserSocialService {
@Override
public UserSocial getByPlatformAndOpenid(SocialPlatformEnum platform, String openid) {
return getOne(new LambdaQueryWrapper<UserSocial>()
.eq(UserSocial::getPlatform, platform)
.eq(UserSocial::getOpenid, openid));
}
@Override
public UserSocial getByUnionid(String unionid) {
if (StrUtil.isBlank(unionid)) {
return null;
}
return getOne(new LambdaQueryWrapper<UserSocial>()
.eq(UserSocial::getUnionid, unionid));
}
@Override
@Transactional(rollbackFor = Exception.class)
public UserSocial bindOrUpdate(Long userId, SocialPlatformEnum platform, String openid, String unionid, String nickname, String avatar, String sessionKey) {
UserSocial userSocial = getByPlatformAndOpenid(platform, openid);
LocalDateTime now = LocalDateTime.now();
if (userSocial == null) {
userSocial = new UserSocial();
userSocial.setUserId(userId);
userSocial.setPlatform(platform);
userSocial.setOpenid(openid);
userSocial.setUnionid(unionid);
userSocial.setNickname(nickname);
userSocial.setAvatar(avatar);
userSocial.setSessionKey(sessionKey);
userSocial.setVerified(1);
userSocial.setCreateTime(now);
userSocial.setUpdateTime(now);
save(userSocial);
log.info("第三方账号绑定成功userId={}, platform={}, openid={}", userId, platform, openid);
} else {
userSocial.setUserId(userId);
userSocial.setUnionid(unionid);
userSocial.setNickname(nickname);
userSocial.setAvatar(avatar);
userSocial.setSessionKey(sessionKey);
userSocial.setUpdateTime(now);
updateById(userSocial);
log.info("第三方账号更新成功userId={}, platform={}, openid={}", userId, platform, openid);
}
return userSocial;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean unbind(Long userId, SocialPlatformEnum platform) {
boolean removed = remove(new LambdaQueryWrapper<UserSocial>()
.eq(UserSocial::getUserId, userId)
.eq(UserSocial::getPlatform, platform));
if (removed) {
log.info("第三方账号解绑成功userId={}, platform={}", userId, platform);
}
return removed;
}
@Override
public UserAuthInfo getAuthInfoByOpenid(SocialPlatformEnum platform, String openid) {
UserSocial userSocial = getByPlatformAndOpenid(platform, openid);
if (userSocial == null) {
return null;
}
return baseMapper.getAuthInfoByUserId(userSocial.getUserId());
}
@Override
public void updateSessionKey(Long id, String sessionKey) {
UserSocial userSocial = getById(id);
if (userSocial != null) {
userSocial.setSessionKey(sessionKey);
userSocial.setUpdateTime(LocalDateTime.now());
updateById(userSocial);
}
}
}

View File

@@ -85,13 +85,10 @@ public class CodegenServiceImpl implements CodegenService {
return templateConfig.getTemplatePath();
}
if ("API".equals(templateName)) {
return "codegen/api.js.vm";
return "codegen/frontend/js/api.js.vm";
}
if ("VIEW".equals(templateName)) {
return "codegen/index.js.vue.vm";
}
if ("API_TYPES".equals(templateName)) {
return "codegen/api-types.js.vm";
return "codegen/frontend/js/index.js.vue.vm";
}
return templateConfig.getTemplatePath();
}

View File

@@ -210,8 +210,11 @@ captcha:
# 验证码有效期(秒)
expire-seconds: 120
# 微信小程配置
# 微信小程配置
wx:
miniapp:
app-id: xxxxxx
app-secret: xxxxxx
appid: your-app-id
secret: your-app-secret
token: your-token
aes-key: your-aes-key
msg-data-format: JSON

View File

@@ -17,45 +17,45 @@ codegen:
## 模板配置
templateConfigs:
API:
templatePath: codegen/api.ts.vm
templatePath: codegen/frontend/ts/api.ts.vm
subpackageName: api
extension: .ts
API_TYPES:
templatePath: codegen/api-types.ts.vm
templatePath: codegen/frontend/ts/api-types.ts.vm
subpackageName: types
extension: .ts
VIEW:
templatePath: codegen/index.vue.vm
templatePath: codegen/frontend/ts/index.vue.vm
subpackageName: views
extension: .vue
Controller:
templatePath: codegen/controller.java.vm
templatePath: codegen/backend/controller.java.vm
subpackageName: controller
Service:
templatePath: codegen/service.java.vm
templatePath: codegen/backend/service.java.vm
subpackageName: service
ServiceImpl:
templatePath: codegen/serviceImpl.java.vm
templatePath: codegen/backend/serviceImpl.java.vm
subpackageName: service.impl
Mapper:
templatePath: codegen/mapper.java.vm
templatePath: codegen/backend/mapper.java.vm
subpackageName: mapper
MapperXml:
templatePath: codegen/mapper.xml.vm
templatePath: codegen/backend/mapper.xml.vm
subpackageName: mapper
extension: .xml
Converter:
templatePath: codegen/converter.java.vm
templatePath: codegen/backend/converter.java.vm
subpackageName: converter
Query:
templatePath: codegen/query.java.vm
templatePath: codegen/backend/query.java.vm
subpackageName: model.query
Form:
templatePath: codegen/form.java.vm
templatePath: codegen/backend/form.java.vm
subpackageName: model.form
Vo:
templatePath: codegen/vo.java.vm
templatePath: codegen/backend/vo.java.vm
subpackageName: model.vo
Entity:
templatePath: codegen/entity.java.vm
templatePath: codegen/backend/entity.java.vm
subpackageName: model.entity

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.youlai.boot.system.mapper.UserSocialMapper">
<!-- 用户认证信息映射 -->
<resultMap id="AuthCredentialsMap" type="com.youlai.boot.security.model.UserAuthInfo">
<id property="userId" column="userId" jdbcType="BIGINT"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="nickname" column="nickname" jdbcType="VARCHAR"/>
<result property="deptId" column="dept_id" jdbcType="BIGINT"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="status" column="status" jdbcType="BOOLEAN"/>
<collection property="roles" ofType="string" javaType="java.util.Set">
<result column="code"/>
</collection>
</resultMap>
<!-- 根据用户ID获取认证信息 -->
<select id="getAuthInfoByUserId" resultMap="AuthCredentialsMap">
SELECT
u.id AS userId,
u.username,
u.nickname,
u.dept_id,
u.password,
u.status,
r.code
FROM
sys_user u
LEFT JOIN sys_user_role ur ON u.id = ur.user_id
LEFT JOIN sys_role r ON ur.role_id = r.id AND r.is_deleted = 0
WHERE
u.id = #{userId}
AND u.is_deleted = 0
</select>
</mapper>

View File

@@ -17,4 +17,4 @@ public interface ${entityName}Converter{
${entityName}Form toForm(${entityName} entity);
${entityName} toEntity(${entityName}Form formData);
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="app-container">
<div class="search-container">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="filter-section">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="auto">
#foreach($fieldConfig in $fieldConfigs)
#if($fieldConfig.isShowInQuery == 1)
<el-form-item label="$fieldConfig.fieldComment" prop="$fieldConfig.fieldName">
@@ -95,29 +95,34 @@
</el-form>
</div>
<el-card shadow="never">
<div class="mb-10px">
<el-button
v-hasPerm="['${moduleName}:${entityKebab}:create']"
type="success"
icon="plus"
@click="handleOpenDialog()"
>新增</el-button>
<el-button
v-hasPerm="['${moduleName}:${entityKebab}:delete']"
type="danger"
:disabled="removeIds.length === 0"
icon="delete"
@click="handleDelete()"
>删除</el-button>
<el-card shadow="hover" class="table-section">
<div class="table-section__toolbar">
<div class="table-section__toolbar--actions">
<el-button
v-hasPerm="['${moduleName}:${entityKebab}:create']"
type="success"
icon="plus"
@click="handleCreateClick()"
>新增</el-button>
<el-button
v-hasPerm="['${moduleName}:${entityKebab}:delete']"
type="danger"
:disabled="!hasSelection"
icon="delete"
@click="handleDelete()"
>删除</el-button>
</div>
</div>
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageData"
highlight-current-row
border
stripe
highlight-current-row
class="table-section__content"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
@@ -148,7 +153,7 @@
size="small"
link
icon="edit"
@click="handleOpenDialog(String(scope.row.id))"
@click="handleEditClick(String(scope.row.id))"
>
编辑
</el-button>
@@ -171,7 +176,7 @@
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery()"
@pagination="fetchList"
/>
</el-card>
@@ -180,7 +185,7 @@
v-model="dialog.visible"
:title="dialog.title"
width="500px"
@close="handleCloseDialog"
@close="closeDialog"
>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
#foreach($fieldConfig in $fieldConfigs)
@@ -260,7 +265,7 @@
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit()">确定</el-button>
<el-button @click="handleCloseDialog()">取消</el-button>
<el-button @click="closeDialog()">取消</el-button>
</div>
</template>
</el-dialog>
@@ -268,18 +273,20 @@
</template>
<script setup>
import { onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { useTableSelection } from "@/composables";
import ${entityName}API from "@/api/${moduleName}/${entityKebab}";
defineOptions({
name: "${entityName}",
inheritAttrs: false,
});
import ${entityName}API from "@/api/${moduleName}/${entityKebab}";
const queryFormRef = ref();
const dataFormRef = ref();
const loading = ref(false);
const removeIds = ref([]);
const total = ref(0);
const queryParams = reactive({
@@ -290,7 +297,6 @@
// $!{businessName}表格数据
const pageData = ref([]);
// 弹窗
const dialog = reactive({
title: "",
visible: false,
@@ -310,42 +316,57 @@
#end
});
const { selectedIds, hasSelection, handleSelectionChange } = useTableSelection();
async function fetchList() {
loading.value = true;
try {
const data = await ${entityName}API.getPage(queryParams);
pageData.value = data.list;
total.value = data.total ?? 0;
} finally {
loading.value = false;
}
}
/** 查询$!{businessName} */
function handleQuery() {
loading.value = true;
${entityName}API.getPage(queryParams)
.then((data) => {
pageData.value = data.list;
total.value = data.total ?? 0;
})
.finally(() => {
loading.value = false;
});
queryParams.pageNum = 1;
fetchList();
}
/** 重置$!{businessName}查询 */
function handleResetQuery() {
queryFormRef.value?.resetFields();
queryParams.pageNum = 1;
handleQuery();
}
/** 行复选框选中记录选中ID集合 */
function handleSelectionChange(selection) {
removeIds.value = selection.map((item) => String(item.id));
function openDialog() {
dialog.visible = true;
}
/** 打开$!{businessName}弹窗 */
function handleOpenDialog(id) {
dialog.visible = true;
if (id) {
dialog.title = "修改$!{businessName}";
${entityName}API.getFormData(id).then((data) => {
Object.assign(formData, data);
});
} else {
dialog.title = "新增$!{businessName}";
}
function closeDialog() {
dialog.visible = false;
resetForm();
}
function resetForm() {
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
formData.id = undefined;
}
async function handleCreateClick() {
dialog.title = "新增$!{businessName}";
openDialog();
}
async function handleEditClick(id) {
dialog.title = "修改$!{businessName}";
${entityName}API.getFormData(id).then((data) => {
Object.assign(formData, data);
openDialog();
});
}
/** 提交$!{businessName}表单 */
@@ -358,7 +379,7 @@
${entityName}API.update(id, formData)
.then(() => {
ElMessage.success("修改成功");
handleCloseDialog();
closeDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
@@ -366,7 +387,7 @@
${entityName}API.create(formData)
.then(() => {
ElMessage.success("新增成功");
handleCloseDialog();
closeDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
@@ -375,17 +396,9 @@
});
}
/** 关闭$!{businessName}弹窗 */
function handleCloseDialog() {
dialog.visible = false;
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
formData.id = undefined;
}
/** 删除$!{businessName} */
function handleDelete(id) {
const ids = [id || removeIds.value].join(",");
const ids = [id || selectedIds.value].join(",");
if (!ids) {
ElMessage.warning("请勾选删除项");
return;

View File

@@ -1,7 +1,7 @@
<template>
<div class="app-container">
<div class="search-container">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<div class="filter-section">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="auto">
#foreach($fieldConfig in $fieldConfigs)
#if($fieldConfig.isShowInQuery == 1)
<el-form-item label="$fieldConfig.fieldComment" prop="$fieldConfig.fieldName">
@@ -95,29 +95,34 @@
</el-form>
</div>
<el-card shadow="never">
<div class="mb-10px">
<el-button
v-hasPerm="['${moduleName}:${entityKebab}:create']"
type="success"
icon="plus"
@click="handleOpenDialog()"
>新增</el-button>
<el-button
v-hasPerm="['${moduleName}:${entityKebab}:delete']"
type="danger"
:disabled="removeIds.length === 0"
icon="delete"
@click="handleDelete()"
>删除</el-button>
<el-card shadow="hover" class="table-section">
<div class="table-section__toolbar">
<div class="table-section__toolbar--actions">
<el-button
v-hasPerm="['${moduleName}:${entityKebab}:create']"
type="success"
icon="plus"
@click="handleCreateClick()"
>新增</el-button>
<el-button
v-hasPerm="['${moduleName}:${entityKebab}:delete']"
type="danger"
:disabled="!hasSelection"
icon="delete"
@click="handleDelete()"
>删除</el-button>
</div>
</div>
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageData"
highlight-current-row
border
stripe
highlight-current-row
class="table-section__content"
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
@@ -148,7 +153,7 @@
size="small"
link
icon="edit"
@click="handleOpenDialog(String(scope.row.id))"
@click="handleEditClick(String(scope.row.id))"
>
编辑
</el-button>
@@ -171,7 +176,7 @@
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="handleQuery()"
@pagination="fetchList"
/>
</el-card>
@@ -180,7 +185,7 @@
v-model="dialog.visible"
:title="dialog.title"
width="500px"
@close="handleCloseDialog"
@close="closeDialog"
>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
#foreach($fieldConfig in $fieldConfigs)
@@ -193,7 +198,7 @@
/>
#elseif($fieldConfig.formType == "SELECT")
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
<Dict v-model="formData.$fieldConfig.fieldName" code="$fieldConfig.dictType" />
<DictSelect v-model="formData.$fieldConfig.fieldName" code="$fieldConfig.dictType" />
#else
<el-select v-model="formData.$fieldConfig.fieldName" placeholder="请选择$fieldConfig.fieldComment">
<el-option :value="0" label="选项一"/>
@@ -202,7 +207,7 @@
#end
#elseif($fieldConfig.formType == "RADIO")
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
<Dict v-model="queryParams.$fieldConfig.fieldName" type="radio" code="$fieldConfig.dictType" />
<DictSelect v-model="formData.$fieldConfig.fieldName" type="radio" code="$fieldConfig.dictType" />
#else
<el-radio-group v-model="formData.$fieldConfig.fieldName">
<el-radio :value="0">选项一</el-radio>
@@ -211,7 +216,7 @@
#end
#elseif($fieldConfig.formType == "CHECK_BOX")
#if($fieldConfig.dictType && $fieldConfig.dictType.trim() != "")
<Dict v-model="queryParams.$fieldConfig.fieldName" type="checkbox" code="$fieldConfig.dictType" />
<DictSelect v-model="formData.$fieldConfig.fieldName" type="checkbox" code="$fieldConfig.dictType" />
#else
<el-checkbox-group v-model="formData.$fieldConfig.fieldName">
<el-checkbox :value="0">选项一</el-checkbox>
@@ -260,7 +265,7 @@
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSubmit()">确定</el-button>
<el-button @click="handleCloseDialog()">取消</el-button>
<el-button @click="closeDialog()">取消</el-button>
</div>
</template>
</el-dialog>
@@ -268,25 +273,27 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from "vue";
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus";
import { useTableSelection } from "@/composables";
import ${entityName}API from "@/api/${moduleName}/${entityKebab}";
import type { ${entityName}Item, ${entityName}Form, ${entityName}QueryParams } from "@/types/api";
defineOptions({
name: "${entityName}",
inheritAttrs: false,
});
import ${entityName}API from "@/api/${moduleName}/${entityKebab}";
import type { ${entityName}Item, ${entityName}Form, ${entityName}QueryParams } from "@/types/api";
const queryFormRef = ref();
const dataFormRef = ref();
const queryFormRef = ref<FormInstance>();
const dataFormRef = ref<FormInstance>();
const loading = ref(false);
const removeIds = ref<string[]>([]);
const total = ref(0);
const queryParams = reactive<${entityName}QueryParams>({
pageNum: 1,
pageSize: 10,
});
} as ${entityName}QueryParams);
// $!{businessName}表格数据
const pageData = ref<${entityName}Item[]>([]);
@@ -298,10 +305,10 @@
});
// $!{businessName}表单数据
const formData = reactive<${entityName}Form>({});
const formData = reactive<${entityName}Form>({} as ${entityName}Form);
// $!{businessName}表单校验规则
const rules = reactive({
const rules: FormRules = {
#if($fieldConfigs)
#foreach($fieldConfig in ${fieldConfigs})
#if($fieldConfig.isShowInForm && $fieldConfig.isRequired)
@@ -309,49 +316,64 @@
#end
#end
#end
});
};
const { selectedIds, hasSelection, handleSelectionChange } = useTableSelection<${entityName}Item>();
async function fetchList(): Promise<void> {
loading.value = true;
try {
const data = await ${entityName}API.getPage(queryParams);
pageData.value = data.list;
total.value = data.total ?? 0;
} finally {
loading.value = false;
}
}
/** 查询$!{businessName} */
function handleQuery() {
loading.value = true;
${entityName}API.getPage(queryParams)
.then((data) => {
pageData.value = data.list;
total.value = data.total ?? 0;
})
.finally(() => {
loading.value = false;
});
function handleQuery(): void {
queryParams.pageNum = 1;
fetchList();
}
/** 重置$!{businessName}查询 */
function handleResetQuery() {
queryFormRef.value!.resetFields();
queryParams.pageNum = 1;
queryFormRef.value?.resetFields();
handleQuery();
}
/** 行复选框选中记录选中ID集合 */
function handleSelectionChange(selection: any) {
removeIds.value = selection.map((item: any) => String(item.id));
function openDialog(): void {
dialog.visible = true;
}
/** 打开$!{businessName}弹窗 */
function handleOpenDialog(id?: string) {
dialog.visible = true;
if (id) {
dialog.title = "修改$!{businessName}";
${entityName}API.getFormData(id).then((data) => {
Object.assign(formData, data);
});
} else {
dialog.title = "新增$!{businessName}";
}
function closeDialog(): void {
dialog.visible = false;
resetForm();
}
function resetForm(): void {
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
formData.id = undefined;
}
async function handleCreateClick(): Promise<void> {
dialog.title = "新增$!{businessName}";
openDialog();
}
async function handleEditClick(id: string): Promise<void> {
dialog.title = "修改$!{businessName}";
${entityName}API.getFormData(id).then((data) => {
Object.assign(formData, data);
openDialog();
});
}
/** 提交$!{businessName}表单 */
function handleSubmit() {
dataFormRef.value.validate((valid: any) => {
dataFormRef.value?.validate((valid: any) => {
if (valid) {
loading.value = true;
const id = formData.id;
@@ -359,7 +381,7 @@
${entityName}API.update(id, formData)
.then(() => {
ElMessage.success("修改成功");
handleCloseDialog();
closeDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
@@ -367,7 +389,7 @@
${entityName}API.create(formData)
.then(() => {
ElMessage.success("新增成功");
handleCloseDialog();
closeDialog();
handleResetQuery();
})
.finally(() => (loading.value = false));
@@ -376,17 +398,9 @@
});
}
/** 关闭$!{businessName}弹窗 */
function handleCloseDialog() {
dialog.visible = false;
dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
formData.id = undefined;
}
/** 删除$!{businessName} */
function handleDelete(id?: string) {
const ids = [id || removeIds.value].join(",");
const ids = [id || selectedIds.value].join(",");
if (!ids) {
ElMessage.warning("请勾选删除项");
return;