785 lines
45 KiB
HTML
785 lines
45 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>沃林学生管理系统</title>
|
||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
|
||
<script src="https://unpkg.com/element-plus"></script>
|
||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||
<script src="https://unpkg.com/echarts/dist/echarts.min.js"></script>
|
||
<style>
|
||
body { margin: 0; padding: 0; font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; background-color: #f0f2f5; }
|
||
#app { display: flex; height: 100vh; }
|
||
.sidebar { width: 220px; background-color: #001529; color: #fff; display: flex; flex-direction: column; }
|
||
.sidebar h2 { text-align: center; color: #fff; padding: 20px 0; margin: 0; font-size: 18px; border-bottom: 1px solid #1f2d3d; }
|
||
.sidebar .el-menu { border-right: none; flex: 1; }
|
||
.main-content { flex: 1; padding: 20px; overflow-y: auto; }
|
||
.header-title { font-size: 24px; margin-bottom: 20px; color: #303133; font-weight: 500; }
|
||
.search-bar { margin-bottom: 20px; display: flex; gap: 10px; flex-wrap: wrap; }
|
||
.table-container { background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05); }
|
||
.chart-container { height: 400px; margin-top: 20px; }
|
||
.stat-card { text-align: center; padding: 20px; }
|
||
.stat-card .num { font-size: 32px; font-weight: bold; color: #409EFF; margin-bottom: 10px; }
|
||
.stat-card .label { color: #909399; font-size: 14px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<!-- 侧边栏 -->
|
||
<div class="sidebar">
|
||
<h2>沃林学生管理系统</h2>
|
||
<el-menu :default-active="activeMenu" background-color="#001529" text-color="#fff" active-text-color="#409EFF" @select="handleMenuSelect">
|
||
<el-menu-item index="dashboard">📊 统计分析</el-menu-item>
|
||
<el-menu-item index="students">👨🎓 学生管理</el-menu-item>
|
||
<el-menu-item index="classes">🏫 班级管理</el-menu-item>
|
||
<el-menu-item index="teachers">👨🏫 教师管理</el-menu-item>
|
||
<el-menu-item index="counselors">💼 顾问管理</el-menu-item>
|
||
<el-menu-item index="scores">📝 成绩管理</el-menu-item>
|
||
<el-menu-item index="employments">💼 就业管理</el-menu-item>
|
||
</el-menu>
|
||
</div>
|
||
|
||
<!-- 主内容区 -->
|
||
<div class="main-content">
|
||
<!-- 统计分析 -->
|
||
<div v-if="activeMenu === 'dashboard'">
|
||
<div class="header-title">统计分析</div>
|
||
<el-row :gutter="20">
|
||
<el-col :span="8">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="num">{{ stats.totalStudents }}</div>
|
||
<div class="label">学生总数</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="num">{{ stats.totalClasses }}</div>
|
||
<div class="label">班级总数</div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-card shadow="hover" class="stat-card">
|
||
<div class="num">{{ stats.totalTeachers }}</div>
|
||
<div class="label">教师总数</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="20" style="margin-top: 20px;">
|
||
<el-col :span="12">
|
||
<el-card shadow="hover">
|
||
<template #header><div class="card-header"><span>班级人数分布</span></div></template>
|
||
<div ref="classChart" class="chart-container"></div>
|
||
</el-card>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-card shadow="hover">
|
||
<template #header><div class="card-header"><span>就业薪资排名 (前5名)</span></div></template>
|
||
<div ref="salaryChart" class="chart-container"></div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
|
||
<!-- 学生管理 -->
|
||
<div v-if="activeMenu === 'students'">
|
||
<div class="header-title">学生管理</div>
|
||
<div class="table-container">
|
||
<div class="search-bar">
|
||
<el-input v-model="studentQuery.id" placeholder="学号" style="width: 150px;" clearable></el-input>
|
||
<el-input v-model="studentQuery.name" placeholder="姓名" style="width: 150px;" clearable></el-input>
|
||
<el-input v-model="studentQuery.class_name" placeholder="班级" style="width: 150px;" clearable></el-input>
|
||
<el-button type="primary" @click="fetchStudents">查询</el-button>
|
||
<el-button type="success" @click="openStudentDialog()">新增学生</el-button>
|
||
</div>
|
||
<el-table :data="studentList" border stripe v-loading="loading">
|
||
<el-table-column prop="id" label="学号" width="150"></el-table-column>
|
||
<el-table-column prop="name" label="姓名" width="100"></el-table-column>
|
||
<el-table-column prop="gender" label="性别" width="80"></el-table-column>
|
||
<el-table-column prop="class_name" label="班级" width="150"></el-table-column>
|
||
<el-table-column prop="age" label="年龄" width="80"></el-table-column>
|
||
<el-table-column prop="major" label="专业" width="150"></el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="scope">
|
||
<el-tag :type="scope.row.status === '已就业' ? 'success' : (scope.row.status === '未就业' ? 'danger' : 'warning')">{{ scope.row.status }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="150" fixed="right">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="openStudentDialog(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="deleteStudent(scope.row.id)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 班级管理 -->
|
||
<div v-if="activeMenu === 'classes'">
|
||
<div class="header-title">班级管理</div>
|
||
<div class="table-container">
|
||
<div class="search-bar">
|
||
<el-input v-model="classQuery.class_name" placeholder="班级名称" style="width: 200px;" clearable></el-input>
|
||
<el-button type="primary" @click="fetchClasses">查询</el-button>
|
||
<el-button type="success" @click="openClassDialog()">新增班级</el-button>
|
||
</div>
|
||
<el-table :data="classList" border stripe v-loading="loading">
|
||
<el-table-column prop="class_id" label="ID" width="80"></el-table-column>
|
||
<el-table-column prop="class_name" label="班级名称"></el-table-column>
|
||
<el-table-column prop="head_teacher_id" label="班主任ID" width="120"></el-table-column>
|
||
<el-table-column prop="counselor_id" label="顾问ID" width="120"></el-table-column>
|
||
<el-table-column prop="create_time" label="创建时间" width="180"></el-table-column>
|
||
<el-table-column label="操作" width="200" fixed="right">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="openClassDialog(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="deleteClass(scope.row.class_id)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination style="margin-top: 20px;" background layout="prev, pager, next" :total="classTotal" :page-size="classQuery.page_size" @current-change="handleClassPageChange"></el-pagination>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 教师管理 -->
|
||
<div v-if="activeMenu === 'teachers'">
|
||
<div class="header-title">教师管理</div>
|
||
<div class="table-container">
|
||
<div class="search-bar"><el-button type="success" @click="openTeacherDialog()">新增教师</el-button></div>
|
||
<el-table :data="teacherList" border stripe v-loading="loading">
|
||
<el-table-column prop="t_id" label="ID" width="80"></el-table-column>
|
||
<el-table-column prop="t_name" label="姓名" width="120"></el-table-column>
|
||
<el-table-column prop="t_gender" label="性别" width="80"></el-table-column>
|
||
<el-table-column prop="t_phone" label="电话" width="150"></el-table-column>
|
||
<el-table-column prop="t_subject" label="教授科目" width="150"></el-table-column>
|
||
<el-table-column prop="t_created" label="入职时间" width="150"></el-table-column>
|
||
<el-table-column label="操作" width="200" fixed="right">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="openTeacherDialog(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="deleteTeacher(scope.row.t_id, scope.row.t_name)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination style="margin-top: 20px;" background layout="prev, pager, next" :total="teacherTotal" :page-size="teacherQuery.size" @current-change="handleTeacherPageChange"></el-pagination>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 顾问管理 -->
|
||
<div v-if="activeMenu === 'counselors'">
|
||
<div class="header-title">顾问管理</div>
|
||
<div class="table-container">
|
||
<div class="search-bar"><el-button type="success" @click="openCounselorDialog()">新增顾问</el-button></div>
|
||
<el-table :data="counselorList" border stripe v-loading="loading">
|
||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||
<el-table-column prop="name" label="姓名"></el-table-column>
|
||
<el-table-column prop="c_time" label="入职时间"></el-table-column>
|
||
<el-table-column label="操作" width="200" fixed="right">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="openCounselorDialog(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="deleteCounselor(scope.row.id)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 成绩管理 -->
|
||
<div v-if="activeMenu === 'scores'">
|
||
<div class="header-title">成绩管理</div>
|
||
<div class="table-container">
|
||
<div class="search-bar">
|
||
<el-input v-model="scoreQuery.id" placeholder="学号" style="width: 150px;" clearable></el-input>
|
||
<el-input v-model="scoreQuery.exam_round" placeholder="考核序次" style="width: 150px;" clearable></el-input>
|
||
<el-button type="primary" @click="fetchScores">查询</el-button>
|
||
<el-button type="success" @click="openScoreDialog()">录入成绩</el-button>
|
||
</div>
|
||
<el-table :data="scoreList" border stripe v-loading="loading">
|
||
<el-table-column prop="id" label="学号" width="150"></el-table-column>
|
||
<el-table-column prop="exam_round" label="考核序次" width="120"></el-table-column>
|
||
<el-table-column prop="score" label="成绩" width="100">
|
||
<template #default="scope">
|
||
<el-tag :type="scope.row.score < 60 ? 'danger' : 'success'">{{ scope.row.score }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="200" fixed="right">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="openScoreDialog(scope.row)">修改</el-button>
|
||
<el-button size="small" type="danger" @click="deleteScore(scope.row.id, scope.row.exam_round)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 就业管理 -->
|
||
<div v-if="activeMenu === 'employments'">
|
||
<div class="header-title">就业管理</div>
|
||
<div class="table-container">
|
||
<div class="search-bar">
|
||
<el-input v-model="employmentQuery.id" placeholder="学号" style="width: 150px;" clearable></el-input>
|
||
<el-input v-model="employmentQuery.name" placeholder="公司名称" style="width: 150px;" clearable></el-input>
|
||
<el-button type="primary" @click="fetchEmployments">查询</el-button>
|
||
</div>
|
||
<el-table :data="employmentList" border stripe v-loading="loading">
|
||
<el-table-column prop="id" label="学号" width="120"></el-table-column>
|
||
<el-table-column prop="name" label="姓名" width="100"></el-table-column>
|
||
<el-table-column prop="class_name" label="班级" width="150"></el-table-column>
|
||
<el-table-column prop="emp" label="就业公司" width="150"></el-table-column>
|
||
<el-table-column prop="sal" label="薪资" width="120"></el-table-column>
|
||
<el-table-column prop="offer_open" label="就业开放时间" width="180"></el-table-column>
|
||
<el-table-column prop="offer_review" label="Offer下发时间" width="180"></el-table-column>
|
||
<el-table-column label="操作" width="150" fixed="right">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="openEmploymentDialog(scope.row)">编辑</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 弹窗:学生 -->
|
||
<el-dialog v-model="studentDialogVisible" :title="isStudentEdit ? '编辑学生' : '新增学生'" width="600px">
|
||
<el-form :model="studentForm" label-width="100px">
|
||
<el-form-item label="学号" v-if="!isStudentEdit">
|
||
<el-input v-model="studentForm.id" placeholder="请输入学号(如 2024001)" maxlength="20" show-word-limit clearable></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="学号" v-else>
|
||
<el-input v-model="studentForm.id" disabled></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="姓名"><el-input v-model="studentForm.name" placeholder="请输入姓名"></el-input></el-form-item>
|
||
<el-form-item label="性别">
|
||
<el-radio-group v-model="studentForm.gender">
|
||
<el-radio label="男">男</el-radio><el-radio label="女">女</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="班级"><el-input v-model="studentForm.class_name" placeholder="请输入班级名称"></el-input></el-form-item>
|
||
<el-form-item label="年龄"><el-input-number v-model="studentForm.age" :min="1" :max="100"></el-input-number></el-form-item>
|
||
<el-form-item label="专业"><el-input v-model="studentForm.major" placeholder="请输入专业"></el-input></el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-select v-model="studentForm.status" placeholder="请选择状态">
|
||
<el-option label="在读" value="在读"></el-option>
|
||
<el-option label="未就业" value="未就业"></el-option>
|
||
<el-option label="已就业" value="已就业"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="studentDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="saveStudent">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 弹窗:班级 -->
|
||
<el-dialog v-model="classDialogVisible" :title="classForm.class_id ? '编辑班级' : '新增班级'" width="600px">
|
||
<el-form :model="classForm" label-width="100px">
|
||
<el-form-item label="班级名称"><el-input v-model="classForm.class_name" placeholder="请输入班级名称"></el-input></el-form-item>
|
||
<el-form-item label="班主任ID"><el-input-number v-model="classForm.head_teacher_id" :min="1"></el-input-number></el-form-item>
|
||
<el-form-item label="顾问ID"><el-input-number v-model="classForm.counselor_id" :min="1"></el-input-number></el-form-item>
|
||
<el-form-item label="开课时间"><el-date-picker v-model="classForm.create_time" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"></el-date-picker></el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="classDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="saveClass">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 弹窗:教师 -->
|
||
<el-dialog v-model="teacherDialogVisible" :title="teacherForm.t_id ? '编辑教师' : '新增教师'" width="600px">
|
||
<el-form :model="teacherForm" label-width="100px">
|
||
<el-form-item label="姓名"><el-input v-model="teacherForm.t_name" placeholder="请输入姓名"></el-input></el-form-item>
|
||
<el-form-item label="性别">
|
||
<el-radio-group v-model="teacherForm.t_gender">
|
||
<el-radio label="男">男</el-radio><el-radio label="女">女</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="电话"><el-input v-model="teacherForm.t_phone" placeholder="请输入电话"></el-input></el-form-item>
|
||
<el-form-item label="教授科目"><el-input v-model="teacherForm.t_subject" placeholder="请输入教授科目"></el-input></el-form-item>
|
||
<el-form-item label="入职时间"><el-date-picker v-model="teacherForm.t_created" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"></el-date-picker></el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="teacherDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="saveTeacher">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 弹窗:顾问 -->
|
||
<el-dialog v-model="counselorDialogVisible" :title="counselorForm.id ? '编辑顾问' : '新增顾问'" width="600px">
|
||
<el-form :model="counselorForm" label-width="100px">
|
||
<el-form-item label="ID" v-if="!counselorForm.id"><el-input-number v-model="counselorForm.id" :min="1"></el-input-number></el-form-item>
|
||
<el-form-item label="姓名"><el-input v-model="counselorForm.name" placeholder="请输入姓名"></el-input></el-form-item>
|
||
<el-form-item label="入职时间"><el-date-picker v-model="counselorForm.c_time" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"></el-date-picker></el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="counselorDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="saveCounselor">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 弹窗:成绩(修复学号无法输入) -->
|
||
<el-dialog v-model="scoreDialogVisible" :title="isScoreEdit ? '修改成绩' : '录入成绩'" width="600px">
|
||
<el-form :model="scoreForm" label-width="100px">
|
||
<el-form-item label="学号">
|
||
<el-input v-model="scoreForm.id" placeholder="请输入学号" :disabled="isScoreEdit"></el-input>
|
||
</el-form-item>
|
||
<el-form-item label="考核序次">
|
||
<el-input-number v-model="scoreForm.exam_round" :min="1" :max="10" :disabled="isScoreEdit"></el-input-number>
|
||
</el-form-item>
|
||
<el-form-item label="成绩">
|
||
<el-input-number v-model="scoreForm.score" :min="0" :max="100"></el-input-number>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="scoreDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="saveScore">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 弹窗:就业 -->
|
||
<el-dialog v-model="employmentDialogVisible" title="编辑就业信息" width="600px">
|
||
<el-form :model="employmentForm" label-width="120px">
|
||
<el-form-item label="学号"><el-input v-model="employmentForm.id" disabled></el-input></el-form-item>
|
||
<el-form-item label="就业公司"><el-input v-model="employmentForm.emp" placeholder="请输入公司名称"></el-input></el-form-item>
|
||
<el-form-item label="薪资"><el-input-number v-model="employmentForm.sal" :min="0" :precision="2"></el-input-number></el-form-item>
|
||
<el-form-item label="就业开放时间"><el-date-picker v-model="employmentForm.offer_open" type="datetime" placeholder="选择时间" value-format="YYYY-MM-DDTHH:mm:ss"></el-date-picker></el-form-item>
|
||
<el-form-item label="Offer下发时间"><el-date-picker v-model="employmentForm.offer_review" type="datetime" placeholder="选择时间" value-format="YYYY-MM-DDTHH:mm:ss"></el-date-picker></el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="employmentDialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="saveEmployment">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
|
||
<script>
|
||
const { createApp, ref, reactive, onMounted, nextTick } = Vue;
|
||
const { ElMessage, ElMessageBox } = ElementPlus;
|
||
|
||
const app = createApp({
|
||
setup() {
|
||
const activeMenu = ref('dashboard');
|
||
const loading = ref(false);
|
||
const baseUrl = 'http://127.0.0.1:8001';
|
||
|
||
// --- 统计分析 ---
|
||
const stats = reactive({ totalStudents: 0, totalClasses: 0, totalTeachers: 0 });
|
||
const classChart = ref(null);
|
||
const salaryChart = ref(null);
|
||
|
||
// --- 学生管理 ---
|
||
const studentList = ref([]);
|
||
const studentQuery = reactive({ id: '', name: '', class_name: '' });
|
||
const studentDialogVisible = ref(false);
|
||
const studentForm = reactive({ id: '', name: '', gender: '男', class_name: '', age: 18, major: '', status: '在读' });
|
||
const isStudentEdit = ref(false);
|
||
|
||
// --- 班级管理 ---
|
||
const classList = ref([]);
|
||
const classTotal = ref(0);
|
||
const classQuery = reactive({ class_name: '', page_num: 1, page_size: 10 });
|
||
const classDialogVisible = ref(false);
|
||
const classForm = reactive({ class_id: null, class_name: '', head_teacher_id: 1, counselor_id: 1, create_time: '' });
|
||
|
||
// --- 教师管理 ---
|
||
const teacherList = ref([]);
|
||
const teacherTotal = ref(0);
|
||
const teacherQuery = reactive({ page: 1, size: 10 });
|
||
const teacherDialogVisible = ref(false);
|
||
const teacherForm = reactive({ t_id: null, t_name: '', t_gender: '男', t_phone: '', t_subject: '', t_created: '' });
|
||
|
||
// --- 顾问管理 ---
|
||
const counselorList = ref([]);
|
||
const counselorDialogVisible = ref(false);
|
||
const counselorForm = reactive({ id: null, name: '', c_time: '' });
|
||
|
||
// --- 成绩管理 ---
|
||
const scoreList = ref([]);
|
||
const scoreQuery = reactive({ id: '', exam_round: '' });
|
||
const scoreDialogVisible = ref(false);
|
||
const scoreForm = reactive({ id: '', exam_round: 1, score: 0 });
|
||
const isScoreEdit = ref(false); // ★ 新增:区分录入与修改
|
||
|
||
// --- 就业管理 ---
|
||
const employmentList = ref([]);
|
||
const employmentQuery = reactive({ id: '', name: '' });
|
||
const employmentDialogVisible = ref(false);
|
||
const employmentForm = reactive({ id: '', emp: '', sal: 0, offer_open: '', offer_review: '' });
|
||
|
||
// --- 菜单切换 ---
|
||
const handleMenuSelect = (index) => {
|
||
activeMenu.value = index;
|
||
if (index === 'dashboard') fetchStats();
|
||
if (index === 'students') fetchStudents();
|
||
if (index === 'classes') fetchClasses();
|
||
if (index === 'teachers') fetchTeachers();
|
||
if (index === 'counselors') fetchCounselors();
|
||
if (index === 'scores') fetchScores();
|
||
if (index === 'employments') fetchEmployments();
|
||
};
|
||
|
||
// --- API 请求方法 ---
|
||
const fetchStats = async () => {
|
||
try {
|
||
const stuRes = await axios.get(`${baseUrl}/students`);
|
||
stats.totalStudents = stuRes.data.length;
|
||
const classRes = await axios.get(`${baseUrl}/api/classes?page_num=1&page_size=100`);
|
||
stats.totalClasses = classRes.data.total;
|
||
const teaRes = await axios.get(`${baseUrl}/teacher/list?page=1&size=100`);
|
||
stats.totalTeachers = teaRes.data.total;
|
||
nextTick(() => { renderClassChart(); renderSalaryChart(); });
|
||
} catch (e) { console.error(e); }
|
||
};
|
||
|
||
const renderClassChart = async () => {
|
||
try {
|
||
const res = await axios.get(`${baseUrl}/analysis/studnet_info/class`);
|
||
const data = res.data;
|
||
const chart = echarts.init(classChart.value);
|
||
chart.setOption({
|
||
tooltip: { trigger: 'axis' },
|
||
legend: { data: ['总人数', '男', '女'] },
|
||
xAxis: { type: 'category', data: data.map(i => i.class_name) },
|
||
yAxis: { type: 'value' },
|
||
series: [
|
||
{ name: '总人数', type: 'bar', data: data.map(i => i['总人数']) },
|
||
{ name: '男', type: 'bar', data: data.map(i => i['男']) },
|
||
{ name: '女', type: 'bar', data: data.map(i => i['女']) }
|
||
]
|
||
});
|
||
} catch (e) { console.error(e); }
|
||
};
|
||
|
||
const renderSalaryChart = async () => {
|
||
try {
|
||
const res = await axios.get(`${baseUrl}/analysis/studnet_employments_salary?n=5`);
|
||
const data = res.data;
|
||
const chart = echarts.init(salaryChart.value);
|
||
chart.setOption({
|
||
tooltip: { trigger: 'item' },
|
||
xAxis: { type: 'category', data: data.map(i => i.student ? i.student.name : i.id) },
|
||
yAxis: { type: 'value' },
|
||
series: [{ data: data.map(i => i.sal), type: 'bar', color: '#67C23A' }]
|
||
});
|
||
} catch (e) { console.error(e); }
|
||
};
|
||
|
||
// ------- 学生 -------
|
||
const fetchStudents = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const params = {};
|
||
if (studentQuery.id) params.id = studentQuery.id;
|
||
if (studentQuery.name) params.name = studentQuery.name;
|
||
if (studentQuery.class_name) params.class_name = studentQuery.class_name;
|
||
const res = await axios.get(`${baseUrl}/students`, { params });
|
||
studentList.value = res.data;
|
||
} catch (e) { ElMessage.error('获取学生列表失败'); }
|
||
loading.value = false;
|
||
};
|
||
|
||
const openStudentDialog = (row = null) => {
|
||
if (row) {
|
||
isStudentEdit.value = true;
|
||
Object.assign(studentForm, row);
|
||
} else {
|
||
isStudentEdit.value = false;
|
||
Object.assign(studentForm, { id: '', name: '', gender: '男', class_name: '', age: 18, major: '', status: '在读' });
|
||
}
|
||
studentDialogVisible.value = true;
|
||
};
|
||
|
||
const saveStudent = async () => {
|
||
if (!studentForm.id) { ElMessage.warning('请填写学号'); return; }
|
||
if (!studentForm.name) { ElMessage.warning('请填写姓名'); return; }
|
||
if (!studentForm.class_name) { ElMessage.warning('请填写班级'); return; }
|
||
try {
|
||
if (isStudentEdit.value) {
|
||
const { id, ...body } = studentForm;
|
||
await axios.put(`${baseUrl}/students/${id}`, body);
|
||
ElMessage.success('更新成功');
|
||
} else {
|
||
await axios.post(`${baseUrl}/students`, studentForm);
|
||
ElMessage.success('创建成功');
|
||
}
|
||
studentDialogVisible.value = false;
|
||
fetchStudents();
|
||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||
};
|
||
|
||
const deleteStudent = async (id) => {
|
||
try {
|
||
await ElMessageBox.confirm(
|
||
'此操作将学生标记为已删除(逻辑删除),数据库记录仍会保留,且支持后续恢复。是否继续?',
|
||
'逻辑删除确认',
|
||
{ type: 'warning', confirmButtonText: '确定删除', cancelButtonText: '取消' }
|
||
);
|
||
await axios.delete(`${baseUrl}/students/${id}`);
|
||
ElMessage.success('学生已逻辑删除,数据仍保留在库中');
|
||
fetchStudents();
|
||
} catch (e) {
|
||
if (e !== 'cancel') ElMessage.error('删除失败');
|
||
}
|
||
};
|
||
|
||
// ------- 班级 -------
|
||
const fetchClasses = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const res = await axios.get(`${baseUrl}/api/classes`, { params: classQuery });
|
||
classList.value = res.data.items;
|
||
classTotal.value = res.data.total;
|
||
} catch (e) { ElMessage.error('获取班级列表失败'); }
|
||
loading.value = false;
|
||
};
|
||
|
||
const handleClassPageChange = (val) => { classQuery.page_num = val; fetchClasses(); };
|
||
|
||
const openClassDialog = (row = null) => {
|
||
if (row) { Object.assign(classForm, row); }
|
||
else { Object.assign(classForm, { class_id: null, class_name: '', head_teacher_id: 1, counselor_id: 1, create_time: '' }); }
|
||
classDialogVisible.value = true;
|
||
};
|
||
|
||
const saveClass = async () => {
|
||
try {
|
||
if (classForm.class_id) {
|
||
await axios.put(`${baseUrl}/api/classes/${classForm.class_id}/base`, classForm);
|
||
ElMessage.success('更新成功');
|
||
} else {
|
||
await axios.post(`${baseUrl}/api/classes`, classForm);
|
||
ElMessage.success('创建成功');
|
||
}
|
||
classDialogVisible.value = false;
|
||
fetchClasses();
|
||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||
};
|
||
|
||
const deleteClass = async (id) => {
|
||
try {
|
||
await ElMessageBox.confirm('确定要删除该班级吗?', '提示', { type: 'warning' });
|
||
await axios.put(`${baseUrl}/api/classes/batch-delete`, { class_ids: [id], deleted: true });
|
||
ElMessage.success('删除成功');
|
||
fetchClasses();
|
||
} catch (e) { if (e !== 'cancel') ElMessage.error('删除失败'); }
|
||
};
|
||
|
||
// ------- 教师 -------
|
||
const fetchTeachers = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const res = await axios.get(`${baseUrl}/teacher/list`, { params: teacherQuery });
|
||
teacherList.value = res.data.list;
|
||
teacherTotal.value = res.data.total;
|
||
} catch (e) { ElMessage.error('获取教师列表失败'); }
|
||
loading.value = false;
|
||
};
|
||
|
||
const handleTeacherPageChange = (val) => { teacherQuery.page = val; fetchTeachers(); };
|
||
|
||
const openTeacherDialog = (row = null) => {
|
||
if (row) { Object.assign(teacherForm, row); }
|
||
else { Object.assign(teacherForm, { t_id: null, t_name: '', t_gender: '男', t_phone: '', t_subject: '', t_created: '' }); }
|
||
teacherDialogVisible.value = true;
|
||
};
|
||
|
||
const saveTeacher = async () => {
|
||
try {
|
||
if (teacherForm.t_id) {
|
||
await axios.put(`${baseUrl}/teacher/${teacherForm.t_id}`, teacherForm);
|
||
ElMessage.success('更新成功');
|
||
} else {
|
||
await axios.post(`${baseUrl}/teacher/teachers`, teacherForm);
|
||
ElMessage.success('创建成功');
|
||
}
|
||
teacherDialogVisible.value = false;
|
||
fetchTeachers();
|
||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||
};
|
||
|
||
const deleteTeacher = async (t_id, t_name) => {
|
||
try {
|
||
await ElMessageBox.confirm('确定要删除该教师吗?', '提示', { type: 'warning' });
|
||
await axios.delete(`${baseUrl}/teacher/${t_id}`, { data: { t_id, t_name } });
|
||
ElMessage.success('删除成功');
|
||
fetchTeachers();
|
||
} catch (e) { if (e !== 'cancel') ElMessage.error('删除失败'); }
|
||
};
|
||
|
||
// ------- 顾问 -------
|
||
const fetchCounselors = async () => {
|
||
loading.value = true;
|
||
try {
|
||
counselorList.value = [
|
||
{ id: 1, name: '吴振玮', c_time: '2023-01-01' },
|
||
{ id: 2, name: '孙波', c_time: '2023-01-01' }
|
||
];
|
||
} catch (e) { ElMessage.error('获取顾问列表失败'); }
|
||
loading.value = false;
|
||
};
|
||
|
||
const openCounselorDialog = (row = null) => {
|
||
if (row) { Object.assign(counselorForm, row); }
|
||
else { Object.assign(counselorForm, { id: null, name: '', c_time: '' }); }
|
||
counselorDialogVisible.value = true;
|
||
};
|
||
|
||
const saveCounselor = async () => {
|
||
try {
|
||
if (counselorForm.id) {
|
||
await axios.put(`${baseUrl}/counselor/${counselorForm.id}`, counselorForm);
|
||
ElMessage.success('更新成功');
|
||
} else {
|
||
await axios.post(`${baseUrl}/counselor/counselors/`, counselorForm);
|
||
ElMessage.success('创建成功');
|
||
}
|
||
counselorDialogVisible.value = false;
|
||
fetchCounselors();
|
||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||
};
|
||
|
||
const deleteCounselor = async (id) => {
|
||
try {
|
||
await ElMessageBox.confirm('确定要删除该顾问吗?', '提示', { type: 'warning' });
|
||
await axios.delete(`${baseUrl}/counselor/${id}`, { data: { id } });
|
||
ElMessage.success('删除成功');
|
||
fetchCounselors();
|
||
} catch (e) { if (e !== 'cancel') ElMessage.error('删除失败'); }
|
||
};
|
||
|
||
// ------- 成绩 -------
|
||
const fetchScores = async () => {
|
||
loading.value = true;
|
||
try {
|
||
if (scoreQuery.id) {
|
||
const res = await axios.get(`${baseUrl}/scores/query/id?id=${scoreQuery.id}`);
|
||
scoreList.value = res.data.msg || [];
|
||
} else if (scoreQuery.exam_round) {
|
||
const res = await axios.get(`${baseUrl}/scores/query/round?exam_round=${scoreQuery.exam_round}`);
|
||
scoreList.value = res.data.msg || [];
|
||
} else {
|
||
scoreList.value = [];
|
||
}
|
||
} catch (e) { ElMessage.error('获取成绩列表失败'); }
|
||
loading.value = false;
|
||
};
|
||
|
||
const openScoreDialog = (row = null) => {
|
||
if (row) {
|
||
// 编辑模式
|
||
isScoreEdit.value = true;
|
||
Object.assign(scoreForm, row);
|
||
} else {
|
||
// 录入模式
|
||
isScoreEdit.value = false;
|
||
Object.assign(scoreForm, { id: '', exam_round: 1, score: 0 });
|
||
}
|
||
scoreDialogVisible.value = true;
|
||
};
|
||
|
||
const saveScore = async () => {
|
||
if (!scoreForm.id) { ElMessage.warning('请输入学号'); return; }
|
||
if (!scoreForm.exam_round) { ElMessage.warning('请输入考核序次'); return; }
|
||
try {
|
||
if (isScoreEdit.value) {
|
||
await axios.put(`${baseUrl}/scores/update?id=${scoreForm.id}&exam_round=${scoreForm.exam_round}&score=${scoreForm.score}`);
|
||
ElMessage.success('修改成功');
|
||
} else {
|
||
await axios.post(`${baseUrl}/scores/add?id=${scoreForm.id}&exam_round=${scoreForm.exam_round}&score=${scoreForm.score}`);
|
||
ElMessage.success('录入成功');
|
||
}
|
||
scoreDialogVisible.value = false;
|
||
fetchScores();
|
||
} catch (e) { ElMessage.error(e.response?.data?.msg || '操作失败'); }
|
||
};
|
||
|
||
const deleteScore = async (id, exam_round) => {
|
||
try {
|
||
await ElMessageBox.confirm('确定要删除该成绩吗?', '提示', { type: 'warning' });
|
||
await axios.delete(`${baseUrl}/scores/delete?id=${id}&exam_round=${exam_round}`);
|
||
ElMessage.success('删除成功');
|
||
fetchScores();
|
||
} catch (e) { if (e !== 'cancel') ElMessage.error('删除失败'); }
|
||
};
|
||
|
||
// ------- 就业 -------
|
||
const fetchEmployments = async () => {
|
||
loading.value = true;
|
||
try {
|
||
const params = {};
|
||
if (employmentQuery.id) params.mid = employmentQuery.id;
|
||
if (employmentQuery.name) params.name = employmentQuery.name;
|
||
const res = await axios.get(`${baseUrl}/employment/query`, { params });
|
||
employmentList.value = res.data;
|
||
} catch (e) { ElMessage.error('获取就业列表失败'); }
|
||
loading.value = false;
|
||
};
|
||
|
||
const openEmploymentDialog = (row) => {
|
||
Object.assign(employmentForm, row);
|
||
employmentDialogVisible.value = true;
|
||
};
|
||
|
||
const saveEmployment = async () => {
|
||
try {
|
||
await axios.post(`${baseUrl}/employment/students/${employmentForm.id}`, {
|
||
offer_open: employmentForm.offer_open || null,
|
||
offer_review: employmentForm.offer_review || null,
|
||
emp: employmentForm.emp || null,
|
||
sal: employmentForm.sal || null
|
||
});
|
||
ElMessage.success('更新成功');
|
||
employmentDialogVisible.value = false;
|
||
fetchEmployments();
|
||
} catch (e) { ElMessage.error(e.response?.data?.detail || '操作失败'); }
|
||
};
|
||
|
||
onMounted(() => { fetchStats(); });
|
||
|
||
return {
|
||
activeMenu, handleMenuSelect, loading, stats, classChart, salaryChart,
|
||
|
||
// 学生
|
||
studentList, studentQuery, fetchStudents,
|
||
studentDialogVisible, studentForm, isStudentEdit,
|
||
openStudentDialog, saveStudent, deleteStudent,
|
||
|
||
// 班级
|
||
classList, classTotal, classQuery, fetchClasses, handleClassPageChange,
|
||
classDialogVisible, classForm, openClassDialog, saveClass, deleteClass,
|
||
|
||
// 教师
|
||
teacherList, teacherTotal, teacherQuery, fetchTeachers, handleTeacherPageChange,
|
||
teacherDialogVisible, teacherForm, openTeacherDialog, saveTeacher, deleteTeacher,
|
||
|
||
// 顾问
|
||
counselorList, fetchCounselors, counselorDialogVisible, counselorForm,
|
||
openCounselorDialog, saveCounselor, deleteCounselor,
|
||
|
||
// 成绩
|
||
scoreList, scoreQuery, fetchScores, scoreDialogVisible, scoreForm,
|
||
isScoreEdit, openScoreDialog, saveScore, deleteScore,
|
||
|
||
// 就业
|
||
employmentList, employmentQuery, fetchEmployments, employmentDialogVisible,
|
||
employmentForm, openEmploymentDialog, saveEmployment
|
||
};
|
||
}
|
||
});
|
||
|
||
app.use(ElementPlus);
|
||
app.mount('#app');
|
||
</script>
|
||
</body>
|
||
</html> |