<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">📚 简单学生成绩管理系统</title>
<style>
* {
box-sizing: border-box;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
}
body {
background: #f4f7fc;
margin: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 1.5rem;
}
.card {
background: white;
max-width: 1000px;
width: 100%;
border-radius: 28px;
box-shadow: 0 20px 40px rgba(0,20,50,0.12);
padding: 1.8rem 2rem 2.5rem;
transition: 0.2s;
}
h1 {
font-size: 1.8rem;
font-weight: 600;
margin-top: 0.2rem;
margin-bottom: 0.3rem;
color: #1e2a3a;
display: flex;
align-items: center;
gap: 0.5rem;
border-bottom: 3px solid #e9edf4;
padding-bottom: 0.75rem;
}
h1 small {
font-size: 0.9rem;
font-weight: 400;
color: #5e6f8d;
margin-left: auto;
}
.grid-2col {
display: grid;
grid-template-columns: 1.2fr 2fr;
gap: 1.8rem;
margin-top: 1.5rem;
}
/* 表单区域 */
.form-section {
background: #f9faff;
border-radius: 20px;
padding: 1.5rem 1.2rem 1.8rem;
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.02);
}
.form-section h2 {
font-size: 1.3rem;
margin-top: 0;
margin-bottom: 1rem;
font-weight: 600;
color: #203349;
display: flex;
align-items: center;
gap: 8px;
}
.input-group {
margin-bottom: 1.1rem;
}
.input-group label {
display: block;
font-size: 0.85rem;
font-weight: 500;
color: #2c3e5a;
margin-bottom: 0.25rem;
}
.input-group input {
width: 100%;
padding: 0.7rem 0.9rem;
font-size: 0.95rem;
border: 1px solid #d7dee9;
border-radius: 14px;
background: white;
transition: 0.15s;
outline: none;
}
.input-group input:focus {
border-color: #3b7cff;
box-shadow: 0 0 0 3px rgba(59, 124, 255, 0.15);
}
.input-row {
display: flex;
gap: 0.8rem;
flex-wrap: wrap;
}
.input-row .input-group {
flex: 1;
min-width: 70px;
}
.btn {
background: white;
border: 1px solid #d0d8e3;
border-radius: 40px;
padding: 0.6rem 1.2rem;
font-weight: 500;
font-size: 0.9rem;
cursor: pointer;
transition: 0.15s;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
color: #1f2b3c;
}
.btn-primary {
background: #1e3c72;
border: 1px solid #1e3c72;
color: white;
box-shadow: 0 4px 8px rgba(30, 60, 114, 0.12);
}
.btn-primary:hover {
background: #15305c;
transform: scale(0.98);
}
.btn-outline {
background: transparent;
border: 1px solid #ccd7e6;
}
.btn-outline:hover {
background: #eef3fa;
}
.btn-danger {
background: #ffe7e7;
border: 1px solid #ffbaba;
color: #a12b2b;
}
.btn-danger:hover {
background: #ffd4d4;
}
.btn-sm {
padding: 0.35rem 0.9rem;
font-size: 0.8rem;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin-top: 1.2rem;
justify-content: flex-start;
}
.action-buttons .btn {
flex: 1 0 auto;
}
.info-note {
font-size: 0.75rem;
color: #5f7492;
margin-top: 0.8rem;
background: #eef3fa;
padding: 0.5rem 0.8rem;
border-radius: 40px;
display: inline-block;
}
/* 表格区域 */
.table-section {
min-width: 0;
}
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px 12px;
margin-bottom: 0.8rem;
}
.toolbar h2 {
font-size: 1.3rem;
font-weight: 600;
margin: 0;
color: #203349;
}
.search-box {
display: flex;
align-items: center;
gap: 6px;
background: #f2f6fd;
border-radius: 40px;
padding: 0.2rem 0.2rem 0.2rem 1rem;
border: 1px solid #dee6f0;
}
.search-box input {
border: none;
background: transparent;
padding: 0.4rem 0;
font-size: 0.9rem;
outline: none;
width: 130px;
}
.search-box input::placeholder {
color: #8a9bb5;
}
.search-box .btn {
border-radius: 40px;
padding: 0.4rem 0.9rem;
background: #1e3c72;
color: white;
border: none;
font-size: 0.8rem;
}
.table-wrapper {
overflow-x: auto;
border-radius: 20px;
background: #fbfdff;
border: 1px solid #e4ebf5;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
min-width: 400px;
}
th {
background: #eaf0f9;
color: #17345c;
font-weight: 600;
padding: 0.8rem 0.6rem;
text-align: left;
border-bottom: 2px solid #d2dcee;
}
td {
padding: 0.7rem 0.6rem;
border-bottom: 1px solid #eaeef6;
color: #1e2d44;
}
tr:last-child td {
border-bottom: none;
}
.badge {
background: #d7e2f5;
color: #1f3f6d;
border-radius: 40px;
padding: 0.15rem 0.6rem;
font-size: 0.7rem;
font-weight: 500;
margin-left: 6px;
}
.empty-row td {
text-align: center;
padding: 3rem 1rem;
color: #7487a6;
font-style: italic;
}
.text-muted {
color: #6e7f98;
}
.grade-high {
font-weight: 600;
color: #1b6b3a;
}
.grade-low {
font-weight: 500;
color: #b34a2c;
}
.footer-actions {
margin-top: 15px;
display: flex;
gap: 12px;
flex-wrap: wrap;
}
@media (max-width: 700px) {
.card {
padding: 1.2rem;
}
.grid-2col {
grid-template-columns: 1fr;
gap: 1rem;
}
.search-box input {
width: 100px;
}
}
</style>
</head>
<body>
<div class="card">
<h1>
🎓 成绩管理
<small>简单 · 本地存储</small>
</h1>
<div class="grid-2col">
<!-- 左侧:添加 / 编辑 -->
<div class="form-section">
<h2>➕ 添加 / 编辑</h2>
<div class="input-group">
<label>👤 学生姓名</label>
<input type="text" id="studentName" placeholder=" 张三" maxlength="20" value="">
</div>
<div class="input-row">
<div class="input-group">
<label>📘 语文</label>
<input type="number" id="scoreChinese" min="0" max="100" placeholder="0-100" value="">
</div>
<div class="input-group">
<label>📗 数学</label>
<input type="number" id="scoreMath" min="0" max="100" placeholder="0-100" value="">
</div>
<div class="input-group">
<label>📙 英语</label>
<input type="number" id="scoreEnglish" min="0" max="100" placeholder="0-100" value="">
</div>
</div>
<div class="action-buttons">
<button class="btn btn-primary" id="addBtn">➕ 增加学生</button>
<button class="btn btn-outline" id="updateBtn">✏️ 更新选中</button>
<button class="btn btn-outline" id="clearFormBtn">🗑️ 清空</button>
</div>
<div class="info-note">
⚡ 点击表格行 · 自动填入表单修改
</div>
</div>
<!-- 右侧:列表 & 搜索 -->
<div class="table-section">
<div class="toolbar">
<h2>📋 成绩单</h2>
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索姓名...">
<button class="btn" id="searchBtn">🔍</button>
</div>
</div>
<div class="table-wrapper">
<table id="scoreTable">
<thead>
<tr>
<th>姓名</th>
<th>语文</th>
<th>数学</th>
<th>英语</th>
<th>均分</th>
<th style="width: 50px;">操作</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- 动态渲染 -->
</tbody>
</table>
</div>
<div class="footer-actions">
<button class="btn btn-outline btn-sm" id="sortByNameBtn">📌 按姓名排序</button>
<button class="btn btn-outline btn-sm" id="sortByAvgBtn">📊 按均分排序</button>
<span style="flex:1;"></span>
<button class="btn btn-danger btn-sm" id="deleteAllBtn">🗑️ 清空全部</button>
</div>
<div style="margin-top: 8px; font-size:0.8rem; color:#3a577a;">
👆 点击行选中 · 双击直接编辑
</div>
</div>
</div>
</div>
<script>
(function() {
// ---------- 存储 ----------
const STORAGE_KEY = 'simple_grade_system';
// 数据格式:[{ id, name, chinese, math, english }]
let students = [];
// DOM 元素
const nameInput = document.getElementById('studentName');
const chineseInput = document.getElementById('scoreChinese');
const mathInput = document.getElementById('scoreMath');
const englishInput = document.getElementById('scoreEnglish');
const addBtn = document.getElementById('addBtn');
const updateBtn = document.getElementById('updateBtn');
const clearFormBtn = document.getElementById('clearFormBtn');
const tableBody = document.getElementById('tableBody');
const searchInput = document.getElementById('searchInput');
const searchBtn = document.getElementById('searchBtn');
const sortByNameBtn = document.getElementById('sortByNameBtn');
const sortByAvgBtn = document.getElementById('sortByAvgBtn');
const deleteAllBtn = document.getElementById('deleteAllBtn');
// 当前选中的行id (编辑时使用)
let selectedStudentId = null;
// ---------- 辅助函数 ----------
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 6);
}
// 从 localStorage 读取
function loadFromStorage() {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
students = JSON.parse(stored);
// 保证每个对象都有 id (旧数据兼容)
students = students.filter(s => s && typeof s === 'object');
students.forEach(s => {
if (!s.id) s.id = generateId();
});
} catch (e) {
students = [];
}
} else {
// 默认两条示例数据
students = [
{ id: generateId(), name: '王小明', chinese: 88, math: 92, english: 79 },
{ id: generateId(), name: '李华', chinese: 73, math: 64, english: 81 },
];
}
}
// 保存到 localStorage
function saveToStorage() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(students));
}
// 计算平均分 (保留1位)
function calcAverage(s) {
return ((s.chinese + s.math + s.english) / 3).toFixed(1);
}
// 获取搜索关键词 (小写)
function getSearchKeyword() {
return searchInput.value.trim().toLowerCase();
}
// 过滤 & 克隆学生列表 (不改变原数组)
function getFilteredList() {
const keyword = getSearchKeyword();
if (!keyword) return students.slice();
return students.filter(s => s.name.toLowerCase().includes(keyword));
}
// ---------- 渲染表格 ----------
function renderTable() {
const filtered = getFilteredList();
// 排序 (如果之后点排序会重新排序源数组? 我们保留源顺序,但排序按钮会重新排序 students 并保存)
// 但为了显示搜索,这里直接用过滤后的数据,但排序状态已经在 students 上。
// 所以我们采用:如果搜索,展示过滤后的顺序;如果没搜索,就用 students 当前顺序。
// 但 sort 按钮改变的是 students 数组本身,很好。
const list = filtered.length ? filtered : students;
// 但如果有搜索关键词,filtered 可能为空,显示无数据就好。
const dataToRender = (getSearchKeyword() === '') ? students : filtered;
if (!dataToRender.length) {
tableBody.innerHTML = `
<tr class="empty-row">
<td colspan="6">📭 没有学生记录,添加一位吧</td>
</tr>
`;
return;
}
let html = '';
dataToRender.forEach((s, index) => {
const avg = calcAverage(s);
const isSelected = (selectedStudentId === s.id);
const rowClass = isSelected ? 'selected-row' : '';
// 标记颜色 (均分高低)
const avgNum = parseFloat(avg);
const gradeClass = avgNum >= 80 ? 'grade-high' : (avgNum < 60 ? 'grade-low' : '');
html += `
<tr data-id="${s.id}" class="${rowClass}" style="cursor:pointer; ${isSelected ? 'background:#e9f0fe;' : ''}">
<td><strong>${s.name}</strong></td>
<td>${s.chinese}</td>
<td>${s.math}</td>
<td>${s.english}</td>
<td class="${gradeClass}">${avg}</td>
<td>
<button class="btn btn-danger btn-sm delete-single" data-id="${s.id}" style="padding:0.2rem 0.7rem;">✕</button>
</td>
</tr>
`;
});
tableBody.innerHTML = html;
// 绑定行点击事件 (选中)
document.querySelectorAll('#tableBody tr[data-id]').forEach(row => {
row.addEventListener('click', function(e) {
// 如果点击的是删除按钮,不触发选中
if (e.target.closest('.delete-single')) return;
const id = this.dataset.id;
selectStudentById(id);
});
// 双击直接填充表单 (更方便)
row.addEventListener('dblclick', function(e) {
if (e.target.closest('.delete-single')) return;
const id = this.dataset.id;
selectStudentById(id);
// 自动聚焦姓名
nameInput.focus();
});
});
// 绑定单个删除按钮
document.querySelectorAll('.delete-single').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const id = this.dataset.id;
if (!id) return;
if (confirm('确定删除该学生成绩吗?')) {
students = students.filter(s => s.id !== id);
if (selectedStudentId === id) {
clearForm();
selectedStudentId = null;
}
saveToStorage();
renderTable();
}
});
});
}
// ---------- 选中学生 (填充表单) ----------
function selectStudentById(id) {
const student = students.find(s => s.id === id);
if (!student) return;
selectedStudentId = student.id;
nameInput.value = student.name;
chineseInput.value = student.chinese;
mathInput.value = student.math;
englishInput.value = student.english;
renderTable(); // 重新渲染高亮
// 更新按钮文案
addBtn.textContent = '➕ 增加学生';
}
// 清空表单 & 取消选中
function clearForm() {
nameInput.value = '';
chineseInput.value = '';
mathInput.value = '';
englishInput.value = '';
selectedStudentId = null;
addBtn.textContent = '➕ 增加学生';
renderTable(); // 取消高亮
}
// ---------- 表单验证 ----------
function getFormData() {
const name = nameInput.value.trim();
if (!name) {
alert('请输入学生姓名');
return null;
}
const chinese = parseFloat(chineseInput.value);
const math = parseFloat(mathInput.value);
const english = parseFloat(englishInput.value);
if (isNaN(chinese) || isNaN(math) || isNaN(english)) {
alert('请完整填写三门成绩 (0-100)');
return null;
}
if (chinese < 0 || chinese > 100 || math < 0 || math > 100 || english < 0 || english > 100) {
alert('成绩范围:0 ~ 100');
return null;
}
return { name, chinese, math, english };
}
// ---------- 增加学生 ----------
function addStudent() {
const data = getFormData();
if (!data) return;
// 如果当前有选中?增加时如果选中状态,默认取消 (或者可以当作增加新学生)
// 但更符合直觉:如果有选中,先清除选中再添加?我们采取:直接新增,不管选中,但清空表单。
const newStudent = {
id: generateId(),
name: data.name,
chinese: data.chinese,
math: data.math,
english: data.english,
};
students.push(newStudent);
saveToStorage();
clearForm(); // 清空并取消选中
renderTable();
// 滚动到底部查看最新
const tableWrapper = document.querySelector('.table-wrapper');
if (tableWrapper) tableWrapper.scrollTop = tableWrapper.scrollHeight;
}
// ---------- 更新学生 (选中) ----------
function updateStudent() {
if (!selectedStudentId) {
alert('请先在表格中点击选择要修改的学生');
return;
}
const data = getFormData();
if (!data) return;
const index = students.findIndex(s => s.id === selectedStudentId);
if (index === -1) {
alert('数据异常,请刷新');
return;
}
// 检查姓名是否与其他学生冲突?不要紧了,可以重名。
students[index].name = data.name;
students[index].chinese = data.chinese;
students[index].math = data.math;
students[index].english = data.english;
saveToStorage();
// 不清除选中,但刷新高亮
renderTable();
// 保持表单填充的是当前数据
// 但可以保持
addBtn.textContent = '➕ 增加学生';
}
// ---------- 排序 ----------
function sortByName() {
students.sort((a, b) => a.name.localeCompare(b.name, 'zh'));
saveToStorage();
renderTable();
}
function sortByAvg() {
students.sort((a, b) => {
const avgA = (a.chinese + a.math + a.english) / 3;
const avgB = (b.chinese + b.math + b.english) / 3;
return avgB - avgA; // 从高到低
});
saveToStorage();
renderTable();
}
// 清空全部
function deleteAll() {
if (!students.length) return;
if (confirm('⚠️ 确定删除所有学生成绩?不可恢复!')) {
students = [];
selectedStudentId = null;
clearForm();
saveToStorage();
renderTable();
}
}
// 搜索
function performSearch() {
renderTable();
}
// ---------- 初始化事件绑定 ----------
function init() {
loadFromStorage();
renderTable();
// 如果没有任何学生,填充示例
if (!students.length) {
students = [
{ id: generateId(), name: '赵小雅', chinese: 91, math: 87, english: 94 },
{ id: generateId(), name: '陈瑞', chinese: 62, math: 58, english: 71 },
];
saveToStorage();
renderTable();
}
// 按钮事件
addBtn.addEventListener('click', addStudent);
updateBtn.addEventListener('click', updateStudent);
clearFormBtn.addEventListener('click', clearForm);
searchBtn.addEventListener('click', performSearch);
searchInput.addEventListener('keyup', function(e) {
if (e.key === 'Enter') performSearch();
});
sortByNameBtn.addEventListener('click', sortByName);
sortByAvgBtn.addEventListener('click', sortByAvg);
deleteAllBtn.addEventListener('click', deleteAll);
// 当点击表格空白处取消选中?可以通过点击其他地方,但为了体验,增加一个全局点击?
// 但为了避免干扰,可以点击清空表单按钮来取消选中。
// 另:点击行外区域不清除,用户可主动点清空。
}
init();
})();
</script>
</body>
</html>
核心功能与操作流程
您可以通过表单添加、修改学生成绩,并利用表格和搜索功能进行查看与管理。

- 数据管理:左侧表单用于添加新学生或修改已有学生的姓名及三门课程(语文、数学、英语)的成绩,点击表格中的任意一行,其数据会自动填充到表单中,方便您进行修改。
- 信息浏览:右侧表格会清晰展示所有学生的成绩列表,包括各科分数和平均分,您可以使用搜索框按姓名快速查找学生,或点击“按姓名排序”、“按均分排序”按钮来整理数据。
- 数据维护:每个学生记录旁都有删除按钮,方便您移除单个学生,页面底部的“清空全部”按钮可用于重置所有数据,所有操作都会自动保存到浏览器的本地存储中,刷新页面后数据不会丢失。