Browse Source

Merge branch 'feature/exam' into test
合并冲突

guqiang 1 week ago
parent
commit
adea7d03f9

+ 95 - 0
src/Hotline.Api/Controllers/Exam/OfflineExamAnalysisController.cs

@@ -0,0 +1,95 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Share.ViewResponses.Exam;
+using Exam.Share.ViewResponses.Question;
+using Hotline.Application.Exam.Constants.ApiRoutes;
+using Hotline.Application.Exam.Interface.ExamManages;
+using Hotline.Application.Exam.Service.ExamManages;
+using Hotline.Application.ExportExcel;
+using Hotline.Exams.ExamManages;
+using Hotline.Exams.Questions;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.Requests.Question;
+using Hotline.Share.ViewResponses.Exam;
+using Hotline.Tools;
+using MapsterMapper;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Hotline.Api.Controllers.Exam
+{
+    public class OfflineExamAnalysisController : BaseController
+    {
+        private readonly IOfflineExamAnalysisService _offineExamAnalysisService;
+        private readonly IMapper _mapper;
+        private readonly IExportApplication _exportApplication;
+
+        public OfflineExamAnalysisController(IOfflineExamAnalysisService offineExamAnalysisService,IMapper mapper, IExportApplication exportApplication)
+        {
+            _offineExamAnalysisService = offineExamAnalysisService;
+            this._mapper = mapper;
+            this._exportApplication = exportApplication;
+        }
+
+        /// <summary>
+        /// 获取线下考试分页列表
+        /// </summary>
+        /// <param name="offlineExamAnalysisPagedRequest"></param>
+        /// <returns></returns>
+        [HttpPost(OfflineExamAnalysisApiRoute.GetPagedList)]
+        public async Task<PageViewResponse<OfflineExamAnalysisViewResponse>> GetPagedList([FromBody] OfflineExamAnalysisPagedRequest offlineExamAnalysisPagedRequest)
+        {
+            var offlineExamAnalysisPageViewResponse = await _offineExamAnalysisService.GetPagedListAsync(offlineExamAnalysisPagedRequest);            
+
+            return offlineExamAnalysisPageViewResponse;
+        }
+
+        /// <summary>
+        /// 导入Excel
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost(OfflineExamAnalysisApiRoute.ImportExcel)]
+        public async Task ImportExcel(IFormFile file)
+        {
+            await _offineExamAnalysisService.ImportExcel(file, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 导出线下考试成绩
+        /// </summary>
+        /// <param name="exportExcelDto"></param>
+        /// <returns></returns>
+        [HttpPost(OfflineExamAnalysisApiRoute.ExportOfflineExamAnalysis)]
+        public async Task<FileStreamResult> ExportOfflineExamAnalysis([FromBody] ExportExcelDto<OfflineExamAnalysisPagedRequest> exportExcelDto)
+        {
+            if (exportExcelDto.IsExportAll)
+            {
+                exportExcelDto.QueryDto.IsPaged = false;
+            }
+            var result = await _offineExamAnalysisService.GetPagedListAsync(exportExcelDto.QueryDto);
+
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass<OfflineExamAnalysisViewResponse>(exportExcelDto.ColumnInfos ?? new List<ColumnInfo>());
+
+            var dtos = result.Items
+                .Select(stu => _mapper.Map(stu, typeof(OfflineExamAnalysisViewResponse), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "导出线下考试成绩");
+
+        }
+
+        /// <summary>
+        /// 下载Excel模版
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet(OfflineExamAnalysisApiRoute.Download)]
+        public FileStreamResult Download()
+        {
+            var list = new List<OfflineExamAnalysisExcel>();
+            return _exportApplication.ExportData(list, "线下考试成绩统计表.xlsx");
+        }
+    }
+}

+ 1 - 1
src/Hotline.Api/Controllers/Exam/QuestionController.cs

@@ -130,7 +130,7 @@ namespace Hotline.Api.Controllers.Exam
         public FileStreamResult Download()
         {
             var list = new List<QuestionExcel>();
-            return _exportApplication.ExportData(list, "Template.xlsx");
+            return _exportApplication.ExportData(list, "题库.xlsx");
         }
     }
 }

+ 3 - 1
src/Hotline.Api/Controllers/Exam/UserExamController.cs

@@ -40,7 +40,9 @@ namespace Hotline.Api.Controllers.Exam
         {
             await _examManageService.UpdateExamStatus(new EntityQueryRequest(), HttpContext.RequestAborted);
 
-            return await _userExamService.GetPagedListAsync(userExamPagedRequest) as UserExamResultPageViewResponse;
+            var userExamResultPageViewResponse = await _userExamService.GetPagedListAsync(userExamPagedRequest);
+
+            return userExamResultPageViewResponse as UserExamResultPageViewResponse;
         }
 
         /// <summary>

+ 14 - 0
src/Hotline.Application/Exam/Constants/ApiRoutes/OfflineExamAnalysisApiRoute.cs

@@ -0,0 +1,14 @@
+using Hotline.Application.Exam.Core.Constants;
+
+namespace Hotline.Application.Exam.Constants.ApiRoutes
+{
+    public class OfflineExamAnalysisApiRoute:ApiRoute
+    {
+        public const string ImportExcel = "ImportExcel";
+
+        public const string Download = "Download";
+
+        public const string ExportOfflineExamAnalysis = "ExportOfflineExamAnalysis";
+
+    }
+}

+ 6 - 0
src/Hotline.Application/Exam/Constants/Messages/ExamSystemConstants.cs

@@ -6,4 +6,10 @@
 
         public static string[] ColumnNames = ["题型","试题标签","难度级别","正式可用","模拟可用", "题干", "选项A", "选项B", "选项C", "选项D", "选项E", "选项F", "选项G", "选项H", "选项I", "选项J", "答案"];
     }
+
+    public class OfflineExamSystemConstants
+    {
+        public static string[] ColumnNames = ["参考人员", "部门名称", "考试标题", "开始时间", "结束时间", "考试总分", "合格分数", "考试分数"];
+
+    }
 }

+ 28 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IOfflineExamAnalysisService.cs

@@ -0,0 +1,28 @@
+using Hotline.Exams.ExamManages;
+using Hotline.Exams.Questions;
+using Hotline.Repository.SqlSugar.Exam.Interface;
+using Hotline.Share.Dtos.ExamManages;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.ViewResponses.Exam;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Exam.Interface.ExamManages
+{
+    public interface IOfflineExamAnalysisService : IQueryService<OfflineExamAnalysisViewResponse, OfflineExamAnalysisDto, OfflineExamAnalysisPagedRequest>, IApiService<AddOfflineExamAnalysisDto, UpdateOfflineExamAnalysisDto, ExamOfflineExamAnalysis>
+    {
+
+        /// <summary>
+        /// 导入Excel
+        /// </summary>
+        /// <param name="file"></param>
+        /// <param name="requestAborted"></param>
+        /// <returns></returns>
+        Task ImportExcel(IFormFile file, CancellationToken requestAborted);
+    }
+}

+ 29 - 0
src/Hotline.Application/Exam/QueryExtensions/ExamManages/OfflineExamQueryExtensions.cs

@@ -0,0 +1,29 @@
+using Hotline.Application.Exam.Core.Utilities;
+using Hotline.Exams.ExamManages;
+using Hotline.Share.Requests.Exam;
+using JiebaNet.Segmenter.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Exam.QueryExtensions.ExamManages
+{
+    public static class OfflineExamQueryExtensions
+    {
+        public static Expression<Func<ExamOfflineExamAnalysis, bool>> GetExpression(this OfflineExamAnalysisPagedRequest offlineExamAnalysisPagedRequest)
+        {
+            Expression<Func<ExamOfflineExamAnalysis, bool>> expression = m => m.Id != null;
+
+            expression = ExpressionableUtility.CreateExpression<ExamOfflineExamAnalysis>()
+            .AndIF(offlineExamAnalysisPagedRequest.StartTime.IsNotNull(), x => x.StartTime >= offlineExamAnalysisPagedRequest.StartTime)
+            .AndIF(offlineExamAnalysisPagedRequest.EndTime.IsNotNull(), x => x.StartTime <= offlineExamAnalysisPagedRequest.EndTime)
+            .AndIF(offlineExamAnalysisPagedRequest.Keyword.IsNotNull(), x => x.ExamName.Contains(offlineExamAnalysisPagedRequest.Keyword))
+            .ToExpression();
+
+            return expression;
+        }
+    }
+}

+ 148 - 0
src/Hotline.Application/Exam/Service/ExamManages/OfflineExamAnalysisService.cs

@@ -0,0 +1,148 @@
+using DocumentFormat.OpenXml.Drawing;
+using Exam.Infrastructure.Data.Entity;
+using FluentValidation;
+using Hotline.Application.Exam.Constants.Messages;
+using Hotline.Application.Exam.Interface.ExamManages;
+using Hotline.Application.Exam.QueryExtensions.ExamManages;
+using Hotline.Exams.ExamManages;
+using Hotline.Exams.Questions;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Repository.SqlSugar.Exam.Interface;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Questions;
+using Hotline.Repository.SqlSugar.Exam.Service;
+using Hotline.Share.Dtos.ExamManages;
+using Hotline.Share.Dtos.TestPapers;
+using Hotline.Share.Enums.Exams;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.ViewResponses.Exam;
+using Hotline.Tools;
+using JiebaNet.Segmenter.Common;
+using MapsterMapper;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Exam.Service.ExamManages
+{
+    public class OfflineExamAnalysisService : ApiService<ExamOfflineExamAnalysis, AddOfflineExamAnalysisDto, UpdateOfflineExamAnalysisDto, HotlineDbContext>, IOfflineExamAnalysisService, IScopeDependency
+    {
+        private readonly IOfflineExamAnalysisRepository _repository;
+        private readonly ISessionContext _sessionContext;
+        private readonly IDataPermissionFilterBuilder _dataPermissionFilterBuilder;
+        private readonly IServiceProvider _serviceProvider;
+        private readonly IMapper _mapper;
+
+        public OfflineExamAnalysisService(IOfflineExamAnalysisRepository repository,
+             ISessionContext sessionContext,
+            IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider,
+            IMapper mapper) : base(repository, mapper, sessionContext)
+        {
+            this._repository = repository;
+            this._sessionContext = sessionContext;
+            this._dataPermissionFilterBuilder = dataPermissionFilterBuilder;
+            this._serviceProvider = serviceProvider;
+            this._mapper = mapper;
+        }
+
+        #region public method
+        public Task<OfflineExamAnalysisDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task<(int, List<OfflineExamAnalysisViewResponse>)> GetListAsync(OfflineExamAnalysisPagedRequest queryRequest)
+        {
+            throw new NotImplementedException();
+        }
+
+        public async Task<PageViewResponse<OfflineExamAnalysisViewResponse>> GetPagedListAsync(OfflineExamAnalysisPagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var offlineExamAnalysisTable = _repository.Queryable().Where(expression);
+
+            var queryable = offlineExamAnalysisTable.Select(x => new OfflineExamAnalysisViewResponse
+            {
+                CutoffScore = x.CutoffScore,
+                ExamName = x.ExamName,
+                DepartmentName = x.DepartmentName,
+                Id = x.Id,
+                Score = x.Score,
+                TotalScore = x.TotalScore,
+                UserName = x.UserName
+            }).OrderByPropertyNameIF(!string.IsNullOrEmpty(queryRequest.SortField), queryRequest.SortField, (OrderByType)(queryRequest.SortRule ?? 0));
+
+            var total = await offlineExamAnalysisTable.CountAsync();
+            var items = queryRequest.IsPaged ? await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize) : await queryable.ToListAsync();
+
+            var result = new PageViewResponse<OfflineExamAnalysisViewResponse>
+            {
+                Items = items,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+
+        public async Task ImportExcel(IFormFile file, CancellationToken cancellationToken)
+        {
+            var stream = file.OpenReadStream();
+            var contents = ExcelHelper.Read(stream, true);
+
+            var offlineExamAnalysisDtos = new List<AddOfflineExamAnalysisDto>();
+
+            contents.ForEach(async item =>
+            {
+                var value = (item as ExpandoObject).GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[0]);
+
+                if (value == null)
+                    return;
+
+                var offlineExamAnalysisDto = BuildOfflineExamAnalysis(item as ExpandoObject);
+
+                if (offlineExamAnalysisDto != null)
+                    offlineExamAnalysisDtos.Add(offlineExamAnalysisDto);
+            });
+
+            await base.AddAsync(offlineExamAnalysisDtos, cancellationToken);
+
+            stream.Close();
+        }
+        #endregion
+
+        #region private method
+        private OfflineExamAnalysisDto BuildOfflineExamAnalysis(ExpandoObject? item)
+        {
+            var totalScore = 0;
+            var cutoffScore = 0;
+            var score = 0;
+
+            var offlineExamAnalysisDto = new OfflineExamAnalysisDto
+            {
+                UserName = item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[0]) != null ? item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[0]).ToString() : string.Empty,
+                DepartmentName= item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[1]) != null ? item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[1]).ToString() : string.Empty,
+                ExamName = item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[2]) != null ? item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[2]).ToString() : string.Empty,
+                StartTime = DateTime.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[3])?.ToString(),out DateTime startTime) ? startTime : DateTime.MinValue,
+                EndTime = item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[4])!=null? (DateTime.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[4])?.ToString(),out DateTime dateTime) ? dateTime : null):null,
+                TotalScore = int.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[5])?.ToString(),out totalScore)?totalScore:0,
+                CutoffScore = int.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[6])?.ToString(),out cutoffScore)? cutoffScore:0,
+                Score = int.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[7])?.ToString(), out score) ? score : 0,
+
+            };
+
+            return offlineExamAnalysisDto; 
+        } 
+        #endregion
+    }
+}

+ 20 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IOfflineExamAnalysisRepository.cs

@@ -0,0 +1,20 @@
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Interface;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 线下考试统计仓储接口
+    /// </summary>
+    [Description("线下考试统计仓储接口")]
+    public interface IOfflineExamAnalysisRepository : IRepository<ExamOfflineExamAnalysis>, IExamRepository<ExamOfflineExamAnalysis, HotlineDbContext>
+    {
+    }
+}

+ 22 - 0
src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamManages/OfflineExamAnalysisRepository.cs

@@ -0,0 +1,22 @@
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Validators.ExamManages;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Exam.Repositories.ExamManages
+{
+    public class OfflineExamAnalysisRepository : ExamRepository<ExamOfflineExamAnalysis>, IOfflineExamAnalysisRepository, IScopeDependency
+    {
+        public OfflineExamAnalysisRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider) : base(uow, dataPermissionFilterBuilder, serviceProvider)
+        {
+            Validator = new OfflineExamAnalysisValidator();
+        }
+    }
+}

+ 63 - 0
src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/OfflineExamAnalysisValidator.cs

@@ -0,0 +1,63 @@
+using Exam.Infrastructure.Extensions;
+using Exam.Infrastructure.Validation.Validation;
+using FluentValidation;
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Core.Constants;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Validate;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Repository.SqlSugar.Exam.Validators.ExamManages
+{
+    public class OfflineExamAnalysisValidator : BaseValidator<ExamOfflineExamAnalysis>
+    {
+        private readonly IExamManageRepository _examManageRepository;
+
+        public OfflineExamAnalysisValidator()
+        {
+            RuleSet(ValidatorTypeConstants.Create, () =>
+            {
+                BaseValidateRule();
+
+                ValidateRuleWithAdd();
+            });
+
+            RuleSet(ValidatorTypeConstants.Modify, () =>
+            {
+                BaseValidateRule();
+
+                ValidateRuleWithModify();
+            });
+        }
+
+        protected override void BaseValidateRule()
+        {
+            base.BaseValidateRule();
+            RuleFor(m => m.UserName).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.UserName))));
+            RuleFor(m => m.DepartmentName).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.DepartmentName))));
+            RuleFor(m => m.ExamName).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.ExamName))));
+            RuleFor(m => m.TotalScore).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.TotalScore))));
+            RuleFor(m => m.Score).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.Score))));
+            RuleFor(m => m.CutoffScore).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.CutoffScore))));
+            RuleFor(m => m.TotalScore).Must((c, v) => c.Score <= v).WithMessage(x => string.Format(ExamErrorMessage.Greater, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.Score)),x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.TotalScore))));
+        }
+
+        protected override void ValidateRuleWithAdd()
+        {
+            base.ValidateRuleWithAdd();
+            RuleFor(m => m.CreationTime).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamAnswer.CreationTime))));
+        }
+
+        protected override void ValidateRuleWithModify()
+        {
+            base.ValidateRuleWithModify();
+            RuleFor(m => m.LastModificationTime).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamAnswer.LastModificationTime))));
+
+        }
+
+    }
+}

+ 75 - 0
src/Hotline.Share/Dtos/ExamManages/OfflineExamAnalysisDto.cs

@@ -0,0 +1,75 @@
+using Exam.Infrastructure.Data.Interface;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.ExamManages
+{
+    /// <summary>
+    /// 线下考试统计实体
+    /// </summary>
+    [Description("线下考试统计实体")]
+    public class OfflineExamAnalysisDto : UpdateOfflineExamAnalysisDto
+    {
+        
+    }
+
+    public class AddOfflineExamAnalysisDto : IAddRequest
+    {
+        /// <summary>
+        /// 参考人员
+        /// </summary>
+        [Description("参考人员")]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        [Description("部门名称")]
+        public string DepartmentName { get; set; }
+
+        /// <summary>
+        /// 考试标题
+        /// </summary>
+        [Description("考试标题")]
+        public string ExamName { get; set; }
+
+        /// <summary>
+        /// 考试总分
+        /// </summary>
+        [Description("考试总分")]
+        public int TotalScore { get; set; }
+
+        /// <summary>
+        /// 合格分数
+        /// </summary>
+        [Description("合格分数")]
+        public int CutoffScore { get; set; }
+
+        /// <summary>
+        /// 考试分数
+        /// </summary>
+        [Description("考试分数")]
+        public int Score { get; set; }
+
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        [Description("开始时间")]
+        public DateTime StartTime { get; set; }
+
+        /// <summary>
+        /// 结束时间
+        /// </summary>
+        [Description("结束时间")]
+        public DateTime? EndTime { get; set; }
+    }
+
+    public class UpdateOfflineExamAnalysisDto : AddOfflineExamAnalysisDto,IActionRequest
+    {
+        public string Id { get; set; }
+    }
+}

+ 6 - 0
src/Hotline.Share/Requests/Exam/OfflineExamAnalysisPagedRequest.cs

@@ -0,0 +1,6 @@
+namespace Hotline.Share.Requests.Exam
+{
+    public record OfflineExamAnalysisPagedRequest : AnalysisReportRequest
+    {
+    }
+}

+ 10 - 0
src/Hotline.Share/ViewResponses/Exam/OfflineExamAnalysisPageViewResponse.cs

@@ -0,0 +1,10 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Share.ViewResponses.Exam;
+
+namespace Hotline.Share.ViewResponses.Exam
+{
+    public class OfflineExamAnalysisPageViewResponse : PageViewResponse<OfflineExamAnalysisViewResponse>
+    {
+    }
+
+}

+ 52 - 0
src/Hotline.Share/ViewResponses/Exam/OfflineExamAnalysisViewResponse.cs

@@ -0,0 +1,52 @@
+using Exam.Infrastructure.Data.Interface;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.ViewResponses.Exam
+{
+    public class OfflineExamAnalysisViewResponse : IViewResponse
+    {
+        /// <summary>
+        /// 参考人员
+        /// </summary>
+        [Description("参考人员")]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        [Description("部门名称")]
+        public string DepartmentName { get; set; }
+
+        /// <summary>
+        /// 考试标题
+        /// </summary>
+        [Description("考试标题")]
+        public string ExamName { get; set; }
+
+        /// <summary>
+        /// 考试总分
+        /// </summary>
+        [Description("考试总分")]
+        public int TotalScore { get; set; }
+
+        /// <summary>
+        /// 合格分数
+        /// </summary>
+        [Description("合格分数")]
+        public int CutoffScore { get; set; }
+
+        /// <summary>
+        /// 考试分数
+        /// </summary>
+        [Description("考试分数")]
+        public int Score { get; set; }
+
+
+        public string Id { get; set ; }
+    }
+}

+ 69 - 0
src/Hotline/Exams/ExamManages/ExamOfflineExamAnalysis.cs

@@ -0,0 +1,69 @@
+using Hotline.Exams.Base;
+using SqlSugar;
+using System.ComponentModel;
+
+namespace Hotline.Exams.ExamManages
+{
+    /// <summary>
+    /// 线下考试统计
+    /// </summary>
+    [Description("线下考试统计")]
+    public class ExamOfflineExamAnalysis: ExamBusinessEntity
+    {
+        /// <summary>
+        /// 参考人员
+        /// </summary>
+        [SugarColumn(ColumnDescription = "参考人员")]
+        [Description("参考人员")]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        [SugarColumn(ColumnDescription = "部门名称")]
+        [Description("部门名称")]
+        public string DepartmentName { get; set; }
+
+        /// <summary>
+        /// 考试标题
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试标题")]
+        [Description("考试标题")]
+        public string ExamName { get; set; }
+
+        /// <summary>
+        /// 考试总分
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试总分")]
+        [Description("考试总分")]
+        public int TotalScore { get; set; }
+
+        /// <summary>
+        /// 合格分数
+        /// </summary>
+        [SugarColumn(ColumnDescription = "合格分数")]
+        [Description("合格分数")]
+        public int CutoffScore { get; set; }
+
+        /// <summary>
+        /// 考试分数
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试分数")]
+        [Description("考试分数")]
+        public int Score { get; set; }
+
+        /// <summary>
+        /// 考试开始时间
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试开始时间")]
+        [Description("考试开始时间")]
+        public DateTime StartTime { get; set; }
+
+        /// <summary>
+        /// 考试结束时间
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试结束时间")]
+        [Description("考试结束时间")]
+        public DateTime? EndTime { get; set; }
+    }
+}

+ 62 - 0
src/Hotline/Exams/ExamManages/OfflineExamAnalysisExcel.cs

@@ -0,0 +1,62 @@
+using MiniExcelLibs.Attributes;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Exams.ExamManages
+{
+    public class OfflineExamAnalysisExcel
+    {
+        /// <summary>
+        /// 参考人员
+        /// </summary>
+        [ExcelColumnName("参考人员")]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        [ExcelColumnName("部门名称")]
+        public string DepartmentName { get; set; }
+
+        /// <summary>
+        /// 考试标题
+        /// </summary>
+        [ExcelColumnName("考试标题")]
+        public string ExamName { get; set; }
+
+        /// <summary>
+        /// 考试总分
+        /// </summary>
+        [ExcelColumnName("考试总分")]
+        public int TotalScore { get; set; }
+
+        /// <summary>
+        /// 合格分数
+        /// </summary>
+        [ExcelColumnName("合格分数")]
+        public int CutoffScore { get; set; }
+
+        /// <summary>
+        /// 考试分数
+        /// </summary>
+        [ExcelColumnName("考试分数")]
+        public int Score { get; set; }
+
+        /// <summary>
+        /// 考试开始时间
+        /// </summary>
+        [ExcelColumnName("考试开始时间")]
+        public DateTime StartTime { get; set; }
+
+        /// <summary>
+        /// 考试结束时间
+        /// </summary>
+        [ExcelColumnName("考试结束时间")]
+        public DateTime? EndTime { get; set; }
+    }
+}