Merge branch 'abp' of https://gitee.com/ccnetcore/Yi into abp

This commit is contained in:
陈淳
2023-12-21 08:57:17 +08:00
60 changed files with 25424 additions and 372 deletions

View File

@@ -8,8 +8,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Json" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Swashbuckle" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Json" Version="8.0.0" />
<PackageReference Include="Volo.Abp.Swashbuckle" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -5,7 +5,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Volo.Abp.Core" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Core" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -3,7 +3,7 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,7 +2,7 @@
<Import Project="..\..\common.props" />
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Ddd.Application" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,7 +4,7 @@
<ItemGroup>
<PackageReference Include="Mapster" Version="7.4.0" />
<PackageReference Include="Volo.Abp.ObjectMapping" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.ObjectMapping" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,7 +4,7 @@
<ItemGroup>
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.124" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -20,7 +20,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
public void Dispose()
{
Console.WriteLine("Dispose");
}
public async Task RollbackAsync(CancellationToken cancellationToken = default)

View File

@@ -102,14 +102,14 @@ namespace Yi.Framework.SqlSugarCore.Uow
//await Console.Out.WriteLineAsync("开始新的事务");
Console.WriteLine(dbContext.SqlSugarClient.ContextID);
// Console.WriteLine(dbContext.SqlSugarClient.ContextID);
await dbContext.SqlSugarClient.Ado.BeginTranAsync();
return dbContext;
}
else
{
// await Console.Out.WriteLineAsync("继续老的事务");
Console.WriteLine(activeTransaction.DbContext.SqlSugarClient);
// Console.WriteLine(activeTransaction.DbContext.SqlSugarClient);
await activeTransaction.DbContext.SqlSugarClient.Ado.BeginTranAsync();
return (TDbContext)activeTransaction.DbContext;
}

View File

@@ -24,5 +24,12 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
/// 封面
/// </summary>
public string? Cover { get; set; }
public int OrderNum { get; set; } = 0;
/// <summary>
/// 是否禁止评论创建功能
/// </summary>
public bool IsDisableCreateComment { get; set; }
}
}

View File

@@ -7,6 +7,10 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
{
public class DiscussGetListOutputDto : EntityDto<Guid>
{
/// <summary>
/// <20>Ƿ<EFBFBD><C7B7><EFBFBD>ֹ<EFBFBD><D6B9><EFBFBD>۴<EFBFBD><DBB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public bool IsDisableCreateComment { get; set; }
/// <summary>
/// <20>Ƿ<EFBFBD><C7B7>ѵ<EFBFBD><D1B5>ޣ<EFBFBD>Ĭ<EFBFBD><C4AC>δ<EFBFBD><CEB4>¼<EFBFBD><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>

View File

@@ -6,6 +6,10 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
{
public class DiscussGetOutputDto : EntityDto<Guid>
{
/// <summary>
/// <20>Ƿ<EFBFBD><C7B7><EFBFBD>ֹ<EFBFBD><D6B9><EFBFBD>۴<EFBFBD><DBB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public bool IsDisableCreateComment { get; set; }
public string Title { get; set; }
public string? Types { get; set; }
public string? Introduction { get; set; }

View File

@@ -20,5 +20,12 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss
/// <20><><EFBFBD><EFBFBD>
/// </summary>
public string? Cover { get; set; }
public int OrderNum { get; set; }
/// <summary>
/// <20>Ƿ<EFBFBD><C7B7><EFBFBD>ֹ<EFBFBD><D6B9><EFBFBD>۴<EFBFBD><DBB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public bool IsDisableCreateComment { get; set; }
}
}

View File

@@ -10,5 +10,9 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate
public string? Introduction { get; set; }
public string Code { get; set; }
public int OrderNum { get; set; }
public bool IsDisableCreateDiscuss { get; set; }
}
}

View File

@@ -12,5 +12,8 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate
public string Code { get; set; }
public DateTime CreationTime { get; set; }
public int OrderNum { get; set; }
public bool IsDisableCreateDiscuss { get; set; }
}
}

View File

@@ -10,5 +10,8 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate
public string Code { get; set; }
public DateTime CreationTime { get; set; }
public int OrderNum { get; set; }
public bool IsDisableCreateDiscuss { get; set; }
}
}

View File

@@ -7,5 +7,10 @@ namespace Yi.Framework.Bbs.Application.Contracts.Dtos.Plate
public string? Introduction { get; set; }
public string? Code { get; set; }
public int OrderNum { get; set; }
public bool IsDisableCreateDiscuss { get; set; }
}
}

View File

@@ -11,6 +11,7 @@ using Yi.Framework.Bbs.Application.Contracts.Dtos.Article;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Plate;
using Yi.Framework.Bbs.Application.Contracts.IServices;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Extensions;
using Yi.Framework.Bbs.Domain.Repositories;
using Yi.Framework.Bbs.Domain.Shared.Consts;
using Yi.Framework.Core.Extensions;
@@ -47,7 +48,7 @@ namespace Yi.Framework.Bbs.Application.Services
RefAsync<int> total = 0;
var entities = await _articleRepository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name.Contains(input.Name!))
//.WhereIF(!string.IsNullOrEmpty(input.Code), x => x.Name.Contains(input.Code!))
//.WhereIF(!string.IsNullOrEmpty(input.Code), x => x.Name.Contains(input.Code!))
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<ArticleGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
@@ -98,34 +99,60 @@ namespace Yi.Framework.Bbs.Application.Services
/// <exception cref="UserFriendlyException"></exception>
public async override Task<ArticleGetOutputDto> CreateAsync(ArticleCreateInputVo input)
{
var discuss = await _discussRepository.GetFirstAsync(x => x.Id == input.DiscussId);
if (discuss is null)
{
throw new UserFriendlyException(DiscussConst.No_Exist);
}
if (input.ParentId != Guid.Empty && !await _articleRepository.IsAnyAsync(x => x.Id == input.ParentId))
{
throw new UserFriendlyException(ArticleConst.No_Exist);
}
await VerifyDiscussCreateIdAsync(discuss.CreatorId);
await VerifyDiscussCreateIdAsync(input.DiscussId);
return await base.CreateAsync(input);
}
/// <summary>
/// 更新文章
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
public override async Task<ArticleGetOutputDto> UpdateAsync(Guid id, ArticleUpdateInputVo input)
{
var entity = await _articleRepository.GetByIdAsync(id);
await VerifyDiscussCreateIdAsync(entity.DiscussId);
return await base.UpdateAsync(id, input);
}
/// <summary>
/// 效验创建权限
/// 删除文章
/// </summary>
/// <param name="userId"></param>
/// <param name="id"></param>
/// <returns></returns>
public async Task VerifyDiscussCreateIdAsync(Guid? userId)
public override async Task DeleteAsync(Guid id)
{
var entity = await _articleRepository.GetByIdAsync(id);
await VerifyDiscussCreateIdAsync(entity.DiscussId);
await base.DeleteAsync(id);
}
/// <summary>
/// 效验创建权限userId为主题创建者
/// </summary>
/// <param name="disucssId"></param>
/// <returns></returns>
private async Task VerifyDiscussCreateIdAsync(Guid disucssId)
{
var discuss = await _discussRepository.GetFirstAsync(x => x.Id == disucssId);
if (discuss is null)
{
throw new UserFriendlyException(DiscussConst.No_Exist);
}
//只有文章是特殊的,不能在其他主题下创建
//主题的创建者不是当前用户,同时,没有权限或者超级管理
//false & true & false ,三个条件任意满意一个,即可成功使用||,最后取反,一个都不满足
//
if (userId != CurrentUser.Id && !UserConst.Admin.Equals(this.CurrentUser.UserName) && this.LazyServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext.GetUserPermissions(TokenTypeConst.Permission).Contains("bbs:discuss:add"))
//一个条件都不满足,即可拦截
if (discuss.CreatorId != CurrentUser.Id && !UserConst.Admin.Equals(this.CurrentUser.UserName) && !CurrentUser.GetPermissions().Contains("bbs:discuss:add"))
{
throw new UserFriendlyException("权限在其他用户主题中创建子文章");
throw new UserFriendlyException("权限不足,请联系主题作者或管理员申请开通");
}
}
}

View File

@@ -102,10 +102,17 @@ namespace Yi.Framework.Bbs.Application.Services
/// <exception cref="UserFriendlyException"></exception>
public override async Task<CommentGetOutputDto> CreateAsync(CommentCreateInputVo input)
{
if (!await _discussRepository.IsAnyAsync(x => x.Id == input.DiscussId))
var discuess = await _discussRepository.GetFirstAsync(x => x.Id == input.DiscussId);
if (discuess is null)
{
throw new UserFriendlyException(DiscussConst.No_Exist);
}
if (discuess.IsDisableCreateComment == true)
{
throw new UserFriendlyException("该主题已禁止评论功能");
}
var entity = await _forumManager.CreateCommentAsync(input.DiscussId, input.ParentId, input.RootId, input.Content);
return await MapToGetOutputDtoAsync(entity);
}

View File

@@ -9,6 +9,7 @@ using Volo.Abp.Users;
using Yi.Framework.Bbs.Application.Contracts.Dtos.Discuss;
using Yi.Framework.Bbs.Application.Contracts.IServices;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Extensions;
using Yi.Framework.Bbs.Domain.Managers;
using Yi.Framework.Bbs.Domain.Shared.Consts;
using Yi.Framework.Bbs.Domain.Shared.Enums;
@@ -16,6 +17,7 @@ using Yi.Framework.Bbs.Domain.Shared.Etos;
using Yi.Framework.Ddd.Application;
using Yi.Framework.Rbac.Application.Contracts.Dtos.User;
using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.Rbac.Domain.Shared.Consts;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Services
@@ -26,11 +28,13 @@ namespace Yi.Framework.Bbs.Application.Services
public class DiscussService : YiCrudAppService<DiscussEntity, DiscussGetOutputDto, DiscussGetListOutputDto, Guid, DiscussGetListInputVo, DiscussCreateInputVo, DiscussUpdateInputVo>,
IDiscussService
{
public DiscussService(ForumManager forumManager, ISqlSugarRepository<PlateEntity> plateEntityRepository, ILocalEventBus localEventBus) : base(forumManager._discussRepository)
private ISqlSugarRepository<DiscussTopEntity> _discussTopEntityRepository;
public DiscussService(ForumManager forumManager, ISqlSugarRepository<DiscussTopEntity> discussTopEntityRepository, ISqlSugarRepository<PlateEntity> plateEntityRepository, ILocalEventBus localEventBus) : base(forumManager._discussRepository)
{
_forumManager = forumManager;
_plateEntityRepository = plateEntityRepository;
_localEventBus = localEventBus;
_discussTopEntityRepository = discussTopEntityRepository;
}
private readonly ILocalEventBus _localEventBus;
private ForumManager _forumManager { get; set; }
@@ -78,12 +82,17 @@ namespace Yi.Framework.Bbs.Application.Services
var items = await _forumManager._discussRepository._DbQueryable
.WhereIF(!string.IsNullOrEmpty(input.Title), x => x.Title.Contains(input.Title))
.WhereIF(input.PlateId is not null, x => x.PlateId == input.PlateId)
.WhereIF(input.IsTop==true, x => x.IsTop == input.IsTop)
.WhereIF(input.IsTop == true, x => x.IsTop == input.IsTop)
.LeftJoin<UserEntity>((discuss, user) => discuss.CreatorId == user.Id)
.OrderByDescending(discuss => discuss.OrderNum)
.OrderByIF(input.Type == QueryDiscussTypeEnum.New, discuss => discuss.CreationTime, OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.Host, discuss => discuss.SeeNum, OrderByType.Desc)
.OrderByIF(input.Type == QueryDiscussTypeEnum.Suggest, discuss => discuss.AgreeNum, OrderByType.Desc)
.Select((discuss, user) => new DiscussGetListOutputDto
{
Id = discuss.Id,
@@ -99,6 +108,18 @@ namespace Yi.Framework.Bbs.Application.Services
return new PagedResultDto<DiscussGetListOutputDto>(total, items);
}
/// <summary>
/// 获取首页的置顶主题
/// </summary>
/// <returns></returns>
public async Task<List<DiscussGetListOutputDto>> GetListTopAsync()
{
var entities = await _discussTopEntityRepository._DbQueryable.Includes(x => x.Discuss).OrderByDescending(x => x.OrderNum).ToListAsync();
var output = await MapToGetListOutputDtosAsync(entities.Select(x => x.Discuss).ToList());
return output;
}
/// <summary>
/// 创建主题
/// </summary>
@@ -106,17 +127,28 @@ namespace Yi.Framework.Bbs.Application.Services
/// <returns></returns>
public override async Task<DiscussGetOutputDto> CreateAsync(DiscussCreateInputVo input)
{
if (!await _plateEntityRepository.IsAnyAsync(x => x.Id == input.PlateId))
var plate = await _plateEntityRepository.FindAsync(x => x.Id == input.PlateId);
if (plate is null)
{
throw new UserFriendlyException(PlateConst.No_Exist);
}
//如果开启了禁用创建主题
if (plate.IsDisableCreateDiscuss == true)
{
if (!CurrentUser.GetPermissions().Contains("") && CurrentUser.UserName != UserConst.Admin)
{
throw new UserFriendlyException("该板块已禁止创建主题,请在其他板块中发布");
}
}
var entity = await _forumManager.CreateDiscussAsync(await MapToEntityAsync(input));
return await MapToGetOutputDtoAsync(entity);
}
/// <summary>
/// 效验主题查询权限
/// </summary>

View File

@@ -29,6 +29,7 @@ namespace Yi.Framework.Bbs.Application.Services
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.Name), x => x.Name.Contains(input.Name!))
.WhereIF(!string.IsNullOrEmpty(input.Code), x => x.Name.Contains(input.Code!))
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
.OrderByDescending(x=>x.OrderNum)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<PlateGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
}

View File

@@ -39,6 +39,8 @@ namespace Yi.Framework.Bbs.Domain.Entities
//是否置顶默认false
public bool IsTop { get; set; }
public int OrderNum { get; set; } = 0;
public DiscussPermissionTypeEnum PermissionType { get; set; }
@@ -57,5 +59,10 @@ namespace Yi.Framework.Bbs.Domain.Entities
/// </summary>
[SugarColumn(IsJson = true)]//使用json处理
public List<Guid>? PermissionUserIds { get; set; }
/// <summary>
/// 是否禁止评论创建功能
/// </summary>
public bool IsDisableCreateComment { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
namespace Yi.Framework.Bbs.Domain.Entities
{
/// <summary>
/// 首页置顶主题
/// </summary>
[SugarTable("DiscussTop")]
public class DiscussTopEntity : Entity<Guid>, IHasModificationTime
{
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public int OrderNum { get; set; }
public Guid DiscussId { get; set; }
[Navigate(NavigateType.OneToOne, nameof(DiscussId))]
public DiscussEntity Discuss { get; set; }
public DateTime? LastModificationTime { get; set; }
}
}

View File

@@ -6,7 +6,7 @@ using Volo.Abp.Auditing;
namespace Yi.Framework.Bbs.Domain.Entities
{
[SugarTable("Plate")]
public class PlateEntity : Entity<Guid>, ISoftDelete,IAuditedObject
public class PlateEntity : Entity<Guid>, ISoftDelete, IAuditedObject
{
[SugarColumn(ColumnName = "Id", IsPrimaryKey = true)]
@@ -27,5 +27,12 @@ namespace Yi.Framework.Bbs.Domain.Entities
public Guid? LastModifierId { get; set; }
public DateTime? LastModificationTime { get; set; }
public int OrderNum { get; set; }
/// <summary>
/// 是否禁用创建主题,禁用后,只有管理员或者权限者能够发送
/// </summary>
public bool IsDisableCreateDiscuss { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Users;
using Yi.Framework.Rbac.Domain.Shared.Consts;
namespace Yi.Framework.Bbs.Domain.Extensions
{
public static class CurrestUserExtensions
{
/// <summary>
/// 获取用户权限codes
/// </summary>
/// <param name="currentUser"></param>
/// <returns></returns>
public static List<string> GetPermissions(this ICurrentUser currentUser)
{
return currentUser.FindClaims(TokenTypeConst.Permission).Select(x => x.Value).ToList();
}
}
}

View File

@@ -41,6 +41,8 @@ namespace Yi.Framework.Rbac.Application.Services
private IDistributedCache<CaptchaPhoneCacheItem, CaptchaPhoneCacheKey> _phoneCache;
private readonly ICaptcha _captcha;
private readonly IGuidGenerator _guidGenerator;
private readonly RbacOptions _rbacOptions;
private readonly IAliyunManger _aliyunManger;
public AccountService(IUserRepository userRepository,
ICurrentUser currentUser,
AccountManager accountManager,
@@ -50,7 +52,11 @@ namespace Yi.Framework.Rbac.Application.Services
IOptions<JwtOptions> jwtOptions,
IDistributedCache<CaptchaPhoneCacheItem, CaptchaPhoneCacheKey> phoneCache,
ICaptcha captcha,
IGuidGenerator guidGenerator)
IGuidGenerator guidGenerator,
IOptions<RbacOptions> options,
IAliyunManger aliyunManger,
ISqlSugarRepository<RoleEntity> roleRepository,
UserManager userManager)
{
_userRepository = userRepository;
_currentUser = currentUser;
@@ -62,6 +68,10 @@ namespace Yi.Framework.Rbac.Application.Services
_phoneCache = phoneCache;
_captcha = captcha;
_guidGenerator = guidGenerator;
_rbacOptions = options.Value;
_aliyunManger = aliyunManger;
_roleRepository = roleRepository;
_userManager = userManager;
}
@@ -79,27 +89,17 @@ namespace Yi.Framework.Rbac.Application.Services
/// </summary>
private void ValidationImageCaptcha(LoginInputVo input)
{
//登录不想要验证码 ,可不效验
if (!_captcha.Validate(input.Uuid, input.Code))
if (_rbacOptions.EnableCaptcha)
{
throw new UserFriendlyException("验证码错误");
//登录不想要验证码 ,可不效验
if (!_captcha.Validate(input.Uuid, input.Code))
{
throw new UserFriendlyException("验证码错误");
}
}
}
/// <summary>
/// 效验电话验证码,需要与电话号码绑定
/// </summary>
private void ValidationPhoneCaptcha(RegisterDto input)
{
//var value = _memoryCache.Get<string>($"Yi:Phone:{input.Phone}");
//if (value is not null && value.Equals($"{input.Code}"))
//{
// //成功,需要清空
// _memoryCache.Remove($"Yi:Phone:{input.Phone}");
// return;
//}
//throw new UserFriendlyException("验证码错误");
}
/// <summary>
/// 登录
@@ -114,7 +114,7 @@ namespace Yi.Framework.Rbac.Application.Services
}
//效验验证码
// ValidationImageCaptcha(input);
ValidationImageCaptcha(input);
UserEntity user = new();
//登录成功
@@ -123,6 +123,12 @@ namespace Yi.Framework.Rbac.Application.Services
//获取用户信息
var userInfo = await _userRepository.GetUserAllInfoAsync(user.Id);
//判断用户状态
if (userInfo.User.State == false)
{
throw new UserFriendlyException(UserConst.State_Is_State);
}
if (userInfo.RoleCodes.Count == 0)
{
throw new UserFriendlyException(UserConst.No_Role);
@@ -216,19 +222,9 @@ namespace Yi.Framework.Rbac.Application.Services
//生成一个4位数的验证码
//发送短信同时生成uuid
////key 电话号码 value:验证码+uuid
//var code = _securityCode.GetRandomEnDigitalText(4);
var code = Guid.NewGuid().ToString().Substring(0, 4);
var uuid = Guid.NewGuid();
//未开启短信验证默认8888
//if (_smsAliyunManagerOptions.Value.EnableFeature)
//{
// await _smsAliyunManager.Send(input.Phone, code);
//}
//else
//{
var code = "8888";
//}
//_memoryCache.Set($"Yi:Phone:{input.Phone}", $"{code}", new TimeSpan(0, 10, 0));
await _aliyunManger.SendSmsAsync(input.Phone, code);
await _phoneCache.SetAsync(new CaptchaPhoneCacheKey(input.Phone), new CaptchaPhoneCacheItem(code), new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(10) });
return new
@@ -237,6 +233,22 @@ namespace Yi.Framework.Rbac.Application.Services
};
}
/// <summary>
/// 效验电话验证码,需要与电话号码绑定
/// </summary>
private async Task ValidationPhoneCaptchaAsync(RegisterDto input)
{
var item = await _phoneCache.GetAsync(new CaptchaPhoneCacheKey(input.Phone.ToString()));
if (item is not null && item.Code.Equals($"{input.Code}"))
{
//成功,需要清空
await _phoneCache.RemoveAsync(new CaptchaPhoneCacheKey(input.Phone.ToString()));
return;
}
throw new UserFriendlyException("验证码错误");
}
/// <summary>
/// 注册,需要验证码通过
/// </summary>
@@ -246,6 +258,11 @@ namespace Yi.Framework.Rbac.Application.Services
[UnitOfWork]
public async Task<object> PostRegisterAsync(RegisterDto input)
{
if (_rbacOptions.EnableRegister == false)
{
throw new UserFriendlyException("该系统暂未开放注册功能");
}
if (input.UserName == UserConst.Admin)
{
throw new UserFriendlyException("用户名无效注册!");
@@ -260,15 +277,13 @@ namespace Yi.Framework.Rbac.Application.Services
throw new UserFriendlyException("密码需大于等于6位");
}
//效验验证码,根据电话号码获取 value比对验证码已经uuid
ValidationPhoneCaptcha(input);
await ValidationPhoneCaptchaAsync(input);
//输入的用户名与电话号码都不能在数据库中存在
UserEntity user = new();
var isExist = await _userRepository.IsAnyAsync(x =>
x.UserName == input.UserName
|| x.Phone == input.Phone);
var isExist = await _userRepository.IsAnyAsync(x =>x.UserName == input.UserName|| x.Phone == input.Phone);
if (isExist)
{
throw new UserFriendlyException("用户已存在,注册失败");
@@ -278,8 +293,7 @@ namespace Yi.Framework.Rbac.Application.Services
var entity = await _userRepository.InsertReturnEntityAsync(newUser);
//赋上一个初始角色
var roleRepository = _roleRepository;
var role = await roleRepository.GetFirstAsync(x => x.RoleCode == UserConst.GuestRoleCode);
var role = await _roleRepository.GetFirstAsync(x => x.RoleCode == UserConst.GuestRoleCode);
if (role is not null)
{
await _userManager.GiveUserSetRoleAsync(new List<Guid> { entity.Id }, new List<Guid> { role.Id });

View File

@@ -11,7 +11,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Lazy.Captcha.Core" Version="2.0.7" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.6" />
<PackageReference Include="Volo.Abp.BackgroundWorkers.Quartz" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.BackgroundWorkers.Quartz" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -16,6 +16,7 @@ namespace Yi.Framework.Rbac.Domain.Shared.Consts
public const string Login_User_No_Exist = "登录失败!用户名不存在!";
public const string Login_Passworld_Error = "密码为空,添加失败!";
public const string User_Exist = "用户已经存在,添加失败!";
public const string State_Is_State = "该用户已被禁用,请联系管理员进行恢复";
public const string No_Permission = "登录禁用!该用户分配无任何权限,无意义登录!";
public const string No_Role = "登录禁用!该用户分配无任何角色,无意义登录!";

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Rbac.Domain.Shared.Options
{
public class AliyunOptions
{
public string AccessKeyId { get; set; }
public string AccessKeySecret { get; set; }
public AliyunSms Sms { get; set; }
}
public class AliyunSms
{
public string SignName { get; set; }
public string TemplateCode { get; set; }
}
}

View File

@@ -12,5 +12,15 @@ namespace Yi.Framework.Rbac.Domain.Shared.Options
/// 超级管理员默认密码
/// </summary>
public string AdminPassword { get; set; } = "123456";
/// <summary>
/// 是否开启登录验证码
/// </summary>
public bool EnableCaptcha { get; set; } = false;
/// <summary>
/// 是否开启用户注册功能
/// </summary>
public bool EnableRegister { get; set; } = false;
}
}

View File

@@ -7,7 +7,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,69 @@
using AlibabaCloud.SDK.Dysmsapi20170525;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Domain.Services;
using Yi.Framework.Rbac.Domain.Shared.Options;
namespace Yi.Framework.Rbac.Domain.Managers
{
public class AliyunManger : DomainService, IAliyunManger
{
private ILogger<AliyunManger> _logger;
private AliyunOptions Options { get; set; }
public AliyunManger(ILogger<AliyunManger> logger, IOptions<AliyunOptions> options)
{
Options = options.Value;
_logger = logger;
}
private Client CreateClient()
{
AlibabaCloud.OpenApiClient.Models.Config config = new AlibabaCloud.OpenApiClient.Models.Config
{
// 必填,您的 AccessKey ID
AccessKeyId = Options.AccessKeyId,
// 必填,您的 AccessKey Secret
AccessKeySecret = Options.AccessKeySecret,
};
// 访问的域名
config.Endpoint = "dysmsapi.aliyuncs.com";
return new Client(config);
}
/// <summary>
/// 发送短信
/// </summary>
/// <param name="phoneNumbers"></param>
/// <param name="code"></param>
/// <returns></returns>
public async Task SendSmsAsync(string phoneNumbers, string code)
{
try
{
var _aliyunClient = CreateClient();
AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest sendSmsRequest = new AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest
{
PhoneNumbers = phoneNumbers,
SignName = Options.Sms.SignName,
TemplateCode = Options.Sms.TemplateCode,
TemplateParam = System.Text.Json.JsonSerializer.Serialize(new { code })
};
var response = await _aliyunClient.SendSmsAsync(sendSmsRequest);
}
catch (Exception _error)
{
_logger.LogError(_error, "阿里云短信发送错误:" + _error.Message);
}
}
}
public interface IAliyunManger
{
Task SendSmsAsync(string phoneNumbers, string code);
}
}

View File

@@ -10,13 +10,13 @@ namespace Yi.Framework.Rbac.Domain.SignalRHubs
{
[HubRoute("/hub/main")]
[Authorize]
public class OnlineUserHub : AbpHub
public class OnlineUserHub : AbpHub
{
public static readonly List<OnlineUserModel> clientUsers = new();
private HttpContext? _httpContext;
private ILogger<OnlineUserHub> _logger=> LoggerFactory.CreateLogger<OnlineUserHub>();
private ILogger<OnlineUserHub> _logger => LoggerFactory.CreateLogger<OnlineUserHub>();
public OnlineUserHub(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor?.HttpContext;
@@ -68,10 +68,15 @@ namespace Yi.Framework.Rbac.Domain.SignalRHubs
//判断用户是否存在,否则添加集合
if (user != null)
{
clientUsers.Remove(user);
Clients.All.SendAsync("onlineNum", clientUsers.Count);
//Clients.All.SendAsync(HubsConstant.OnlineUser, clientUsers);
_logger.LogInformation($"用户{user?.UserName}离开了,当前已连接{clientUsers.Count}个");
var clientUser = clientUsers.FirstOrDefault(x => x.ConnnectionId == user.ConnnectionId);
if (clientUser is not null)
{
clientUsers.Remove(clientUser);
Clients.All.SendAsync("onlineNum", clientUsers.Count);
//Clients.All.SendAsync(HubsConstant.OnlineUser, clientUsers);
_logger.LogInformation($"用户{user?.UserName}离开了,当前已连接{clientUsers.Count}个");
}
}
return base.OnDisconnectedAsync(exception);
}

View File

@@ -1,19 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.props" />
<ItemGroup>
<PackageReference Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="2.0.24" />
<PackageReference Include="IPTools.China" Version="1.6.0" />
<PackageReference Include="TencentCloudSDK" Version="3.0.917" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Volo.Abp.AspNetCore.SignalR" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.AspNetCore.SignalR" Version="8.0.0" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Caching" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0" />
<PackageReference Include="Volo.Abp.Caching" Version="8.0.0" />
</ItemGroup>

View File

@@ -7,6 +7,7 @@ using Yi.Framework.Mapster;
using Yi.Framework.Rbac.Domain.Authorization;
using Yi.Framework.Rbac.Domain.Operlog;
using Yi.Framework.Rbac.Domain.Shared;
using Yi.Framework.Rbac.Domain.Shared.Options;
namespace Yi.Framework.Rbac.Domain
{
@@ -22,11 +23,15 @@ namespace Yi.Framework.Rbac.Domain
public override void ConfigureServices(ServiceConfigurationContext context)
{
var service = context.Services;
var configuration = context.Services.GetConfiguration();
service.AddControllers(options =>
{
options.Filters.Add<PermissionGlobalAttribute>();
options.Filters.Add<OperLogGlobalAttribute>();
});
//配置阿里云短信
Configure<AliyunOptions>(configuration.GetSection(nameof(AliyunOptions)));
}
}
}

View File

@@ -2,7 +2,7 @@
<Import Project="..\..\common.props" />
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Caching" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="8.0.0" />
<PackageReference Include="Volo.Abp.Caching" Version="8.0.0" />
</ItemGroup>

View File

@@ -8,10 +8,10 @@
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.Autofac" Version="8.0.0-rc.3" />
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="8.0.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Serilog" Version="8.0.0" />
<PackageReference Include="Volo.Abp.Autofac" Version="8.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -41,6 +41,12 @@
//Rbac模块
"RbacOptions": {
//超级管理员种子数据默认密码
"AdminPassword": "123456"
"AdminPassword": "123456",
//是否开启验证码验证
"EnableCaptcha": true,
//是否开启注册功能
"EnableRegister": false
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -63,3 +63,13 @@ export function getCodePhone(data) {
data,
});
}
/**
* 获取登录验证码
*/
export function getLoginCode() {
return request({
url: `/account/captcha-image`,
method: "get",
});
}

View File

@@ -46,3 +46,10 @@ export function del(ids) {
method: "delete",
});
}
export function getHomeDiscuss() {
return request({
url: `/discuss/top`,
method: "get",
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@@ -1,49 +1,46 @@
<template>
<el-row>
<el-col
>
<el-card :body-style="{ padding: '0px' }" shadow="never">
<img
src=""
class="image"
/>
<div style="padding: 14px">
<span>{{props.name}}</span>
<div class="bottom">
<time class="remarks">{{ props.introduction }}</time>
<RouterLink :to="`/discuss/${props.id}`"> <el-button text class="button" type="primary">进入<el-icon><CaretRight /></el-icon></el-button> </RouterLink>
</div>
<el-row>
<el-col>
<el-card :body-style="{ padding: '0px' }" shadow="never">
<img src="" class="image" />
<div style="padding: 14px">
<span>{{ props.name }}</span>
<div class="bottom">
<time class="remarks">{{ props.introduction }}</time>
<RouterLink :to="`/discuss/${props.id}/${props.isPublish}`">
<el-button text class="button" type="primary"
>进入<el-icon><CaretRight /></el-icon
></el-button>
</RouterLink>
</div>
</el-card>
</el-col>
</el-row>
</template>
<script setup>
import { onMounted } from 'vue';
</div>
</el-card>
</el-col>
</el-row>
</template>
const props = defineProps(['name','introduction','id'])
<script setup>
import { onMounted } from "vue";
</script>
<style>
.remarks {
font-size: 12px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
const props = defineProps(["name", "introduction", "id", "isPublish"]);
</script>
.image {
width: 100%;
display: block;
}
</style>
<style>
.remarks {
font-size: 12px;
color: #999;
}
.bottom {
margin-top: 13px;
line-height: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.image {
width: 100%;
display: block;
}
</style>

View File

@@ -2,7 +2,12 @@ import { ElMessage, ElMessageBox } from "element-plus";
import useUserStore from "@/stores/user";
import router from "@/router";
import { Session, Local } from "@/utils/storage";
import { userLogin, getUserDetailInfo, userLogout } from "@/apis/auth";
import {
userLogin,
getUserDetailInfo,
userLogout,
userRegister,
} from "@/apis/auth";
const TokenKey = "AccessToken";
export const AUTH_MENUS = "AUTH_MENUS";
@@ -112,7 +117,7 @@ export default function useAuths(opt) {
setToken(token);
try {
// 存储用户信息
await getUserInfo(); // 用户信息
await useUserStore().getInfo(); // 用户信息
// 登录成功后 路由跳转
// 如果有记录当前跳转页面
const currentPath = Session.get("currentPath");
@@ -130,6 +135,19 @@ export default function useAuths(opt) {
}
};
// 注册
const registerFun = async (params) => {
try {
await userRegister(params);
ElMessage({
message: `恭喜!${params.userName},注册成功!请登录!`,
type: "success",
});
} catch (error) {
console.log(error);
}
};
return {
getToken,
setToken,
@@ -138,5 +156,6 @@ export default function useAuths(opt) {
getUserInfo,
logoutFun,
clearStorage,
registerFun,
};
}

View File

@@ -1,24 +1,20 @@
<template class="back-color">
<div class="content-main">
<RouterView />
</div>
<template class="back-color">
<div class="content-main">
<RouterView />
</div>
</template>
<style scoped>
.body-main{
min-height: 10rem;
min-width: 10rem;
background-color:#F0F2F5;
.body-main {
min-height: 10rem;
min-width: 10rem;
background-color: #f0f2f5;
}
.content-main{
margin: 0 auto;
display: flex;
justify-content: center;
min-height: 1150px;
.content-main {
margin: 0 auto;
display: flex;
justify-content: center;
width: 1300px;
min-height: 1150px;
}
</style>
</style>

View File

@@ -123,7 +123,7 @@ const isLogin = getToken("AccessToken") ? true : false;
<style scoped lang="scss">
.header {
padding: 0 100px;
width: 1200px;
width: 1300px;
display: flex;
align-items: center;
justify-content: space-between;

View File

@@ -1,8 +1,19 @@
<template>
<div class="box">
<div class="content">
<RouterView/>
</div>
<div class="box">
<div class="content">
<RouterView />
</div>
</template>
<style src="@/assets/styles/login.scss" scoped></style>
</div>
</template>
<!-- <style src="@/assets/styles/login.scss" scoped></style> -->
<style scoped lang="scss">
.box {
width: 100vw;
height: 100vh;
.content {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -23,7 +23,8 @@ const router = createRouter({
{
name: "login",
path: "/login",
component: () => import("../views/Login.vue"),
// component: () => import("../views/Login.vue"),
component: () => import("../views/login/index.vue"),
},
{
name: "register",
@@ -53,7 +54,7 @@ const router = createRouter({
},
{
name: "discuss",
path: "/discuss/:plateId?",
path: "/discuss/:plateId?/:isPublish?",
component: () => import("../views/Discuss.vue"),
meta: {
title: "板块",

View File

@@ -14,6 +14,7 @@ const useUserStore = defineStore("user", {
icon: null,
roles: [],
permissions: [],
hasPermissions: false,
}),
getters: {},
actions: {
@@ -42,18 +43,22 @@ const useUserStore = defineStore("user", {
getUserDetailInfo()
.then((response) => {
const res = response.data;
const user = res.user;
console.log(user, "user");
const avatar =
user.icon == "" || user.icon == null
? "/favicon.ico"
: import.meta.env.VITE_APP_BASEAPI + "/file/" + user.icon;
console.log(avatar, "avatar");
const all_permission = "*:*:*";
if (res.roleCodes && res.roleCodes.length > 0) {
// 验证返回的roles是否是一个非空数组
this.roles = res.roleCodes;
this.permissions = res.permissionCodes;
this.hasPermissions = res.permissionCodes.some((permission) => {
return (
all_permission === permission ||
permissionFlag.includes(permission)
);
});
// this.roles = ["admin"];
// this.permissions=["*:*:*"]
} else {
@@ -116,5 +121,9 @@ const useUserStore = defineStore("user", {
this.id = "";
},
},
persist: {
key: "userInfo",
storage: window.sessionStorage,
},
});
export default useUserStore;

View File

@@ -1,5 +1,5 @@
<template>
<div style="width: 90%; min-width: 1200px">
<div class="article-box">
<!-- <div style="width: 1200px;"> -->
<el-row :gutter="20" class="top-div">
<el-col :span="5">
@@ -383,88 +383,92 @@ watch(
);
</script>
<style scoped>
.comment {
min-height: 40rem;
}
.art-info-left .el-col {
margin-bottom: 1rem;
}
.art-info-ul span {
margin-left: 1rem;
}
.art-info-ul .el-tag {
margin-left: 1rem;
}
.art-info-ul {
padding: 0;
margin: 0;
}
li {
list-style: none;
margin-bottom: 0.5rem;
}
.art-info-right {
.article-box {
width: 100%;
height: 100%;
}
.comment {
min-height: 40rem;
}
.left-div .el-col {
background-color: #ffffff;
min-height: 12rem;
margin-bottom: 1rem;
}
.art-info-left .el-col {
margin-bottom: 1rem;
}
.right-div .el-col {
background-color: #ffffff;
min-height: 10rem;
margin-bottom: 1rem;
}
.art-info-ul span {
margin-left: 1rem;
}
.left-top-div .el-col {
min-height: 2rem;
background-color: #fafafa;
margin-bottom: 1rem;
margin-left: 10px;
}
.art-info-ul .el-tag {
margin-left: 1rem;
}
.top-div {
padding-top: 1rem;
}
.art-info-ul {
padding: 0;
margin: 0;
}
.left-top-div {
font-size: small;
text-align: center;
line-height: 2rem;
}
li {
list-style: none;
margin-bottom: 0.5rem;
}
h2 {
margin-bottom: 0.5em;
color: rgba(0, 0, 0, 0.85);
font-weight: 600;
font-size: 30px;
line-height: 1.35;
}
.art-info-right {
height: 100%;
}
.left-div .el-col {
padding: 1.4rem 1.4rem 0.5rem 1.4rem;
}
.left-div .el-col {
background-color: #ffffff;
min-height: 12rem;
margin-bottom: 1rem;
}
.el-space {
display: flex;
vertical-align: top;
justify-content: space-evenly;
}
.right-div .el-col {
background-color: #ffffff;
min-height: 10rem;
margin-bottom: 1rem;
}
.tab-divider {
margin-bottom: 0.5rem;
}
.left-top-div .el-col {
min-height: 2rem;
background-color: #fafafa;
margin-bottom: 1rem;
margin-left: 10px;
}
.breadcrumb {
margin-bottom: 10px;
.top-div {
padding-top: 1rem;
}
.left-top-div {
font-size: small;
text-align: center;
line-height: 2rem;
}
h2 {
margin-bottom: 0.5em;
color: rgba(0, 0, 0, 0.85);
font-weight: 600;
font-size: 30px;
line-height: 1.35;
}
.left-div .el-col {
padding: 1.4rem 1.4rem 0.5rem 1.4rem;
}
.el-space {
display: flex;
vertical-align: top;
justify-content: space-evenly;
}
.tab-divider {
margin-bottom: 0.5rem;
}
.breadcrumb {
margin-bottom: 10px;
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div style="width: 1200px" class="body-div">
<div class="discuss-box">
<div class="header">
<el-form :inline="true">
<el-form-item label="标题:">
@@ -23,8 +23,8 @@
<el-button
@click="enterEditArticle"
type="primary"
v-hasPer="['bbs:discuss:add']"
>分享</el-button
:class="[!isEditArticle ? 'el-button--disabled' : '']"
>发布主题</el-button
>
<el-dropdown>
<span class="el-dropdown-link">
@@ -102,9 +102,10 @@
<script setup>
import DisscussCard from "@/components/DisscussCard.vue";
import { getList, getTopList } from "@/apis/discussApi.js";
import { ref, reactive, watch } from "vue";
import { ref, reactive, watch, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import BottomInfo from "@/components/BottomInfo.vue";
import useUserStore from "@/stores/user";
//数据定义
const route = useRoute();
@@ -158,23 +159,31 @@ const loadDiscussList = async () => {
};
//进入添加主题页面
const isEditArticle = computed(
() =>
useUserStore().hasPermissions &&
!(route.params.isPublish === "false" ? false : true)
);
const enterEditArticle = () => {
//跳转路由
var routerPer = {
path: "/editArt",
query: {
operType: "create",
artType: "discuss",
plateId: route.params.plateId,
},
};
router.push(routerPer);
if (isEditArticle.value) {
//跳转路由
var routerPer = {
path: "/editArt",
query: {
operType: "create",
artType: "discuss",
plateId: route.params.plateId,
},
};
router.push(routerPer);
} else {
ElMessage.warning("暂无发布权限!");
}
};
watch(
() => route.query.q,
async (val) => {
console.log(val);
if (val) {
query.title = val ?? "";
}
@@ -183,65 +192,74 @@ watch(
{ immediate: true }
);
</script>
<style scoped>
.el-pagination {
margin: 2rem 0rem 2rem 0rem;
justify-content: right;
}
.body-div {
<style scoped lang="scss">
.discuss-box {
width: 100%;
height: 100%;
.el-pagination {
margin: 2rem 0rem 2rem 0rem;
justify-content: right;
}
/* .body-div {
min-height: 1000px;
}
.el-dropdown-link {
cursor: pointer;
color: var(--el-color-primary);
display: flex;
align-items: center;
}
.header {
background-color: #ffffff;
padding: 1rem;
margin: 1rem 0rem;
}
.collapse-top {
padding-left: 2rem;
}
.header .el-input {
}
.el-tabs {
background-color: #ffffff;
padding-left: 2rem;
}
.el-tabs >>> .el-tabs__header {
margin-bottom: 0;
}
.div-item {
margin-bottom: 1rem;
}
} */
.el-dropdown-link {
cursor: pointer;
color: var(--el-color-primary);
display: flex;
align-items: center;
}
.header {
background-color: #ffffff;
padding: 1rem;
margin: 1rem 0rem;
}
.collapse-top {
padding-left: 2rem;
}
.header .el-input {
}
.el-tabs {
background-color: #ffffff;
padding-left: 2rem;
}
.el-tabs >>> .el-tabs__header {
margin-bottom: 0;
}
.div-item {
margin-bottom: 1rem;
}
.el-form {
--el-form-label-font-size: var(--el-font-size-base);
display: flex;
align-items: center;
}
.el-form-item {
padding-top: 0.8rem;
}
.form-right {
align-items: center;
display: flex;
margin-left: auto;
}
.form-right .el-button {
margin-right: 0.6rem;
}
.header .el-input {
width: 20rem;
}
.collapse-list >>> .el-collapse-item__header {
border-bottom-color: #f0f2f5 !important;
}
.el-form {
--el-form-label-font-size: var(--el-font-size-base);
display: flex;
align-items: center;
}
.el-form-item {
padding-top: 0.8rem;
}
.form-right {
align-items: center;
display: flex;
margin-left: auto;
}
.form-right .el-button {
margin-right: 0.6rem;
}
.header .el-input {
width: 20rem;
}
.collapse-list >>> .el-collapse-item__header {
border-bottom-color: #f0f2f5 !important;
}
.el-divider {
margin: 0.5rem 0;
.el-divider {
margin: 0.5rem 0;
}
}
/* 禁用状态下的样式 */
.el-button.el-button--disabled {
opacity: 0.6;
pointer-events: auto;
}
</style>

View File

@@ -44,12 +44,6 @@
</div>
</div>
</div>
<!-- <h2> 登录-欢迎</h2>
<el-input v-model="loginForm.userName" placeholder="用户名" />
<el-input v-model="loginForm.password" placeholder="密码" show-password />
<el-button class="login-btn" type="primary" @click="login">登录</el-button>
<br>
<el-button class="login-btn" type="primary" @click="guestlogin">游客临时登录</el-button> -->
</template>
<script setup>
import { ref, reactive } from "vue";

View File

@@ -1,5 +1,5 @@
<template>
<div style="width: 1200px">
<div class="home-box">
<el-row :gutter="20" class="top-div">
<el-col :span="17">
<div class="scrollbar">
@@ -19,6 +19,7 @@
:name="i.name"
:introduction="i.introduction"
:id="i.id"
:isPublish="i.isDisableCreateDiscuss"
/>
</el-col>
@@ -111,7 +112,7 @@ import VisitsLineChart from "./components/VisitsLineChart.vue";
import { access } from "@/apis/accessApi.js";
import { getList } from "@/apis/plateApi.js";
import { getList as bannerGetList } from "@/apis/bannerApi.js";
import { getList as discussGetList } from "@/apis/discussApi.js";
import { getHomeDiscuss } from "@/apis/discussApi.js";
import { getWeek } from "@/apis/accessApi.js";
var plateList = ref([]);
@@ -132,8 +133,8 @@ onMounted(async () => {
access();
const { data: plateData } = await getList();
plateList.value = plateData.items;
const { data: discussData } = await discussGetList(query);
discussList.value = discussData.items;
const { data: discussData } = await getHomeDiscuss();
discussList.value = discussData;
const { data: bannerData } = await bannerGetList();
bannerList.value = bannerData.items;
const { data: weekData } = await getWeek();
@@ -157,48 +158,52 @@ const statisOptions = computed(() => {
};
});
</script>
<style scoped>
.introduce {
color: rgba(0, 0, 0, 0.45);
font-size: small;
}
.plate {
background: transparent !important;
}
.left-div .el-col {
background-color: #ffffff;
margin-bottom: 1rem;
}
.right-div .el-col {
background-color: #ffffff;
margin-bottom: 1rem;
}
.carousel-font {
position: absolute;
z-index: 1;
top: 10%;
left: 10%;
}
.top-div {
padding-top: 0.5rem;
}
.scrollbar {
display: block;
margin-bottom: 0.5rem;
}
.VisitsLineChart >>> .el-card__body {
padding: 0.5rem;
}
.statisChart {
<style scoped lang="scss">
.home-box {
width: 100%;
height: 300px;
height: 100%;
.introduce {
color: rgba(0, 0, 0, 0.45);
font-size: small;
}
.plate {
background: transparent !important;
}
.left-div .el-col {
background-color: #ffffff;
margin-bottom: 1rem;
}
.right-div .el-col {
background-color: #ffffff;
margin-bottom: 1rem;
}
.carousel-font {
position: absolute;
z-index: 1;
top: 10%;
left: 10%;
}
.top-div {
padding-top: 0.5rem;
}
.scrollbar {
display: block;
margin-bottom: 0.5rem;
}
.VisitsLineChart >>> .el-card__body {
padding: 0.5rem;
}
.statisChart {
width: 100%;
height: 300px;
}
}
</style>

View File

@@ -0,0 +1,438 @@
<template>
<div class="login">
<div class="login-box">
<div class="left"></div>
<div class="right">
<div class="header-box">
<div class="text" @click="guestlogin" v-if="isRegister">返回首页</div>
<div class="text" @click="handleSignInNow" v-else>
已有账号立即登录
</div>
<el-icon size="15"><DArrowRight /></el-icon>
</div>
<div class="top">
<div class="title">意社区登录 | SIGN IN</div>
</div>
<div class="center">
<div class="login-form">
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="rules"
v-if="isRegister"
>
<el-form-item label="账号" class="title-item"></el-form-item>
<el-form-item prop="userName">
<el-input
size="large"
type="text"
v-model="loginForm.userName"
placeholder="请输入用户名"
/>
</el-form-item>
<el-form-item label="密码" class="title-item"></el-form-item>
<el-form-item prop="password">
<el-input
size="large"
type="password"
v-model="loginForm.password"
placeholder="请输入密码"
show-password
/>
</el-form-item>
<el-form-item label="验证码" class="title-item"></el-form-item>
<div class="flex-between">
<el-col :span="18">
<el-form-item prop="phone">
<el-input
size="large"
type="text"
v-model.trim="loginForm.code"
placeholder="请输入验证码"
/>
</el-form-item>
</el-col>
<el-image
@click="handleGetCode"
style="width: 120px; height: 40px; cursor: pointer"
:src="codeImageURL"
:fit="fit"
/>
</div>
</el-form>
<el-form
class="registerForm"
ref="registerFormRef"
:model="registerForm"
:rules="registerRules"
v-else
>
<el-form-item label="账号" class="title-item"></el-form-item>
<el-form-item prop="userName">
<el-input
size="large"
type="text"
v-model.trim="registerForm.userName"
placeholder="请输入用户名"
/>
</el-form-item>
<el-form-item label="手机号" class="title-item"></el-form-item>
<div class="flex-between">
<el-col :span="18">
<el-form-item prop="phone">
<el-input
size="large"
type="text"
v-model.trim="registerForm.phone"
placeholder="请输入手机号"
/>
</el-form-item>
</el-col>
<el-button
type="primary"
size="large"
@click="captcha"
:disabled="isDisabledCode"
>
{{ codeInfo }}
</el-button>
</div>
<el-form-item label="验证码" class="title-item"></el-form-item>
<el-form-item prop="code">
<el-input
size="large"
type="text"
v-model.trim="registerForm.code"
placeholder="请输入验证码"
/>
</el-form-item>
<el-form-item label="新密码" class="title-item"></el-form-item>
<el-form-item prop="password">
<el-input
size="large"
type="password"
v-model.trim="registerForm.password"
placeholder="请输入新密码"
/>
</el-form-item>
<el-form-item label="确认密码" class="title-item"></el-form-item>
<el-form-item>
<el-input
size="large"
type="password"
v-model.trim="passwordConfirm"
placeholder="请确认密码"
show-password
/>
</el-form-item>
</el-form>
<div class="link" v-if="isRegister">
<div class="text" @click="handleRegister">没有账号前往注册</div>
</div>
<div
class="login-btn"
@click="login(loginFormRef)"
v-if="isRegister"
>
</div>
<div class="login-btn" @click="register(registerFormRef)" v-else>
</div>
</div>
</div>
<div class="bottom" v-if="isRegister">
<div class="title">
<div>或者</div>
<div>其他方式登录</div>
</div>
<div class="icon-list">
<div class="icon">
<img src="@/assets/login_images/QQ.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/login_images/WeChat.png" alt="" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
import useAuths from "@/hooks/useAuths";
import { getCodePhone } from "@/apis/accountApi";
import { getLoginCode } from "@/apis/auth";
const { loginFun, registerFun } = useAuths();
const router = useRouter();
const route = useRoute();
const loginFormRef = ref();
const rules = reactive({
userName: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
});
const loginForm = reactive({
userName: "",
password: "",
uuid: "",
code: "",
});
const guestlogin = async () => {
const redirect = route.query?.redirect ?? "/index";
router.push(redirect);
};
const login = async (formEl) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (valid) {
try {
loginFun(loginForm);
} catch (error) {
ElMessage({
message: error.message,
type: "error",
duration: 2000,
});
}
}
});
};
// 获取图片验证码
const codeImageURL = ref("");
const handleGetCode = () => {
getImageCode();
};
// 注册逻辑
const isRegister = ref(true);
const registerFormRef = ref();
// 确认密码
const passwordConfirm = ref("");
const registerForm = reactive({
userName: "",
phone: "",
password: "",
uuid: "",
code: "",
});
const registerRules = reactive({
userName: [{ required: true, message: "请输入账号", trigger: "blur" }],
phone: [{ required: true, message: "请输入手机号", trigger: "blur" }],
code: [{ required: true, message: "请输入验证码", trigger: "blur" }],
password: [{ required: true, message: "请输入薪密码", trigger: "blur" }],
});
// const handleRegister = () => {
// isRegister.value = !isRegister.value;
// };
const register = async (formEl) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (valid) {
try {
if (registerForm.password != passwordConfirm.value) {
ElMessage.error("两次密码输入不一致");
return;
}
registerFun(registerForm);
} catch (error) {
ElMessage({
message: error.message,
type: "error",
duration: 2000,
});
}
}
});
};
//验证码
const codeInfo = ref("发送验证码");
const isDisabledCode = ref(false);
const captcha = async () => {
if (registerForm.phone !== "") {
const { data } = await getCodePhone(registerForm.phone);
registerForm.uuid = data.uuid;
ElMessage({
message: `已向${registerForm.phone}发送验证码,请注意查收`,
type: "success",
});
isDisabledCode.value = true;
let time = 60; //定义剩下的秒数
let timer = setInterval(function () {
if (time == 0) {
//清除定时器和复原按钮
clearInterval(timer);
codeInfo.value = "发送验证码";
time = 60; //这个10是重新开始
} else {
codeInfo.value = "剩余" + time + "秒";
time--;
}
}, 1000);
} else {
ElMessage({
message: `清先输入手机号`,
type: "warning",
});
}
};
// 立即登录
const handleSignInNow = () => {
isRegister.value = !isRegister.value;
};
const getImageCode = async () => {
const { data } = await getLoginCode();
codeImageURL.value = "data:image/jpg;base64," + data.img;
loginForm.uuid = data.uuid;
};
onMounted(async () => {
await getImageCode();
});
</script>
<style scoped lang="scss">
.login {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: url("@/assets/login_images/login_bg.jpg") no-repeat;
&-box {
display: flex;
width: 70%;
height: 80%;
border-radius: 20px;
background-color: #fff;
box-shadow: 15px 15px 30px -10px rgba(0, 0, 0, 0.2),
inset 20px 20px 15px rgba(255, 255, 255, 0.7);
.left {
width: 55%;
height: 100%;
display: flex;
background: url("@/assets/login_images/welcome.jpg") no-repeat;
background-size: 100% auto;
background-position: 50%;
border-right: 2px solid #eeefef;
}
.right {
display: flex;
flex-direction: column;
width: 45%;
padding: 40px 30px 40px 30px;
border-radius: 20px;
// color: #06035a;
background-color: #fff;
.header-box {
cursor: pointer;
margin-bottom: 20px;
height: 10px;
display: flex;
justify-content: flex-end;
align-items: center;
color: #409eff;
}
.top {
height: 40px;
.title {
font-size: 25px;
font-weight: bold;
}
.text {
margin-top: 10px;
}
}
.center {
flex: 1;
.login-form {
width: 100%;
height: 100%;
padding: 10px 0;
display: flex;
flex-direction: column;
justify-content: space-around;
.input-item {
width: 100%;
height: 45px;
outline: none;
border: 2px solid #dde0df;
border-radius: 5px;
padding: 0 10px;
&:hover {
outline: none;
}
}
.login-btn {
cursor: pointer;
width: 100%;
height: 45px;
color: #fff;
text-align: center;
line-height: 50px;
border-radius: 5px;
background-color: #2282fe;
}
.link {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
.text {
cursor: pointer;
}
}
.visitor {
margin-top: 10px;
}
.registerForm {
:deep(.el-form-item) {
margin-bottom: 1px;
}
}
}
}
.bottom {
width: 100%;
height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
.title {
> div {
text-align: center;
margin: 10px;
}
}
.icon-list {
margin-top: 10px;
width: 100%;
display: flex;
justify-content: center;
.icon {
width: 25px;
height: 25px;
margin: 0 10px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
}
:deep(.title-item) {
margin-bottom: 0;
}
.flex-between {
display: flex;
justify-content: space-between;
}
}
</style>

View File

@@ -294,12 +294,7 @@ function cancel() {
}
/** 表单重置 */
function reset() {
form.value = {
id: undefined,
title: undefined,
isDeleted: false,
remark: undefined,
};
form.value = {};
proxy.resetForm("dataRef");
}
/** 搜索按钮操作 */

View File

@@ -84,6 +84,7 @@
prop="introduction"
:show-overflow-tooltip="true"
/>
<el-table-column label="显示顺序" prop="orderNum" />
<el-table-column
label="创建时间"
align="center"
@@ -121,6 +122,9 @@
<el-form-item label="板块Logo" prop="logo">
<el-input v-model="form.logo" placeholder="请输入板块图片连接" />
</el-form-item>
<el-form-item label="显示顺序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
<!-- <el-form-item label="状态" prop="isDeleted">
<el-radio-group v-model="form.isDeleted">
<el-radio
@@ -207,6 +211,8 @@ function cancel() {
/** 表单重置 */
function reset() {
proxy.resetForm("dataRef");
form.value = {
};
}
/** 搜索按钮操作 */
function handleQuery() {
@@ -224,6 +230,7 @@ function handleAdd() {
reset();
open.value = true;
title.value = "添加板块";
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {