Files
2026-09-21 17:39:16 +08:00

785 lines
45 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>