This article outlines a modern student administration interface built with Vue 3 and Element Plus, backed by a Spring Boot REST API. The system supports role-agnostic user authentication, class–student relationship management, paginated student queries with filtering, and CRUD operations via modal dialogs.
Database Schema Highlights
The relational model includes three core tables:
tb_class: Stores class identifeirs and names.tb_student: Holds student records with foreign keyc_idreferencingtb_class. Fields includes_id,sname,age,birthday(datetime),gender(char(1)), andc_id.tb_user: Manages login credentials. Usesu_idas primary key, enforces uniqueuser_name, and stores gender as aBIT(1)flag.
Sample insertions populate initial test data—e.g., students linked to classes c001 (Java12) and c002 (Java56).
Backend Controller Design
REST endpoints follow resource-oriented conventions with standardized response wrappers (BaseResult) for consistency.
ClassesController
@RestController
@RequestMapping("/classes")
@CrossOrigin
public class ClassesController {
@Resource private ClassesService classesService;
@GetMapping
public BaseResult<List<Classes>> list() {
return BaseResult.ok("Success", classesService.selectAll());
}
@PostMapping
public BaseResult add(@RequestBody Classes clazz) {
return classesService.add(clazz)
? BaseResult.ok("Created")
: BaseResult.error("Failed");
}
@GetMapping("/{cid}")
public BaseResult<Classes> getById(@PathVariable String cid) {
return BaseResult.ok("Fetched", classesService.selectById(cid));
}
@PutMapping
public BaseResult update(@RequestBody Classes clazz) {
return classesService.update(clazz)
? BaseResult.ok("Updated")
: BaseResult.error("Update failed");
}
@DeleteMapping("/{classesId}")
public BaseResult delete(@PathVariable String classesId) {
return classesService.deleteById(classesId)
? BaseResult.ok("Deleted")
: BaseResult.error("Deletion failed");
}
}
StudentController
Supports conditional pagination, batch deletion, and upsert semantics:
@PostMapping("/condition/{pageSize}/{pageNum}")
public BaseResult condition(
@PathVariable Integer pageSize,
@PathVariable Integer pageNum,
@RequestBody StudentVo filter) {
PageInfo<Student> page = studentService.condition(pageSize, pageNum, filter);
return BaseResult.ok("Query completed", page);
}
@PostMapping("/batchDelete")
public BaseResult bulkDelete(@RequestBody List<String> ids) {
studentService.deleteIds(ids);
return BaseResult.ok("Records removed");
}
@PostMapping("/save")
public BaseResult saveOrUpdate(@RequestBody Student student) {
boolean success = studentService.saveOrUpdate(student);
return success
? BaseResult.ok("Saved or updated")
: BaseResult.error("Operation rejected");
}
User Authentication Flow
UserController exposes two secure endpoints:
POST /user/login: Validates credentials and returns fullUserobject on success.POST /user/check: Checks username availability without exposing pasword hashes.
Frontend Architecture
The Vue 3 application uses Vue Router with nested routes and Element Plus components for UI consistency.
Routing Configuration
const routes = [
{ path: '/', redirect: '/home' },
{ path: '/login', name: 'Login', component: () => import('../views/Login.vue') },
{
path: '/home',
name: 'Home',
component: () => import('../views/Home.vue'),
children: [
{ path: 'studentList', name: 'StudentList', component: () => import('../views/StudentList.vue') },
{ path: 'classesList', name: 'ClassesList', component: () => import('../views/ClassesList.vue') }
]
}
];
Layout & Navigation
The Home.vue layout implements a fixed header with horizontal menu and dynamic content area:
<el-header class="home-header">
<el-menu
:default-active="$route.path"
router
mode="horizontal"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b">
<el-menu-item index="/home">Dashboard</el-menu-item>
<el-submenu index="/classes">
<template #title>Class Management</template>
<el-menu-item index="/classesList">Class List</el-menu-item>
</el-submenu>
<el-submenu index="/student">
<template #title>Student Management</template>
<el-menu-item index="/studentList">Student List</el-menu-item>
</el-submenu>
</el-menu>
</el-header>
<el-main>
<router-view />
</el-main>
<el-footer>Student Management System v2.0</el-footer>
Student List Page with Filtering & Pagination
Uses el-form, el-table, and el-pagination with reactive filters:
<el-form :inline="true" :model="filterForm" size="small">
<el-form-item label="Class">
<el-select v-model="filterForm.cid" clearable placeholder="Select class">
<el-option
v-for="cls in classOptions"
:key="cls.cid"
:label="cls.cname"
:value="cls.cid" />
</el-select>
</el-form-item>
<el-form-item label="Name">
<el-input v-model="filterForm.sname" clearable placeholder="Enter name" />
</el-form-item>
<el-form-item label="Age Range">
<el-col :span="11">
<el-input v-model="filterForm.minAge" placeholder="Min age" />
</el-col>
<el-col class="line" :span="2">—</el-col>
<el-col :span="11">
<el-input v-model="filterForm.maxAge" placeholder="Max age" />
</el-col>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchStudents(1)">Search</el-button>
</el-form-item>
</el-form>
<el-button type="primary" @click="showAddDialog = true">+ Add Student</el-button>
<el-table :data="studentPage.list" stripe style="width: 100%">
<el-table-column type="selection" width="55" />
<el-table-column prop="sid" label="ID" width="120" />
<el-table-column prop="sname" label="Name" width="140" />
<el-table-column prop="cname" label="Class" width="160" />
<el-table-column prop="age" label="Age" width="90" />
<el-table-column prop="birthday" label="Birth Date" width="160" />
<el-table-column label="Gender" width="100">
<template #default="{ row }">{{ row.gender === '1' ? 'Male' : 'Female' }}</template>
</el-table-column>
<el-table-column label="Actions" width="180">
<template #default="{ row }">
<el-button size="small" @click="openEditDialog(row)">Edit</el-button>
<el-button size="small" type="danger" @click="deleteStudent(row.sid)">Delete</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handlePageSizeChange"
@current-change="handlePageChange"
:current-page="studentPage.pageNum"
:page-sizes="[5, 10, 20]"
:page-size="studentPage.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="studentPage.total" />
Modal Dialog for Student Creation
Implements form binding, class dropdown population, and date selection:
<el-dialog title="Add New Student" v-model="showAddDialog" width="50%">
<el-form :model="newStudent" label-width="90px" :rules="formRules">
<el-form-item label="Class" prop="cid">
<el-select v-model="newStudent.cid" placeholder="Choose class" style="width: 100%">
<el-option
v-for="cls in classOptions"
:key="cls.cid"
:label="cls.cname"
:value="cls.cid" />
</el-select>
</el-form-item>
<el-form-item label="Student ID" prop="sid">
<el-input v-model="newStudent.sid" />
</el-form-item>
<el-form-item label="Full Name" prop="sname">
<el-input v-model="newStudent.sname" />
</el-form-item>
<el-form-item label="Age" prop="age">
<el-input-number v-model="newStudent.age" :min="1" :max="120" />
</el-form-item>
<el-form-item label="Birthday">
<el-date-picker
v-model="newStudent.birthday"
type="date"
placeholder="Select birth date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD" />
</el-form-item>
<el-form-item label="Gender" prop="gender">
<el-radio-group v-model="newStudent.gender">
<el-radio label="1">Male</el-radio>
<el-radio label="0">Female</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showAddDialog = false">Cancel</el-button>
<el-button type="primary" @click="submitStudent">Confirm</el-button>
</span>
</template>
</el-dialog>
On submission, the client dispatches a POST /student request with validated payload and refreshes the table upon success.