Merge remote-tracking branch 'origin/ai-hub' into ai-hub

This commit is contained in:
Gsh
2025-07-14 22:20:55 +08:00
19 changed files with 485 additions and 215 deletions

View File

@@ -2,7 +2,9 @@
{ {
public class PhoneCaptchaImageDto public class PhoneCaptchaImageDto
{ {
public string Phone { get; set; } public string? Phone { get; set; }
public string? Email { get; set; }
public string Uuid { get; set; } public string Uuid { get; set; }

View File

@@ -25,6 +25,8 @@
/// </summary> /// </summary>
public long? Phone { get; set; } public long? Phone { get; set; }
public string? Email { get; set; }
/// <summary> /// <summary>
/// 验证码 /// 验证码
/// </summary> /// </summary>

View File

@@ -7,9 +7,9 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.User
public string? Name { get; set; } public string? Name { get; set; }
public int? Age { get; set; } public int? Age { get; set; }
public string? Nick { get; set; } public string? Nick { get; set; }
public string? Email { get; set; } // public string? Email { get; set; }
public string? Address { get; set; } public string? Address { get; set; }
public long? Phone { get; set; } // public long? Phone { get; set; }
public string? Introduction { get; set; } public string? Introduction { get; set; }
public string? Remark { get; set; } public string? Remark { get; set; }
public SexEnum? Sex { get; set; } public SexEnum? Sex { get; set; }

View File

@@ -1,3 +1,6 @@
using System.Net.Mail;
using System.Net.Mime;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Lazy.Captcha.Core; using Lazy.Captcha.Core;
using Mapster; using Mapster;
@@ -9,6 +12,7 @@ using Microsoft.Extensions.Options;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Authorization; using Volo.Abp.Authorization;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.Emailing;
using Volo.Abp.EventBus.Local; using Volo.Abp.EventBus.Local;
using Volo.Abp.Guids; using Volo.Abp.Guids;
using Volo.Abp.Uow; using Volo.Abp.Uow;
@@ -34,13 +38,15 @@ namespace Yi.Framework.Rbac.Application.Services
{ {
protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>(); protected ILocalEventBus LocalEventBus => LazyServiceProvider.LazyGetRequiredService<ILocalEventBus>();
private IDistributedCache<CaptchaPhoneCacheItem, CaptchaPhoneCacheKey> _phoneCache; private IDistributedCache<CaptchaPhoneCacheItem, CaptchaPhoneCacheKey> _phoneCache;
private IDistributedCache<CaptchaEmailCacheItem, CaptchaEmailCacheKey> _emailCache;
private readonly ICaptcha _captcha; private readonly ICaptcha _captcha;
private readonly IGuidGenerator _guidGenerator; private readonly IGuidGenerator _guidGenerator;
private readonly RbacOptions _rbacOptions; private readonly RbacOptions _rbacOptions;
private readonly IAliyunManger _aliyunManger; private readonly IAliyunManger _aliyunManger;
private IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> _userCache; private readonly IDistributedCache<UserInfoCacheItem, UserInfoCacheKey> _userCache;
private UserManager _userManager; private readonly UserManager _userManager;
private IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IEmailSender _emailSender;
public AccountService(IUserRepository userRepository, public AccountService(IUserRepository userRepository,
ICurrentUser currentUser, ICurrentUser currentUser,
@@ -52,7 +58,8 @@ namespace Yi.Framework.Rbac.Application.Services
IGuidGenerator guidGenerator, IGuidGenerator guidGenerator,
IOptions<RbacOptions> options, IOptions<RbacOptions> options,
IAliyunManger aliyunManger, IAliyunManger aliyunManger,
UserManager userManager, IHttpContextAccessor httpContextAccessor) UserManager userManager, IHttpContextAccessor httpContextAccessor,
IDistributedCache<CaptchaEmailCacheItem, CaptchaEmailCacheKey> emailCache, IEmailSender emailSender)
{ {
_userRepository = userRepository; _userRepository = userRepository;
_currentUser = currentUser; _currentUser = currentUser;
@@ -66,6 +73,8 @@ namespace Yi.Framework.Rbac.Application.Services
_userCache = userCache; _userCache = userCache;
_userManager = userManager; _userManager = userManager;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_emailCache = emailCache;
_emailSender = emailSender;
} }
@@ -167,115 +176,6 @@ namespace Yi.Framework.Rbac.Application.Services
return new CaptchaImageDto { Img = captcha.Bytes, Uuid = uuid, IsEnableCaptcha = enableCaptcha }; return new CaptchaImageDto { Img = captcha.Bytes, Uuid = uuid, IsEnableCaptcha = enableCaptcha };
} }
/// <summary>
/// 验证电话号码
/// </summary>
/// <param name="phone"></param>
private async Task ValidationPhone(string phone)
{
var res = Regex.IsMatch(phone, @"^\d{11}$");
if (res == false)
{
throw new UserFriendlyException("手机号码格式错误!请检查");
}
}
/// <summary>
/// 手机验证码-注册
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-phone")]
[AllowAnonymous]
public async Task<object> PostCaptchaPhoneForRegisterAsync(PhoneCaptchaImageDto input)
{
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Register, input);
}
/// <summary>
/// 手机验证码-找回密码
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-phone/repassword")]
public async Task<object> PostCaptchaPhoneForRetrievePasswordAsync(PhoneCaptchaImageDto input)
{
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.RetrievePassword, input);
}
/// <summary>
/// 手机验证码-绑定
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-phone/bind")]
[AllowAnonymous]
public async Task<object> PostCaptchaPhoneForBindAsync(PhoneCaptchaImageDto input)
{
return await PostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Bind, input);
}
/// <summary>
/// 手机验证码-需通过图形验证码
/// </summary>
/// <returns></returns>
[RemoteService(isEnabled: false)]
private async Task<object> PostCaptchaPhoneAsync(ValidationPhoneTypeEnum validationPhoneType,
PhoneCaptchaImageDto input)
{
//验证uuid 和 验证码
ValidationImageCaptcha(input.Uuid, input.Code);
await ValidationPhone(input.Phone);
if (validationPhoneType == ValidationPhoneTypeEnum.Register &&
await _userRepository.IsAnyAsync(x => x.Phone.ToString() == input.Phone))
{
throw new UserFriendlyException("该手机号已被注册!");
}
var value = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone));
//防止暴刷
if (value is not null)
{
throw new UserFriendlyException($"{input.Phone}已发送过验证码10分钟后可重试");
}
//生成一个4位数的验证码
//发送短信同时生成uuid
////key 电话号码 value:验证码+uuid
var code = Guid.NewGuid().ToString().Substring(0, 4);
var uuid = Guid.NewGuid();
await _aliyunManger.SendSmsAsync(input.Phone, code);
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone),
new CaptchaPhoneCacheItem(code),
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
return new
{
Uuid = uuid
};
}
/// <summary>
/// 校验电话验证码,需要与电话号码绑定
/// </summary>
public async Task ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum validationPhoneType, long phone,
string code)
{
var item = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, phone.ToString()));
if (item is not null && item.Code.Equals($"{code}"))
{
//成功,需要清空
await _phoneCache.RemoveAsync(new CaptchaPhoneCacheKey(validationPhoneType, code.ToString()));
return;
}
throw new UserFriendlyException("验证码错误");
}
/// <summary> /// <summary>
/// 找回密码 /// 找回密码
/// </summary> /// </summary>
@@ -284,6 +184,11 @@ namespace Yi.Framework.Rbac.Application.Services
[UnitOfWork] [UnitOfWork]
public async Task<string> PostRetrievePasswordAsync(RetrievePasswordDto input) public async Task<string> PostRetrievePasswordAsync(RetrievePasswordDto input)
{ {
if (_rbacOptions.CaptchaType == CaptchaTypeEnum.Email)
{
throw new UserFriendlyException("当前模式,不允许手机号找回密码,请联系管理员");
}
//校验验证码,根据电话号码获取 value比对验证码已经uuid //校验验证码,根据电话号码获取 value比对验证码已经uuid
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.RetrievePassword, input.Phone, input.Code); await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.RetrievePassword, input.Phone, input.Code);
@@ -298,7 +203,6 @@ namespace Yi.Framework.Rbac.Application.Services
return entity.UserName; return entity.UserName;
} }
/// <summary> /// <summary>
/// 注册,需要验证码通过 /// 注册,需要验证码通过
/// </summary> /// </summary>
@@ -313,9 +217,9 @@ namespace Yi.Framework.Rbac.Application.Services
throw new UserFriendlyException("该系统暂未开放注册功能"); throw new UserFriendlyException("该系统暂未开放注册功能");
} }
if (input.Phone is null) if (input.Phone is null && input.Email is null)
{ {
throw new UserFriendlyException("手机号不能为空"); throw new UserFriendlyException("手机号和邮箱不能为空");
} }
//临时账号 //临时账号
@@ -326,12 +230,23 @@ namespace Yi.Framework.Rbac.Application.Services
if (_rbacOptions.EnableCaptcha) if (_rbacOptions.EnableCaptcha)
{ {
//校验验证码,根据电话号码获取 value比对验证码已经uuid switch (_rbacOptions.CaptchaType)
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.Register, input.Phone.Value, input.Code); {
case CaptchaTypeEnum.Phone:
//校验验证码,根据电话号码获取 value比对验证码已经uuid
await ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum.Register, input.Phone.Value,
input.Code);
break;
case CaptchaTypeEnum.Email:
//校验验证码,根据电子邮箱获取 value比对验证码已经uuid
await ValidationEmailCaptchaAsync(ValidationEmailTypeEnum.Register, input.Email, input.Code);
break;
}
} }
//注册之后免再次登录直接给前端token //注册之后免再次登录直接给前端token
var userId = await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Nick); var userId = await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Email,
input.Nick);
return await this.PostLoginAsync(userId); return await this.PostLoginAsync(userId);
} }
@@ -344,7 +259,7 @@ namespace Yi.Framework.Rbac.Application.Services
public async Task PostTempRegisterAsync(RegisterDto input) public async Task PostTempRegisterAsync(RegisterDto input)
{ {
//注册领域逻辑 //注册领域逻辑
await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Nick); await _accountManager.RegisterAsync(input.UserName, input.Password, input.Phone, input.Email, input.Nick);
} }
/// <summary> /// <summary>
@@ -520,5 +435,241 @@ namespace Yi.Framework.Rbac.Application.Services
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateIcon, userId), false); new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateIcon, userId), false);
return true; return true;
} }
#region
/// <summary>
/// 验证电话号码
/// </summary>
/// <param name="phone"></param>
private async Task ValidationPhone(string phone)
{
var res = Regex.IsMatch(phone, @"^\d{11}$");
if (res == false)
{
throw new UserFriendlyException("手机号码格式错误!请检查");
}
}
/// <summary>
/// 手机验证码-注册
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-phone/register")]
[AllowAnonymous]
public async Task<object> PostCaptchaPhoneForRegisterAsync(PhoneCaptchaImageDto input)
{
return await InternalPostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Register, input);
}
/// <summary>
/// 手机验证码-找回密码
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-phone/repassword")]
public async Task<object> PostCaptchaPhoneForRetrievePasswordAsync(PhoneCaptchaImageDto input)
{
return await InternalPostCaptchaPhoneAsync(ValidationPhoneTypeEnum.RetrievePassword, input);
}
/// <summary>
/// 手机验证码-绑定
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-phone/bind")]
[AllowAnonymous]
public async Task<object> PostCaptchaPhoneForBindAsync(PhoneCaptchaImageDto input)
{
return await InternalPostCaptchaPhoneAsync(ValidationPhoneTypeEnum.Bind, input);
}
/// <summary>
/// 手机验证码-内部使用-需通过图形验证码
/// </summary>
/// <returns></returns>
[RemoteService(isEnabled: false)]
private async Task<object> InternalPostCaptchaPhoneAsync(ValidationPhoneTypeEnum validationPhoneType,
PhoneCaptchaImageDto input)
{
//验证uuid 和 验证码
ValidationImageCaptcha(input.Uuid, input.Code);
await ValidationPhone(input.Phone);
if (validationPhoneType == ValidationPhoneTypeEnum.Register &&
await _userRepository.IsAnyAsync(x => x.Phone.ToString() == input.Phone))
{
throw new UserFriendlyException("该手机号已被注册!");
}
var value = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone));
//防止暴刷
if (value is not null)
{
throw new UserFriendlyException($"{input.Phone}已发送过验证码10分钟后可重试");
}
//生成一个4位数的验证码
//发送短信同时生成uuid
////key 电话号码 value:验证码+uuid
var code = Guid.NewGuid().ToString().Substring(0, 4);
var uuid = Guid.NewGuid();
await _aliyunManger.SendSmsAsync(input.Phone, code);
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(validationPhoneType, input.Phone),
new CaptchaPhoneCacheItem(code),
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
return new
{
Uuid = uuid
};
}
/// <summary>
/// 校验电话验证码,需要与电话号码绑定
/// </summary>
public async Task ValidationPhoneCaptchaAsync(ValidationPhoneTypeEnum validationPhoneType, long phone,
string code)
{
var item = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(validationPhoneType, phone.ToString()));
if (item is not null && item.Code.Equals($"{code}"))
{
//成功,需要清空
await _phoneCache.RemoveAsync(new CaptchaPhoneCacheKey(validationPhoneType, phone.ToString()));
return;
}
throw new UserFriendlyException("验证码错误");
}
#endregion
#region
/// <summary>
/// 验证电子邮箱
/// </summary>
/// <param name="email"></param>
private async Task ValidationEmail(string email)
{
// 简单邮箱正则表达式
var res = Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
if (res == false)
{
throw new UserFriendlyException("邮箱格式错误!请检查");
}
}
/// <summary>
/// 邮箱验证码-注册
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-email/register")]
[AllowAnonymous]
public async Task<object> PostCaptchaEmailForRegisterAsync(PhoneCaptchaImageDto input)
{
return await InternalPostCaptchaEmailAsync(ValidationEmailTypeEnum.Register, input);
}
/// <summary>
/// 邮箱验证码-找回密码
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-email/repassword")]
public async Task<object> PostCaptchaEmailForRetrievePasswordAsync(PhoneCaptchaImageDto input)
{
return await InternalPostCaptchaEmailAsync(ValidationEmailTypeEnum.RetrievePassword, input);
}
/// <summary>
/// 邮箱验证码-绑定
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("account/captcha-email/bind")]
[AllowAnonymous]
public async Task<object> PostCaptchaEmailForBindAsync(PhoneCaptchaImageDto input)
{
return await InternalPostCaptchaEmailAsync(ValidationEmailTypeEnum.Bind, input);
}
/// <summary>
/// 邮箱验证码-内部使用-需通过图形验证码
/// </summary>
/// <returns></returns>
[RemoteService(isEnabled: false)]
private async Task<object> InternalPostCaptchaEmailAsync(ValidationEmailTypeEnum validationEmailType,
PhoneCaptchaImageDto input)
{
//验证uuid 和 验证码
ValidationImageCaptcha(input.Uuid, input.Code);
await ValidationEmail(input.Email);
if (validationEmailType == ValidationEmailTypeEnum.Register)
{
//处理大小写问题
var emailOrNull = await _userRepository._DbQueryable.Where(x => x.Email == input.Email)
.Select(x => x.Email)
.FirstAsync();
if (emailOrNull is not null && emailOrNull.Equals(input.Email))
{
throw new UserFriendlyException("该邮箱已被注册!");
}
}
var value = await _emailCache.GetAsync(new CaptchaEmailCacheKey(validationEmailType, input.Email));
//防止暴刷
if (value is not null)
{
throw new UserFriendlyException($"{input.Email}已发送过验证码10分钟后可重试");
}
//生成一个4位数的验证码
//发送邮箱同时生成uuid
////key 邮箱 value:验证码+uuid
var code = Guid.NewGuid().ToString().Substring(0, 4);
var uuid = Guid.NewGuid();
//await _aliyunManger.SendSmsAsync(input.Phone, code);
//发送邮件
await _emailSender.SendAsync(input.Email,
"意社区官方邮件",
$"欢迎加入我们,您的验证码为 {code} 该验证码10分钟内有效请勿泄露于他人。");
await _emailCache.SetAsync(new CaptchaEmailCacheKey(validationEmailType, input.Email),
new CaptchaEmailCacheItem(code),
new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
return new
{
Uuid = uuid
};
}
/// <summary>
/// 校验电子邮箱验证码,需要与电子邮箱绑定
/// </summary>
public async Task ValidationEmailCaptchaAsync(ValidationEmailTypeEnum validationEmailType, string email,
string code)
{
var item = await _emailCache.GetAsync(new CaptchaEmailCacheKey(validationEmailType, email.ToString()));
if (item is not null && item.Code.Equals($"{code}"))
{
//成功,需要清空
await _emailCache.RemoveAsync(new CaptchaEmailCacheKey(validationEmailType, email.ToString()));
return;
}
throw new UserFriendlyException("验证码错误");
}
#endregion
} }
} }

View File

@@ -191,7 +191,7 @@ namespace Yi.Framework.Rbac.Application.Services.System
await _repository.UpdateAsync(entity); await _repository.UpdateAsync(entity);
var dto = await MapToGetOutputDtoAsync(entity); var dto = await MapToGetOutputDtoAsync(entity);
//发布更新昵称任务事件 //发布更新昵称任务事件
if (input.Nick != entity.Icon) if (input.Nick != entity.Nick)
{ {
await this.LocalEventBus.PublishAsync( await this.LocalEventBus.PublishAsync(
new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateNick, _currentUser.GetId(), input.Nick), new AssignmentEventArgs(AssignmentRequirementTypeEnum.UpdateNick, _currentUser.GetId(), input.Nick),

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Rbac.Domain.Shared.Enums;
namespace Yi.Framework.Rbac.Domain.Shared.Caches
{
public class CaptchaEmailCacheItem
{
public CaptchaEmailCacheItem(string code)
{
Code = code;
}
public string Code { get; set; }
}
public class CaptchaEmailCacheKey
{
public CaptchaEmailCacheKey(ValidationEmailTypeEnum validationPhoneType, string email)
{
Email = email;
ValidationEmailType = validationPhoneType;
}
public ValidationEmailTypeEnum ValidationEmailType { get; set; }
public string Email { get; set; }
public override string ToString()
{
return $"Email:{ValidationEmailType.ToString()}:{Email}";
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Yi.Framework.Rbac.Domain.Shared.Enums;
public enum ValidationEmailTypeEnum
{
/// <summary>
/// 注册
/// </summary>
Register,
/// <summary>
/// 忘记密码
/// </summary>
RetrievePassword,
/// <summary>
/// 绑定
/// </summary>
Bind
}

View File

@@ -23,6 +23,11 @@ namespace Yi.Framework.Rbac.Domain.Shared.Options
/// </summary> /// </summary>
public bool EnableCaptcha { get; set; } = false; public bool EnableCaptcha { get; set; } = false;
/// <summary>
/// 验证类型
/// </summary>
public CaptchaTypeEnum CaptchaType { get; set; } = CaptchaTypeEnum.Phone;
/// <summary> /// <summary>
/// 是否开启用户注册功能 /// 是否开启用户注册功能
/// </summary> /// </summary>
@@ -33,4 +38,20 @@ namespace Yi.Framework.Rbac.Domain.Shared.Options
/// </summary> /// </summary>
public bool EnableDataBaseBackup { get; set; } = false; public bool EnableDataBaseBackup { get; set; } = false;
} }
}
/// <summary>
/// 验证类型
/// </summary>
public enum CaptchaTypeEnum
{
/// <summary>
/// 手机号
/// </summary>
Phone = 0,
/// <summary>
/// 邮箱
/// </summary>
Email = 1,
}
}

View File

@@ -17,14 +17,15 @@ namespace Yi.Framework.Rbac.Domain.Entities
{ {
public UserAggregateRoot() public UserAggregateRoot()
{ {
} }
public UserAggregateRoot(string userName, string password, long? phone, string? nick = null)
public UserAggregateRoot(string userName, string password, long? phone, string? email, string? nick = null)
{ {
UserName = userName; UserName = userName;
EncryPassword.Password = password; EncryPassword.Password = password;
Phone = phone; Phone = phone;
Nick =string.IsNullOrWhiteSpace(nick)?"萌新-"+userName:nick.Trim(); Email = email;
Nick = string.IsNullOrWhiteSpace(nick) ? "萌新-" + userName : nick.Trim();
BuildPassword(); BuildPassword();
} }
@@ -185,8 +186,10 @@ namespace Yi.Framework.Rbac.Domain.Entities
{ {
throw new ArgumentNullException(nameof(EncryPassword.Password)); throw new ArgumentNullException(nameof(EncryPassword.Password));
} }
password = EncryPassword.Password; password = EncryPassword.Password;
} }
EncryPassword.Salt = MD5Helper.GenerateSalt(); EncryPassword.Salt = MD5Helper.GenerateSalt();
EncryPassword.Password = MD5Helper.SHA2Encode(password, EncryPassword.Salt); EncryPassword.Password = MD5Helper.SHA2Encode(password, EncryPassword.Salt);
return this; return this;
@@ -203,14 +206,14 @@ namespace Yi.Framework.Rbac.Domain.Entities
{ {
throw new ArgumentNullException(EncryPassword.Salt); throw new ArgumentNullException(EncryPassword.Salt);
} }
var p = MD5Helper.SHA2Encode(password, EncryPassword.Salt); var p = MD5Helper.SHA2Encode(password, EncryPassword.Salt);
if (EncryPassword.Password == MD5Helper.SHA2Encode(password, EncryPassword.Salt)) if (EncryPassword.Password == MD5Helper.SHA2Encode(password, EncryPassword.Salt))
{ {
return true; return true;
} }
return false; return false;
} }
} }
}
}

View File

@@ -24,7 +24,6 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Rbac.Domain.Managers namespace Yi.Framework.Rbac.Domain.Managers
{ {
/// <summary> /// <summary>
/// 用户领域服务 /// 用户领域服务
/// </summary> /// </summary>
@@ -62,7 +61,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
/// <param name="getUserInfo"></param> /// <param name="getUserInfo"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="UserFriendlyException"></exception> /// <exception cref="UserFriendlyException"></exception>
public async Task<string> GetTokenByUserIdAsync(Guid userId,Action<UserRoleMenuDto>? getUserInfo=null) public async Task<string> GetTokenByUserIdAsync(Guid userId, Action<UserRoleMenuDto>? getUserInfo = null)
{ {
//获取用户信息 //获取用户信息
var userInfo = await _userManager.GetInfoAsync(userId); var userInfo = await _userManager.GetInfoAsync(userId);
@@ -77,6 +76,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
{ {
throw new UserFriendlyException(UserConst.No_Role); throw new UserFriendlyException(UserConst.No_Role);
} }
if (!userInfo.PermissionCodes.Any()) if (!userInfo.PermissionCodes.Any())
{ {
throw new UserFriendlyException(UserConst.No_Permission); throw new UserFriendlyException(UserConst.No_Permission);
@@ -86,7 +86,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
{ {
getUserInfo(userInfo); getUserInfo(userInfo);
} }
var accessToken = CreateToken(this.UserInfoToClaim(userInfo)); var accessToken = CreateToken(this.UserInfoToClaim(userInfo));
//将用户信息添加到缓存中,需要考虑的是更改了用户、角色、菜单等整个体系都需要将缓存进行刷新,看具体业务进行选择 //将用户信息添加到缓存中,需要考虑的是更改了用户、角色、菜单等整个体系都需要将缓存进行刷新,看具体业务进行选择
return accessToken; return accessToken;
@@ -103,12 +103,12 @@ namespace Yi.Framework.Rbac.Domain.Managers
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = kvs.Select(x => new Claim(x.Key, x.Value.ToString())).ToList(); var claims = kvs.Select(x => new Claim(x.Key, x.Value.ToString())).ToList();
var token = new JwtSecurityToken( var token = new JwtSecurityToken(
issuer: _jwtOptions.Issuer, issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience, audience: _jwtOptions.Audience,
claims: claims, claims: claims,
expires: DateTime.Now.AddSeconds(_jwtOptions.ExpiresSecondTime), expires: DateTime.Now.AddSeconds(_jwtOptions.ExpiresSecondTime),
notBefore: DateTime.Now, notBefore: DateTime.Now,
signingCredentials: creds); signingCredentials: creds);
string returnToken = new JwtSecurityTokenHandler().WriteToken(token); string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
return returnToken; return returnToken;
@@ -119,22 +119,23 @@ namespace Yi.Framework.Rbac.Domain.Managers
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_refreshJwtOptions.SecurityKey)); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_refreshJwtOptions.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//添加用户id及刷新token的标识 //添加用户id及刷新token的标识
var claims = new List<Claim> { var claims = new List<Claim>
new Claim(AbpClaimTypes.UserId,userId.ToString()), {
new Claim(AbpClaimTypes.UserId, userId.ToString()),
new Claim(TokenTypeConst.Refresh, "true") new Claim(TokenTypeConst.Refresh, "true")
}; };
var token = new JwtSecurityToken( var token = new JwtSecurityToken(
issuer: _refreshJwtOptions.Issuer, issuer: _refreshJwtOptions.Issuer,
audience: _refreshJwtOptions.Audience, audience: _refreshJwtOptions.Audience,
claims: claims, claims: claims,
expires: DateTime.Now.AddSeconds(_refreshJwtOptions.ExpiresSecondTime), expires: DateTime.Now.AddSeconds(_refreshJwtOptions.ExpiresSecondTime),
notBefore: DateTime.Now, notBefore: DateTime.Now,
signingCredentials: creds); signingCredentials: creds);
string returnToken = new JwtSecurityTokenHandler().WriteToken(token); string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
return returnToken; return returnToken;
} }
/// <summary> /// <summary>
/// 登录校验 /// 登录校验
/// </summary> /// </summary>
@@ -142,7 +143,8 @@ namespace Yi.Framework.Rbac.Domain.Managers
/// <param name="password"></param> /// <param name="password"></param>
/// <param name="userAction"></param> /// <param name="userAction"></param>
/// <returns></returns> /// <returns></returns>
public async Task LoginValidationAsync(string userName, string password, Action<UserAggregateRoot> userAction = null) public async Task LoginValidationAsync(string userName, string password,
Action<UserAggregateRoot> userAction = null)
{ {
var user = new UserAggregateRoot(); var user = new UserAggregateRoot();
if (await ExistAsync(userName, o => user = o)) if (await ExistAsync(userName, o => user = o))
@@ -151,12 +153,15 @@ namespace Yi.Framework.Rbac.Domain.Managers
{ {
userAction.Invoke(user); userAction.Invoke(user);
} }
if (user.EncryPassword.Password == MD5Helper.SHA2Encode(password, user.EncryPassword.Salt)) if (user.EncryPassword.Password == MD5Helper.SHA2Encode(password, user.EncryPassword.Salt))
{ {
return; return;
} }
throw new UserFriendlyException(UserConst.Login_Error); throw new UserFriendlyException(UserConst.Login_Error);
} }
throw new UserFriendlyException(UserConst.Login_User_No_Exist); throw new UserFriendlyException(UserConst.Login_User_No_Exist);
} }
@@ -173,22 +178,22 @@ namespace Yi.Framework.Rbac.Domain.Managers
{ {
userAction.Invoke(user); userAction.Invoke(user);
} }
//这里为了兼容解决数据库开启了大小写不敏感问题,还要将用户名进行二次校验 //这里为了兼容解决数据库开启了大小写不敏感问题,还要将用户名进行二次校验
if (user != null && user.UserName == userName) if (user != null && user.UserName == userName)
{ {
return true; return true;
} }
return false; return false;
} }
/// <summary> /// <summary>
/// 令牌转换 /// 令牌转换
/// </summary> /// </summary>
/// <param name="dto"></param> /// <param name="dto"></param>
/// <returns></returns> /// <returns></returns>
public List<KeyValuePair<string, string>> UserInfoToClaim(UserRoleMenuDto dto) public List<KeyValuePair<string, string>> UserInfoToClaim(UserRoleMenuDto dto)
{ {
var claims = new List<KeyValuePair<string, string>>(); var claims = new List<KeyValuePair<string, string>>();
@@ -198,18 +203,24 @@ namespace Yi.Framework.Rbac.Domain.Managers
{ {
AddToClaim(claims, TokenTypeConst.DeptId, dto.User.DeptId.ToString()); AddToClaim(claims, TokenTypeConst.DeptId, dto.User.DeptId.ToString());
} }
if (dto.User.Email is not null) if (dto.User.Email is not null)
{ {
AddToClaim(claims, AbpClaimTypes.Email, dto.User.Email); AddToClaim(claims, AbpClaimTypes.Email, dto.User.Email);
} }
if (dto.User.Phone is not null) if (dto.User.Phone is not null)
{ {
AddToClaim(claims, AbpClaimTypes.PhoneNumber, dto.User.Phone.ToString()); AddToClaim(claims, AbpClaimTypes.PhoneNumber, dto.User.Phone.ToString());
} }
if (dto.Roles.Count > 0) if (dto.Roles.Count > 0)
{ {
AddToClaim(claims, TokenTypeConst.RoleInfo, JsonConvert.SerializeObject(dto.Roles.Select(x => new RoleTokenInfoModel { Id = x.Id, DataScope = x.DataScope }))); AddToClaim(claims, TokenTypeConst.RoleInfo,
JsonConvert.SerializeObject(dto.Roles.Select(x => new RoleTokenInfoModel
{ Id = x.Id, DataScope = x.DataScope })));
} }
if (UserConst.Admin.Equals(dto.User.UserName)) if (UserConst.Admin.Equals(dto.User.UserName))
{ {
AddToClaim(claims, TokenTypeConst.Permission, UserConst.AdminPermissionCode); AddToClaim(claims, TokenTypeConst.Permission, UserConst.AdminPermissionCode);
@@ -246,6 +257,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
{ {
throw new UserFriendlyException("无效更新!原密码错误!"); throw new UserFriendlyException("无效更新!原密码错误!");
} }
user.EncryPassword.Password = newPassword; user.EncryPassword.Password = newPassword;
user.BuildPassword(); user.BuildPassword();
await _repository.UpdateAsync(user); await _repository.UpdateAsync(user);
@@ -271,14 +283,21 @@ namespace Yi.Framework.Rbac.Domain.Managers
/// <param name="userName"></param> /// <param name="userName"></param>
/// <param name="password"></param> /// <param name="password"></param>
/// <param name="phone"></param> /// <param name="phone"></param>
/// <param name="email"></param>
/// <param name="nick"></param>
/// <returns></returns> /// <returns></returns>
public async Task<Guid> RegisterAsync(string userName, string password, long? phone,string? nick) public async Task<Guid> RegisterAsync(string userName, string password, long? phone, string? email,
string? nick)
{ {
var user = new UserAggregateRoot(userName, password, phone,nick); if (phone is null && string.IsNullOrWhiteSpace(email))
var userId=await _userManager.CreateAsync(user); {
throw new UserFriendlyException("注册时,电话与邮箱不能同时为空");
}
var user = new UserAggregateRoot(userName, password, phone, email, nick);
var userId = await _userManager.CreateAsync(user);
await _userManager.SetDefautRoleAsync(user.Id); await _userManager.SetDefautRoleAsync(user.Id);
return userId; return userId;
} }
} }
}
}

View File

@@ -14,7 +14,7 @@ namespace Yi.Framework.Rbac.Domain.Managers
string CreateRefreshToken(Guid userId); string CreateRefreshToken(Guid userId);
Task<string> GetTokenByUserIdAsync(Guid userId,Action<UserRoleMenuDto>? getUserInfo=null); Task<string> GetTokenByUserIdAsync(Guid userId,Action<UserRoleMenuDto>? getUserInfo=null);
Task LoginValidationAsync(string userName, string password, Action<UserAggregateRoot>? userAction = null); Task LoginValidationAsync(string userName, string password, Action<UserAggregateRoot>? userAction = null);
Task<Guid> RegisterAsync(string userName, string password, long? phone,string? nick); Task<Guid> RegisterAsync(string userName, string password, long? phone, string? email, string? nick);
Task<bool> RestPasswordAsync(Guid userId, string password); Task<bool> RestPasswordAsync(Guid userId, string password);
Task UpdatePasswordAsync(Guid userId, string newPassword, string oldPassword); Task UpdatePasswordAsync(Guid userId, string newPassword, string oldPassword);
} }

View File

@@ -23,6 +23,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DistributedLock.Redis" Version="1.0.3" /> <PackageReference Include="DistributedLock.Redis" Version="1.0.3" />
<PackageReference Include="Volo.Abp.DistributedLocking" Version="$(AbpVersion)" /> <PackageReference Include="Volo.Abp.DistributedLocking" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.MailKit" Version="$(AbpVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj" /> <ProjectReference Include="..\..\..\framework\Yi.Framework.Caching.FreeRedis\Yi.Framework.Caching.FreeRedis.csproj" />

View File

@@ -7,10 +7,10 @@ using Volo.Abp.AspNetCore.SignalR;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.DistributedLocking; using Volo.Abp.DistributedLocking;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.Emailing;
using Volo.Abp.Imaging; using Volo.Abp.Imaging;
using Volo.Abp.Modularity; using Volo.Abp.MailKit;
using Yi.Framework.Caching.FreeRedis; using Yi.Framework.Caching.FreeRedis;
using Yi.Framework.Mapster;
using Yi.Framework.Rbac.Domain.Authorization; using Yi.Framework.Rbac.Domain.Authorization;
using Yi.Framework.Rbac.Domain.Operlog; using Yi.Framework.Rbac.Domain.Operlog;
using Yi.Framework.Rbac.Domain.Shared; using Yi.Framework.Rbac.Domain.Shared;
@@ -26,7 +26,8 @@ namespace Yi.Framework.Rbac.Domain
typeof(AbpDddDomainModule), typeof(AbpDddDomainModule),
typeof(AbpCachingModule), typeof(AbpCachingModule),
typeof(AbpImagingImageSharpModule), typeof(AbpImagingImageSharpModule),
typeof(AbpDistributedLockingModule) typeof(AbpDistributedLockingModule),
typeof(AbpMailKitModule)
)] )]
public class YiFrameworkRbacDomainModule : AbpModule public class YiFrameworkRbacDomainModule : AbpModule
{ {

View File

@@ -25,7 +25,17 @@
}, },
//配置 //配置
"Settings": { "Settings": {
"Test": "hello" "Test": "hello",
//邮箱
"Abp.Mailing.Smtp.Host": "127.0.0.1",
"Abp.Mailing.Smtp.Port": "25",
"Abp.Mailing.Smtp.UserName": "",
"Abp.Mailing.Smtp.Password": "",
"Abp.Mailing.Smtp.Domain": "",
"Abp.Mailing.Smtp.EnableSsl": "false",
"Abp.Mailing.Smtp.UseDefaultCredentials": "true",
"Abp.Mailing.DefaultFromAddress": "noreply@abp.io",
"Abp.Mailing.DefaultFromDisplayName": "ABP application"
}, },
//数据库类型列表 //数据库类型列表
"DbList": [ "DbList": [
@@ -95,6 +105,8 @@
"AdminPassword": "123456", "AdminPassword": "123456",
//是否开启验证码验证 //是否开启验证码验证
"EnableCaptcha": true, "EnableCaptcha": true,
//验证类型email/Phone
"CaptchaType": "Email",
//是否开启注册功能 //是否开启注册功能
"EnableRegister": false, "EnableRegister": false,
//开启定时数据库备份 //开启定时数据库备份
@@ -107,15 +119,5 @@
], ],
"Endpoint": "https://xxx.com/v1", "Endpoint": "https://xxx.com/v1",
"ApiKey": "sk-xxxxxx" "ApiKey": "sk-xxxxxx"
},
//AI网关
"AiGateWay": {
"Chats": {
"AzureChatService": {
"ModelIds": ["gpt-4o"],
"Endpoint": "https://xxx.com/v1",
"ApiKey": "sk-xxxxxx"
}
}
} }
} }

View File

@@ -85,7 +85,19 @@ export function getCodeImg() {
// 获取短信验证码 // 获取短信验证码
export function getCodePhone(phoneForm) { export function getCodePhone(phoneForm) {
return request({ return request({
url: "/account/captcha-phone", url: "/account/captcha-phone/register",
headers: {
isToken: false,
},
method: "post",
timeout: 20000,
data: phoneForm,
});
}
// 获取邮箱验证码
export function getCodeEmail(phoneForm) {
return request({
url: "/account/captcha-email/register",
headers: { headers: {
isToken: false, isToken: false,
}, },

View File

@@ -59,7 +59,7 @@ export function userLogout() {
*/ */
export function getCodePhone(data) { export function getCodePhone(data) {
return request({ return request({
url: `/account/captcha-phone`, url: `/account/captcha-phone/register`,
method: "post", method: "post",
data, data,
}); });

View File

@@ -234,7 +234,8 @@ const enterStart = () => {
} }
const enterTemp=()=>{ const enterTemp=()=>{
alert("即将上线!意社区-高质量ai平台一心一意只为打造更良心的ai平台") alert("默认访问海外意心ai节点受网络影响首次加载静态资源需要一定时间")
window.location.href = 'https://yxai.chat';
//router.push("/discuss/24cc0526-86e7-aabf-e091-3a0f83c3e604/false"); //router.push("/discuss/24cc0526-86e7-aabf-e091-3a0f83c3e604/false");
} }
const enterShop=()=>{ const enterShop=()=>{

View File

@@ -2,7 +2,7 @@
// 注册逻辑 // 注册逻辑
import {computed, reactive, ref} from "vue"; import {computed, reactive, ref} from "vue";
import {getCodePhone} from "@/apis/accountApi"; import {getCodeEmail} from "@/apis/accountApi";
import useAuths from "@/hooks/useAuths"; import useAuths from "@/hooks/useAuths";
import {useRoute, useRouter} from "vue-router"; import {useRoute, useRouter} from "vue-router";
import useUserStore from "@/stores/user"; import useUserStore from "@/stores/user";
@@ -20,15 +20,16 @@ const codeUUid = computed(() => useUserStore().codeUUid);
const passwordConfirm = ref(""); const passwordConfirm = ref("");
const registerForm = reactive({ const registerForm = reactive({
userName: "", userName: "",
phone: "", //phone: "",
password: "", password: "",
uuid: "", uuid: "",
code: "", code: "",
nick:"" nick:"",
email: "",
}); });
const phoneForm=reactive({ const phoneForm=reactive({
code:"", code:"",
phone:"", email:"",
uuid:codeUUid uuid:codeUUid
}); });
const registerRules = reactive({ const registerRules = reactive({
@@ -39,7 +40,7 @@ const registerRules = reactive({
{ required: true, message: "请输入用户名", trigger: "blur" }, { required: true, message: "请输入用户名", trigger: "blur" },
{ min: 2, message: "用户名需大于两位", trigger: "blur" }, { min: 2, message: "用户名需大于两位", trigger: "blur" },
], ],
phone: [{ required: true, message: "请输入手机号", trigger: "blur" }], email: [{ required: true, message: "请输入邮箱", trigger: "blur" }],
code: [{ required: true, message: "请输入验证码", trigger: "blur" }], code: [{ required: true, message: "请输入验证码", trigger: "blur" }],
password: [ password: [
{ required: true, message: "请输入新的密码", trigger: "blur" }, { required: true, message: "请输入新的密码", trigger: "blur" },
@@ -52,7 +53,7 @@ const register = async (formEl) => {
if (valid) { if (valid) {
try { try {
if (registerForm.password != passwordConfirm.value) { if (registerForm.password !== passwordConfirm.value) {
ElMessage.error("两次密码输入不一致"); ElMessage.error("两次密码输入不一致");
return; return;
} }
@@ -72,7 +73,7 @@ const register = async (formEl) => {
//验证码 //验证码
const codeInfo = ref("发送短信"); const codeInfo = ref("发送邮箱");
const isDisabledCode = ref(false); const isDisabledCode = ref(false);
//点击验证码 //点击验证码
@@ -80,9 +81,9 @@ const handleGetCodeImage=()=>{
useUserStore().updateCodeImage(); useUserStore().updateCodeImage();
} }
//点击手机发送短信 //点击手机发送邮箱
const clickPhoneCaptcha=()=>{ const clickPhoneCaptcha=()=>{
if (registerForm.phone !== "") if (registerForm.email !== "")
{ {
isDisabledCode.value=true; isDisabledCode.value=true;
handleGetCodeImage(); handleGetCodeImage();
@@ -90,7 +91,7 @@ const clickPhoneCaptcha=()=>{
} }
else { else {
ElMessage({ ElMessage({
message: `请先输入手机号`, message: `请先输入邮箱`,
type: "warning", type: "warning",
}); });
} }
@@ -101,14 +102,14 @@ const handleSignInNow=()=>{
router.push("/login"); router.push("/login");
} }
const captcha = async () => { const captcha = async () => {
if (registerForm.phone!==""&&phoneForm.code!=="") if (registerForm.email!==""&&phoneForm.code!=="")
{ {
phoneForm.phone=registerForm.phone; phoneForm.email=registerForm.email;
const { data } = await getCodePhone(phoneForm); const { data } = await getCodeEmail(phoneForm);
registerForm.uuid = data.uuid; registerForm.uuid = data.uuid;
codeDialogVisible.value=false; codeDialogVisible.value=false;
ElMessage({ ElMessage({
message: `已向${registerForm.phone}发送验证码,请注意查收`, message: `已向${registerForm.email}发送验证码,请注意查收`,
type: "success", type: "success",
}); });
isDisabledCode.value = true; isDisabledCode.value = true;
@@ -171,16 +172,16 @@ const captcha = async () => {
</div> </div>
<div class="input" style="margin-top: 0"> <div class="input" style="margin-top: 0">
<p>*电话</p> <p>*邮箱</p>
<el-form-item prop="phone"> <el-form-item prop="email">
<div class="phone-code"> <div class="phone-code">
<input class="phone-code-input" type="text" v-model.trim="registerForm.phone"> <input class="phone-code-input" type="text" v-model.trim="registerForm.email">
<button type="button" class="phone-code-btn" @click="clickPhoneCaptcha()">{{codeInfo}}</button> <button type="button" class="phone-code-btn" @click="clickPhoneCaptcha()">{{codeInfo}}</button>
</div> </div>
</el-form-item> </el-form-item>
</div> </div>
<div class="input"> <div class="input">
<p>*短信验证码</p> <p>*邮箱验证码</p>
<el-form-item prop="code" > <el-form-item prop="code" >
<input :disabled="!isDisabledCode" type="text" v-model.trim="registerForm.code"> <input :disabled="!isDisabledCode" type="text" v-model.trim="registerForm.code">
</el-form-item> </el-form-item>
@@ -211,7 +212,7 @@ const captcha = async () => {
<el-dialog <el-dialog
v-model="codeDialogVisible" v-model="codeDialogVisible"
title="发送短信" title="发送邮箱"
width="500" width="500"
center center
> >
@@ -223,7 +224,8 @@ const captcha = async () => {
<el-form-item prop="code" > <el-form-item prop="code" >
<input type="text" v-model.trim="phoneForm.code"> <input type="text" v-model.trim="phoneForm.code">
</el-form-item> </el-form-item>
<p style="color: red">由于国内短信严格程度在2025年5月连续加强3次你的验证码有一定概率被运营商拦截</p> <!-- <p style="color: red">由于国内短信严格程度在2025年5月连续加强3次你的验证码有一定概率被运营商拦截</p>-->
<p style="color: red">请检查你的邮箱垃圾箱可能在那</p>
<p style="color: red">如果未收到验证码请联系微信chengzilaoge520 站长进行手动创建</p> <p style="color: red">如果未收到验证码请联系微信chengzilaoge520 站长进行手动创建</p>
</div> </div>
</div> </div>

View File

@@ -7,10 +7,10 @@
<el-input v-model="user.nick" maxlength="30" :disabled="isDisable"/> <el-input v-model="user.nick" maxlength="30" :disabled="isDisable"/>
</el-form-item> </el-form-item>
<el-form-item label="手机号码" prop="phone"> <el-form-item label="手机号码" prop="phone">
<el-input v-model="user.phone" maxlength="11" :disabled="isDisable" /> <el-input v-model="user.phone" maxlength="11" disabled />
</el-form-item> </el-form-item>
<el-form-item label="邮箱" prop="email"> <el-form-item label="邮箱" prop="email">
<el-input v-model="user.email" maxlength="50" :disabled="isDisable" /> <el-input v-model="user.email" maxlength="50" disabled />
</el-form-item> </el-form-item>
<el-form-item label="性别"> <el-form-item label="性别">
<el-radio-group v-model="user.sex" :disabled="isDisable"> <el-radio-group v-model="user.sex" :disabled="isDisable">
@@ -46,22 +46,22 @@ const userRef = ref(null);
const rules = ref({ const rules = ref({
nick: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }], nick: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
email: [ // email: [
{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, // { required: true, message: "邮箱地址不能为空", trigger: "blur" },
{ // {
type: "email", // type: "email",
message: "请输入正确的邮箱地址", // message: "请输入正确的邮箱地址",
trigger: ["blur", "change"], // trigger: ["blur", "change"],
}, // },
], // ],
phone: [ // phone: [
{ required: true, message: "手机号码不能为空", trigger: "blur" }, // { required: true, message: "手机号码不能为空", trigger: "blur" },
{ // {
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, // pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: "请输入正确的手机号码", // message: "请输入正确的手机号码",
trigger: "blur", // trigger: "blur",
}, // },
], // ],
}); });
/** 提交按钮 */ /** 提交按钮 */