- 添加学生提交管理服务 (StudentSubmissionService, StudentSubmissionDetailService) - 新增学生提交相关控制器 (StudentSubmissionController, StudentSubmissionDetailController) - 添加学生提交数据传输对象 (StudentSubmissionDetailDto, StudentSubmissionSummaryDto) - 新增学生提交相关页面组件 (StudentExamView, ExamDetailView, StudentCard等) - 添加学生提交信息卡片组件 (SubmissionInfoCard, TeacherSubmissionInfoCard) - 更新数据库迁移文件以支持提交系统
This commit is contained in:
@@ -24,13 +24,9 @@
|
||||
<MudNavMenu Bordered="true" Dense="true" Rounded="true" Color="Color.Error" Margin="Margin.Dense">
|
||||
<ApplicationMainIconCard></ApplicationMainIconCard>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudNavLink Href="/">Dashboard</MudNavLink>
|
||||
<MudNavLink Href="/">Home</MudNavLink>
|
||||
<MudNavLink Href="/exam">Exam</MudNavLink>
|
||||
<MudNavLink Href="/exam">Billing</MudNavLink>
|
||||
<MudNavGroup Title="Settings" Expanded="true">
|
||||
<MudNavLink Href="/users">Users</MudNavLink>
|
||||
<MudNavLink Href="/security">Security</MudNavLink>
|
||||
</MudNavGroup>
|
||||
<MudNavLink Href="/students">Students</MudNavLink>
|
||||
|
||||
<MudSpacer />
|
||||
<MudNavLink Class="align-content-end" Href="/about">About</MudNavLink>
|
||||
@@ -38,7 +34,7 @@
|
||||
<MudSpacer />
|
||||
<MudNavMenu Class="align-content-end " Bordered="true" Dense="true" Rounded="true" Margin="Margin.Dense">
|
||||
<TechHelper.Client.Pages.Global.LoginInOut.LoginInOut></TechHelper.Client.Pages.Global.LoginInOut.LoginInOut>
|
||||
<MudNavLink Class="align-content-end" Href="/about">Setting</MudNavLink>
|
||||
<MudNavLink Class="align-content-end" Href="/Account/Manage">Setting</MudNavLink>
|
||||
</MudNavMenu>
|
||||
</MudPaper>
|
||||
</MudDrawerHeader>
|
||||
@@ -46,7 +42,7 @@
|
||||
<MudMainContent Style="background: #f5f6fb">
|
||||
<SnackErrorBoundary @ref="errorBoundary">
|
||||
<MudPaper Height="calc(100vh - 64px)" Style="background-color:transparent" Class="overflow-hidden px-1 py-2" Elevation="0">
|
||||
<MudPaper Style="background-color:#eeeeeeef" Elevation="3" Class="d-flex w-100 h-100 overflow-hidden pa-2 rounded-xl">
|
||||
<MudPaper Style="background-color:transparent" Elevation="0" Class="d-flex w-100 h-100 overflow-hidden pa-2 rounded-xl">
|
||||
@Body
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
<MudGrid Class="h-100">
|
||||
|
||||
<MudItem xs="12" sm="2" Class="h-100 pa-1 mt-1">
|
||||
<MudItem sm="2" Class="h-100 pa-1 mt-1">
|
||||
<MudStack Class="h-100">
|
||||
<MudText Style="color:white"> BETA版本 </MudText>
|
||||
<MudText Style="color:white" Typo="Typo.h3"><b> 75 </b></MudText>
|
||||
@@ -44,7 +44,7 @@
|
||||
</MudItem>
|
||||
|
||||
|
||||
<MudItem xs="12" sm="9">
|
||||
<MudItem sm="9">
|
||||
<MudPaper Style="background-color:transparent" Class="w-100 mt-n3" Height="100%" Elevation="0">
|
||||
<MudChart ChartType="ChartType.Line" Class="pt-0" ChartSeries="@Series" XAxisLabels="@XAxisLabels" CanHideSeries
|
||||
Height="150px" Width="100%" AxisChartOptions="_axisChartOptions" ChartOptions="options">
|
||||
@@ -72,12 +72,6 @@
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@* <MudChip Size="Size.Small" Text="pink" Variant="Variant.Text" Color="Color.Secondary">成绩趋势</MudChip>
|
||||
<MudChip Size="Size.Small" Text="blue" Variant="Variant.Text" Color="Color.Info">分值区间</MudChip>
|
||||
<MudChip Size="Size.Small" Text="green" Variant="Variant.Text" Color="Color.Success">Success</MudChip>
|
||||
<MudChip Size="Size.Small" Text="orange" Variant="Variant.Text" Color="Color.Warning">Warning</MudChip>
|
||||
<MudChip Size="Size.Small" Text="red" Variant="Variant.Text" Color="Color.Error">Error</MudChip>
|
||||
<MudChip Size="Size.Small" Text="black" Variant="Variant.Text" Color="Color.Dark">Dark</MudChip> *@
|
||||
|
||||
@code {
|
||||
public double[] data = { 25, 77, 28, 5 };
|
||||
|
191
TechHelper.Client/Pages/Common/Exam/SubmissionInfoCard.razor
Normal file
191
TechHelper.Client/Pages/Common/Exam/SubmissionInfoCard.razor
Normal file
@@ -0,0 +1,191 @@
|
||||
@using Entities.DTO
|
||||
@using TechHelper.Client.Services
|
||||
<MudPaper Class="rounded-xl w-100 px-10 ma-3 pt-5" Elevation="5" Height="170px" Style="background-color:#6bc6be">
|
||||
|
||||
<MudGrid Class="h-100">
|
||||
|
||||
<MudItem sm="2" Class="h-100 pa-1 mt-1">
|
||||
<MudStack Class="h-100">
|
||||
<MudText Style="color:white"> BETA版本 </MudText>
|
||||
<MudText Style="color:white" Typo="Typo.h3"><b> @StudentSubmissionDetail.AverageScore </b></MudText>
|
||||
|
||||
<MudPaper Elevation=0 Class="h-100 w-100" Style="background-color:transparent">
|
||||
<MudStack Class="h-100" Row=true>
|
||||
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
|
||||
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
|
||||
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
|
||||
总数:
|
||||
<span style="color: #fefefe;"> @StudentSubmissionDetail.TotalQuestions </span>
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
|
||||
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
|
||||
总分:
|
||||
<span style="color: #fefefe;"> 150 </span>
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
|
||||
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
|
||||
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
|
||||
排名:
|
||||
<span style="color: #fefefe;"> @StudentSubmissionDetail.TotalRank </span>
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
|
||||
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
|
||||
平均:
|
||||
<span style="color: #fefefe;"> @StudentSubmissionDetail.ClassAverageScore </span>
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
|
||||
<MudItem sm="9">
|
||||
<MudPaper Style="background-color:transparent" Class="w-100 mt-n3" Height="100%" Elevation="0">
|
||||
<MudChart ChartType="ChartType.Line" Class="pt-0" ChartSeries="@Series" XAxisLabels="@XAxisLabels" CanHideSeries
|
||||
Height="150px" Width="100%" AxisChartOptions="_axisChartOptions" ChartOptions="options">
|
||||
<CustomGraphics>
|
||||
<style>
|
||||
.heavy {
|
||||
font: normal 12px helvetica;
|
||||
fill: rgb(255,255,255);
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<text x="60" y="15" class="heavy"> 成绩的整体分布情况 </text>
|
||||
</CustomGraphics>
|
||||
</MudChart>
|
||||
</MudPaper>
|
||||
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="1">
|
||||
<MudChipSet T="string" SelectedValuesChanged="HandleSelectedValuesChanged" SelectedValues="@_selected" SelectionMode="SelectionMode.MultiSelection" CheckMark="true">
|
||||
<MudChip Size="Size.Small" Text="类型错误数量分布" Variant="Variant.Text" Color="Color.Default">类型分布</MudChip>
|
||||
<MudChip Size="Size.Small" Text="类型错误成绩分布" Variant="Variant.Text" Color="Color.Primary">课时分布</MudChip>
|
||||
</MudChipSet>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@* <MudChip Size="Size.Small" Text="pink" Variant="Variant.Text" Color="Color.Secondary">成绩趋势</MudChip>
|
||||
<MudChip Size="Size.Small" Text="blue" Variant="Variant.Text" Color="Color.Info">分值区间</MudChip>
|
||||
<MudChip Size="Size.Small" Text="green" Variant="Variant.Text" Color="Color.Success">Success</MudChip>
|
||||
<MudChip Size="Size.Small" Text="orange" Variant="Variant.Text" Color="Color.Warning">Warning</MudChip>
|
||||
<MudChip Size="Size.Small" Text="red" Variant="Variant.Text" Color="Color.Error">Error</MudChip>
|
||||
<MudChip Size="Size.Small" Text="black" Variant="Variant.Text" Color="Color.Dark">Dark</MudChip> *@
|
||||
|
||||
@code {
|
||||
private AxisChartOptions _axisChartOptions = new AxisChartOptions
|
||||
{
|
||||
};
|
||||
private ChartOptions options = new ChartOptions
|
||||
{
|
||||
InterpolationOption = InterpolationOption.NaturalSpline,
|
||||
YAxisFormat = "c2",
|
||||
ShowLegend = false,
|
||||
YAxisLines = false,
|
||||
XAxisLines = false,
|
||||
XAxisLabelPosition = XAxisLabelPosition.None,
|
||||
YAxisLabelPosition = YAxisLabelPosition.None,
|
||||
YAxisTicks = 100,
|
||||
ShowLabels = false,
|
||||
ShowLegendLabels = false
|
||||
|
||||
};
|
||||
public List<ChartSeries> Series = new List<ChartSeries>()
|
||||
{
|
||||
new ChartSeries() { Name = "类型错误数量分布", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } },
|
||||
};
|
||||
public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" };
|
||||
|
||||
Random random = new Random();
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
options.InterpolationOption = InterpolationOption.NaturalSpline;
|
||||
options.YAxisFormat = "c2";
|
||||
options.ShowLegend = false;
|
||||
options.YAxisLines = false;
|
||||
options.XAxisLines = false;
|
||||
options.XAxisLabelPosition = XAxisLabelPosition.None;
|
||||
options.YAxisLabelPosition = YAxisLabelPosition.None;
|
||||
options.ShowLabels = false;
|
||||
options.ShowLegendLabels = false;
|
||||
options.LineStrokeWidth = 1;
|
||||
_axisChartOptions.MatchBoundsToSize = true;
|
||||
|
||||
Series[0].LineDisplayType = LineDisplayType.Area;
|
||||
|
||||
}
|
||||
|
||||
[Parameter]
|
||||
public Guid SubmissionID { get; set; } = Guid.Empty;
|
||||
|
||||
private StudentSubmissionDetailDto StudentSubmissionDetail { get; set; } = new StudentSubmissionDetailDto();
|
||||
private IReadOnlyCollection<string> _selected;
|
||||
#pragma warning restore 1998
|
||||
#nullable restore
|
||||
#line (82, 8) - (143, 1) "D:\AllWX\AllC\TechHelper\TechHelper.Client\Pages\Common\Exam\SubmissionInfoCard.razor"
|
||||
|
||||
|
||||
|
||||
[Inject]
|
||||
public IStudentSubmissionDetailService StudentSubmissionDetailService { get; set; }
|
||||
[Inject]
|
||||
public ISnackbar Snackbar { get; set; }
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (SubmissionID != Guid.Empty)
|
||||
{
|
||||
|
||||
StudentSubmissionDetailDto result;
|
||||
try
|
||||
{
|
||||
result = await StudentSubmissionDetailService.GetSubmissionDetailAsync(SubmissionID);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
StudentSubmissionDetail = result;
|
||||
XAxisLabels = result.ErrorTypeDistribution.Keys.ToArray();
|
||||
Series.Clear();
|
||||
Series.Add(new ChartSeries
|
||||
{
|
||||
Name = "类型错误数量分布",
|
||||
Data = result.ErrorTypeDistribution.Values.Select(d => (double)d).ToArray()
|
||||
});
|
||||
Series.Add(new ChartSeries
|
||||
{
|
||||
Name = "类型错误成绩分布",
|
||||
Data = result.ErrorTypeScoreDistribution.Values.Select(d => (double)d).ToArray()
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"获取提交错误, 请重试, {ex.Message}", Severity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void HandleSelectedValuesChanged(IReadOnlyCollection<string> selected)
|
||||
{
|
||||
Series.ForEach(x => x.Visible = false);
|
||||
|
||||
foreach (var item in selected)
|
||||
{
|
||||
var sv = Series.FirstOrDefault(predicate: x => x.Name == item);
|
||||
if (sv != null)
|
||||
{
|
||||
sv.Visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
<MudPaper Class="rounded-xl w-100 px-10 ma-3 pt-5" Elevation="5" Height="170px" Style="background-color:#6bc6be">
|
||||
|
||||
<MudGrid Class="h-100">
|
||||
|
||||
<MudItem xs="12" sm="2" Class="h-100 pa-1 mt-1">
|
||||
<MudStack Class="h-100">
|
||||
<MudText Style="color:white"> BETA版本 </MudText>
|
||||
<MudText Style="color:white" Typo="Typo.h3"><b> 75 </b></MudText>
|
||||
|
||||
<MudPaper Elevation=0 Class="h-100 w-100" Style="background-color:transparent">
|
||||
<MudStack Class="h-100" Row=true>
|
||||
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
|
||||
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
|
||||
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
|
||||
总数:
|
||||
<span style="color: #fefefe;">15</span>
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
|
||||
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
|
||||
总分:
|
||||
<span style="color: #fefefe;">15</span>
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Elevation=0 Height="100%" Width="100%" Style="background-color:transparent">
|
||||
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
|
||||
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
|
||||
中位:
|
||||
<span style="color: #fefefe;">15</span>
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
<MudPaper Elevation=0 Height="50%" Style="background-color:transparent">
|
||||
<MudText Style="color:#9ed5f7" Typo="Typo.body2">
|
||||
方差:
|
||||
<span style="color: #fefefe;">15</span>
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
|
||||
<MudItem xs="12" sm="9">
|
||||
<MudPaper Style="background-color:transparent" Class="w-100 mt-n3" Height="100%" Elevation="0">
|
||||
<MudChart ChartType="ChartType.Line" Class="pt-0" ChartSeries="@Series" XAxisLabels="@XAxisLabels" CanHideSeries
|
||||
Height="150px" Width="100%" AxisChartOptions="_axisChartOptions" ChartOptions="options">
|
||||
<CustomGraphics>
|
||||
<style>
|
||||
.heavy {
|
||||
font: normal 12px helvetica;
|
||||
fill: rgb(255,255,255);
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<text x="60" y="15" class="heavy"> 成绩的整体分布情况 </text>
|
||||
</CustomGraphics>
|
||||
</MudChart>
|
||||
</MudPaper>
|
||||
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="1">
|
||||
<MudChipSet T="string" SelectedValuesChanged="HandleSelectedValuesChanged" SelectedValues="@_selected" SelectionMode="SelectionMode.MultiSelection" CheckMark="true">
|
||||
<MudChip Size="Size.Small" Text="类型错误数量分布" Variant="Variant.Text" Color="Color.Default">类型分布</MudChip>
|
||||
<MudChip Size="Size.Small" Text="类型错误成绩分布" Variant="Variant.Text" Color="Color.Primary">课时分布</MudChip>
|
||||
</MudChipSet>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@* <MudChip Size="Size.Small" Text="pink" Variant="Variant.Text" Color="Color.Secondary">成绩趋势</MudChip>
|
||||
<MudChip Size="Size.Small" Text="blue" Variant="Variant.Text" Color="Color.Info">分值区间</MudChip>
|
||||
<MudChip Size="Size.Small" Text="green" Variant="Variant.Text" Color="Color.Success">Success</MudChip>
|
||||
<MudChip Size="Size.Small" Text="orange" Variant="Variant.Text" Color="Color.Warning">Warning</MudChip>
|
||||
<MudChip Size="Size.Small" Text="red" Variant="Variant.Text" Color="Color.Error">Error</MudChip>
|
||||
<MudChip Size="Size.Small" Text="black" Variant="Variant.Text" Color="Color.Dark">Dark</MudChip> *@
|
||||
|
||||
@code {
|
||||
public double[] data = { 25, 77, 28, 5 };
|
||||
public string[] labels = { "Oil", "Coal", "Gas", "Biomass" };
|
||||
private AxisChartOptions _axisChartOptions = new AxisChartOptions
|
||||
{
|
||||
};
|
||||
private ChartOptions options = new ChartOptions
|
||||
{
|
||||
InterpolationOption = InterpolationOption.NaturalSpline,
|
||||
YAxisFormat = "c2",
|
||||
ShowLegend = false,
|
||||
YAxisLines = false,
|
||||
XAxisLines = false,
|
||||
XAxisLabelPosition = XAxisLabelPosition.None,
|
||||
YAxisLabelPosition = YAxisLabelPosition.None,
|
||||
YAxisTicks = 100,
|
||||
ShowLabels = false,
|
||||
ShowLegendLabels = false
|
||||
|
||||
};
|
||||
public List<ChartSeries> Series = new List<ChartSeries>()
|
||||
{
|
||||
new ChartSeries() { Name = "类型错误数量分布", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } },
|
||||
new ChartSeries() { Name = "类型错误成绩分布", Data = new double[] { 55, 21, 45, 11, 45, 23, 11, 56, 13 } },
|
||||
};
|
||||
public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" };
|
||||
|
||||
Random random = new Random();
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
options.InterpolationOption = InterpolationOption.NaturalSpline;
|
||||
options.YAxisFormat = "c2";
|
||||
options.ShowLegend = false;
|
||||
options.YAxisLines = false;
|
||||
options.XAxisLines = false;
|
||||
options.XAxisLabelPosition = XAxisLabelPosition.None;
|
||||
options.YAxisLabelPosition = YAxisLabelPosition.None;
|
||||
options.ShowLabels = false;
|
||||
options.ShowLegendLabels = false;
|
||||
options.LineStrokeWidth = 1;
|
||||
_axisChartOptions.MatchBoundsToSize = true;
|
||||
|
||||
Series[0].LineDisplayType = LineDisplayType.Area;
|
||||
|
||||
}
|
||||
|
||||
private IReadOnlyCollection<string> _selected;
|
||||
|
||||
private void HandleSelectedValuesChanged(IReadOnlyCollection<string> selected)
|
||||
{
|
||||
Series.ForEach(x => x.Visible = false);
|
||||
|
||||
foreach(var item in selected)
|
||||
{
|
||||
var sv = Series.FirstOrDefault(predicate: x => x.Name == item);
|
||||
if(sv != null)
|
||||
{
|
||||
sv.Visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
TechHelper.Client/Pages/Exam/AssignmentManagerCard.razor
Normal file
7
TechHelper.Client/Pages/Exam/AssignmentManagerCard.razor
Normal file
@@ -0,0 +1,7 @@
|
||||
<MudPaper>
|
||||
<MudText> ExamName </MudText>
|
||||
<MudText> 已经指派人数 </MudText>
|
||||
<MudText> 总人数 </MudText>
|
||||
<MudText> 平均S </MudText>
|
||||
<MudText> 指派 </MudText>
|
||||
</MudPaper>
|
@@ -21,8 +21,6 @@ else
|
||||
<MudPaper Class="d-flex flex-wrap flex-grow-0 gap-4" Height="100%" Width="100%">
|
||||
@foreach (var item in examDtos)
|
||||
{
|
||||
@* <ExamPreview AssignmentDto="item" Width="256px" Height="256px"> </ExamPreview> *@
|
||||
<AssignmentInfoCard></AssignmentInfoCard>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
|
@@ -1,7 +1,34 @@
|
||||
@page "/exam"
|
||||
@using TechHelper.Client.Pages.Student.BaseInfoCard
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<AuthorizeView Roles="Teacher">
|
||||
<Authorized>
|
||||
<MudPaper Class="rounded-xl ma-2 px-2 overflow-auto w-100 h-100">
|
||||
<StudentSubmissionPreviewTableCard />
|
||||
</MudPaper>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
|
||||
<MudText>HELLO WORLD</MudText>
|
||||
<AuthorizeView Roles="Student">
|
||||
<Authorized>
|
||||
<MudPaper Class="rounded-xl ma-2 px-2 overflow-auto w-100 h-100">
|
||||
<StudentSubmissionPreviewTableCard />
|
||||
</MudPaper>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
@code {
|
||||
|
||||
|
||||
[CascadingParameter]
|
||||
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (authenticationStateTask is null)
|
||||
{
|
||||
NavigationManager.Refresh(forceReload: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
TechHelper.Client/Pages/Exam/StudentExamView.razor
Normal file
4
TechHelper.Client/Pages/Exam/StudentExamView.razor
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
@code {
|
||||
|
||||
}
|
@@ -7,7 +7,6 @@
|
||||
<TechHelper.Client.Pages.Student.HomePage />
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||
|
@@ -1,39 +1,97 @@
|
||||
<MudPaper Class="ma-2 pa-2 rounded-xl d-flex flex-column flex-grow-1 overflow-auto" MaxHeight="100%">
|
||||
@using TechHelper.Client.Services
|
||||
@inject IStudentSubmissionService StudentSubmissionService
|
||||
|
||||
<MudPaper Class="ma-2 pa-2 rounded-xl d-flex flex-column flex-grow-1 overflow-auto" MaxHeight="100%">
|
||||
|
||||
<StudentSubmissionPreviewCard />
|
||||
@foreach (var submission in _studentSubmissions)
|
||||
@if (_isLoading)
|
||||
{
|
||||
<StudentSubmissionPreviewCard StudentSubmission="@submission" />
|
||||
<div class="d-flex justify-content-center align-items-center" style="height: 200px;">
|
||||
<MudProgressCircular Color="Color.Primary" Size="Size.Large" />
|
||||
</div>
|
||||
}
|
||||
else if (_studentSubmissions == null || _studentSubmissions.Count == 0)
|
||||
{
|
||||
<div class="d-flex justify-content-center align-items-center" style="height: 200px;">
|
||||
<MudText TextColor="Color.TextSecondary" Align="Align.Center">暂无提交记录</MudText>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var submission in _studentSubmissions)
|
||||
{
|
||||
<StudentSubmissionPreviewCard StudentSubmission="@submission" />
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
// 假设的学生提交数据模型
|
||||
// 学生提交数据模型
|
||||
public class StudentSubmission
|
||||
{
|
||||
public string StudentName { get; set; }
|
||||
public int TotalProblems { get; set; }
|
||||
public int ErrorCount { get; set; }
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public float Score { get; set; }
|
||||
public string AssignmentName { get; set; }
|
||||
public string Status { get; set; }
|
||||
public TimeSpan TimeSpent { get; set; }
|
||||
public int Score { get; set; }
|
||||
}
|
||||
|
||||
// 模拟数据列表
|
||||
// 学生提交列表
|
||||
private List<StudentSubmission> _studentSubmissions = new();
|
||||
private bool _isLoading = true;
|
||||
|
||||
protected override void OnInitialized()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// 模拟获取或初始化数据,实际应用中可能来自数据库或API
|
||||
_studentSubmissions = new List<StudentSubmission>
|
||||
{
|
||||
new() { StudentName = "张三", TotalProblems = 10, ErrorCount = 2, TimeSpent = TimeSpan.FromMinutes(25), Score = 80 },
|
||||
new() { StudentName = "李四", TotalProblems = 10, ErrorCount = 1, TimeSpent = TimeSpan.FromMinutes(20), Score = 90 },
|
||||
new() { StudentName = "王五", TotalProblems = 10, ErrorCount = 5, TimeSpent = TimeSpan.FromMinutes(30), Score = 50 },
|
||||
new() { StudentName = "赵六", TotalProblems = 10, ErrorCount = 3, TimeSpent = TimeSpan.FromMinutes(28), Score = 70 },
|
||||
new() { StudentName = "钱七", TotalProblems = 10, ErrorCount = 0, TimeSpent = TimeSpan.FromMinutes(18), Score = 100 }
|
||||
// ... 可以添加更多模拟数据
|
||||
};
|
||||
await LoadStudentSubmissions();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadStudentSubmissions()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
var result = await StudentSubmissionService.GetMySubmissionsAsync();
|
||||
|
||||
if (result.Status && result.Result != null)
|
||||
{
|
||||
// 从服务器获取的数据映射到我们的模型
|
||||
var submissions = result.Result as List<Entities.DTO.StudentSubmissionSummaryDto>;
|
||||
|
||||
if (submissions != null)
|
||||
{
|
||||
_studentSubmissions = submissions.Select(submission => new StudentSubmission
|
||||
{
|
||||
AssignmentName = submission.AssignmentName,
|
||||
CreatedDate = submission.CreatedDate,
|
||||
ErrorCount = submission.ErrorCount,
|
||||
Score = submission.Score,
|
||||
StudentName = submission.StudentName,
|
||||
Status = submission.Status,
|
||||
TotalProblems = submission.TotalQuestions,
|
||||
TimeSpent = TimeSpan.FromMinutes(30) // 默认值,实际应用中可以从服务器获取
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果API调用失败,使用空列表
|
||||
_studentSubmissions = new List<StudentSubmission>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 处理异常,可以记录日志
|
||||
_studentSubmissions = new List<StudentSubmission>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,10 +2,10 @@
|
||||
@using TechHelper.Client.Pages.Student.BaseInfoCard;
|
||||
@using TechHelper.Client.Pages.Common;
|
||||
|
||||
<MudPaper Class="w-100 h-100 d-flex flex-row">
|
||||
<MudPaper Class="flex-grow-1 mx-2 d-flex flex-column">
|
||||
<AssignmentInfoCard></AssignmentInfoCard>
|
||||
<MudPaper Class="d-flex flex-row">
|
||||
<MudPaper Class="w-100 h-100 d-flex flex-row" Style="background-color: transparent" Elevation="0">
|
||||
<MudPaper Class="flex-grow-1 mx-2 d-flex flex-column" Style="background-color:transparent" Elevation="0">
|
||||
<SubmissionInfoCard ></SubmissionInfoCard>
|
||||
<MudPaper Class="d-flex flex-row" Style="background-color:transparent" Elevation="0">
|
||||
<NotifyCard></NotifyCard>
|
||||
<HomeworkCard></HomeworkCard>
|
||||
<NotifyCard></NotifyCard>
|
||||
@@ -13,7 +13,7 @@
|
||||
</MudPaper>
|
||||
<StudentSubmissionPreviewTableCard></StudentSubmissionPreviewTableCard>
|
||||
</MudPaper>
|
||||
<MudPaper Width="300px" Class="mx-2 align-content-center d-flex flex-column flex-grow-1">
|
||||
<MudPaper Width="300px" Class="mx-2 align-content-center d-flex flex-column flex-grow-1" Style="background-color: transparent" Elevation="0">
|
||||
<HeadIconCard></HeadIconCard>
|
||||
<TotalErrorQuestionType></TotalErrorQuestionType>
|
||||
|
||||
@@ -22,42 +22,4 @@
|
||||
|
||||
|
||||
@code {
|
||||
public double[] data = { 25, 77, 28, 5 };
|
||||
public string[] labels = { "Oil", "Coal", "Gas", "Biomass" };
|
||||
private AxisChartOptions _axisChartOptions = new AxisChartOptions();
|
||||
private ChartOptions options = new ChartOptions();
|
||||
public List<ChartSeries> Series = new List<ChartSeries>()
|
||||
{
|
||||
new ChartSeries() { Name = "Series 1", Data = new double[] { 90, 79, 72, 69, 62, 62, 55, 65, 70 } },
|
||||
new ChartSeries() { Name = "Series 2", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } },
|
||||
};
|
||||
public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" };
|
||||
|
||||
Random random = new Random();
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
options.InterpolationOption = InterpolationOption.NaturalSpline;
|
||||
options.YAxisFormat = "c2";
|
||||
_axisChartOptions.MatchBoundsToSize = true;
|
||||
}
|
||||
|
||||
public void RandomizeData()
|
||||
{
|
||||
foreach (var series in Series)
|
||||
{
|
||||
for (int i = 0; i < series.Data.Length - 1; i++)
|
||||
{
|
||||
series.Data[i] = random.NextDouble() * 100 + 10;
|
||||
}
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
void OnClickMenu(InterpolationOption interpolationOption)
|
||||
{
|
||||
options.InterpolationOption = interpolationOption;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
3
TechHelper.Client/Pages/Teacher/ExamDetailView.razor
Normal file
3
TechHelper.Client/Pages/Teacher/ExamDetailView.razor
Normal file
@@ -0,0 +1,3 @@
|
||||
<MudPaper>
|
||||
<MudText> EXAM NAME</MudText>
|
||||
</MudPaper>
|
7
TechHelper.Client/Pages/Teacher/StudentCard.razor
Normal file
7
TechHelper.Client/Pages/Teacher/StudentCard.razor
Normal file
@@ -0,0 +1,7 @@
|
||||
@using Entities.DTO
|
||||
<h3>StudentCard</h3>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public StudentDto StudentDto{ get; set; }
|
||||
}
|
@@ -6,7 +6,9 @@
|
||||
|
||||
@foreach(var cs in ClassStudents)
|
||||
{
|
||||
<MudText> @cs.DisplayName </MudText>
|
||||
<StudentCard StudentDto="@cs">
|
||||
</StudentCard>
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@@ -39,6 +39,8 @@ builder.Services.AddLocalStorageServices();
|
||||
|
||||
builder.Services.AddScoped<IAuthenticationClientService, AuthenticationClientService>();
|
||||
builder.Services.AddScoped<IExamService, ExamService>();
|
||||
builder.Services.AddScoped<IStudentSubmissionService, StudentSubmissionService>();
|
||||
builder.Services.AddScoped<IStudentSubmissionDetailService, StudentSubmissionDetailService>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, AuthStateProvider>();
|
||||
builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();
|
||||
builder.Services.AddScoped<IClassServices, ClasssServices>();
|
||||
|
@@ -54,6 +54,11 @@ namespace TechHelper.Client.Services
|
||||
}
|
||||
}
|
||||
|
||||
public StudentDto GetStudents(byte Class)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<ResponseDto> UserRegister(UserRegistrationToClassDto userRegistrationToClassDto)
|
||||
{
|
||||
try
|
||||
|
@@ -10,5 +10,6 @@ namespace TechHelper.Client.Services
|
||||
public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass);
|
||||
public Task<ApiResponse> GetClassStudents();
|
||||
public Task<ApiResponse> GetGradeClasses(byte grade);
|
||||
public StudentDto GetStudents(byte Class);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,14 @@
|
||||
using Entities.DTO;
|
||||
|
||||
namespace TechHelper.Client.Services
|
||||
{
|
||||
public interface IStudentSubmissionDetailService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取学生提交的详细信息
|
||||
/// </summary>
|
||||
/// <param name="submissionId">提交ID</param>
|
||||
/// <returns>学生提交详细信息</returns>
|
||||
Task<StudentSubmissionDetailDto> GetSubmissionDetailAsync(Guid submissionId);
|
||||
}
|
||||
}
|
22
TechHelper.Client/Services/IStudentSubmissionService.cs
Normal file
22
TechHelper.Client/Services/IStudentSubmissionService.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Entities.DTO;
|
||||
using TechHelper.Services;
|
||||
|
||||
namespace TechHelper.Client.Services
|
||||
{
|
||||
public interface IStudentSubmissionService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前学生的所有提交摘要
|
||||
/// </summary>
|
||||
/// <returns>学生提交摘要列表</returns>
|
||||
Task<ApiResponse> GetMySubmissionsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前学生的提交摘要(分页)
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">页码,默认为1</param>
|
||||
/// <param name="pageSize">每页数量,默认为10</param>
|
||||
/// <returns>分页的学生提交摘要列表</returns>
|
||||
Task<ApiResponse> GetMySubmissionsPagedAsync(int pageNumber = 1, int pageSize = 10);
|
||||
}
|
||||
}
|
26
TechHelper.Client/Services/StudentSubmissionDetailService.cs
Normal file
26
TechHelper.Client/Services/StudentSubmissionDetailService.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Entities.DTO;
|
||||
using TechHelper.Client.HttpRepository;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace TechHelper.Client.Services
|
||||
{
|
||||
public class StudentSubmissionDetailService : IStudentSubmissionDetailService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public StudentSubmissionDetailService(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<StudentSubmissionDetailDto> GetSubmissionDetailAsync(Guid submissionId)
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"api/student-submission-detail/{submissionId}");
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await response.Content.ReadFromJsonAsync<StudentSubmissionDetailDto>();
|
||||
}
|
||||
throw new HttpRequestException($"获取学生提交详细信息失败: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
}
|
75
TechHelper.Client/Services/StudentSubmissionService.cs
Normal file
75
TechHelper.Client/Services/StudentSubmissionService.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Entities.DTO;
|
||||
using TechHelper.Services;
|
||||
using System.Net.Http.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TechHelper.Client.Services
|
||||
{
|
||||
public class StudentSubmissionService : IStudentSubmissionService
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public StudentSubmissionService(HttpClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前学生的所有提交摘要
|
||||
/// </summary>
|
||||
/// <returns>学生提交摘要列表</returns>
|
||||
public async Task<ApiResponse> GetMySubmissionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _client.GetAsync("student-submission/my-submissions");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var submissions = JsonConvert.DeserializeObject<List<StudentSubmissionSummaryDto>>(content);
|
||||
return ApiResponse.Success(result: submissions);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse.Error(message: $"获取学生提交信息失败: {response.StatusCode} - {errorContent}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error(message: $"内部错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前学生的提交摘要(分页)
|
||||
/// </summary>
|
||||
/// <param name="pageNumber">页码,默认为1</param>
|
||||
/// <param name="pageSize">每页数量,默认为10</param>
|
||||
/// <returns>分页的学生提交摘要列表</returns>
|
||||
public async Task<ApiResponse> GetMySubmissionsPagedAsync(int pageNumber = 1, int pageSize = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _client.GetAsync($"student-submission/my-submissions-paged?pageNumber={pageNumber}&pageSize={pageSize}");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<StudentSubmissionSummaryResponseDto>(content);
|
||||
return ApiResponse.Success(result: result);
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse.Error(message: $"获取学生提交信息失败: {response.StatusCode} - {errorContent}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error(message: $"内部错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +1,44 @@
|
||||
@inherits LayoutComponentBase
|
||||
@layout AccountLayout
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<AuthorizeView Roles="Teacher">
|
||||
<Authorized>
|
||||
<MudPaper Class="d-flex flex-row flex-grow-1 overflow-hidden" Height="100%">
|
||||
<MudPaper Width="200px">
|
||||
<MudDivider Class="flex-grow-0" />
|
||||
<ExamNavMenu />
|
||||
</MudPaper>
|
||||
<MudPaper Class="d-flex flex-grow-1 overflow-auto">
|
||||
@Body
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
<MudPaper Class="d-flex flex-row flex-grow-1 overflow-hidden" Height="100%">
|
||||
<AuthorizeView Roles="Student">
|
||||
<Authorized>
|
||||
<MudPaper Class="d-flex flex-row flex-grow-1 overflow-hidden" Height="100%">
|
||||
<MudPaper Width="200px">
|
||||
<MudDivider Class="flex-grow-0" />
|
||||
<ExamNavMenu />
|
||||
</MudPaper>
|
||||
<MudPaper Class="d-flex h-100 w-100 flex-grow-1 overflow-auto">
|
||||
@Body
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
<MudPaper Width="200px">
|
||||
<MudDivider Class="flex-grow-0" />
|
||||
<ExamNavMenu />
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="flex-grow-1 overflow-auto">
|
||||
@Body
|
||||
</MudPaper>
|
||||
</MudPaper>
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (authenticationStateTask is null)
|
||||
{
|
||||
NavigationManager.Refresh(forceReload: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,39 @@
|
||||
@using Microsoft.AspNetCore.Identity
|
||||
|
||||
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="exam/create" Match="NavLinkMatch.All">创建</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="exam/manage">管理</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="Account/Manage/ChangePassword">Password</NavLink>
|
||||
</li>
|
||||
@* <li class="nav-item">
|
||||
<NavLink class="nav-link" href="Account/Manage/TwoFactorAuthentication">Two-factor authentication</NavLink>
|
||||
</li> *@
|
||||
</ul>
|
||||
|
||||
<AuthorizeView Roles="Teacher">
|
||||
<Authorized>
|
||||
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="exam/create" Match="NavLinkMatch.All">创建</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="exam/manage">管理</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="Account/Manage/ChangePassword">Password</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
|
||||
<AuthorizeView Roles="Student">
|
||||
<Authorized>
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="exam/studentHomework" Match="NavLinkMatch.All">Homework</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="exam/manage">管理</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
@code {
|
||||
private bool hasExternalLogins;
|
||||
private bool hasExternalLogins;
|
||||
|
||||
}
|
||||
|
@@ -9,3 +9,17 @@
|
||||
@Body
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private Task<AuthenticationState> authenticationStateTask { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (authenticationStateTask is null)
|
||||
{
|
||||
// NavigationManager.Refresh(forceReload: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,8 @@
|
||||
<NavLink class="nav-link" href="Account/Manage/Class">Class</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="Account/Manage/ChangePassword">Password</NavLink>
|
||||
<NavLink class="nav-link" href="Account/Manage/ChangePassword">重设密码</NavLink>
|
||||
</li>
|
||||
@* <li class="nav-item">
|
||||
<NavLink class="nav-link" href="Account/Manage/TwoFactorAuthentication">Two-factor authentication</NavLink>
|
||||
</li> *@
|
||||
</ul>
|
||||
|
||||
@code {
|
||||
|
Reference in New Issue
Block a user