Browse Source

增加密码重置功能

pull/4/head
cKey 6 years ago
parent
commit
9367bc58e7
  1. 24
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/PasswordResetDto.cs
  2. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IAccountAppService.cs
  3. 91
      aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs
  4. 1
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/AccountRegisterVerifyCacheItem.cs
  5. 4
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/AccountSettingNames.cs
  6. 3
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/PhoneNumberVerifyType.cs
  7. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN/Abp/Account/AbpAccountSettingDefinitionProvider.cs
  8. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN/Abp/Account/IIdentityUserRepository.cs
  9. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN/Abp/Account/Localization/Resources/en.json
  10. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json
  11. 7
      aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AccountController.cs
  12. 4
      aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj
  13. 8
      aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/Class1.cs
  14. 12
      aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj
  15. 9
      aspnet-core/services/account/AuthServer.Host/EntityFrameworkCore/Identity/EfCoreIdentityUserExtensionRepository.cs
  16. 12
      aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/EntityFrameworkCore/Identity/EfCoreIdentityUserExtensionRepository.cs
  17. 24
      vueJs/src/api/users.ts
  18. 1
      vueJs/src/components/LangSelect/index.vue
  19. 2
      vueJs/src/lang/zh.ts
  20. 2
      vueJs/src/permission.ts
  21. 5
      vueJs/src/router/index.ts
  22. 27
      vueJs/src/views/login/index.vue
  23. 4
      vueJs/src/views/register/index.vue
  24. 339
      vueJs/src/views/reset-password/index.vue

24
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/Dto/PasswordResetDto.cs

@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Auditing;
using Volo.Abp.Identity;
namespace LINGYUN.Abp.Account
{
public class PasswordResetDto
{
[Required]
[Phone]
[StringLength(IdentityUserConsts.MaxPhoneNumberLength)]
public string PhoneNumber { get; set; }
[Required]
[StringLength(IdentityUserConsts.MaxPasswordLength)]
[DataType(DataType.Password)]
[DisableAuditing]
public string NewPassword { get; set; }
[Required]
[StringLength(6)]
public string VerifyCode { get; set; }
}
}

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Application.Contracts/LINGYUN/Abp/Account/IAccountAppService.cs

@ -8,6 +8,8 @@ namespace LINGYUN.Abp.Account
{ {
Task<IdentityUserDto> RegisterAsync(RegisterVerifyDto input); Task<IdentityUserDto> RegisterAsync(RegisterVerifyDto input);
Task ResetPasswordAsync(PasswordResetDto passwordReset);
Task VerifyPhoneNumberAsync(VerifyDto input); Task VerifyPhoneNumberAsync(VerifyDto input);
} }
} }

91
aspnet-core/modules/account/LINGYUN.Abp.Account.Application/LINGYUN/Abp/Account/AccountAppService.cs

@ -8,6 +8,7 @@ using Volo.Abp.Caching;
using Volo.Abp.Identity; using Volo.Abp.Identity;
using Volo.Abp.Settings; using Volo.Abp.Settings;
using Volo.Abp.Sms; using Volo.Abp.Sms;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.Account namespace LINGYUN.Abp.Account
{ {
@ -87,6 +88,34 @@ namespace LINGYUN.Abp.Account
return ObjectMapper.Map<IdentityUser, IdentityUserDto>(user); return ObjectMapper.Map<IdentityUser, IdentityUserDto>(user);
} }
// TODO: 是否有必要移动到ProfileService
/// <summary>
/// 重置用户密码
/// </summary>
/// <param name="passwordReset"></param>
/// <returns></returns>
public virtual async Task ResetPasswordAsync(PasswordResetDto passwordReset)
{
// 本来可以不需要的,令牌算法有一个有效期
// 不过这里采用令牌强制过期策略,避免一个令牌多次使用
var phoneVerifyCacheKey = NormalizeCacheKey(passwordReset.PhoneNumber);
var phoneVerifyCacheItem = await Cache.GetAsync(phoneVerifyCacheKey);
if (phoneVerifyCacheItem == null || !phoneVerifyCacheItem.VerifyCode.Equals(passwordReset.VerifyCode))
{
throw new UserFriendlyException(L["PhoneVerifyCodeInvalid"]);
}
var userId = await GetUserIdByPhoneNumberAsync(passwordReset.PhoneNumber);
var user = await UserManager.GetByIdAsync(userId);
(await UserManager.ResetPasswordAsync(user, phoneVerifyCacheItem.VerifyToken, passwordReset.NewPassword)).CheckErrors();
await Cache.RemoveAsync(phoneVerifyCacheKey);
}
/// <summary> /// <summary>
/// 验证手机号码 /// 验证手机号码
/// </summary> /// </summary>
@ -124,18 +153,26 @@ namespace LINGYUN.Abp.Account
{ {
PhoneNumber = input.PhoneNumber, PhoneNumber = input.PhoneNumber,
}; };
if (input.VerifyType == PhoneNumberVerifyType.Register) switch (input.VerifyType)
{ {
case PhoneNumberVerifyType.Register:
var phoneVerifyCode = new Random().Next(100000, 999999); var phoneVerifyCode = new Random().Next(100000, 999999);
verifyCacheItem.VerifyCode = phoneVerifyCode.ToString(); verifyCacheItem.VerifyCode = phoneVerifyCode.ToString();
var templateCode = await SettingProvider.GetOrNullAsync(AccountSettingNames.SmsRegisterTemplateCode); var templateCode = await SettingProvider.GetOrNullAsync(AccountSettingNames.SmsRegisterTemplateCode);
await SendPhoneVerifyMessageAsync(templateCode, input.PhoneNumber, phoneVerifyCode.ToString()); await SendPhoneVerifyMessageAsync(templateCode, input.PhoneNumber, phoneVerifyCode.ToString());
return;
case PhoneNumberVerifyType.Signin:
var phoneSigninCode = await SendSigninVerifyCodeAsync(input.PhoneNumber);
verifyCacheItem.VerifyCode = phoneSigninCode;
break;
case PhoneNumberVerifyType.ResetPassword:
var resetPasswordCode = new Random().Next(100000, 999999);
verifyCacheItem.VerifyCode = resetPasswordCode.ToString();
var resetPasswordToken = await SendResetPasswordVerifyCodeAsync(input.PhoneNumber, verifyCacheItem.VerifyCode);
verifyCacheItem.VerifyToken = resetPasswordToken;
break;
} }
else
{
var phoneVerifyCode = await SendSigninVerifyCodeAsync(input.PhoneNumber);
verifyCacheItem.VerifyCode = phoneVerifyCode;
}
var cacheOptions = new DistributedCacheEntryOptions var cacheOptions = new DistributedCacheEntryOptions
{ {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(verifyCodeExpiration) AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(verifyCodeExpiration)
@ -151,11 +188,7 @@ namespace LINGYUN.Abp.Account
protected virtual async Task<string> SendSigninVerifyCodeAsync(string phoneNumber) protected virtual async Task<string> SendSigninVerifyCodeAsync(string phoneNumber)
{ {
// 查找用户信息 // 查找用户信息
var user = await UserRepository.FindByPhoneNumberAsync(phoneNumber); var user = await GetUserByPhoneNumberAsync(phoneNumber);
if (user == null)
{
throw new UserFriendlyException(L["PhoneNumberNotRegisterd"]);
}
// 获取登录验证码模板号 // 获取登录验证码模板号
var templateCode = await SettingProvider.GetOrNullAsync(AccountSettingNames.SmsSigninTemplateCode); var templateCode = await SettingProvider.GetOrNullAsync(AccountSettingNames.SmsSigninTemplateCode);
// 生成手机验证码 // 生成手机验证码
@ -165,6 +198,42 @@ namespace LINGYUN.Abp.Account
return phoneVerifyCode; return phoneVerifyCode;
} }
protected virtual async Task<string> SendResetPasswordVerifyCodeAsync(string phoneNumber, string phoneVerifyCode)
{
// 查找用户信息
var user = await GetUserByPhoneNumberAsync(phoneNumber);
// 获取登录验证码模板号
var templateCode = await SettingProvider.GetOrNullAsync(AccountSettingNames.SmsResetPasswordTemplateCode);
// 生成重置密码验证码
var phoneVerifyToken = await UserManager.GeneratePasswordResetTokenAsync(user);
// 发送短信验证码
await SendPhoneVerifyMessageAsync(templateCode, user.PhoneNumber, phoneVerifyCode);
return phoneVerifyToken;
}
protected virtual async Task<IdentityUser> GetUserByPhoneNumberAsync(string phoneNumber)
{
// 查找用户信息
var user = await UserRepository.FindByPhoneNumberAsync(phoneNumber);
if (user == null)
{
throw new UserFriendlyException(L["PhoneNumberNotRegisterd"]);
}
return user;
}
protected virtual async Task<Guid> GetUserIdByPhoneNumberAsync(string phoneNumber)
{
// 查找用户信息
var userId = await UserRepository.GetIdByPhoneNumberAsync(phoneNumber);
if (!userId.HasValue)
{
throw new UserFriendlyException(L["PhoneNumberNotRegisterd"]);
}
return userId.Value;
}
/// <summary> /// <summary>
/// 检查是否允许用户注册 /// 检查是否允许用户注册
/// </summary> /// </summary>

1
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/AccountRegisterVerifyCacheItem.cs

@ -4,5 +4,6 @@
{ {
public string PhoneNumber { get; set; } public string PhoneNumber { get; set; }
public string VerifyCode { get; set; } public string VerifyCode { get; set; }
public string VerifyToken { get; set; }
} }
} }

4
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/AccountSettingNames.cs

@ -15,5 +15,9 @@
/// 用户登录短信验证码模板号 /// 用户登录短信验证码模板号
/// </summary> /// </summary>
public const string SmsSigninTemplateCode = GroupName + ".SmsSigninTemplateCode"; public const string SmsSigninTemplateCode = GroupName + ".SmsSigninTemplateCode";
/// <summary>
/// 用户重置密码短信验证码模板号
/// </summary>
public const string SmsResetPasswordTemplateCode = GroupName + ".SmsResetPasswordTemplateCode";
} }
} }

3
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain.Shared/LINGYUN/Abp/Account/PhoneNumberVerifyType.cs

@ -3,6 +3,7 @@
public enum PhoneNumberVerifyType : sbyte public enum PhoneNumberVerifyType : sbyte
{ {
Register = 0, Register = 0,
Signin = 10 Signin = 10,
ResetPassword = 20
} }
} }

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN/Abp/Account/AbpAccountSettingDefinitionProvider.cs

@ -20,6 +20,8 @@ namespace LINGYUN.Abp.Account
"SMS_190728520", L("DisplayName:SmsRegisterTemplateCode"), L("Description:SmsRegisterTemplateCode")), "SMS_190728520", L("DisplayName:SmsRegisterTemplateCode"), L("Description:SmsRegisterTemplateCode")),
new SettingDefinition(AccountSettingNames.SmsSigninTemplateCode, new SettingDefinition(AccountSettingNames.SmsSigninTemplateCode,
"SMS_190728516", L("DisplayName:SmsSigninTemplateCode"), L("Description:SmsSigninTemplateCode")), "SMS_190728516", L("DisplayName:SmsSigninTemplateCode"), L("Description:SmsSigninTemplateCode")),
new SettingDefinition(AccountSettingNames.SmsResetPasswordTemplateCode,
"SMS_192530831", L("DisplayName:SmsResetPasswordTemplateCode"), L("Description:SmsResetPasswordTemplateCode")),
new SettingDefinition(AccountSettingNames.PhoneVerifyCodeExpiration, new SettingDefinition(AccountSettingNames.PhoneVerifyCodeExpiration,
"3", L("DisplayName:PhoneVerifyCodeExpiration"), L("Description:PhoneVerifyCodeExpiration")), "3", L("DisplayName:PhoneVerifyCodeExpiration"), L("Description:PhoneVerifyCodeExpiration")),
}; };

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN/Abp/Account/IIdentityUserRepository.cs

@ -10,5 +10,7 @@ namespace LINGYUN.Abp.Account
Task<bool> PhoneNumberHasRegistedAsync(string phoneNumber); Task<bool> PhoneNumberHasRegistedAsync(string phoneNumber);
Task<IdentityUser> FindByPhoneNumberAsync(string phoneNumber); Task<IdentityUser> FindByPhoneNumberAsync(string phoneNumber);
Task<Guid?> GetIdByPhoneNumberAsync(string phoneNumber);
} }
} }

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN/Abp/Account/Localization/Resources/en.json

@ -8,6 +8,8 @@
"Description:SmsRegisterTemplateCode": "When the user registers, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration", "Description:SmsRegisterTemplateCode": "When the user registers, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration",
"DisplayName:SmsSigninTemplateCode": "Signin sms template", "DisplayName:SmsSigninTemplateCode": "Signin sms template",
"Description:SmsSigninTemplateCode": "When the user logs in, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration", "Description:SmsSigninTemplateCode": "When the user logs in, he/she should send the template number of the SMS verification code and fill in the template number of the corresponding cloud platform registration",
"DisplayName:SmsResetPasswordTemplateCode": "Reset password sms template",
"Description:SmsResetPasswordTemplateCode": "When the user resets the password, he/she sends the template number of SMS verification code and fills in the template number registered on the cloud platform",
"DisplayName:PhoneVerifyCodeExpiration": "SMS verification code validity", "DisplayName:PhoneVerifyCodeExpiration": "SMS verification code validity",
"Description:PhoneVerifyCodeExpiration": "The valid time for the user to send SMS verification code, unit m, default 3m", "Description:PhoneVerifyCodeExpiration": "The valid time for the user to send SMS verification code, unit m, default 3m",
"RequiredEmailAddress": "Email address required", "RequiredEmailAddress": "Email address required",

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Domain/LINGYUN/Abp/Account/Localization/Resources/zh-Hans.json

@ -8,6 +8,8 @@
"Description:SmsRegisterTemplateCode": "用户注册时发送短信验证码的模板号,填写对应云平台注册的模板号", "Description:SmsRegisterTemplateCode": "用户注册时发送短信验证码的模板号,填写对应云平台注册的模板号",
"DisplayName:SmsSigninTemplateCode": "用户登录短信模板", "DisplayName:SmsSigninTemplateCode": "用户登录短信模板",
"Description:SmsSigninTemplateCode": "用户登录时发送短信验证码的模板号,填写对应云平台注册的模板号", "Description:SmsSigninTemplateCode": "用户登录时发送短信验证码的模板号,填写对应云平台注册的模板号",
"DisplayName:SmsResetPasswordTemplateCode": "用户重置密码短信模板",
"Description:SmsResetPasswordTemplateCode": "用户重置密码时发送短信验证码的模板号,填写对应云平台注册的模板号",
"DisplayName:PhoneVerifyCodeExpiration": "短信验证码有效期", "DisplayName:PhoneVerifyCodeExpiration": "短信验证码有效期",
"Description:PhoneVerifyCodeExpiration": "用户发送短信验证码的有效时长,单位m,默认3m", "Description:PhoneVerifyCodeExpiration": "用户发送短信验证码的有效时长,单位m,默认3m",
"RequiredEmailAddress": "邮件地址必须输入", "RequiredEmailAddress": "邮件地址必须输入",

7
aspnet-core/modules/account/LINGYUN.Abp.Account.HttpApi/LINGYUN/Abp/Account/AccountController.cs

@ -32,5 +32,12 @@ namespace LINGYUN.Abp.Account
{ {
await AccountAppService.VerifyPhoneNumberAsync(input); await AccountAppService.VerifyPhoneNumberAsync(input);
} }
[HttpPut]
[Route("reset-password")]
public virtual async Task ResetPasswordAsync(PasswordResetDto passwordReset)
{
await AccountAppService.ResetPasswordAsync(passwordReset);
}
} }
} }

4
aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj

@ -13,4 +13,8 @@
<ProjectReference Include="..\LINGYUN.Abp.FileStorage\LINGYUN.Abp.FileStorage.csproj" /> <ProjectReference Include="..\LINGYUN.Abp.FileStorage\LINGYUN.Abp.FileStorage.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Qiniu\Http\" />
</ItemGroup>
</Project> </Project>

8
aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/Class1.cs

@ -0,0 +1,8 @@
using System;
namespace LINGYUN.Abp.IdentityServer.WeChatValidator
{
public class Class1
{
}
}

12
aspnet-core/modules/common/LINGYUN.Abp.IdentityServer.WeChatValidator/LINGYUN.Abp.IdentityServer.WeChatValidator.csproj

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.IdentityServer.Domain" Version="2.8.0" />
</ItemGroup>
</Project>

9
aspnet-core/services/account/AuthServer.Host/EntityFrameworkCore/Identity/EfCoreIdentityUserExtensionRepository.cs

@ -24,10 +24,19 @@ namespace AuthServer.Host.EntityFrameworkCore.Identity
return await DbSet.AnyAsync(x => x.PhoneNumberConfirmed && x.PhoneNumber.Equals(phoneNumber)); return await DbSet.AnyAsync(x => x.PhoneNumberConfirmed && x.PhoneNumber.Equals(phoneNumber));
} }
public virtual async Task<Guid?> GetIdByPhoneNumberAsync(string phoneNumber)
{
return await DbSet
.Where(x => x.PhoneNumber.Equals(phoneNumber))
.Select(x => x.Id)
.FirstOrDefaultAsync();
}
public virtual async Task<IdentityUser> FindByPhoneNumberAsync(string phoneNumber) public virtual async Task<IdentityUser> FindByPhoneNumberAsync(string phoneNumber)
{ {
return await WithDetails() return await WithDetails()
.Where(usr => usr.PhoneNumber.Equals(phoneNumber)) .Where(usr => usr.PhoneNumber.Equals(phoneNumber))
.AsNoTracking()
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
} }

12
aspnet-core/services/platform/LINGYUN.Platform.HttpApi.Host/EntityFrameworkCore/Identity/EfCoreIdentityUserExtensionRepository.cs

@ -24,9 +24,19 @@ namespace LINGYUN.Platform.EntityFrameworkCore.Identity
return await DbSet.AnyAsync(x => x.PhoneNumberConfirmed && x.PhoneNumber.Equals(phoneNumber)); return await DbSet.AnyAsync(x => x.PhoneNumberConfirmed && x.PhoneNumber.Equals(phoneNumber));
} }
public virtual async Task<Guid?> GetIdByPhoneNumberAsync(string phoneNumber)
{
return await DbSet
.Where(x => x.PhoneNumber.Equals(phoneNumber))
.Select(x => x.Id)
.FirstOrDefaultAsync();
}
public virtual async Task<IdentityUser> FindByPhoneNumberAsync(string phoneNumber) public virtual async Task<IdentityUser> FindByPhoneNumberAsync(string phoneNumber)
{ {
return await DbSet.Where(usr => usr.PhoneNumber.Equals(phoneNumber)).FirstOrDefaultAsync(); return await DbSet.Where(usr => usr.PhoneNumber.Equals(phoneNumber))
.AsNoTracking()
.FirstOrDefaultAsync();
} }
} }
} }

24
vueJs/src/api/users.ts

@ -79,6 +79,16 @@ export default class UserApiService {
}) })
} }
public static resetPassword(input: UserResetPasswordData) {
const _url = '/api/account/phone/reset-password'
return ApiService.HttpRequest({
baseURL: IdentityServiceUrl,
url: _url,
data: input,
method: 'PUT'
})
}
public static userRegister(registerData: UserRegisterData) { public static userRegister(registerData: UserRegisterData) {
const _url = '/api/account/phone/register' const _url = '/api/account/phone/register'
return ApiService.HttpRequest<UserDataDto>({ return ApiService.HttpRequest<UserDataDto>({
@ -224,8 +234,9 @@ export class UserLoginData {
} }
export enum VerifyType { export enum VerifyType {
register = 0, Register = 0,
signin = 10 Signin = 10,
ResetPassword = 20
} }
export class PhoneVerify { export class PhoneVerify {
@ -233,6 +244,15 @@ export class PhoneVerify {
verifyType!:VerifyType verifyType!:VerifyType
} }
export class UserResetPasswordData {
/** 手机号码 */
phoneNumber!: string
/** 手机验证码 */
verifyCode!: string
/** 新密码 */
newPassword!: string
}
/** 用户手机登录对象 */ /** 用户手机登录对象 */
export class UserLoginPhoneData { export class UserLoginPhoneData {
/** 手机号码 */ /** 手机号码 */

1
vueJs/src/components/LangSelect/index.vue

@ -30,6 +30,7 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator' import { Component, Vue, Watch } from 'vue-property-decorator'
import { AppModule } from '@/store/modules/app' import { AppModule } from '@/store/modules/app'
import { AbpConfigurationModule } from '@/store/modules/abp'
@Component({ @Component({
name: 'Login' name: 'Login'

2
vueJs/src/lang/zh.ts

@ -93,8 +93,10 @@ export default {
register: '注册', register: '注册',
notAccount: '没有账户?', notAccount: '没有账户?',
existsAccount: '已有账户?', existsAccount: '已有账户?',
forgotPassword: '忘记密码?',
userLogin: '用户密码登录', userLogin: '用户密码登录',
phoneLogin: '手机免密登录', phoneLogin: '手机免密登录',
resetpassword: '重置密码',
tenantName: '租户', tenantName: '租户',
username: '账号', username: '账号',
password: '密码', password: '密码',

2
vueJs/src/permission.ts

@ -11,7 +11,7 @@ import settings from './settings'
NProgress.configure({ showSpinner: false }) NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/auth-redirect', '/register'] const whiteList = ['/login', '/auth-redirect', '/register', '/reset-password']
const getPageTitle = (key: string) => { const getPageTitle = (key: string) => {
const hasKey = i18n.te(`route.${key}`) const hasKey = i18n.te(`route.${key}`)

5
vueJs/src/router/index.ts

@ -66,6 +66,11 @@ export const constantRoutes: RouteConfig[] = [
component: () => import(/* webpackChunkName: "login" */ '@/views/register/index.vue'), component: () => import(/* webpackChunkName: "login" */ '@/views/register/index.vue'),
meta: { hidden: true } meta: { hidden: true }
}, },
{
path: '/reset-password',
component: () => import(/* webpackChunkName: "login" */ '@/views/reset-password/index.vue'),
meta: { hidden: true }
},
{ {
path: '/auth-redirect', path: '/auth-redirect',
component: () => import(/* webpackChunkName: "auth-redirect" */ '@/views/login/auth-redirect.vue'), component: () => import(/* webpackChunkName: "auth-redirect" */ '@/views/login/auth-redirect.vue'),

27
vueJs/src/views/login/index.vue

@ -104,6 +104,9 @@
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<el-row>
<el-col :span="12">
<el-form-item <el-form-item
v-show="selfRegistration" v-show="selfRegistration"
label-width="100px" label-width="100px"
@ -116,6 +119,22 @@
{{ $t('login.register') }} {{ $t('login.register') }}
</el-link> </el-link>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
v-show="selfRegistration"
label-width="100px"
:label="$t('login.forgotPassword')"
>
<el-link
type="info"
@click="handleRedirectResetPassword"
>
{{ $t('login.resetpassword') }}
</el-link>
</el-form-item>
</el-col>
</el-row>
<el-form-item style="width:100%;"> <el-form-item style="width:100%;">
<el-button <el-button
@ -167,7 +186,7 @@ export default class extends Vue {
} }
get isMultiEnabled() { get isMultiEnabled() {
return AbpConfigurationModule.configuration.multiTenancy.isEnabled return AbpConfigurationModule.configuration?.multiTenancy?.isEnabled
} }
get selfRegistration() { get selfRegistration() {
@ -241,6 +260,10 @@ export default class extends Vue {
this.$router.replace('register') this.$router.replace('register')
} }
private handleRedirectResetPassword() {
this.$router.replace('reset-password')
}
private handleUserLogin() { private handleUserLogin() {
const frmLogin = this.$refs.formLogin as any const frmLogin = this.$refs.formLogin as any
frmLogin.validate(async(valid: boolean) => { frmLogin.validate(async(valid: boolean) => {
@ -282,7 +305,7 @@ export default class extends Vue {
this.sending = true this.sending = true
const phoneVerify = new PhoneVerify() const phoneVerify = new PhoneVerify()
phoneVerify.phoneNumber = this.loginForm.phoneNumber phoneVerify.phoneNumber = this.loginForm.phoneNumber
phoneVerify.verifyType = VerifyType.signin phoneVerify.verifyType = VerifyType.Signin
UserService.sendPhoneVerifyCode(phoneVerify).then(() => { UserService.sendPhoneVerifyCode(phoneVerify).then(() => {
let interValTime = 60 let interValTime = 60
const sendingName = this.l('login.afterSendVerifyCode') const sendingName = this.l('login.afterSendVerifyCode')

4
vueJs/src/views/register/index.vue

@ -151,7 +151,7 @@ export default class extends Vue {
} }
get isMultiEnabled() { get isMultiEnabled() {
return AbpConfigurationModule.configuration.multiTenancy.isEnabled return AbpConfigurationModule.configuration?.multiTenancy?.isEnabled
} }
private validatePhoneNumberValue = (rule: any, value: string, callback: any) => { private validatePhoneNumberValue = (rule: any, value: string, callback: any) => {
@ -248,7 +248,7 @@ export default class extends Vue {
this.sending = true this.sending = true
const phoneVerify = new PhoneVerify() const phoneVerify = new PhoneVerify()
phoneVerify.phoneNumber = this.registerForm.phoneNumber phoneVerify.phoneNumber = this.registerForm.phoneNumber
phoneVerify.verifyType = VerifyType.register phoneVerify.verifyType = VerifyType.Register
UserService.sendPhoneVerifyCode(phoneVerify).then(() => { UserService.sendPhoneVerifyCode(phoneVerify).then(() => {
let interValTime = 60 let interValTime = 60
const sendingName = this.l('login.afterSendVerifyCode') const sendingName = this.l('login.afterSendVerifyCode')

339
vueJs/src/views/reset-password/index.vue

@ -0,0 +1,339 @@
<template>
<div class="login-container">
<el-form
ref="formResetPassword"
:model="resetPasswordForm"
:rules="resetPasswordFormRules"
label-position="left"
label-width="0px"
class="demo-ruleForm login-page"
>
<div class="title-container">
<h3 class="title">
{{ $t('login.resetpassword') }}
</h3>
<lang-select class="set-language" />
</div>
<el-form-item label-width="0px">
<tenant-box
v-if="isMultiEnabled"
v-model="resetPasswordForm.tenantName"
/>
</el-form-item>
<el-form-item
prop="phoneNumber"
>
<el-input
v-model="resetPasswordForm.phoneNumber"
prefix-icon="el-icon-mobile-phone"
type="text"
maxlength="11"
auto-complete="off"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.phoneNumber')})"
/>
</el-form-item>
<el-form-item
prop="verifyCode"
>
<el-row>
<el-col :span="16">
<el-input
v-model="resetPasswordForm.verifyCode"
auto-complete="off"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.phoneVerifyCode')})"
prefix-icon="el-icon-key"
style="margin:-right: 10px;"
/>
</el-col>
<el-col :span="8">
<el-button
ref="sendButton"
style="margin-left: 10px;width: 132px;"
:disabled="sending"
@click="handleSendPhoneVerifyCode"
>
{{ sendButtonName }}
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item
prop="newPassword"
>
<el-input
:key="passwordType"
ref="newPassword"
v-model="resetPasswordForm.newPassword"
prefix-icon="el-icon-lock"
:type="passwordType"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.password')})"
name="newPassword"
tabindex="2"
@keyup.enter.native="handleResetPassword"
/>
<span
class="show-pwd"
@click="showPwd"
>
<svg-icon :name="passwordType === 'password' ? 'eye-off' : 'eye-on'" />
</span>
</el-form-item>
<el-form-item
label-width="100px"
:label="$t('login.existsAccount')"
>
<el-link
type="success"
@click="handleRedirectLogin"
>
{{ $t('login.logIn') }}
</el-link>
</el-form-item>
<el-form-item style="width:100%;">
<el-button
type="primary"
style="width:100%;"
:loading="reseting"
@click="handleResetPassword"
>
{{ $t('login.resetpassword') }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts">
import { Input } from 'element-ui'
import { Route } from 'vue-router'
import { Dictionary } from 'vue-router/types/router'
import TenantBox from '@/components/TenantBox/index.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import { Component, Vue, Watch } from 'vue-property-decorator'
import UserService, { PhoneVerify, VerifyType, UserResetPasswordData } from '@/api/users'
import { AbpConfigurationModule } from '@/store/modules/abp'
@Component({
name: 'Register',
components: {
LangSelect,
TenantBox
}
})
export default class extends Vue {
private passwordType = 'password'
private redirect?: string
private sendTimer: any
private sending = false
private sendButtonName = this.l('login.sendVerifyCode')
private reseting = false
private resetPasswordForm = {
tenantName: '',
newPassword: '',
phoneNumber: '',
verifyCode: ''
}
get isMultiEnabled() {
return AbpConfigurationModule.configuration?.multiTenancy?.isEnabled
}
private validatePhoneNumberValue = (rule: any, value: string, callback: any) => {
const phoneReg = /^1[34578]\d{9}$/
if (!value || !phoneReg.test(value)) {
callback(new Error(this.l('global.pleaseInputBy', { key: this.l('global.correctPhoneNumber') })))
} else {
callback()
}
}
private resetPasswordFormRules = {
newPassword: [
{
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.password') }), trigger: 'blur'
}
],
phoneNumber: [
{
required: true, validator: this.validatePhoneNumberValue, trigger: 'blur'
}
],
verifyCode: [
{
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.phoneVerifyCode') }), trigger: 'blur'
}
]
}
destroyed() {
if (this.sendTimer) {
clearInterval(this.sendTimer)
}
}
@Watch('$route', { immediate: true })
private onRouteChange(route: Route) {
// TODO: remove the "as Dictionary<string>" hack after v4 release for vue-router
// See https://github.com/vuejs/vue-router/pull/2050 for details
const query = route.query as Dictionary<string>
if (query) {
this.redirect = query.redirect
}
}
private showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
(this.$refs.newPassword as Input).focus()
})
}
private handleRedirectLogin() {
this.$router.replace('login')
}
private handleResetPassword() {
const frmResetPassword = this.$refs.formResetPassword as any
frmResetPassword.validate(async(valid: boolean) => {
if (valid) {
this.reseting = true
try {
const userReserPassword = new UserResetPasswordData()
userReserPassword.phoneNumber = this.resetPasswordForm.phoneNumber
userReserPassword.verifyCode = this.resetPasswordForm.verifyCode
userReserPassword.newPassword = this.resetPasswordForm.newPassword
UserService.resetPassword(userReserPassword).then(() => {
this.handleRedirectLogin()
}).finally(() => {
this.resetLoginButton()
})
} catch {
this.resetLoginButton()
}
}
})
}
private handleSendPhoneVerifyCode() {
const frmResetPassword = this.$refs.formResetPassword as any
frmResetPassword.validateField('phoneNumber', (errorMsg: string) => {
if (!errorMsg) {
this.sending = true
const phoneVerify = new PhoneVerify()
phoneVerify.phoneNumber = this.resetPasswordForm.phoneNumber
phoneVerify.verifyType = VerifyType.ResetPassword
UserService.sendPhoneVerifyCode(phoneVerify).then(() => {
let interValTime = 60
const sendingName = this.l('login.afterSendVerifyCode')
const sendedName = this.l('login.sendVerifyCode')
this.sendTimer = setInterval(() => {
this.sendButtonName = interValTime + sendingName
--interValTime
if (interValTime < 0) {
this.sendButtonName = sendedName
this.sending = false
clearInterval(this.sendTimer)
}
}, 1000)
}).catch(() => {
this.sending = false
})
}
})
}
private l(name: string, values?: any[] | { [key: string]: any }) {
return this.$t(name, values).toString()
}
private resetLoginButton() {
setTimeout(() => {
this.reseting = false
}, 0.5 * 1000)
}
}
</script>
<style lang="scss" scoped>
.login-container {
width: 100%;
height: 100%;
overflow: hidden;
background-color: $loginBg;
.svg-container {
padding: 6px 5px 6px 15px;
color: $darkGray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
margin: 0px auto 20px auto;
text-align: center;
font-weight: bold;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.set-language {
position: absolute;
top: 3px;
font-size: 18px;
right: 0px;
cursor: pointer;
}
}
.show-pwd {
position: absolute;
right: 10px;
font-size: 16px;
color: $darkGray;
cursor: pointer;
user-select: none;
}
}
.login-page {
-webkit-border-radius: 5px;
border-radius: 5px;
margin: 130px auto;
width: 500px;
padding: 35px 35px 15px;
border: 1px solid #8c9494;
box-shadow: 0 0 25px #454646;
background-color:rgb(247, 255, 255);
.loginTab.el-tabs__item {
width: 180px;
}
}
label.el-checkbox.rememberme {
margin: 0px 0px 15px;
text-align: left;
}
</style>
Loading…
Cancel
Save