优化服务器端分页

This commit is contained in:
2026-08-11 11:52:43 +08:00 Unverified
parent 83103eebb8
commit 5d7322507b
6 changed files with 225 additions and 50 deletions
@@ -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")]
+42 -10
View File
@@ -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 -->
+32 -12
View File
@@ -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">
+27 -7
View File
@@ -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">