feat:完善任务界面

This commit is contained in:
chenchun
2024-08-14 18:31:37 +08:00
parent 27051aa01d
commit b619204c5e
11 changed files with 257 additions and 140 deletions

View File

@@ -23,7 +23,10 @@ public class AssignmentGetListOutputDto:EntityDto<Guid>
/// 总共步骤数 /// 总共步骤数
/// </summary> /// </summary>
public int TotalStepNumber { get; set; } public int TotalStepNumber { get; set; }
/// <summary>
/// 任务类型
/// </summary>
public AssignmentTypeEnum AssignmentType { get; set; }
/// <summary> /// <summary>
/// 任务需求类型 /// 任务需求类型

View File

@@ -1,109 +1,51 @@
using Microsoft.AspNetCore.Http; using FreeRedis;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Caches; using Yi.Framework.Bbs.Domain.Shared.Caches;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Extensions; namespace Yi.Framework.Bbs.Application.Extensions;
/// <summary> /// <summary>
/// 访问日志中间件 /// 访问日志中间件
/// 并发最高采用缓存默认10分钟才会真正操作一次数据库 /// 并发最高采用缓存默认10分钟才会真正操作一次数据库
///
/// 需考虑一致性问题,又不能上锁影响性能 /// 需考虑一致性问题,又不能上锁影响性能
/// </summary> /// </summary>
public class AccessLogMiddleware : IMiddleware, ITransientDependency public class AccessLogMiddleware : IMiddleware, ITransientDependency
{ {
private readonly IDistributedCache<AccessLogCacheItem> _accessLogCache; /// <summary>
private readonly ISqlSugarRepository<AccessLogAggregateRoot> _repository; /// 缓存前缀
/// </summary>
private string CacheKeyPrefix => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDistributedCacheOptions>>()
.Value.KeyPrefix;
public AccessLogMiddleware(IDistributedCache<AccessLogCacheItem> accessLogCache, /// <summary>
ISqlSugarRepository<AccessLogAggregateRoot> repository) /// 使用懒加载防止报错
/// </summary>
private IRedisClient RedisClient => LazyServiceProvider.LazyGetRequiredService<IRedisClient>();
/// <summary>
/// 属性注入
/// </summary>
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
private bool EnableRedisCache
{ {
_accessLogCache = accessLogCache; get
_repository = repository; {
var redisEnabled = LazyServiceProvider.LazyGetRequiredService<IConfiguration>()["Redis:IsEnabled"];
return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled);
}
} }
public async Task InvokeAsync(HttpContext context, RequestDelegate next) public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{ {
await next(context); await next(context);
if (EnableRedisCache)
//获取缓存
var cache = await _accessLogCache.GetAsync(AccessLogCacheConst.Key);
//当前缓存需赋的值
long currentNumber = 1;
//最后插入的时间
DateTime lastInsertTime = DateTime.Now;
//cahce是空创建缓存当前数量从数据库中获取
//不等于空如果大于当前天插入数据库间隔超过10分钟也插入
//获取当前访问数量
//没有缓存
if (cache is null)
{ {
//获取数据库今天最后最后一条数据 await RedisClient.IncrByAsync($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date}", 1);
var currentDayEntity = await GetDateAccessLogEntityAsync();
//数据库有数据,以数据库数据为准+1
if (currentDayEntity is not null)
{
currentNumber = currentDayEntity.Number + 1;
}
//数据库没有数据就是默认的1重新开始
} }
else
{
//有缓存,根据根据缓存的值来
currentNumber = cache.Number + 1;
lastInsertTime = cache.LastInsertTime;
//数据库持久化
//缓存的日期大于当天的日期,插入到数据库,同时缓存变成0重新开始统计
if (cache.LastModificationTime.Date > DateTime.Today)
{
await _repository.InsertAsync(new AccessLogAggregateRoot()
{ AccessLogType = AccessLogTypeEnum.Request, Number = currentNumber });
currentNumber = 0;
}
else
{
if (cache.LastInsertTime <= DateTime.Now - TimeSpan.FromMinutes(10))
{
//这里还需要判断数据库是否有值,不能之间更新
//缓存的时间大于当前数据库时间10分钟之后更新减少数据库交互10分钟才更新一次
var currentDayEntity = await GetDateAccessLogEntityAsync();
if (currentDayEntity is null)
{
await _repository.InsertAsync(new AccessLogAggregateRoot()
{ AccessLogType = AccessLogTypeEnum.Request, Number = currentNumber });
}
await _repository.UpdateAsync(currentDayEntity);
}
}
}
//覆盖缓存
var newCache = new AccessLogCacheItem(currentNumber) { LastInsertTime = lastInsertTime };
await _accessLogCache.SetAsync(AccessLogCacheConst.Key, newCache);
}
/// <summary>
/// 获取今天的统计
/// </summary>
/// <returns></returns>
private async Task<AccessLogAggregateRoot?> GetDateAccessLogEntityAsync()
{
//获取数据库今天最后最后一条数据
var currentDayEntity = await _repository._DbQueryable
.Where(x => x.AccessLogType == AccessLogTypeEnum.Request)
.Where(x => x.CreationTime == DateTime.Today)
.OrderByDescending(x => x.CreationTime)
.FirstAsync();
return currentDayEntity;
} }
} }

View File

@@ -0,0 +1,72 @@
using FreeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Quartz;
using Volo.Abp.BackgroundWorkers.Quartz;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Caches;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Jobs;
public class AccessLogStoreJob : QuartzBackgroundWorkerBase
{
private readonly ISqlSugarRepository<AccessLogAggregateRoot> _repository;
/// <summary>
/// 缓存前缀
/// </summary>
private string CacheKeyPrefix => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDistributedCacheOptions>>()
.Value.KeyPrefix;
/// <summary>
/// 使用懒加载防止报错
/// </summary>
private IRedisClient RedisClient => LazyServiceProvider.LazyGetRequiredService<IRedisClient>();
/// <summary>
/// 属性注入
/// </summary>
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
private bool EnableRedisCache
{
get
{
var redisEnabled = LazyServiceProvider.LazyGetRequiredService<IConfiguration>()["Redis:IsEnabled"];
return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled);
}
}
public AccessLogStoreJob(ISqlSugarRepository<AccessLogAggregateRoot> repository)
{
_repository = repository;
JobDetail = JobBuilder.Create<AccessLogStoreJob>().WithIdentity(nameof(AccessLogStoreJob))
.Build();
//每分钟执行一次
Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogStoreJob))
.WithCronSchedule("* * * * *")
.Build();
}
public override async Task Execute(IJobExecutionContext context)
{
if (EnableRedisCache)
{
//当天的访问量
var number =
await RedisClient.GetAsync<long>($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date}");
var entity = await _repository._DbQueryable.Where(x => x.AccessLogType == AccessLogTypeEnum.Request)
.Where(x => x.CreationTime.Date == DateTime.Today)
.FirstAsync();
// _repository._Db.Storageable(list2).ExecuteCommandAsync();
}
}
}

View File

@@ -64,9 +64,10 @@ public class AssignmentService : ApplicationService
var output = await _assignmentManager._assignmentRepository._DbQueryable var output = await _assignmentManager._assignmentRepository._DbQueryable
.Where(x => x.UserId == CurrentUser.GetId()) .Where(x => x.UserId == CurrentUser.GetId())
.WhereIF(input.AssignmentQueryState == AssignmentQueryStateEnum.Progress, .WhereIF(input.AssignmentQueryState == AssignmentQueryStateEnum.Progress,
x => x.AssignmentState == AssignmentStateEnum.Progress) x => x.AssignmentState == AssignmentStateEnum.Progress||
x.AssignmentState == AssignmentStateEnum.Completed)
.WhereIF(input.AssignmentQueryState == AssignmentQueryStateEnum.End, .WhereIF(input.AssignmentQueryState == AssignmentQueryStateEnum.End,
x => x.AssignmentState == AssignmentStateEnum.Completed || x => x.AssignmentState == AssignmentStateEnum.End ||
x.AssignmentState == AssignmentStateEnum.Expired) x.AssignmentState == AssignmentStateEnum.Expired)
.OrderBy(x=>x.CreationTime) .OrderBy(x=>x.CreationTime)
.LeftJoin<AssignmentDefineAggregateRoot>((x, define) => x.AssignmentDefineId==define.Id) .LeftJoin<AssignmentDefineAggregateRoot>((x, define) => x.AssignmentDefineId==define.Id)

View File

@@ -15,5 +15,10 @@ public enum AssignmentStateEnum
/// <summary> /// <summary>
/// 已过期 /// 已过期
/// </summary> /// </summary>
Expired Expired,
/// <summary>
/// 已结束
/// </summary>
End
} }

View File

@@ -52,7 +52,7 @@ public class AssignmentAggregateRoot : AggregateRoot<Guid>, IHasCreationTime, IO
/// 任务需求类型 /// 任务需求类型
/// </summary> /// </summary>
public AssignmentRequirementTypeEnum AssignmentRequirementType{ get; set; } public AssignmentRequirementTypeEnum AssignmentRequirementType{ get; set; }
public DateTime? CompleteTime { get; set; } public DateTime? EndTime { get; set; }
public DateTime CreationTime { get; set; } public DateTime CreationTime { get; set; }
@@ -62,7 +62,7 @@ public class AssignmentAggregateRoot : AggregateRoot<Guid>, IHasCreationTime, IO
public bool IsAllowCompleted() public bool IsAllowCompleted()
{ {
return AssignmentState == AssignmentStateEnum.Progress && this.CurrentStepNumber == this.TotalStepNumber; return AssignmentState == AssignmentStateEnum.Completed && this.CurrentStepNumber == this.TotalStepNumber;
} }
public bool TrySetExpire() public bool TrySetExpire()
@@ -76,10 +76,10 @@ public class AssignmentAggregateRoot : AggregateRoot<Guid>, IHasCreationTime, IO
return true; return true;
} }
public void SetComplete() public void SetEnd()
{ {
this.AssignmentState = AssignmentStateEnum.Completed; this.AssignmentState = AssignmentStateEnum.End;
this.CompleteTime=DateTime.Now; this.EndTime=DateTime.Now;
} }
} }

View File

@@ -68,10 +68,14 @@ public class AssignmentEventHandler : ILocalEventHandler<AssignmentEventArgs>, I
{ {
currentAssignmentList.ForEach(x => currentAssignmentList.ForEach(x =>
{ {
if (x.AssignmentRequirementType == AssignmentRequirementTypeEnum.Agree && if (x.AssignmentRequirementType == requirementType &&
x.CurrentStepNumber < x.TotalStepNumber) x.CurrentStepNumber < x.TotalStepNumber)
{ {
x.CurrentStepNumber += 1; x.CurrentStepNumber += 1;
if (x.CurrentStepNumber==x.TotalStepNumber)
{
x.AssignmentState = AssignmentStateEnum.Completed;
}
} }
}); });
} }

View File

@@ -74,7 +74,7 @@ public class AssignmentManager : DomainService
new MoneyChangeEventArgs { UserId = assignment.UserId, Number = assignment.RewardsMoneyNumber }, false); new MoneyChangeEventArgs { UserId = assignment.UserId, Number = assignment.RewardsMoneyNumber }, false);
//设置已完成,并领取奖励,钱钱 //设置已完成,并领取奖励,钱钱
assignment.SetComplete(); assignment.SetEnd();
await _assignmentRepository.UpdateAsync(assignment); await _assignmentRepository.UpdateAsync(assignment);
} }
else else

View File

@@ -24,10 +24,11 @@ public abstract class TimerProvider : IAssignmentProvider
//2: 存在已完成,但是完成时间是过期的 //2: 存在已完成,但是完成时间是过期的
var assignmentFilterIds = context.CurrentUserAssignments var assignmentFilterIds = context.CurrentUserAssignments
.Where(x => .Where(x =>
//正在进行的,要去掉 //正在进行的,已经完成,要去掉
x.AssignmentState == AssignmentStateEnum.Progress|| x.AssignmentState == AssignmentStateEnum.Progress||
//已完成,但是还没过期,要去掉 x.AssignmentState==AssignmentStateEnum.Completed||
(x.AssignmentState == AssignmentStateEnum.Completed&&!AssignmentType.IsExpire(x.CompleteTime!.Value))) //已结束,但是还没过期,要去掉
(x.AssignmentState == AssignmentStateEnum.End&&!AssignmentType.IsExpire(x.EndTime!.Value)))
.Select(x => x.AssignmentDefineId) .Select(x => x.AssignmentDefineId)
.ToList(); .ToList();

View File

@@ -2,6 +2,7 @@
import {getAssignmentList, getCanReceiveAssignment, acceptAssignment, receiveAssignment} from '@/apis/assignmentApi' import {getAssignmentList, getCanReceiveAssignment, acceptAssignment, receiveAssignment} from '@/apis/assignmentApi'
import {onMounted, reactive, ref} from "vue"; import {onMounted, reactive, ref} from "vue";
import AssignmentCard from "./components/AssignmentCard.vue" import AssignmentCard from "./components/AssignmentCard.vue"
const canReceiveAssignmentList = ref([]); const canReceiveAssignmentList = ref([]);
const assignmentList = ref([]); const assignmentList = ref([]);
@@ -14,7 +15,6 @@ const currentTableSelect = ref("canAccept");
//切换tab //切换tab
const changeClickTable = async (tabName) => { const changeClickTable = async (tabName) => {
console.log(tabName,"tabName")
switch (tabName) { switch (tabName) {
case "canAccept": case "canAccept":
const {data: canReceiveAssignmentListData} = await getCanReceiveAssignment(); const {data: canReceiveAssignmentListData} = await getCanReceiveAssignment();
@@ -47,11 +47,20 @@ const refreshData = async () => {
//接收任务 //接收任务
const onClickAcceptAssignment = async (item) => { const onClickAcceptAssignment = async (item) => {
await acceptAssignment(item.id); await acceptAssignment(item.id);
ElMessage({
type: 'success',
message: '接受任务成功',
});
await refreshData(); await refreshData();
} }
const onClickReceiveAssignment = async (id) => { //领取奖励
await receiveAssignment(id); const onClickReceiveAssignment = async (item) => {
await receiveAssignment(item.id);
ElMessage({
type: 'success',
message: '任务奖励领取成功',
});
await refreshData(); await refreshData();
} }
@@ -74,21 +83,32 @@ const changeTab = async (state) => {
<el-tab-pane label="可接受" name="canAccept"/> <el-tab-pane label="可接受" name="canAccept"/>
<el-tab-pane label="已接受" name="progress"/> <el-tab-pane label="已接受" name="progress"/>
<el-tab-pane label="已结束" name="end"/> <el-tab-pane label="已结束" name="end"/>
<div v-for="item in canReceiveAssignmentList" v-if="currentTableSelect==='canAccept'">
<AssignmentCard :data="item" @onClick="onClickAcceptAssignment"/>
</div>
<div v-for="item in assignmentList" v-else>{{ item }} <div v-if="currentTableSelect==='canAccept'">
<button type="button" @click="onClickReceiveAssignment(item.id)">领取奖励</button> <div v-for="item in canReceiveAssignmentList" class="assign-box" v-if="canReceiveAssignmentList.length>0">
<AssignmentCard :isDefind="true" :data="item" @onClick="onClickAcceptAssignment"/>
</div>
<el-empty v-else description="暂时没有可领取的任务" />
</div>
<div v-else>
<div v-for="item in assignmentList" class="assign-box" v-if="assignmentList.length>0">
<AssignmentCard :isDefind="false" :data="item" @onClick="onClickReceiveAssignment"/>
</div>
<el-empty v-else description="暂时没有任务" />
</div> </div>
</el-tabs> </el-tabs>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.content-body{ .content-body {
padding: 30px ; padding: 30px;
.assign-box {
margin-bottom: 10px;
}
} }

View File

@@ -1,51 +1,105 @@
<script setup> <script setup>
import {ref} from "vue"; import {computed, ref, watch} from "vue";
import { dayjs } from 'element-plus'
const props = defineProps(['data', 'isDefind'])
const cardData = ref(props.data);
const props = defineProps(['data']) const emit = defineEmits(['onClick'])
const cardData=ref(props.data); const onClick = () => {
emit('onClick', cardData.value)
const emit =defineEmits(['onClick'])
const onClick=()=>{
emit('onClick',cardData.value)
} }
//监视组件传值变化
watch(() => props.data, (n, o) => {
cardData.value = n;
})
//任务类型
const assignmentTypeEnum = {
"Daily": "每日任务",
"Weekly": "每周任务",
"Novice": "新手任务"
}
const computedAssignmentState = computed(() => {
if (props.isDefind) {
return btnAssignmentStateEnum["CanReceive"];
} else {
return btnAssignmentStateEnum[cardData.value.assignmentState];
}
});
const btnAssignmentStateEnum = {
"CanReceive": {name: "接受任务", backgroundColor: "primary",isDisabled:false},
"End": {name: "已完成", backgroundColor: "info",isDisabled:true},
"Progress": {name: "正在进行", backgroundColor: "Default",isDisabled:true},
"Completed": {name: "领取奖励", backgroundColor: "success",isDisabled:false},
"Expired": {name: "已过期", backgroundColor: "info",isDisabled:true}
}
</script> </script>
<template> <template>
<div class="card-box"> <div class="card-box">
<div class="left"> <div class="left">
<div class="left-type">每日任务</div> <div class="left-type">{{ assignmentTypeEnum[cardData.assignmentType] }}</div>
<div class="content"> <div class="content">
<h2>{{cardData.name}}</h2> <div class="content-title">
<h4>{{cardData.remarks}}</h4> <h2>{{ cardData.name }}</h2>
<h4>{{ cardData.remarks }}</h4>
</div>
<div class="progress" v-if="props.isDefind===false">
<el-progress
:text-inside="true"
:stroke-width="20"
:percentage=" Math.round((cardData.currentStepNumber/cardData.totalStepNumber)*100)"
striped
striped-flow
status="success"
/>
<span>{{cardData.currentStepNumber}}/{{cardData.totalStepNumber}}</span>
</div>
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<div class="right-btn"> <div class="right-btn">
<h3>过期时间无限制</h3> <h5>过期时间{{ cardData.expireTime ==null? "无限制": dayjs(cardData.expireTime).format('YYYY年M月D日') }}</h5>
<el-button @click="onClick()" type="primary">接受任务</el-button> <h5>奖励<span style="color: #FF0000;font-weight: bolder ">{{cardData.rewardsMoneyNumber}}</span> 钱钱</h5>
<el-button @click="onClick()" :disabled="computedAssignmentState.isDisabled" :type="computedAssignmentState.backgroundColor">
{{ computedAssignmentState.name }}
</el-button>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.el-button{ .el-progress{
width: 450px;
}
.el-button {
width: 100px; width: 100px;
height: 40px; height: 40px;
margin-top: 5px;
} }
h2{
h2 {
margin: 0 0 10px 0; margin: 0 0 10px 0;
} }
h3{
h4 {
margin: 0 0 0 20px;
display: flex;
align-items: center;
}
h5 {
margin: 0; margin: 0;
} }
h4{
margin: 0;
} .card-box {
.card-box{
padding: 10px; padding: 10px;
border: 2px solid #dcdfe6; border: 2px solid #dcdfe6;
display: flex; display: flex;
@@ -54,21 +108,36 @@ h4{
width: 100%; width: 100%;
align-content: center; align-content: center;
flex-wrap: wrap; flex-wrap: wrap;
.right{
.right {
display: flex; display: flex;
align-content: center; align-content: center;
flex-wrap: wrap; flex-wrap: wrap;
.right-btn{
.right-btn {
text-align: right; text-align: right;
} }
} }
.left{
.left {
display: flex; display: flex;
.content{
align-content: center;
flex-wrap: wrap;
.content {
margin-left: 30px; margin-left: 30px;
.content-title{
display: flex;
}
.progress{
display: flex;
}
span{
margin: 0 10px;
}
} }
.left-type
{ .left-type {
border: 1px solid #dcdfe6; border: 1px solid #dcdfe6;
height: 60px; height: 60px;
width: 100px; width: 100px;