Vue-Based Student Management System with Element Plus

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 key c_id referencing tb_class. Fields include s_id, sname, age, birthday (datetime), gender (char(1)), and c_id.
  • tb_user: Manages login credentials. Uses u_id as primary key, enforces unique user_name, and stores gender as a BIT(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 full User object 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.

Tags: vue3 element-plus spring-boot MySQL rest-api

Posted on Tue, 29 Sep 2026 16:10:30 +0000 by manyamile