feat(exam): 增加试卷结果分页查询接口及前端显示功能

- 新增ExamWordsResultReqVO和ExamWordsResultRspVO用于请求与响应封装
- ExamWordsController新增getExamWordsResult方法支持分页查询试卷结果
- ExamWordsJudgeService接口及实现中添加分页获取试卷结果方法及统计总数
- Mapper层添加分页查询和统计的SQL语句支持
- Vue前端uploadpng页面优化为两列布局,新增结果集表格与分页控件
- 上传功能改用自定义http-request,上传后自动刷新结果列表
- Class页面调整布局增加额外展示内容
- 删除未使用接口ExamWordsJudge接口及相关引用
- 重命名ExamWordsJudge相关类和测试类以统一命名规范
This commit is contained in:
lbw
2025-12-14 12:49:53 +08:00
parent 9a11a7c094
commit c1b3c92244
24 changed files with 904 additions and 38 deletions

View File

@@ -1,11 +1,16 @@
<script setup>
</script>
<template>
<router-view></router-view>
</template>
<style scoped>
<script setup>
</script>
<style>
/* 自定义顶部加载 Loading 颜色 */
#nprogress .bar {
background: #409eff !important;
}
</style>

View File

@@ -0,0 +1,12 @@
import axios from "@/axios";
export function uploadExamWordsPng(data) {
return axios.post('/exam/words/genexam', data)
}
export function getExamWordsResult(page, size) {
return axios.post('/exam/words/get', {
page: page,
size: size
})
}

10
enlish-vue/src/axios.js Normal file
View File

@@ -0,0 +1,10 @@
import axios from "axios";
// 创建 Axios 实例
const instance = axios.create({
baseURL: "/api", // 你的 API 基础 URL
timeout: 7000, // 请求超时时间
})
// 暴露出去
export default instance;

View File

@@ -0,0 +1,21 @@
import 'element-plus/es/components/message/style/css'
import nprogress from "nprogress"
// 消息提示
export function showMessage(message = '提示内容', type = 'success', customClass = '') {
return ElMessage({
type: type,
message,
customClass,
})
}
// 显示页面加载 Loading
export function showPageLoading() {
nprogress.start()
}
// 隐藏页面加载 Loading
export function hidePageLoading() {
nprogress.done()
}

View File

@@ -0,0 +1,80 @@
<template>
<header>
<nav class="bg-white border-gray-200 px-4 lg:px-6 py-2.5 dark:bg-gray-800">
<div class="flex flex-wrap justify-between items-center mx-auto max-w-screen-xl">
<a href="#" class="flex items-center">
<img src="https://flowbite.com/docs/images/logo.svg" class="mr-3 h-6 sm:h-9" alt="Flowbite Logo" />
<span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">Flowbite</span>
</a>
<div class="flex items-center lg:order-2">
<a href="#"
class="text-gray-800 dark:text-white hover:bg-gray-50 focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 dark:hover:bg-gray-700 focus:outline-none dark:focus:ring-gray-800">Log
in</a>
<a href="#"
class="text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800">Get
started</a>
<button data-collapse-toggle="mobile-menu-2" type="button"
class="inline-flex items-center p-2 ml-1 text-sm text-gray-500 rounded-lg lg:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
aria-controls="mobile-menu-2" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clip-rule="evenodd"></path>
</svg>
<svg class="hidden w-6 h-6" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</button>
</div>
<div class="hidden justify-between items-center w-full lg:flex lg:w-auto lg:order-1" id="mobile-menu-2">
<ul class="flex flex-col mt-4 font-medium lg:flex-row lg:space-x-8 lg:mt-0">
<li>
<a href="#"
class="block py-2 pr-4 pl-3 text-white rounded bg-primary-700 lg:bg-transparent lg:text-primary-700 lg:p-0 dark:text-white"
aria-current="page">Home</a>
</li>
<li>
<router-link
to="/class"
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
班级
</router-link>
</li>
<li>
<a href="#"
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
Marketplace
</a>
</li>
<li>
<a href="#"
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
Features
</a>
</li>
<li>
<a href="#"
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
Team
</a>
</li>
<li>
<router-link
to="/uploadpng"
class="block py-2 pr-4 pl-3 text-gray-700 border-b border-gray-100 hover:bg-gray-50 lg:hover:bg-transparent lg:border-0 lg:hover:text-primary-700 lg:p-0 dark:text-gray-400 lg:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white lg:dark:hover:bg-transparent dark:border-gray-700">
上传图片
</router-link>
</li>
</ul>
</div>
</div>
</nav>
</header>
</template>
<script setup>
</script>

View File

@@ -1,8 +1,13 @@
import '@/assets/main.css'
import { createApp } from 'vue'
import App from '@/App.vue'
import 'nprogress/nprogress.css'
// 导入路由
import router from '@/router'
// 导入全局路由守卫
import '@/permission'
import { createApp } from 'vue'
import App from '@/App.vue'
const app = createApp(App)

View File

@@ -0,0 +1,26 @@
<template>
<div class="common-layout">
<el-container>
<el-header>
<Header></Header>
</el-header>
<el-main>
<div class="flex">
<div>
班级
</div>
<div>
hellow
</div>
</div>
</el-main>
</el-container>
</div>
</template>
<script setup>
import Header from '@/layouts/components/Header.vue'
</script>

View File

@@ -1,4 +1,26 @@
<template>
<div class="common-layout">
<el-container>
<el-header>
<Header></Header>
</el-header>
<el-container>
<el-aside width="200px">
Aside
</el-aside>
<el-main>
Main
</el-main>
</el-container>
</el-container>
</div>
</template>
<script setup>
import Header from '@/layouts/components/Header.vue'
</script>

View File

@@ -0,0 +1,113 @@
<template>
<div class="common-layout">
<el-container>
<el-header>
<Header></Header>
</el-header>
<el-main class="p-4">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-lg font-semibold mb-4">上传图片</div>
<el-upload
:show-file-list="false"
:http-request="doUpload"
accept="image/*"
>
<el-button type="primary">选择图片并上传</el-button>
</el-upload>
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="text-lg font-semibold mb-4">结果集</div>
<el-table
:data="list"
border
class="w-full"
v-loading="loading"
>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="studentId" label="学生ID" width="100" />
<el-table-column prop="examWordsId" label="试题ID" width="100" />
<el-table-column prop="correctWordCount" label="正确词数" width="110" />
<el-table-column prop="wrongWordCount" label="错误词数" width="110" />
<el-table-column label="完成状态" width="110">
<template #default="{ row }">
<el-tag :type="row.isFinished === 1 ? 'success' : 'info'">
{{ row.isFinished === 1 ? '已完成' : '未完成' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="startDate" label="开始时间" min-width="160" />
<el-table-column prop="errorMsg" label="错误信息" min-width="160" />
</el-table>
<div class="mt-4 flex justify-end">
<el-pagination
background
layout="prev, pager, next, sizes, total"
:total="totalCount"
:page-size="pageSize"
:current-page="pageNo"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</div>
</el-main>
</el-container>
</div>
</template>
<script setup>
import Header from '@/layouts/components/Header.vue'
import { ref, onMounted } from 'vue'
import { uploadExamWordsPng, getExamWordsResult } from '@/api/exam'
const list = ref([])
const pageNo = ref(1)
const pageSize = ref(10)
const totalCount = ref(0)
const loading = ref(false)
async function fetchList() {
loading.value = true
try {
const res = await getExamWordsResult(pageNo.value, pageSize.value)
const d = res.data
list.value = Array.isArray(d.data) ? d.data : []
totalCount.value = d.totalCount || 0
pageNo.value = d.pageNo || pageNo.value
pageSize.value = d.pageSize || pageSize.value
} finally {
loading.value = false
}
}
function handlePageChange(p) {
pageNo.value = p
fetchList()
}
function handleSizeChange(s) {
pageSize.value = s
pageNo.value = 1
fetchList()
}
async function doUpload(options) {
const file = options.file
const formData = new FormData()
formData.append('file', file)
await uploadExamWordsPng(formData)
ElMessage.success('上传成功,已开始生成结果')
fetchList()
}
onMounted(() => {
fetchList()
})
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,23 @@
import router from '@/router/index'
import { showMessage } from '@/composables/util'
import { showPageLoading, hidePageLoading } from '@/composables/util'
// 全局路由前置守卫
router.beforeEach((to, from, next) => {
console.log('==> 全局路由前置守卫')
// 展示页面加载 Loading
showPageLoading()
next()
})
// 全局路由后置守卫
router.afterEach((to, from) => {
// 动态设置页面 Title
let title = (to.meta.title ? to.meta.title : '') + ' - Weblog'
document.title = title
// 隐藏页面加载 Loading
hidePageLoading()
})

View File

@@ -1,4 +1,6 @@
import Index from '@/pages/index.vue'
import Uploadpng from '@/pages/uploadpng.vue'
import Class from '@/pages/class.vue'
import { createRouter, createWebHashHistory } from 'vue-router'
// 统一在这里声明所有路由
@@ -7,7 +9,21 @@ const routes = [
path: '/', // 路由地址
component: Index, // 对应组件
meta: { // meta 信息
title: 'Weblog 首页' // 页面标题
title: '首页' // 页面标题
}
},
{
path: '/uploadpng', // 路由地址
component: Uploadpng, // 对应组件
meta: { // meta 信息
title: '上传图片' // 页面标题
}
},
{
path: '/class', // 路由地址
component: Class, // 对应组件
meta: { // meta 信息
title: '班级' // 页面标题
}
}
]