飞算JavaAI智慧校园场景实践:智慧校园课表管理系统设计与实现
·
在校园管理领域,“规模化运营”与“个性化服务”的矛盾、“管理效率”与“服务体验”的平衡始终是技术团队的核心挑战。传统开发模式下,一套覆盖校园管理、师生服务、资源调度的智慧校园系统需投入35人团队开发16个月以上,且频繁面临“流程繁琐”“数据孤岛”“服务响应滞后”等问题。飞算JavaAI通过校园场景深度适配,构建了从智能管理到精准服务的全栈解决方案,将核心系统开发周期缩短70%的同时,实现师生服务满意度提升55%,为校园数字化转型提供技术支撑。本文聚焦智慧校园领域的技术实践,解析飞算JavaAI如何重塑校园系统开发范式。
作为每天在不同教学楼间狂奔的大学生,试过不少课表APP,要么广告弹窗不断,要么功能复杂到需要看教程。干脆自己动手做了个纯前端课表网页,支持手动添加、修改课程,数据存在浏览器里不用担心丢失。记录下开发过程和使用体验,说不定能帮到同样需要的同学。
先明确核心需求
其实我的需求很简单:
- 能按星期和上课时间清晰展示课程
- 随时能添加新课程(课程名、老师、教室这几个核心信息必须有)
- 填错了能改,停课了能删,操作要简单
- 关掉网页再打开,之前的课程还在
- 界面干净点,别搞花里胡哨的装饰
技术选型
纯前端实现足够了,毕竟只是个人用,没必要搞服务器和数据库:
- HTML + Tailwind CSS 搭页面框架
- JavaScript 处理添加、修改这些逻辑
- localStorage 存课程数据(简单直接,不用复杂配置)
- Font Awesome 加几个必要的小图标(编辑、删除这些)
核心功能实现过程
1. 先设计数据结构
首先得想清楚一个课程需要包含哪些信息,用JavaScript对象来存:
// 课程数据结构设计
const course = {
id: '唯一标识', // 用来区分不同课程,避免冲突
name: '大学物理', // 课程名称
teacher: '李教授', // 授课教师
classroom: '2号实验楼302', // 上课地点
day: 3, // 星期几(1-7对应周一到周日)
section: 5, // 第几节课(1-10)
color: '#10b981' // 课程卡片颜色(随机生成,方便区分)
}
2. 课表渲染逻辑
用表格来展示星期和时间段,然后动态生成课程卡片:
<!-- 课表表格结构 -->
<table class="w-full border-collapse">
<thead>
<tr>
<th class="border border-gray-200 p-3 bg-gray-50">时间段</th>
<th class="border border-gray-200 p-3 bg-gray-50">周一</th>
<th class="border border-gray-200 p-3 bg-gray-50">周二</th>
<!-- 其他星期的表头... -->
</tr>
</thead>
<tbody id="timetable-body">
<!-- 这里用JS动态生成课程行 -->
</tbody>
</table>
渲染课程的核心代码:
// 渲染课表函数
function renderTimetable() {
const tbody = document.getElementById('timetable-body');
tbody.innerHTML = ''; // 清空现有内容
// 循环生成10节课的行(假设每天最多10节课)
for (let section = 1; section <= 10; section++) {
const row = document.createElement('tr');
// 添加时间段单元格(第几节课)
const timeCell = document.createElement('td');
timeCell.className = 'border border-gray-200 p-2 bg-gray-50';
timeCell.textContent = `${section}节`;
row.appendChild(timeCell);
// 循环生成星期列
for (let day = 1; day <= 7; day++) {
const cell = document.createElement('td');
cell.className = 'border border-gray-200 p-1 min-h-[100px]';
// 查找这个时间段的课程
const courses = getCoursesByDayAndSection(day, section);
// 生成课程卡片并添加到单元格
courses.forEach(course => {
const courseCard = createCourseCard(course);
cell.appendChild(courseCard);
});
row.appendChild(cell);
}
tbody.appendChild(row);
}
}
// 创建课程卡片
function createCourseCard(course) {
const card = document.createElement('div');
card.className = 'rounded-md p-2 mb-1 text-white relative';
card.style.backgroundColor = course.color; // 设置卡片颜色
// 卡片内容
card.innerHTML = `
<div class="text-sm font-bold">${course.name}</div>
<div class="text-xs">${course.teacher || '无教师'}</div>
<div class="text-xs">${course.classroom || '无教室'}</div>
<!-- 编辑和删除按钮 -->
<div class="absolute top-1 right-1 flex gap-1">
<i class="fa fa-edit text-xs" onclick="editCourse('${course.id}')"></i>
<i class="fa fa-trash text-xs" onclick="deleteCourse('${course.id}')"></i>
</div>
`;
return card;
}
3. 添加和编辑课程功能
用一个弹窗表单来处理添加和编辑操作:
<!-- 添加/编辑课程弹窗 -->
<div id="course-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white p-6 rounded-lg w-full max-w-md">
<h3 id="modal-title" class="text-xl font-bold mb-4">添加课程</h3>
<form id="course-form">
<input type="hidden" id="course-id"> <!-- 用于编辑时存储课程ID -->
<div class="mb-3">
<label class="block text-sm">课程名 *</label>
<input type="text" id="course-name" class="w-full p-2 border border-gray-300 rounded" required>
</div>
<div class="mb-3">
<label class="block text-sm">教师</label>
<input type="text" id="course-teacher" class="w-full p-2 border border-gray-300 rounded">
</div>
<div class="mb-3">
<label class="block text-sm">教室</label>
<input type="text" id="course-classroom" class="w-full p-2 border border-gray-300 rounded">
</div>
<div class="grid grid-cols-2 gap-4 mb-3">
<div>
<label class="block text-sm">星期 *</label>
<select id="course-day" class="w-full p-2 border border-gray-300 rounded" required>
<option value="1">周一</option>
<option value="2">周二</option>
<!-- 其他星期选项... -->
</select>
</div>
<div>
<label class="block text-sm">节次 *</label>
<select id="course-section" class="w-full p-2 border border-gray-300 rounded" required>
<option value="1">1节 (8:00-8:45)</option>
<option value="2">2节 (8:55-9:40)</option>
<!-- 其他节次选项... -->
</select>
</div>
</div>
<div class="flex justify-end gap-2">
<button type="button" id="cancel-btn" class="px-4 py-2 border border-gray-300 rounded">取消</button>
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded">保存</button>
</div>
</form>
</div>
</div>
处理表单提交的逻辑:
// 处理表单提交
document.getElementById('course-form').addEventListener('submit', function(e) {
e.preventDefault(); // 阻止表单默认提交行为
// 收集表单数据
const courseData = {
id: document.getElementById('course-id').value || generateId(), // 编辑时用现有ID,新增时生成新ID
name: document.getElementById('course-name').value,
teacher: document.getElementById('course-teacher').value,
classroom: document.getElementById('course-classroom').value,
day: parseInt(document.getElementById('course-day').value),
section: parseInt(document.getElementById('course-section').value),
// 编辑时保留原有颜色,新增时随机生成
color: document.getElementById('course-id').value ?
getCourseById(document.getElementById('course-id').value).color :
getRandomColor()
};
// 保存到本地存储
saveCourse(courseData);
// 刷新课表显示
renderTimetable();
// 关闭弹窗
closeModal();
});
4. 本地数据存储
用localStorage简单处理数据存储:
// 保存课程到本地存储
function saveCourse(course) {
// 从本地存储获取现有课程列表,没有则为空数组
let courses = JSON.parse(localStorage.getItem('myTimetable') || '[]');
// 查找是否已有该课程(编辑模式)
const index = courses.findIndex(c => c.id === course.id);
if (index > -1) {
// 替换现有课程
courses[index] = course;
} else {
// 添加新课程
courses.push(course);
}
// 保存回本地存储
localStorage.setItem('myTimetable', JSON.stringify(courses));
}
// 删除课程
function deleteCourse(id) {
if (confirm('确定要删除这门课吗?')) {
let courses = JSON.parse(localStorage.getItem('myTimetable') || '[]');
// 过滤掉要删除的课程
courses = courses.filter(c => c.id !== id);
localStorage.setItem('myTimetable', JSON.stringify(courses));
// 刷新课表
renderTimetable();
}
}
// 页面加载时初始化
function init() {
renderTimetable(); // 渲染课表
// 绑定添加课程按钮事件
document.getElementById('add-course-btn').addEventListener('click', function() {
openModal(); // 打开添加课程弹窗
});
// 绑定取消按钮事件
document.getElementById('cancel-btn').addEventListener('click', closeModal);
}
使用体验和待优化的点
测试了几次,基本满足我的日常需求,比之前用Excel记课表方便太多。但用着用着也发现一些可以改进的地方:
- 现在只能添加单节课程,没法设置连堂(比如2-3节这种情况),只能手动添加两节相同的课程
- 没有拖拽功能,想调整课程时间只能先删除再添加
- 换浏览器或者清理缓存会丢失数据,虽然概率不高但确实有点麻烦
- 手机上查看时表格会横向滚动,虽然能用但体验一般
下一步打算先加上连堂功能,这个需求最迫切。然后优化下移动端显示,毕竟上课前基本都是用手机看课表。
整体来说,这个小工具虽然简单,但完全是按自己的使用习惯做的,没有多余功能,打开就能用。这种"量身定制"的感觉真的很好,开发过程也学到了不少前端小技巧。如果你也在为课表管理烦恼,不妨试试自己动手做一个,其实没想象中那么难~
<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://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
.course-card {
transition: all 0.2s;
}
.course-card:hover {
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto p-4 max-w-6xl">
<header class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">我的课表</h1>
<button id="add-course-btn" class="bg-blue-600 text-white px-4 py-2 rounded-md flex items-center gap-2 hover:bg-blue-700 transition-colors">
<i class="fa fa-plus"></i> 添加课程
</button>
</header>
<!-- 课表表格 -->
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
<table class="w-full border-collapse">
<thead>
<tr class="bg-gray-50">
<th class="border border-gray-200 p-3 text-left">时间段</th>
<th class="border border-gray-200 p-3 text-center">周一</th>
<th class="border border-gray-200 p-3 text-center">周二</th>
<th class="border border-gray-200 p-3 text-center">周三</th>
<th class="border border-gray-200 p-3 text-center">周四</th>
<th class="border border-gray-200 p-3 text-center">周五</th>
<th class="border border-gray-200 p-3 text-center">周六</th>
<th class="border border-gray-200 p-3 text-center">周日</th>
</tr>
</thead>
<tbody id="timetable-body">
<!-- 课程内容将通过JS动态生成 -->
</tbody>
</table>
</div>
<!-- 使用提示 -->
<div class="mt-4 text-sm text-gray-500">
<p><i class="fa fa-lightbulb-o"></i> 提示:点击课程卡片可编辑,数据保存在浏览器本地</p>
</div>
</div>
<!-- 添加/编辑课程弹窗 -->
<div id="course-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
<div class="bg-white p-6 rounded-lg w-full max-w-md mx-4">
<h3 id="modal-title" class="text-xl font-bold mb-4 text-gray-800">添加课程</h3>
<form id="course-form">
<input type="hidden" id="course-id">
<div class="mb-4">
<label for="course-name" class="block text-sm font-medium text-gray-700 mb-1">课程名 <span class="text-red-500">*</span></label>
<input type="text" id="course-name" class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500/50" required>
</div>
<div class="mb-4">
<label for="course-teacher" class="block text-sm font-medium text-gray-700 mb-1">教师</label>
<input type="text" id="course-teacher" class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500/50">
</div>
<div class="mb-4">
<label for="course-classroom" class="block text-sm font-medium text-gray-700 mb-1">教室</label>
<input type="text" id="course-classroom" class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500/50">
</div>
<div class="grid grid-cols-2 gap-4 mb-6">
<div>
<label for="course-day" class="block text-sm font-medium text-gray-700 mb-1">星期 <span class="text-red-500">*</span></label>
<select id="course-day" class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500/50" required>
<option value="1">周一</option>
<option value="2">周二</option>
<option value="3">周三</option>
<option value="4">周四</option>
<option value="5">周五</option>
<option value="6">周六</option>
<option value="7">周日</option>
</select>
</div>
<div>
<label for="course-section" class="block text-sm font-medium text-gray-700 mb-1">节次 <span class="text-red-500">*</span></label>
<select id="course-section" class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500/50" required>
<option value="1">1节 (8:00-8:45)</option>
<option value="2">2节 (8:55-9:40)</option>
<option value="3">3节 (10:00-10:45)</option>
<option value="4">4节 (10:55-11:40)</option>
<option value="5">5节 (14:00-14:45)</option>
<option value="6">6节 (14:55-15:40)</option>
<option value="7">7节 (16:00-16:45)</option>
<option value="8">8节 (16:55-17:40)</option>
<option value="9">9节 (19:00-19:45)</option>
<option value="10">10节 (19:55-20:40)</option>
</select>
</div>
</div>
<div class="flex justify-end gap-3">
<button type="button" id="cancel-btn" class="px-4 py-2 border border-gray-300 rounded hover:bg-gray-100 transition-colors">取消</button>
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">保存</button>
</div>
</form>
</div>
</div>
<script>
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', init);
// 初始化函数
function init() {
renderTimetable(); // 渲染课表
// 绑定按钮事件
document.getElementById('add-course-btn').addEventListener('click', () => openModal());
document.getElementById('cancel-btn').addEventListener('click', () => closeModal());
document.getElementById('course-form').addEventListener('submit', handleFormSubmit);
}
// 渲染课表
function renderTimetable() {
const tbody = document.getElementById('timetable-body');
tbody.innerHTML = ''; // 清空现有内容
// 生成10节课的行
for (let section = 1; section <= 10; section++) {
const row = document.createElement('tr');
// 时间段单元格
const timeCell = document.createElement('td');
timeCell.className = 'border border-gray-200 p-2 bg-gray-50 font-medium';
timeCell.textContent = `${section}节`;
row.appendChild(timeCell);
// 生成星期列
for (let day = 1; day <= 7; day++) {
const cell = document.createElement('td');
cell.className = 'border border-gray-200 p-1 min-h-[100px] vertical-align-top';
// 查找该时间段的课程
const courses = getCoursesByDayAndSection(day, section);
// 添加课程卡片
courses.forEach(course => {
const courseCard = createCourseCard(course);
cell.appendChild(courseCard);
});
row.appendChild(cell);
}
tbody.appendChild(row);
}
}
// 创建课程卡片
function createCourseCard(course) {
const card = document.createElement('div');
card.className = 'course-card rounded-md p-2 mb-1 text-white relative';
card.style.backgroundColor = course.color;
// 课程卡片内容
card.innerHTML = `
<div class="text-sm font-bold truncate">${course.name}</div>
<div class="text-xs truncate">${course.teacher || '无教师'}</div>
<div class="text-xs truncate">${course.classroom || '无教室'}</div>
<div class="absolute top-1 right-1 flex gap-1 opacity-70">
<i class="fa fa-edit text-xs" onclick="editCourse('${course.id}', event)"></i>
<i class="fa fa-trash text-xs" onclick="deleteCourse('${course.id}', event)"></i>
</div>
`;
return card;
}
// 打开弹窗
function openModal(courseId = null) {
const modal = document.getElementById('course-modal');
const title = document.getElementById('modal-title');
// 重置表单
document.getElementById('course-form').reset();
document.getElementById('course-id').value = '';
// 如果是编辑模式
if (courseId) {
title.textContent = '编辑课程';
const course = getCourseById(courseId);
if (course) {
document.getElementById('course-id').value = course.id;
document.getElementById('course-name').value = course.name;
document.getElementById('course-teacher').value = course.teacher || '';
document.getElementById('course-classroom').value = course.classroom || '';
document.getElementById('course-day').value = course.day;
document.getElementById('course-section').value = course.section;
}
} else {
title.textContent = '添加课程';
}
modal.classList.remove('hidden');
}
// 关闭弹窗
function closeModal() {
const modal = document.getElementById('course-modal');
modal.classList.add('hidden');
}
// 处理表单提交
function handleFormSubmit(e) {
e.preventDefault();
// 收集表单数据
const courseData = {
id: document.getElementById('course-id').value || generateId(),
name: document.getElementById('course-name').value,
teacher: document.getElementById('course-teacher').value,
classroom: document.getElementById('course-classroom').value,
day: parseInt(document.getElementById('course-day').value),
section: parseInt(document.getElementById('course-section').value),
color: document.getElementById('course-id').value ?
getCourseById(document.getElementById('course-id').value).color :
getRandomColor()
};
// 保存课程
saveCourse(courseData);
// 刷新课表
renderTimetable();
// 关闭弹窗
closeModal();
}
// 编辑课程
function editCourse(courseId, e) {
e.stopPropagation(); // 防止事件冒泡
openModal(courseId);
}
// 删除课程
function deleteCourse(courseId, e) {
e.stopPropagation(); // 防止事件冒泡
if (confirm('确定要删除这门课程吗?')) {
let courses = getCourses();
courses = courses.filter(course => course.id !== courseId);
localStorage.setItem('myTimetable', JSON.stringify(courses));
renderTimetable();
}
}
// 生成唯一ID
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
}
// 获取随机颜色
function getRandomColor() {
const colors = [
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6',
'#ec4899', '#06b6d4', '#6366f1', '#14b8a6', '#f97316'
];
return colors[Math.floor(Math.random() * colors.length)];
}
// 从本地存储获取所有课程
function getCourses() {
return JSON.parse(localStorage.getItem('myTimetable') || '[]');
}
// 根据ID获取课程
function getCourseById(id) {
return getCourses().find(course => course.id === id);
}
// 根据星期和节次获取课程
function getCoursesByDayAndSection(day, section) {
return getCourses().filter(course => course.day === day && course.section === section);
}
// 保存课程到本地存储
function saveCourse(course) {
let courses = getCourses();
const index = courses.findIndex(c => c.id === course.id);
if (index > -1) {
// 更新现有课程
courses[index] = course;
} else {
// 添加新课程
courses.push(course);
}
localStorage.setItem('myTimetable', JSON.stringify(courses));
}
</script>
</body>
</html>
这个课表工具用下来最舒服的点就是"轻量",打开网页就能用,不用安装APP,也不用注册登录。
开发的时候特意做了这些小细节:
- 课程卡片用不同颜色区分,视觉上更清晰
- 手机上也能正常操作,虽然表格会横向滚动但不影响使用
- 输入框只做必要的验证,避免填写时太繁琐
- 卡片内容会自动省略过长文本,不会出现排版错乱
目前遇到的最大问题是如果同一时间有两门课,卡片会叠在一起,之后打算加个自动换行的功能。另外考虑加个"课程导出"功能,能生成图片分享给同学,这样小组讨论时就不用一个个报时间了。
总的来说,花了大半天时间做的这个小工具,解决了我实际的痛点,这种自己动手解决问题的感觉真的很棒~
更多推荐
所有评论(0)