优化服务器端分页
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Jiaowu.Api.Contracts;
|
||||||
using Jiaowu.Api.Domain.Academic;
|
using Jiaowu.Api.Domain.Academic;
|
||||||
using Jiaowu.Api.Domain.Identity;
|
using Jiaowu.Api.Domain.Identity;
|
||||||
using Jiaowu.Api.Infrastructure.Auth;
|
using Jiaowu.Api.Infrastructure.Auth;
|
||||||
@@ -176,8 +177,13 @@ public sealed class CourseAdjustmentsController(
|
|||||||
public async Task<ActionResult> GetMine(
|
public async Task<ActionResult> GetMine(
|
||||||
Guid? academicTermId,
|
Guid? academicTermId,
|
||||||
CourseAdjustmentStatus? status,
|
CourseAdjustmentStatus? status,
|
||||||
CancellationToken cancellationToken)
|
int page = 1,
|
||||||
|
int pageSize = 20,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (page < 1 || pageSize is < 1 or > 100)
|
||||||
|
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||||
|
|
||||||
var userId = currentUserDataScope.Current.UserId;
|
var userId = currentUserDataScope.Current.UserId;
|
||||||
var source = db.CourseAdjustments.AsNoTracking()
|
var source = db.CourseAdjustments.AsNoTracking()
|
||||||
.Where(x => x.ApplicantUserId == userId);
|
.Where(x => x.ApplicantUserId == userId);
|
||||||
@@ -187,10 +193,15 @@ public sealed class CourseAdjustmentsController(
|
|||||||
if (status.HasValue)
|
if (status.HasValue)
|
||||||
source = source.Where(x => x.Status == status);
|
source = source.Where(x => x.Status == status);
|
||||||
|
|
||||||
return Ok(await source
|
var total = await source.CountAsync(cancellationToken);
|
||||||
|
var items = await source
|
||||||
.OrderByDescending(x => x.CreatedAt)
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.ThenByDescending(x => x.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
.Select(AdjustmentProjection())
|
.Select(AdjustmentProjection())
|
||||||
.ToListAsync(cancellationToken));
|
.ToListAsync(cancellationToken);
|
||||||
|
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════ Pending reviews ═══════════════
|
// ═══════════════ Pending reviews ═══════════════
|
||||||
@@ -199,8 +210,13 @@ public sealed class CourseAdjustmentsController(
|
|||||||
[Authorize(Roles = Reviewers)]
|
[Authorize(Roles = Reviewers)]
|
||||||
public async Task<ActionResult> GetPendingReviews(
|
public async Task<ActionResult> GetPendingReviews(
|
||||||
Guid? academicTermId,
|
Guid? academicTermId,
|
||||||
CancellationToken cancellationToken)
|
int page = 1,
|
||||||
|
int pageSize = 20,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (page < 1 || pageSize is < 1 or > 100)
|
||||||
|
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||||
|
|
||||||
var scope = currentUserDataScope.Current;
|
var scope = currentUserDataScope.Current;
|
||||||
var source = db.CourseAdjustments.AsNoTracking()
|
var source = db.CourseAdjustments.AsNoTracking()
|
||||||
.Where(x => x.Status == CourseAdjustmentStatus.Submitted);
|
.Where(x => x.Status == CourseAdjustmentStatus.Submitted);
|
||||||
@@ -211,10 +227,15 @@ public sealed class CourseAdjustmentsController(
|
|||||||
source = source.Where(x =>
|
source = source.Where(x =>
|
||||||
x.TeachingTask!.AcademicTermId == academicTermId);
|
x.TeachingTask!.AcademicTermId == academicTermId);
|
||||||
|
|
||||||
return Ok(await source
|
var total = await source.CountAsync(cancellationToken);
|
||||||
|
var items = await source
|
||||||
.OrderByDescending(x => x.SubmittedAt)
|
.OrderByDescending(x => x.SubmittedAt)
|
||||||
|
.ThenByDescending(x => x.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
.Select(AdjustmentProjection())
|
.Select(AdjustmentProjection())
|
||||||
.ToListAsync(cancellationToken));
|
.ToListAsync(cancellationToken);
|
||||||
|
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════ Detail ═══════════════
|
// ═══════════════ Detail ═══════════════
|
||||||
|
|||||||
@@ -17,17 +17,53 @@ public sealed class StudentStatusChangesController(
|
|||||||
ICurrentUserDataScope currentUserDataScope) : ControllerBase
|
ICurrentUserDataScope currentUserDataScope) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult> Get(CancellationToken token)
|
public async Task<ActionResult<StudentStatusChangePage>> Get(
|
||||||
|
int page = 1,
|
||||||
|
int pageSize = 20,
|
||||||
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
if (page < 1 || pageSize is < 1 or > 100)
|
||||||
|
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||||
|
|
||||||
var source = ScopedChanges().AsNoTracking();
|
var source = ScopedChanges().AsNoTracking();
|
||||||
return Ok(await source.OrderByDescending(x => x.SubmittedAt).Select(x => new
|
var userScope = currentUserDataScope.Current;
|
||||||
{
|
var total = await source.CountAsync(token);
|
||||||
x.Id, x.StudentId, x.Student!.StudentNumber, x.Student.Name,
|
var actionableTotal = await source.CountAsync(change =>
|
||||||
ClassName = x.Student.AdministrativeClass!.Name,
|
(change.State == StudentStatusChangeState.Submitted &&
|
||||||
CollegeName = x.Student.AdministrativeClass.Major!.College!.Name,
|
userScope.IsInRole(SystemRoles.Counselor)) ||
|
||||||
x.Type, x.OriginalStatus, x.TargetStatus, x.Reason, x.State,
|
(change.State == StudentStatusChangeState.CounselorApproved &&
|
||||||
x.ReviewComment, x.SubmittedAt, x.ReviewedAt, x.ApprovedAt
|
userScope.IsInRole(SystemRoles.CollegeAdmin)) ||
|
||||||
}).ToListAsync(token));
|
(change.State == StudentStatusChangeState.CollegeApproved &&
|
||||||
|
(userScope.IsInRole(SystemRoles.AcademicAdmin) ||
|
||||||
|
userScope.IsInRole(SystemRoles.SuperAdmin))), token);
|
||||||
|
var finishedTotal = await source.CountAsync(change =>
|
||||||
|
change.State == StudentStatusChangeState.Approved ||
|
||||||
|
change.State == StudentStatusChangeState.Rejected ||
|
||||||
|
change.State == StudentStatusChangeState.Cancelled, token);
|
||||||
|
var items = await source
|
||||||
|
.OrderByDescending(x => x.SubmittedAt)
|
||||||
|
.ThenByDescending(x => x.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.Select(x => new StudentStatusChangeListItem(
|
||||||
|
x.Id,
|
||||||
|
x.StudentId,
|
||||||
|
x.Student!.StudentNumber,
|
||||||
|
x.Student.Name,
|
||||||
|
x.Student.AdministrativeClass!.Name,
|
||||||
|
x.Student.AdministrativeClass.Major!.College!.Name,
|
||||||
|
x.Type,
|
||||||
|
x.OriginalStatus,
|
||||||
|
x.TargetStatus,
|
||||||
|
x.Reason,
|
||||||
|
x.State,
|
||||||
|
x.ReviewComment,
|
||||||
|
x.SubmittedAt,
|
||||||
|
x.ReviewedAt,
|
||||||
|
x.ApprovedAt))
|
||||||
|
.ToListAsync(token);
|
||||||
|
return Ok(new StudentStatusChangePage(
|
||||||
|
items, total, page, pageSize, actionableTotal, finishedTotal));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("options")]
|
[HttpGet("options")]
|
||||||
@@ -199,6 +235,31 @@ public sealed class StudentStatusChangesController(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed record StudentStatusChangePage(
|
||||||
|
IReadOnlyCollection<StudentStatusChangeListItem> Items,
|
||||||
|
int Total,
|
||||||
|
int Page,
|
||||||
|
int PageSize,
|
||||||
|
int ActionableTotal,
|
||||||
|
int FinishedTotal);
|
||||||
|
|
||||||
|
public sealed record StudentStatusChangeListItem(
|
||||||
|
Guid Id,
|
||||||
|
Guid StudentId,
|
||||||
|
string StudentNumber,
|
||||||
|
string Name,
|
||||||
|
string ClassName,
|
||||||
|
string CollegeName,
|
||||||
|
StudentStatusChangeType Type,
|
||||||
|
StudentStatus OriginalStatus,
|
||||||
|
StudentStatus TargetStatus,
|
||||||
|
string Reason,
|
||||||
|
StudentStatusChangeState State,
|
||||||
|
string? ReviewComment,
|
||||||
|
DateTime SubmittedAt,
|
||||||
|
DateTime? ReviewedAt,
|
||||||
|
DateTime? ApprovedAt);
|
||||||
|
|
||||||
public sealed record StudentStatusChangeRequest(
|
public sealed record StudentStatusChangeRequest(
|
||||||
StudentStatusChangeType Type,
|
StudentStatusChangeType Type,
|
||||||
[Required, MinLength(10), MaxLength(1000)] string Reason);
|
[Required, MinLength(10), MaxLength(1000)] string Reason);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Jiaowu.Api.Contracts;
|
||||||
using Jiaowu.Api.Domain.Academic;
|
using Jiaowu.Api.Domain.Academic;
|
||||||
using Jiaowu.Api.Domain.Identity;
|
using Jiaowu.Api.Domain.Identity;
|
||||||
using Jiaowu.Api.Infrastructure.Auth;
|
using Jiaowu.Api.Infrastructure.Auth;
|
||||||
@@ -46,17 +47,26 @@ public sealed class TeacherCourseApplicationsController(
|
|||||||
[Authorize(Roles = SystemRoles.Teacher)]
|
[Authorize(Roles = SystemRoles.Teacher)]
|
||||||
public async Task<ActionResult> GetMine(
|
public async Task<ActionResult> GetMine(
|
||||||
Guid? academicTermId,
|
Guid? academicTermId,
|
||||||
CancellationToken cancellationToken)
|
int page = 1,
|
||||||
|
int pageSize = 20,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (page < 1 || pageSize is < 1 or > 100)
|
||||||
|
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||||
|
|
||||||
var teacher = await CurrentTeacherAsync(cancellationToken);
|
var teacher = await CurrentTeacherAsync(cancellationToken);
|
||||||
if (teacher is null) return ConflictProblem("当前账号尚未关联在职教师档案。");
|
if (teacher is null) return ConflictProblem("当前账号尚未关联在职教师档案。");
|
||||||
var source = db.TeacherCourseApplications.AsNoTracking()
|
var source = db.TeacherCourseApplications.AsNoTracking()
|
||||||
.Where(x => x.TeacherId == teacher.Id);
|
.Where(x => x.TeacherId == teacher.Id);
|
||||||
if (academicTermId.HasValue)
|
if (academicTermId.HasValue)
|
||||||
source = source.Where(x => x.AcademicTermId == academicTermId);
|
source = source.Where(x => x.AcademicTermId == academicTermId);
|
||||||
return Ok(await source
|
var total = await source.CountAsync(cancellationToken);
|
||||||
|
var items = await source
|
||||||
.OrderByDescending(x => x.AcademicTerm!.StartDate)
|
.OrderByDescending(x => x.AcademicTerm!.StartDate)
|
||||||
.ThenBy(x => x.Course!.Code)
|
.ThenBy(x => x.Course!.Code)
|
||||||
|
.ThenBy(x => x.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
.Select(x => new
|
.Select(x => new
|
||||||
{
|
{
|
||||||
x.Id,
|
x.Id,
|
||||||
@@ -72,7 +82,8 @@ public sealed class TeacherCourseApplicationsController(
|
|||||||
x.SubmittedAt,
|
x.SubmittedAt,
|
||||||
x.ReviewedAt
|
x.ReviewedAt
|
||||||
})
|
})
|
||||||
.ToListAsync(cancellationToken));
|
.ToListAsync(cancellationToken);
|
||||||
|
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("mine")]
|
[HttpPost("mine")]
|
||||||
@@ -144,15 +155,24 @@ public sealed class TeacherCourseApplicationsController(
|
|||||||
public async Task<ActionResult> GetReviews(
|
public async Task<ActionResult> GetReviews(
|
||||||
Guid? academicTermId,
|
Guid? academicTermId,
|
||||||
TeacherCourseApplicationStatus? status,
|
TeacherCourseApplicationStatus? status,
|
||||||
CancellationToken cancellationToken)
|
int page = 1,
|
||||||
|
int pageSize = 20,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (page < 1 || pageSize is < 1 or > 100)
|
||||||
|
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
|
||||||
|
|
||||||
var source = ScopedApplications().AsNoTracking();
|
var source = ScopedApplications().AsNoTracking();
|
||||||
if (academicTermId.HasValue)
|
if (academicTermId.HasValue)
|
||||||
source = source.Where(x => x.AcademicTermId == academicTermId);
|
source = source.Where(x => x.AcademicTermId == academicTermId);
|
||||||
if (status.HasValue) source = source.Where(x => x.Status == status);
|
if (status.HasValue) source = source.Where(x => x.Status == status);
|
||||||
return Ok(await source
|
var total = await source.CountAsync(cancellationToken);
|
||||||
|
var items = await source
|
||||||
.OrderBy(x => x.Status)
|
.OrderBy(x => x.Status)
|
||||||
.ThenByDescending(x => x.SubmittedAt)
|
.ThenByDescending(x => x.SubmittedAt)
|
||||||
|
.ThenBy(x => x.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
.Select(x => new
|
.Select(x => new
|
||||||
{
|
{
|
||||||
x.Id,
|
x.Id,
|
||||||
@@ -172,7 +192,8 @@ public sealed class TeacherCourseApplicationsController(
|
|||||||
x.SubmittedAt,
|
x.SubmittedAt,
|
||||||
x.ReviewedAt
|
x.ReviewedAt
|
||||||
})
|
})
|
||||||
.ToListAsync(cancellationToken));
|
.ToListAsync(cancellationToken);
|
||||||
|
return Ok(new PagedResult<object>(items, total, page, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("assignment-options")]
|
[HttpGet("assignment-options")]
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ const sessionTask = ref<any>(null)
|
|||||||
const sessionLoading = ref(false)
|
const sessionLoading = ref(false)
|
||||||
const termId = ref<string>()
|
const termId = ref<string>()
|
||||||
const tab = ref(isManager.value ? 'reviews' : 'mine')
|
const tab = ref(isManager.value ? 'reviews' : 'mine')
|
||||||
|
const minePage = ref(1)
|
||||||
|
const mineTotal = ref(0)
|
||||||
|
const reviewPage = ref(1)
|
||||||
|
const reviewTotal = ref(0)
|
||||||
|
const pageSize = 20
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
teachingTaskId: undefined as string | undefined,
|
teachingTaskId: undefined as string | undefined,
|
||||||
@@ -79,21 +84,30 @@ function sessionLabel(session: any) {
|
|||||||
return `第 ${session.week} 周 · ${session.date} 周${weekdayLabel(session.dayOfWeek)} · 第 ${session.startPeriod}–${end} 节${room}`
|
return `第 ${session.week} 周 · ${session.date} 周${weekdayLabel(session.dayOfWeek)} · 第 ${session.startPeriod}–${end} 节${room}`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load(resetPages = false) {
|
||||||
|
if (resetPages) {
|
||||||
|
minePage.value = 1
|
||||||
|
reviewPage.value = 1
|
||||||
|
}
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
reviewLoading.value = isManager.value
|
||||||
try {
|
try {
|
||||||
if (isTeacher.value || (!isManager.value)) {
|
if (isTeacher.value || (!isManager.value)) {
|
||||||
adjustments.value = (await http.get('/course-adjustments/mine', {
|
const response = await http.get('/course-adjustments/mine', {
|
||||||
params: { academicTermId: termId.value },
|
params: { academicTermId: termId.value, page: minePage.value, pageSize },
|
||||||
})).data
|
})
|
||||||
|
adjustments.value = response.data.items
|
||||||
|
mineTotal.value = response.data.total
|
||||||
}
|
}
|
||||||
if (isManager.value) {
|
if (isManager.value) {
|
||||||
pendingReviews.value = (await http.get('/course-adjustments/pending-reviews', {
|
const response = await http.get('/course-adjustments/pending-reviews', {
|
||||||
params: { academicTermId: termId.value },
|
params: { academicTermId: termId.value, page: reviewPage.value, pageSize },
|
||||||
})).data
|
})
|
||||||
|
pendingReviews.value = response.data.items
|
||||||
|
reviewTotal.value = response.data.total
|
||||||
}
|
}
|
||||||
} catch (e) { ElMessage.error(apiErrorMessage(e)) }
|
} catch (e) { ElMessage.error(apiErrorMessage(e)) }
|
||||||
finally { loading.value = false }
|
finally { loading.value = false; reviewLoading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCreate() {
|
function openCreate() {
|
||||||
@@ -255,12 +269,12 @@ function showSub(type: string) { return type === 'Substitute' }
|
|||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px;align-items:center">
|
<div style="display:flex;gap:8px;align-items:center">
|
||||||
<el-button v-if="isTeacher" type="primary" :icon="Plus" @click="openCreate">提交申请</el-button>
|
<el-button v-if="isTeacher" type="primary" :icon="Plus" @click="openCreate">提交申请</el-button>
|
||||||
<el-button :icon="Refresh" @click="load">刷新</el-button>
|
<el-button :icon="Refresh" @click="() => load()">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="adj-toolbar">
|
<section class="adj-toolbar">
|
||||||
<el-select v-model="termId" clearable placeholder="全部学期" @change="load(); loadTaskOptions()">
|
<el-select v-model="termId" clearable placeholder="全部学期" @change="() => { load(true); loadTaskOptions() }">
|
||||||
<el-option v-for="t in terms" :key="t.id" :label="academicTermLabel(t)" :value="t.id" :class="academicTermOptionClass(t)" />
|
<el-option v-for="t in terms" :key="t.id" :label="academicTermLabel(t)" :value="t.id" :class="academicTermOptionClass(t)" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-segmented v-model="tab" :options="[
|
<el-segmented v-model="tab" :options="[
|
||||||
@@ -307,6 +321,15 @@ function showSub(type: string) { return type === 'Substitute' }
|
|||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
<el-empty v-if="!adjustments.length" description="暂无调停课申请" />
|
<el-empty v-if="!adjustments.length" description="暂无调停课申请" />
|
||||||
|
<el-pagination
|
||||||
|
v-if="mineTotal > pageSize"
|
||||||
|
v-model:current-page="minePage"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:total="mineTotal"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
class="table-pagination"
|
||||||
|
@current-change="() => load()"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Pending reviews -->
|
<!-- Pending reviews -->
|
||||||
@@ -345,6 +368,15 @@ function showSub(type: string) { return type === 'Substitute' }
|
|||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
<el-empty v-if="!pendingReviews.length" description="暂无待审核申请" />
|
<el-empty v-if="!pendingReviews.length" description="暂无待审核申请" />
|
||||||
|
<el-pagination
|
||||||
|
v-if="reviewTotal > pageSize"
|
||||||
|
v-model:current-page="reviewPage"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:total="reviewTotal"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
class="table-pagination"
|
||||||
|
@current-change="() => load()"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Create dialog -->
|
<!-- Create dialog -->
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ const selected = ref<any | null>(null)
|
|||||||
const reviewApproved = ref(true)
|
const reviewApproved = ref(true)
|
||||||
const applyForm = reactive({ type: '', reason: '' })
|
const applyForm = reactive({ type: '', reason: '' })
|
||||||
const reviewForm = reactive({ comment: '' })
|
const reviewForm = reactive({ comment: '' })
|
||||||
|
const page = ref(1)
|
||||||
|
const total = ref(0)
|
||||||
|
const actionableTotal = ref(0)
|
||||||
|
const finishedTotal = ref(0)
|
||||||
|
const pageSize = 20
|
||||||
|
|
||||||
const typeLabels: Record<string, string> = {
|
const typeLabels: Record<string, string> = {
|
||||||
Suspension: '休学',
|
Suspension: '休学',
|
||||||
@@ -48,10 +53,6 @@ const steps = [
|
|||||||
{ label: '学院审核', state: 'CounselorApproved' },
|
{ label: '学院审核', state: 'CounselorApproved' },
|
||||||
{ label: '校级审批', state: 'CollegeApproved' },
|
{ label: '校级审批', state: 'CollegeApproved' },
|
||||||
]
|
]
|
||||||
const currentQueue = computed(() => changes.value.filter(canReview))
|
|
||||||
const finishedCount = computed(() =>
|
|
||||||
changes.value.filter((x) => ['Approved', 'Rejected', 'Cancelled'].includes(x.state)).length)
|
|
||||||
|
|
||||||
function dateText(value?: string) {
|
function dateText(value?: string) {
|
||||||
if (!value) return '—'
|
if (!value) return '—'
|
||||||
return new Intl.DateTimeFormat('zh-CN', {
|
return new Intl.DateTimeFormat('zh-CN', {
|
||||||
@@ -90,14 +91,24 @@ function stateTagType(state: ChangeState) {
|
|||||||
return 'warning'
|
return 'warning'
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load(resetPage = false) {
|
||||||
|
if (resetPage) page.value = 1
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const requests = [http.get('/student-status-changes')]
|
const requests = [http.get('/student-status-changes', {
|
||||||
|
params: { page: page.value, pageSize },
|
||||||
|
})]
|
||||||
if (isStudent.value) requests.push(http.get('/student-status-changes/options'))
|
if (isStudent.value) requests.push(http.get('/student-status-changes/options'))
|
||||||
const [changeResponse, optionResponse] = await Promise.all(requests)
|
const [changeResponse, optionResponse] = await Promise.all(requests)
|
||||||
changes.value = changeResponse.data
|
changes.value = changeResponse.data.items
|
||||||
|
total.value = changeResponse.data.total
|
||||||
|
actionableTotal.value = changeResponse.data.actionableTotal
|
||||||
|
finishedTotal.value = changeResponse.data.finishedTotal
|
||||||
if (optionResponse) options.value = optionResponse.data
|
if (optionResponse) options.value = optionResponse.data
|
||||||
|
if (changes.value.length === 0 && page.value > 1) {
|
||||||
|
page.value--
|
||||||
|
await load()
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(apiErrorMessage(error))
|
ElMessage.error(apiErrorMessage(error))
|
||||||
} finally {
|
} finally {
|
||||||
@@ -118,7 +129,7 @@ async function submitApply() {
|
|||||||
await http.post('/student-status-changes', applyForm)
|
await http.post('/student-status-changes', applyForm)
|
||||||
applyDialog.value = false
|
applyDialog.value = false
|
||||||
ElMessage.success('申请已提交,等待辅导员审核。')
|
ElMessage.success('申请已提交,等待辅导员审核。')
|
||||||
await load()
|
await load(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(apiErrorMessage(error))
|
ElMessage.error(apiErrorMessage(error))
|
||||||
}
|
}
|
||||||
@@ -177,7 +188,7 @@ onMounted(load)
|
|||||||
:disabled="options?.hasPending || !options?.types?.length"
|
:disabled="options?.hasPending || !options?.types?.length"
|
||||||
@click="openApply"
|
@click="openApply"
|
||||||
>发起申请</el-button>
|
>发起申请</el-button>
|
||||||
<el-button v-else :icon="RefreshRight" @click="load">刷新队列</el-button>
|
<el-button v-else :icon="RefreshRight" @click="() => load()">刷新队列</el-button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-if="isStudent && options" class="status-identity">
|
<section v-if="isStudent && options" class="status-identity">
|
||||||
@@ -194,9 +205,9 @@ onMounted(load)
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-if="!isStudent" class="status-review-summary">
|
<section v-if="!isStudent" class="status-review-summary">
|
||||||
<div><span>当前待我审核</span><b>{{ currentQueue.length }}</b><small>件</small></div>
|
<div><span>当前待我审核</span><b>{{ actionableTotal }}</b><small>件</small></div>
|
||||||
<div><span>辖区申请总数</span><b>{{ changes.length }}</b><small>件</small></div>
|
<div><span>辖区申请总数</span><b>{{ total }}</b><small>件</small></div>
|
||||||
<div><span>已结束</span><b>{{ finishedCount }}</b><small>件</small></div>
|
<div><span>已结束</span><b>{{ finishedTotal }}</b><small>件</small></div>
|
||||||
<p>系统仅开放当前审核层级的操作,所有越级请求都会由服务端拒绝。</p>
|
<p>系统仅开放当前审核层级的操作,所有越级请求都会由服务端拒绝。</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -243,6 +254,15 @@ onMounted(load)
|
|||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
<el-empty v-if="!changes.length && !loading" :description="isStudent ? '尚未提交学籍异动申请' : '当前辖区暂无学籍异动申请'" />
|
<el-empty v-if="!changes.length && !loading" :description="isStudent ? '尚未提交学籍异动申请' : '当前辖区暂无学籍异动申请'" />
|
||||||
|
<el-pagination
|
||||||
|
v-if="total > pageSize"
|
||||||
|
v-model:current-page="page"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
class="table-pagination"
|
||||||
|
@current-change="() => load()"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<el-dialog v-model="applyDialog" title="发起学籍异动申请" width="620px">
|
<el-dialog v-model="applyDialog" title="发起学籍异动申请" width="620px">
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ const teachers = ref<any[]>([])
|
|||||||
const submitDialog = ref(false)
|
const submitDialog = ref(false)
|
||||||
const reviewDialog = ref(false)
|
const reviewDialog = ref(false)
|
||||||
const assignmentDialog = ref(false)
|
const assignmentDialog = ref(false)
|
||||||
|
const page = ref(1)
|
||||||
|
const total = ref(0)
|
||||||
|
const pageSize = 20
|
||||||
const form = reactive<Record<string, any>>({})
|
const form = reactive<Record<string, any>>({})
|
||||||
const reviewForm = reactive<Record<string, any>>({})
|
const reviewForm = reactive<Record<string, any>>({})
|
||||||
const assignmentForm = reactive<Record<string, any>>({})
|
const assignmentForm = reactive<Record<string, any>>({})
|
||||||
@@ -39,14 +42,22 @@ const statusTypes: Record<string, 'success' | 'warning' | 'danger' | 'info'> = {
|
|||||||
Withdrawn: 'info',
|
Withdrawn: 'info',
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load(resetPage = false) {
|
||||||
|
if (resetPage) page.value = 1
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const endpoint = isTeacher.value
|
const endpoint = isTeacher.value
|
||||||
? '/teacher-course-applications/mine'
|
? '/teacher-course-applications/mine'
|
||||||
: '/teacher-course-applications/reviews'
|
: '/teacher-course-applications/reviews'
|
||||||
const { data } = await http.get(endpoint, { params: filters })
|
const { data } = await http.get(endpoint, {
|
||||||
rows.value = data
|
params: { ...filters, page: page.value, pageSize },
|
||||||
|
})
|
||||||
|
rows.value = data.items
|
||||||
|
total.value = data.total
|
||||||
|
if (rows.value.length === 0 && page.value > 1) {
|
||||||
|
page.value--
|
||||||
|
await load()
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(apiErrorMessage(error))
|
ElMessage.error(apiErrorMessage(error))
|
||||||
} finally {
|
} finally {
|
||||||
@@ -195,14 +206,14 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<section class="data-card">
|
<section class="data-card">
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<el-select v-model="filters.academicTermId" clearable placeholder="全部学期" @change="load">
|
<el-select v-model="filters.academicTermId" clearable placeholder="全部学期" @change="load(true)">
|
||||||
<el-option v-for="item in terms" :key="item.id" :label="academicTermLabel(item)" :value="item.id" :class="academicTermOptionClass(item)" />
|
<el-option v-for="item in terms" :key="item.id" :label="academicTermLabel(item)" :value="item.id" :class="academicTermOptionClass(item)" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-select v-model="filters.status" clearable placeholder="全部状态" @change="load">
|
<el-select v-model="filters.status" clearable placeholder="全部状态" @change="load(true)">
|
||||||
<el-option v-for="(label, value) in statusLabels" :key="value" :label="label" :value="value" />
|
<el-option v-for="(label, value) in statusLabels" :key="value" :label="label" :value="value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-button :icon="Refresh" @click="load">刷新</el-button>
|
<el-button :icon="Refresh" @click="() => load()">刷新</el-button>
|
||||||
<span>共 {{ rows.length }} 条</span>
|
<span>共 {{ total }} 条</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table v-loading="loading" :data="rows" class="data-table">
|
<el-table v-loading="loading" :data="rows" class="data-table">
|
||||||
@@ -235,6 +246,15 @@ onMounted(async () => {
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<template #empty><el-empty :description="isTeacher ? '尚未申报授课科目' : '没有待处理的授课申报'" /></template>
|
<template #empty><el-empty :description="isTeacher ? '尚未申报授课科目' : '没有待处理的授课申报'" /></template>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
v-if="total > pageSize"
|
||||||
|
v-model:current-page="page"
|
||||||
|
:page-size="pageSize"
|
||||||
|
:total="total"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
class="table-pagination"
|
||||||
|
@current-change="() => load()"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<el-dialog v-model="submitDialog" title="申报授课科目" width="600px">
|
<el-dialog v-model="submitDialog" title="申报授课科目" width="600px">
|
||||||
|
|||||||
Reference in New Issue
Block a user