服务器端分页优化
This commit is contained in:
@@ -54,6 +54,9 @@ const isTeacher = computed(() => auth.user?.roles.includes('Teacher') && !isMana
|
||||
const isStudent = computed(() => auth.user?.roles.includes('Student') && !isManager.value)
|
||||
|
||||
const pending = ref<any[]>([])
|
||||
const pendingPage = ref(1)
|
||||
const pendingTotal = ref(0)
|
||||
const pendingPageSize = 20
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const tab = ref(isManager.value ? 'pending' : 'mine')
|
||||
@@ -168,7 +171,17 @@ const recordCount = computed(() =>
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
if (isManager.value) pending.value = (await http.get('/approvals/pending')).data
|
||||
if (isManager.value) {
|
||||
const { data } = await http.get('/approvals/pending', {
|
||||
params: { page: pendingPage.value, pageSize: pendingPageSize },
|
||||
})
|
||||
pending.value = data.items
|
||||
pendingTotal.value = data.total
|
||||
if (!pending.value.length && pendingPage.value > 1) {
|
||||
pendingPage.value--
|
||||
return await load()
|
||||
}
|
||||
}
|
||||
if (isStudent.value) {
|
||||
const [courses, grades, ex, df, substitutions] = await Promise.all([
|
||||
http.get('/approvals/my-courses'),
|
||||
@@ -358,7 +371,7 @@ onMounted(load)
|
||||
<el-segmented
|
||||
v-model="tab"
|
||||
:options="[
|
||||
...(isManager ? [{ label: `待审批 (${pending.length})`, value: 'pending' }] : []),
|
||||
...(isManager ? [{ label: `待审批 (${pendingTotal})`, value: 'pending' }] : []),
|
||||
{ label: `我的记录${isStudent ? ` (${recordCount})` : ''}`, value: 'mine' },
|
||||
]"
|
||||
/>
|
||||
@@ -389,6 +402,15 @@ onMounted(load)
|
||||
</div>
|
||||
</article>
|
||||
<el-empty v-if="!pending.length" description="暂无待审批" />
|
||||
<el-pagination
|
||||
v-if="pendingTotal > pendingPageSize"
|
||||
v-model:current-page="pendingPage"
|
||||
class="approval-pagination"
|
||||
layout="prev, pager, next"
|
||||
:page-size="pendingPageSize"
|
||||
:total="pendingTotal"
|
||||
@current-change="() => load()"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section v-if="tab === 'mine'" v-loading="loading">
|
||||
|
||||
+40
-16
@@ -28,6 +28,9 @@ const editingRoles = ref<string[]>([])
|
||||
const editingStaffNumber = ref('')
|
||||
const editingCollegeId = ref<string>()
|
||||
const keyword = ref('')
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = 20
|
||||
const passwordForm = reactive({ newPassword: '', confirmPassword: '' })
|
||||
const form = reactive({
|
||||
userName: '', displayName: '', password: '', staffNumber: '',
|
||||
@@ -56,25 +59,29 @@ const editingScope = computed(() => {
|
||||
)
|
||||
return scopeNames[effective]
|
||||
})
|
||||
const filteredUsers = computed(() => {
|
||||
const value = keyword.value.trim().toLowerCase()
|
||||
if (!value) return users.value
|
||||
return users.value.filter((user) =>
|
||||
[user.userName, user.displayName, user.staffNumber, ...user.roles]
|
||||
.filter(Boolean)
|
||||
.some((item) => String(item).toLowerCase().includes(value)),
|
||||
)
|
||||
})
|
||||
|
||||
async function load() {
|
||||
async function load(resetPage = false) {
|
||||
if (resetPage) page.value = 1
|
||||
loading.value = true
|
||||
try {
|
||||
const [userRes, roleRes, collegeRes] = await Promise.all([
|
||||
http.get('/users'), http.get('/users/roles'), http.get('/base-data/colleges'),
|
||||
http.get('/users', {
|
||||
params: {
|
||||
page: page.value,
|
||||
pageSize,
|
||||
keyword: keyword.value.trim() || undefined,
|
||||
},
|
||||
}),
|
||||
http.get('/users/roles'),
|
||||
http.get('/base-data/colleges'),
|
||||
])
|
||||
users.value = userRes.data
|
||||
users.value = userRes.data.items
|
||||
total.value = userRes.data.total
|
||||
roles.value = roleRes.data
|
||||
colleges.value = collegeRes.data
|
||||
if (users.value.length === 0 && page.value > 1) {
|
||||
page.value--
|
||||
await load()
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
} finally {
|
||||
@@ -216,7 +223,15 @@ onMounted(load)
|
||||
|
||||
<section class="data-card">
|
||||
<div class="table-toolbar">
|
||||
<el-input v-model="keyword" :prefix-icon="Search" clearable placeholder="搜索账号、姓名或工号" />
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
:prefix-icon="Search"
|
||||
clearable
|
||||
placeholder="搜索账号、姓名、工号或角色"
|
||||
@clear="load(true)"
|
||||
@keyup.enter="load(true)"
|
||||
/>
|
||||
<el-button :icon="Search" @click="load(true)">查询</el-button>
|
||||
<el-button :icon="Document" @click="downloadTemplate">下载模板</el-button>
|
||||
<el-button :icon="Upload" :loading="importing" @click="chooseImportFile">Excel 导入</el-button>
|
||||
<el-button :icon="Download" @click="exportRows">导出全部</el-button>
|
||||
@@ -227,11 +242,11 @@ onMounted(load)
|
||||
accept=".xlsx"
|
||||
@change="handleImport"
|
||||
/>
|
||||
<span>共 {{ filteredUsers.length }} / {{ users.length }} 个账号</span>
|
||||
<span>共 {{ total }} 个账号</span>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="filteredUsers"
|
||||
:data="users"
|
||||
>
|
||||
<el-table-column prop="userName" label="账号" min-width="130" />
|
||||
<el-table-column prop="displayName" label="姓名" min-width="120" />
|
||||
@@ -263,6 +278,15 @@ onMounted(load)
|
||||
</template>
|
||||
</el-table-column>
|
||||
</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>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="创建账号" width="540px">
|
||||
|
||||
@@ -17,6 +17,9 @@ const myWarnings = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const detecting = ref(false)
|
||||
const filterType = ref('')
|
||||
const recordsPage = ref(1)
|
||||
const recordsTotal = ref(0)
|
||||
const recordsPageSize = 20
|
||||
|
||||
const typeLabels: Record<number, string> = { 1: '不及格学分', 2: '低绩点', 3: '缺勤', 4: '延毕风险' }
|
||||
const statusLabels: Record<number, string> = { 1: '生效中', 2: '已确认', 3: '已处理', 4: '已忽略' }
|
||||
@@ -40,7 +43,8 @@ const ruleRows = reactive([
|
||||
{ type: 4, name: '延毕风险', threshold: 5, isEnabled: true, notifyStudent: true, notifyCounselor: true, description: '', autoCheckEnabled: false, checkDayOfWeek: 0, checkHour: 8, checkMinute: 0, lastCheckAt: null as string | null },
|
||||
])
|
||||
|
||||
async function load() {
|
||||
async function load(resetRecordsPage = false) {
|
||||
if (resetRecordsPage) recordsPage.value = 1
|
||||
loading.value = true
|
||||
try {
|
||||
if (isWarningManager.value && termId.value) {
|
||||
@@ -63,7 +67,22 @@ async function load() {
|
||||
}
|
||||
}
|
||||
if (isWarningManager.value || isCounselor.value)
|
||||
records.value = (await http.get('/warnings/records', { params: { academicTermId: termId.value || undefined, type: filterType.value || undefined } })).data
|
||||
{
|
||||
const response = await http.get('/warnings/records', {
|
||||
params: {
|
||||
academicTermId: termId.value || undefined,
|
||||
type: filterType.value || undefined,
|
||||
page: recordsPage.value,
|
||||
pageSize: recordsPageSize,
|
||||
},
|
||||
})
|
||||
records.value = response.data.items
|
||||
recordsTotal.value = response.data.total
|
||||
if (records.value.length === 0 && recordsPage.value > 1) {
|
||||
recordsPage.value--
|
||||
await load()
|
||||
}
|
||||
}
|
||||
if (isStudent.value) myWarnings.value = (await http.get('/warnings/my-warnings')).data
|
||||
} catch (e) { ElMessage.error(apiErrorMessage(e)) } finally { loading.value = false }
|
||||
}
|
||||
@@ -105,8 +124,8 @@ onMounted(async () => {
|
||||
<section class="page-intro">
|
||||
<div><span class="section-kicker">ACADEMIC WARNING</span><h2>{{ isStudent ? '我的预警' : '学业预警' }}</h2><p>{{ isWarningManager ? '配置预警规则,执行检测,查看预警记录。' : isCounselor ? '查看所管学生的学业预警情况。' : '查看并确认您的学业预警通知。' }}</p></div>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<el-select v-model="termId" clearable @change="load" style="width:240px"><el-option v-for="t in terms" :key="t.id" :label="academicTermLabel(t)" :value="t.id" :class="academicTermOptionClass(t)" /></el-select>
|
||||
<el-button :icon="Refresh" @click="load">刷新</el-button>
|
||||
<el-select v-model="termId" clearable @change="load(true)" style="width:240px"><el-option v-for="t in terms" :key="t.id" :label="academicTermLabel(t)" :value="t.id" :class="academicTermOptionClass(t)" /></el-select>
|
||||
<el-button :icon="Refresh" @click="() => load()">刷新</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -164,7 +183,7 @@ onMounted(async () => {
|
||||
<section v-if="isWarningManager || isCounselor" v-loading="loading" class="warn-records">
|
||||
<div style="display:flex;gap:12px;align-items:center;margin-bottom:12px">
|
||||
<h3 style="margin:0">预警记录</h3>
|
||||
<el-select v-model="filterType" clearable placeholder="全部类型" @change="load" style="width:140px"><el-option v-for="(v,k) in typeLabels" :key="k" :label="v" :value="Number(k)" /></el-select>
|
||||
<el-select v-model="filterType" clearable placeholder="全部类型" @change="load(true)" style="width:140px"><el-option v-for="(v,k) in typeLabels" :key="k" :label="v" :value="Number(k)" /></el-select>
|
||||
</div>
|
||||
<el-table :data="records" size="small" v-if="records.length">
|
||||
<el-table-column label="学生" min-width="150"><template #default="{row}"><b>{{ row.studentName }}</b><span style="font-size:11px;color:var(--muted);margin-left:6px">{{ row.studentNumber }}</span></template></el-table-column>
|
||||
@@ -173,6 +192,15 @@ onMounted(async () => {
|
||||
<el-table-column label="阈值/详情" min-width="200"><template #default="{row}"><span>{{ row.triggerValue }}</span><p style="margin:0;font-size:11px;color:var(--muted)">{{ row.detail }}</p></template></el-table-column>
|
||||
<el-table-column label="状态" width="100"><template #default="{row}"><el-tag size="small" :type="row.status===1?'danger':row.status===2?'success':'info'">{{ statusLabels[row.status] }}</el-tag></template></el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-if="recordsTotal > recordsPageSize"
|
||||
v-model:current-page="recordsPage"
|
||||
:page-size="recordsPageSize"
|
||||
:total="recordsTotal"
|
||||
layout="total, prev, pager, next"
|
||||
class="table-pagination"
|
||||
@current-change="() => load()"
|
||||
/>
|
||||
<el-empty v-if="!records.length" description="暂无预警记录,请先执行检测。" />
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user