Files
stu_teacher/static/index.html
T

317 lines
20 KiB
HTML
Raw 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>
<link rel="stylesheet" href="/static/style.css" />
<script src="https://cdn.jsdelivr.net/npm/vue@3.4.27/dist/vue.global.prod.js"></script>
</head>
<body>
<div id="app">
<!-- ==================== 登录页 ==================== -->
<div v-if="!token" class="login-wrap">
<div class="login-card">
<h2>沃林学生管理系统</h2>
<p class="sub">FastAPI + MySQL 管理后台</p>
<label>用户名</label>
<input v-model.trim="loginForm.username" @keyup.enter="login" placeholder="请输入用户名" />
<label>密码</label>
<input v-model="loginForm.password" type="password" @keyup.enter="login" placeholder="请输入密码" />
<button class="btn primary block" :disabled="loading" @click="login">{{ loading ? '登录中...' : '登 录' }}</button>
<p class="tip">{{ tip }}</p>
</div>
</div>
<!-- ==================== 主界面 ==================== -->
<template v-else>
<header class="topbar">
<span class="logo">沃林学生管理系统</span>
<span class="spacer"></span>
<span class="role-tag">{{ roleLabel }}</span>
<span>{{ me.username }}</span>
<button class="btn ghost" @click="logout">退出登录</button>
</header>
<nav class="tabs">
<button v-for="t in tabs" :key="t.key" :class="['tab', { active: tab === t.key }]" @click="switchTab(t.key)">{{ t.label }}</button>
</nav>
<main class="content">
<!-- ============ 学生管理 ============ -->
<section v-show="tab === 'students'">
<div class="toolbar">
<input v-model.number="stuQ.stu_id" placeholder="学号" class="w90" />
<input v-model.trim="stuQ.stu_name" placeholder="姓名(模糊)" class="w120" />
<select v-model="stuQ.class_id"><option value="">全部班级</option><option v-for="c in classes" :key="c.class_id" :value="c.class_id">{{ c.class_name }}</option></select>
<select v-model="stuQ.status"><option value="">全部状态</option><option>在读</option><option>进入就业</option><option>已就业</option></select>
<button class="btn" @click="loadStudents(1)">查询</button>
<button class="btn ghost" @click="resetStuQ">重置</button>
<span class="spacer"></span>
<button v-if="me.role !== 'student'" class="btn primary" @click="openStuForm()">+ 新增学生</button>
</div>
<table>
<thead><tr><th>学号</th><th>姓名</th><th>班级</th><th>性别</th><th>年龄</th><th>学历</th><th>专业</th><th>籍贯</th><th>毕业院校</th><th>入学</th><th>状态</th><th v-if="me.role!=='student'">操作</th></tr></thead>
<tbody>
<tr v-for="s in students" :key="s.stu_id">
<td>{{ s.stu_id }}</td><td>{{ s.stu_name }}</td><td>{{ s.class_name }}</td><td>{{ s.gender }}</td><td>{{ s.age }}</td>
<td>{{ s.education }}</td><td>{{ s.major }}</td><td>{{ s.native_place }}</td><td>{{ s.graduate_school }}</td>
<td>{{ s.enroll_time }}</td><td><span :class="['badge', s.status==='已就业'?'ok':(s.status==='进入就业'?'warn':'')]">{{ s.status }}</span></td>
<td v-if="me.role!=='student'">
<button class="btn mini" @click="openStuForm(s)">编辑</button>
<button class="btn mini danger" @click="delStudent(s)">删除</button>
</td>
</tr>
<tr v-if="!students.length"><td colspan="12" class="empty">暂无数据</td></tr>
</tbody>
</table>
<pager :page="stuPage" :total="stuTotal" @go="loadStudents"></pager>
</section>
<!-- ============ 成绩管理 ============ -->
<section v-show="tab === 'scores'">
<div class="toolbar">
<input v-model.number="scoreQ.stu_id" placeholder="学号" class="w90" />
<input v-model.number="scoreQ.exam_id" placeholder="考核序次" class="w90" />
<button class="btn" @click="loadScores">查询</button>
<span class="spacer"></span>
<button v-if="me.role !== 'student'" class="btn primary" @click="openScoreForm()">+ 录入成绩</button>
</div>
<p class="warn-tip" v-if="scoreWarning">{{ scoreWarning }}</p>
<table>
<thead><tr><th>学号</th><th>姓名</th><th>考核序次</th><th>成绩</th><th>预警</th><th v-if="me.role!=='student'">操作</th></tr></thead>
<tbody>
<tr v-for="s in scores" :key="s.stu_id + '-' + s.exam_id">
<td>{{ s.stu_id }}</td><td>{{ s.stu_name }}</td><td>第 {{ s.exam_id }} 次</td><td>{{ s.score }}</td>
<td><span v-if="s.is_warning" class="badge danger">低于60分</span><span v-else class="badge ok">正常</span></td>
<td v-if="me.role!=='student'">
<button class="btn mini" @click="openScoreForm(s)">编辑</button>
<button class="btn mini danger" @click="delScore(s)">删除</button>
</td>
</tr>
<tr v-if="!scores.length"><td colspan="6" class="empty">暂无数据</td></tr>
</tbody>
</table>
</section>
<!-- ============ 就业管理 ============ -->
<section v-show="tab === 'employment'">
<div class="toolbar">
<input v-model.number="empQ.stu_id" placeholder="学号" class="w90" />
<input v-model.trim="empQ.company_name" placeholder="公司名称(模糊)" class="w140" />
<input v-model.number="empQ.salary_min" placeholder="薪资下限" class="w90" />
<input v-model.number="empQ.salary_max" placeholder="薪资上限" class="w90" />
<button class="btn" @click="loadEmployment(1)">查询</button>
<span class="spacer"></span>
<button v-if="me.role !== 'student'" class="btn primary" @click="empFormMode='open'; showEmp=true">+ 就业开放</button>
<button v-if="me.role !== 'student'" class="btn primary" @click="empFormMode='offer'; showEmp=true">+ 登记 offer</button>
</div>
<table>
<thead><tr><th>学号</th><th>姓名</th><th>班级</th><th>开放时间</th><th>offer时间</th><th>公司</th><th>薪资</th><th v-if="me.role!=='student'">操作</th></tr></thead>
<tbody>
<tr v-for="e in employment" :key="e.stu_id">
<td>{{ e.stu_id }}</td><td>{{ e.stu_name }}</td><td>{{ e.class_name }}</td>
<td>{{ e.employment_open_time }}</td><td>{{ e.job_time || '—' }}</td><td>{{ e.company_name }}</td><td>¥{{ e.salary }}</td>
<td v-if="me.role!=='student'">
<button class="btn mini" @click="openEmpEdit(e)">编辑</button>
<button class="btn mini danger" @click="delEmployment(e)">删除</button>
</td>
</tr>
<tr v-if="!employment.length"><td colspan="8" class="empty">暂无数据</td></tr>
</tbody>
</table>
<pager :page="empPage" :total="empTotal" @go="loadEmployment"></pager>
</section>
<!-- ============ 班级管理 ============ -->
<section v-show="tab === 'classes'">
<div class="toolbar">
<span class="spacer"></span>
<button v-if="me.role === 'admin'" class="btn primary" @click="openClassForm()">+ 新增班级</button>
</div>
<table>
<thead><tr><th>班级ID</th><th>班级名称</th><th>开课时间</th><th v-if="me.role==='admin'">操作</th></tr></thead>
<tbody>
<tr v-for="c in classes" :key="c.class_id">
<td>{{ c.class_id }}</td><td>{{ c.class_name }}</td><td>{{ c.start_time }}</td>
<td v-if="me.role==='admin'">
<button class="btn mini" @click="openClassForm(c)">编辑</button>
<button class="btn mini danger" @click="delClass(c)">删除</button>
</td>
</tr>
<tr v-if="!classes.length"><td colspan="4" class="empty">暂无数据</td></tr>
</tbody>
</table>
</section>
<!-- ============ 老师管理 ============ -->
<section v-show="tab === 'teachers'">
<div class="toolbar">
<span class="spacer"></span>
<button v-if="me.role === 'admin'" class="btn primary" @click="openTeacherForm()">+ 新增老师</button>
</div>
<table>
<thead><tr><th>工号</th><th>姓名</th><th>职务</th><th>带班</th><th v-if="me.role==='admin'">操作</th></tr></thead>
<tbody>
<tr v-for="t in teachers" :key="t.teacher_id">
<td>{{ t.teacher_id }}</td><td>{{ t.teacher_name }}</td><td>{{ t.job_name }}</td><td>{{ t.class_name }}</td>
<td v-if="me.role==='admin'">
<button class="btn mini" @click="openTeacherForm(t)">编辑</button>
<button class="btn mini danger" @click="delTeacher(t)">删除</button>
</td>
</tr>
<tr v-if="!teachers.length"><td colspan="5" class="empty">暂无数据</td></tr>
</tbody>
</table>
<pager :page="teacherPage" :total="teacherTotal" @go="loadTeachers"></pager>
</section>
<!-- ============ 统计分析 ============ -->
<section v-show="tab === 'statistics'">
<div class="toolbar wrap">
<button class="btn" @click="runStat('byAge')">动态年龄查询</button>
<button class="btn" @click="runStat('genderStats')">班级性别统计</button>
<button class="btn" @click="runStat('allAbove')">分数线以上学生</button>
<button class="btn" @click="runStat('fail')">不及格>=N次</button>
<button class="btn" @click="runStat('classAvg')">班级考试平均分</button>
<button class="btn" @click="runStat('topSalary')">薪资TopN</button>
<button class="btn" @click="runStat('duration')">就业时长</button>
<button class="btn" @click="runStat('classAvgDuration')">班级平均就业时长</button>
<button class="btn" @click="runStat('volatility')">成绩波动Top5</button>
<button class="btn" @click="runStat('funnel')">班级就业漏斗</button>
</div>
<!-- 动态年龄查询参数 -->
<div class="param-box" v-if="statKey==='byAge'">
<select v-model="statParams.op"><option value="gt">大于</option><option value="lt">小于</option><option value="eq">等于</option><option value="gte">大于等于</option><option value="lte">小于等于</option><option value="between">区间</option></select>
<input v-model.number="statParams.value" placeholder="年龄" v-if="statParams.op!=='between'" class="w90" />
<input v-model.number="statParams.min_value" placeholder="最小" v-if="statParams.op==='between'" class="w70" />
<input v-model.number="statParams.max_value" placeholder="最大" v-if="statParams.op==='between'" class="w70" />
<button class="btn primary" @click="doStat">执行</button>
</div>
<div class="param-box" v-if="['allAbove','fail','topSalary','classAvg','volatility'].includes(statKey)">
<label v-if="statKey==='allAbove'">分数线 <input v-model.number="statParams.line" class="w70" /></label>
<label v-if="statKey==='fail'">不及格次数>= <input v-model.number="statParams.times" class="w70" /></label>
<label v-if="statKey==='topSalary'">Top <input v-model.number="statParams.n" class="w70" /></label>
<label v-if="statKey==='classAvg'">考核序次(可空) <input v-model.number="statParams.exam_id" class="w70" /></label>
<label v-if="statKey==='classAvg'">排序 <select v-model="statParams.order"><option value="desc">从高到低</option><option value="asc">从低到高</option></select></label>
<label v-if="statKey==='volatility'">Top <input v-model.number="statParams.top_n" class="w70" /></label>
<button class="btn primary" @click="doStat">执行</button>
</div>
<table v-if="statRows.length">
<thead><tr><th v-for="h in statHeaders" :key="h">{{ h }}</th></tr></thead>
<tbody>
<tr v-for="(r,i) in statRows" :key="i"><td v-for="h in statKeys" :key="h">{{ r[h] }}</td></tr>
</tbody>
</table>
<p v-else class="empty">点击上方按钮执行统计查询</p>
</section>
<!-- ============ 高级筛选器 ============ -->
<section v-show="tab === 'filter'">
<p class="sub-tip">通用高级筛选器:对 student 模型进行 AND/OR 嵌套规则筛选。支持字段:stu_id, stu_name, age, gender, education, major, native_place, status, class_id, class_name, salary, company_name;操作符:&gt; &lt; = != >= <= like in</p>
<textarea class="filter-json" v-model="filterJson" rows="12"></textarea>
<div class="toolbar">
<button class="btn primary" @click="runFilter">执行筛选</button>
<button class="btn ghost" @click="resetFilterJson">恢复示例</button>
</div>
<table v-if="filterRows.length">
<thead><tr><th>学号</th><th>姓名</th><th>班级</th><th>性别</th><th>年龄</th><th>学历</th><th>状态</th><th>薪资</th><th>公司</th></tr></thead>
<tbody>
<tr v-for="s in filterRows" :key="s.stu_id">
<td>{{ s.stu_id }}</td><td>{{ s.stu_name }}</td><td>{{ s.class_name }}</td><td>{{ s.gender }}</td><td>{{ s.age }}</td><td>{{ s.education }}</td><td>{{ s.status }}</td>
<td>{{ s.salary ?? '—' }}</td><td>{{ s.company_name ?? '—' }}</td>
</tr>
</tbody>
</table>
<p class="empty" v-else>输入规则 JSON 后执行筛选</p>
</section>
</main>
</template>
<!-- ==================== 弹窗表单 ==================== -->
<div class="modal-mask" v-if="showStu || showScore || showEmp || showClass || showTeacher" @click.self="closeAll">
<!-- 学生表单 -->
<div class="modal" v-if="showStu">
<h3>{{ stuForm.stu_id ? '编辑学生' : '新增学生' }}</h3>
<div class="form-grid">
<label>学号(留空自动生成)<input v-model.trim="stuForm.stu_id" placeholder="留空自动生成" :disabled="!!stuForm._editing" /></label>
<label>班级<select v-model.number="stuForm.class_id"><option v-for="c in classes" :key="c.class_id" :value="c.class_id">{{ c.class_id }} - {{ c.class_name }}</option></select></label>
<label>姓名<input v-model.trim="stuForm.stu_name" /></label>
<label>性别<select v-model="stuForm.gender"><option>男</option><option>女</option></select></label>
<label>年龄<input v-model.number="stuForm.age" type="number" /></label>
<label>学历<input v-model.trim="stuForm.education" placeholder="大专/本科/硕士" /></label>
<label>专业<input v-model.trim="stuForm.major" /></label>
<label>籍贯<input v-model.trim="stuForm.native_place" /></label>
<label>毕业院校<input v-model.trim="stuForm.graduate_school" /></label>
<label>顾问ID<input v-model.number="stuForm.advisor_id" type="number" :disabled="!!stuForm._editing" /></label>
<label>入学时间<input v-model.trim="stuForm.enroll_time" type="date" :disabled="!!stuForm._editing" /></label>
<label>毕业时间<input v-model.trim="stuForm.graduate_time" type="date" :disabled="!!stuForm._editing" /></label>
<label>状态<select v-model="stuForm.status"><option>在读</option><option>进入就业</option><option>已就业</option></select></label>
</div>
<div class="modal-actions"><button class="btn" @click="closeAll">取消</button><button class="btn primary" @click="saveStudent">保存</button></div>
</div>
<!-- 成绩表单 -->
<div class="modal" v-if="showScore">
<h3>{{ scoreForm._editing ? '修改成绩' : '录入成绩' }}</h3>
<div class="form-grid">
<label>学号<input v-model.number="scoreForm.stu_id" type="number" :disabled="!!scoreForm._editing" /></label>
<label>考核序次<input v-model.number="scoreForm.exam_id" type="number" :disabled="!!scoreForm._editing" /></label>
<label>成绩(0-100)<input v-model.number="scoreForm.score" type="number" min="0" max="100" /></label>
</div>
<div class="modal-actions"><button class="btn" @click="closeAll">取消</button><button class="btn primary" @click="saveScore">保存</button></div>
</div>
<!-- 就业表单 -->
<div class="modal" v-if="showEmp">
<h3>{{ empFormMode === 'open' ? '登记就业开放' : empFormMode === 'offer' ? '登记 offer' : '编辑就业信息' }}</h3>
<div class="form-grid">
<label>学号<input v-model.number="empForm.stu_id" type="number" /></label>
<template v-if="empFormMode === 'open'">
<label>就业开放时间<input v-model.trim="empForm.employment_open_time" type="date" /></label>
</template>
<template v-else-if="empFormMode === 'offer'">
<label>offer 下发时间<input v-model.trim="empForm.offer_time" type="date" /></label>
<label>公司名称<input v-model.trim="empForm.company_name" /></label>
<label>薪资<input v-model.number="empForm.salary" type="number" /></label>
</template>
<template v-else>
<label>公司名称<input v-model.trim="empForm.company_name" /></label>
<label>薪资<input v-model.number="empForm.salary" type="number" /></label>
</template>
</div>
<div class="modal-actions"><button class="btn" @click="closeAll">取消</button><button class="btn primary" @click="saveEmployment">保存</button></div>
</div>
<!-- 班级表单 -->
<div class="modal" v-if="showClass">
<h3>{{ classForm.class_id ? '编辑班级' : '新增班级' }}</h3>
<div class="form-grid">
<label>班级名称<input v-model.trim="classForm.class_name" /></label>
<label>开课时间<input v-model.trim="classForm.start_time" type="date" /></label>
</div>
<div class="modal-actions"><button class="btn" @click="closeAll">取消</button><button class="btn primary" @click="saveClass">保存</button></div>
</div>
<!-- 老师表单 -->
<div class="modal" v-if="showTeacher">
<h3>{{ teacherForm.teacher_id ? '编辑老师' : '新增老师' }}</h3>
<div class="form-grid">
<label>姓名<input v-model.trim="teacherForm.teacher_name" /></label>
<label>职务<select v-model="teacherForm.job_name"><option>主讲</option><option>班主任</option><option>助教</option></select></label>
<label>带班班级<select v-model.number="teacherForm.class_id"><option v-for="c in classes" :key="c.class_id" :value="c.class_id">{{ c.class_id }} - {{ c.class_name }}</option></select></label>
</div>
<div class="modal-actions"><button class="btn" @click="closeAll">取消</button><button class="btn primary" @click="saveTeacher">保存</button></div>
</div>
</div>
<!-- 全局提示 -->
<div class="toast" v-if="tip && token">{{ tip }}</div>
</div>
<script src="/static/app.js"></script>
</body>
</html>