From b4fac2bd3caaeaa76a48657c1f5c17a4eb18c6d7 Mon Sep 17 00:00:00 2001 From: biss Date: Wed, 16 Sep 2026 17:26:59 +0800 Subject: [PATCH] update --- .../CampusRegistrationsController.cs | 25 +++++++++++++++++-- web/src/views/CampusRegistrationsView.vue | 11 +++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Jiaowu.Api/Controllers/CampusRegistrationsController.cs b/src/Jiaowu.Api/Controllers/CampusRegistrationsController.cs index c07fb2b..c3ba81c 100644 --- a/src/Jiaowu.Api/Controllers/CampusRegistrationsController.cs +++ b/src/Jiaowu.Api/Controllers/CampusRegistrationsController.cs @@ -265,12 +265,33 @@ public sealed class CampusRegistrationsController(AppDbContext db, ICurrentUserD [HttpGet("applications")] [Authorize(Roles = Managers)] - public async Task GetApplications([FromQuery] Guid? projectId, [FromQuery] CampusRegistrationApplicationStatus? status, CancellationToken ct) + public async Task GetApplications( + [FromQuery] Guid? projectId, + [FromQuery] CampusRegistrationApplicationStatus? status, + [FromQuery] int? page, + [FromQuery] int? pageSize, + CancellationToken ct = default) { var source = ScopedApplications().AsNoTracking(); if (projectId.HasValue) source = source.Where(x => x.ProjectId == projectId); if (status.HasValue) source = source.Where(x => x.Status == status); - return Ok(await source.OrderByDescending(x => x.CreatedAt).Select(ProjectApplicationDto()).ToListAsync(ct)); + + var ordered = source + .OrderByDescending(x => x.CreatedAt) + .ThenByDescending(x => x.Id); + if (!page.HasValue && !pageSize.HasValue) + return Ok(await ordered.Select(ProjectApplicationDto()).ToListAsync(ct)); + + var normalizedPage = Math.Max(1, page ?? 1); + var normalizedPageSize = Math.Clamp(pageSize ?? 20, 1, 100); + + var total = await source.CountAsync(ct); + var items = await ordered + .Skip((normalizedPage - 1) * normalizedPageSize) + .Take(normalizedPageSize) + .Select(ProjectApplicationDto()) + .ToListAsync(ct); + return Ok(new PagedResult(items, total, normalizedPage, normalizedPageSize)); } [HttpGet("applications/export.xlsx")] diff --git a/web/src/views/CampusRegistrationsView.vue b/web/src/views/CampusRegistrationsView.vue index e87c8d7..7cf60c8 100644 --- a/web/src/views/CampusRegistrationsView.vue +++ b/web/src/views/CampusRegistrationsView.vue @@ -8,12 +8,13 @@ type ProjectStatus = 'Draft' | 'Published' | 'Closed' type ApplicationStatus = 'PendingReview' | 'PendingPayment' | 'Confirmed' | 'Waitlisted' | 'Rejected' | 'Cancelled' interface Project { id: string; code: string; name: string; description?: string; registrationStartsAt: string; registrationEndsAt: string; capacity: number; feeAmount: number; requiresReview: boolean; status: ProjectStatus; contactName?: string; contactPhone?: string; notes?: string; collegeName: string; confirmedCount: number; isAvailableNow: boolean } interface Application { id: string; projectId: string; projectCode: string; projectName: string; status: ApplicationStatus; applicantNote?: string; createdAt: string; reviewComment?: string; studentId: string; studentNumber: string; studentName: string; order?: { id: string; orderNumber: string; amount: number; paymentMethod: 'Free' | 'Offline' | 'Balance'; status: 'Pending' | 'Paid' | 'Cancelled'; referenceNumber?: string } } +interface PagedResult { items: T[]; total: number; page: number; pageSize: number } const auth = useAuthStore() const isStudent = computed(() => auth.user?.roles.includes('Student') ?? false) const canManage = computed(() => auth.user?.roles.some(x => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(x)) ?? false) const tab = ref(isStudent.value ? 'projects' : 'manage') -const projects = ref([]); const mine = ref([]); const applications = ref([]); const loading = ref(false); const dialog = ref(false); const editingId = ref(null) +const projects = ref([]); const mine = ref([]); const applications = ref([]); const applicationsPage = ref(1); const applicationsPageSize = ref(20); const applicationsTotal = ref(0); const loading = ref(false); const dialog = ref(false); const editingId = ref(null) const balance = ref(0) const form = reactive({ code: '', name: '', description: '', registrationStartsAt: '', registrationEndsAt: '', capacity: 50, feeAmount: 0, requiresReview: true, status: 'Draft' as ProjectStatus, contactName: '', contactPhone: '', notes: '' }) const statusLabel: Record = { PendingReview: '待审核', PendingPayment: '待缴费', Confirmed: '已确认', Waitlisted: '候补中', Rejected: '已驳回', Cancelled: '已取消' } @@ -28,7 +29,9 @@ function resetForm(project?: Project) { editingId.value = project?.id ?? null; O async function loadProjects() { const { data } = await http.get('/campus-registrations/projects'); projects.value = data } async function loadMine() { if (isStudent.value) { const { data } = await http.get('/campus-registrations/mine'); mine.value = data } } async function loadWallet() { if (isStudent.value) { const { data } = await http.get('/campus-registrations/wallet/mine'); balance.value = data.balance } } -async function loadApplications() { if (canManage.value) { const { data } = await http.get('/campus-registrations/applications'); applications.value = data } } +async function loadApplications() { if (canManage.value) { const { data } = await http.get>('/campus-registrations/applications', { params: { page: applicationsPage.value, pageSize: applicationsPageSize.value } }); applications.value = data.items; applicationsTotal.value = data.total; if (applications.value.length === 0 && applicationsTotal.value > 0 && applicationsPage.value > 1) { applicationsPage.value--; await loadApplications() } } } +async function changeApplicationsPage(page: number) { applicationsPage.value = page; await loadApplications() } +async function changeApplicationsPageSize(pageSize: number) { applicationsPageSize.value = pageSize; applicationsPage.value = 1; await loadApplications() } async function saveProject() { try { const payload = { ...form, registrationStartsAt: new Date(form.registrationStartsAt).toISOString(), registrationEndsAt: new Date(form.registrationEndsAt).toISOString(), collegeId: null }; if (editingId.value) await http.put(`/campus-registrations/projects/${editingId.value}`, payload); else await http.post('/campus-registrations/projects', payload); ElMessage.success('项目已保存'); dialog.value = false; await loadProjects() } catch (e) { ElMessage.error(apiErrorMessage(e)) } } async function apply(project: Project) { try { const { value } = await ElMessageBox.prompt('可选:填写报名说明', `报名:${project.name}`, { inputPlaceholder: '报名说明' }); await http.post(`/campus-registrations/projects/${project.id}/apply`, { note: value || null }); ElMessage.success('报名已提交'); await Promise.all([loadProjects(), loadMine()]) } catch (e) { if (e !== 'cancel' && e !== 'close') ElMessage.error(apiErrorMessage(e)) } } async function review(item: Application, approved: boolean) { try { const { value } = await ElMessageBox.prompt(approved ? '审核通过后将生成缴费订单或直接确认免费报名。' : '请填写驳回原因', approved ? '通过报名' : '驳回报名', { inputPlaceholder: approved ? '可选审核意见' : '驳回原因' }); await http.post(`/campus-registrations/applications/${item.id}/review`, { approved, comment: value || null }); ElMessage.success('处理完成'); await loadApplications() } catch (e) { if (e !== 'cancel' && e !== 'close') ElMessage.error(apiErrorMessage(e)) } } @@ -44,11 +47,11 @@ onMounted(async () => { loading.value = true; try { await Promise.all([loadProje

CAMPUS SERVICES

校园支付与报名

报名、审核、线下缴费确认和名单导出在同一闭环内完成。

新建报名项目

{{ project.code }} · {{ project.collegeName }}

{{ project.description || '暂无项目说明。' }}

报名时间
{{ localDateTime(project.registrationStartsAt) }} 至 {{ localDateTime(project.registrationEndsAt) }}
名额 / 已确认
{{ project.capacity }} / {{ project.confirmedCount }} 人
费用
{{ amount(project.feeAmount) }} {{ project.requiresReview ? '· 需审核' : '· 自动受理' }}
{{ project.isAvailableNow ? '立即报名' : '当前不可报名' }}编辑项目
校园余额{{ amount(balance) }}余额由管理员调整,可用于支付待缴费报名。
-
所有操作均按项目所属学院的数据范围限制。导出当前可见报名
+
所有操作均按项目所属学院的数据范围限制。导出当前可见报名
草稿发布关闭