基础数据大列表优化

This commit is contained in:
2026-08-11 11:56:00 +08:00 Unverified
parent 5d7322507b
commit 4005b78d2e
2 changed files with 113 additions and 14 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.Common; using Jiaowu.Api.Domain.Common;
using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Domain.Identity;
@@ -232,8 +233,40 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
} }
[HttpGet("classes")] [HttpGet("classes")]
public async Task<ActionResult<object>> GetClasses(CancellationToken cancellationToken) public async Task<ActionResult<object>> GetClasses(
int? page,
int? pageSize,
string? keyword,
CancellationToken cancellationToken)
{ {
if (page.HasValue || pageSize.HasValue)
{
if (page is < 1 || pageSize is < 1 or > 100)
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
var source = db.AdministrativeClasses.AsNoTracking();
if (!string.IsNullOrWhiteSpace(keyword))
{
keyword = keyword.Trim();
source = source.Where(x =>
x.Code.Contains(keyword) || x.Name.Contains(keyword) ||
x.Major!.Name.Contains(keyword) ||
x.Major.College!.Name.Contains(keyword));
}
var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderByDescending(x => x.Grade).ThenBy(x => x.Code)
.Skip((page!.Value - 1) * pageSize!.Value)
.Take(pageSize.Value)
.Select(x => new AdministrativeClassListItem(
x.Id, x.Code, x.Name, x.MajorId, x.Major!.Name,
x.Major.College!.Name, x.Grade, x.CounselorUserId,
x.CounselorName, x.IsEnabled, x.SortOrder))
.ToListAsync(cancellationToken);
return Ok(new PagedResult<AdministrativeClassListItem>(
items, total, page.Value, pageSize.Value));
}
var result = await cache.GetOrCreateAsync( var result = await cache.GetOrCreateAsync(
AppCacheKeys.BaseData("classes"), AppCacheKeys.BaseData("classes"),
token => db.AdministrativeClasses.AsNoTracking() token => db.AdministrativeClasses.AsNoTracking()
@@ -474,8 +507,41 @@ public sealed class BaseDataController(AppDbContext db, IAppCache cache) : Contr
} }
[HttpGet("classrooms")] [HttpGet("classrooms")]
public async Task<ActionResult<object>> GetClassrooms(CancellationToken cancellationToken) public async Task<ActionResult<object>> GetClassrooms(
int? page,
int? pageSize,
string? keyword,
CancellationToken cancellationToken)
{ {
if (page.HasValue || pageSize.HasValue)
{
if (page is < 1 || pageSize is < 1 or > 100)
return ValidationProblem("页码必须大于 0,且每页条数应在 1 至 100 之间。");
var source = db.Classrooms.AsNoTracking();
if (!string.IsNullOrWhiteSpace(keyword))
{
keyword = keyword.Trim();
source = source.Where(x =>
x.Code.Contains(keyword) || x.Name.Contains(keyword) ||
x.Building!.Name.Contains(keyword) ||
x.Building.Campus!.Name.Contains(keyword));
}
var total = await source.CountAsync(cancellationToken);
var items = await source
.OrderBy(x => x.Building!.Campus!.SortOrder).ThenBy(x => x.Code)
.Skip((page!.Value - 1) * pageSize!.Value)
.Take(pageSize.Value)
.Select(x => new ClassroomListItem(
x.Id, x.Code, x.Name, x.BuildingId, x.Building!.CampusId,
x.Building.Name, x.Building.Campus!.Name, x.Capacity,
x.RoomType, x.TeachingVenueNature, x.Equipment,
x.IsEnabled, x.SortOrder))
.ToListAsync(cancellationToken);
return Ok(new PagedResult<ClassroomListItem>(
items, total, page.Value, pageSize.Value));
}
var result = await cache.GetOrCreateAsync( var result = await cache.GetOrCreateAsync(
AppCacheKeys.BaseData("classrooms"), AppCacheKeys.BaseData("classrooms"),
token => db.Classrooms.AsNoTracking() token => db.Classrooms.AsNoTracking()
+45 -12
View File
@@ -92,10 +92,16 @@ const filteredRows = computed(() => {
}) })
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(20) const pageSize = ref(20)
const pagedRows = computed(() => filteredRows.value.slice( const total = ref(0)
(currentPage.value - 1) * pageSize.value, const serverPaged = computed(() =>
currentPage.value * pageSize.value, active.value === 'classes' || active.value === 'classrooms')
)) let keywordTimer: ReturnType<typeof setTimeout> | undefined
const pagedRows = computed(() => serverPaged.value
? filteredRows.value
: filteredRows.value.slice(
(currentPage.value - 1) * pageSize.value,
currentPage.value * pageSize.value,
))
const venueNatureOptions = [ const venueNatureOptions = [
{ value: 1, label: '普通教室' }, { value: 1, label: '普通教室' },
{ value: 2, label: '实验室' }, { value: 2, label: '实验室' },
@@ -138,11 +144,30 @@ function resetForm(row?: Row) {
} }
} }
async function load() { async function load(resetPage = true) {
loading.value = true loading.value = true
currentPage.value = 1 if (resetPage) currentPage.value = 1
try { try {
rows.value = (await http.get(`/base-data/${active.value}`)).data const response = await http.get(`/base-data/${active.value}`, {
params: serverPaged.value
? {
page: currentPage.value,
pageSize: pageSize.value,
keyword: keyword.value.trim() || undefined,
}
: undefined,
})
if (serverPaged.value) {
rows.value = response.data.items
total.value = response.data.total
if (rows.value.length === 0 && currentPage.value > 1) {
currentPage.value--
await load(false)
}
} else {
rows.value = response.data
total.value = rows.value.length
}
} catch (error) { } catch (error) {
ElMessage.error(apiErrorMessage(error)) ElMessage.error(apiErrorMessage(error))
} finally { } finally {
@@ -308,7 +333,13 @@ onMounted(async () => {
watch( watch(
() => keyword.value, () => keyword.value,
() => { currentPage.value = 1 }, () => {
currentPage.value = 1
if (keywordTimer) clearTimeout(keywordTimer)
if (serverPaged.value) {
keywordTimer = setTimeout(() => { void load(false) }, 250)
}
},
) )
watch( watch(
@@ -357,7 +388,7 @@ watch(
<div class="table-toolbar"> <div class="table-toolbar">
<el-input v-model="keyword" :prefix-icon="Search" clearable placeholder="搜索编码、名称或所属单位" /> <el-input v-model="keyword" :prefix-icon="Search" clearable placeholder="搜索编码、名称或所属单位" />
<el-button :icon="Refresh" @click="load">刷新</el-button> <el-button :icon="Refresh" @click="() => load(false)">刷新</el-button>
<template v-if="canManage"> <template v-if="canManage">
<el-button :icon="Document" @click="downloadTemplate">下载模板</el-button> <el-button :icon="Document" @click="downloadTemplate">下载模板</el-button>
<el-button :icon="Upload" :loading="importing" @click="chooseImportFile">Excel 导入</el-button> <el-button :icon="Upload" :loading="importing" @click="chooseImportFile">Excel 导入</el-button>
@@ -370,7 +401,7 @@ watch(
@change="handleImport" @change="handleImport"
/> />
</template> </template>
<span> {{ filteredRows.length }} </span> <span> {{ serverPaged ? total : filteredRows.length }} </span>
</div> </div>
<el-alert <el-alert
@@ -494,13 +525,15 @@ watch(
<template #empty><el-empty description="暂无数据,点击右上角开始新增" /></template> <template #empty><el-empty description="暂无数据,点击右上角开始新增" /></template>
</el-table> </el-table>
<el-pagination <el-pagination
v-if="filteredRows.length > pageSize" v-if="(serverPaged ? total : filteredRows.length) > pageSize"
v-model:current-page="currentPage" v-model:current-page="currentPage"
v-model:page-size="pageSize" v-model:page-size="pageSize"
class="table-pagination" class="table-pagination"
layout="total, sizes, prev, pager, next" layout="total, sizes, prev, pager, next"
:page-sizes="[20, 50, 100]" :page-sizes="[20, 50, 100]"
:total="filteredRows.length" :total="serverPaged ? total : filteredRows.length"
@current-change="() => { if (serverPaged) void load(false) }"
@size-change="() => { if (serverPaged) void load(true) }"
/> />
</section> </section>