第 2 阶段:账号与班级,作品保存在服务器
- SQLite(bun:sqlite,编号迁移):班级、用户、会话(只存令牌哈希)、作品 - 登录取代统一访问口令:老师账号由配置设定;接口和 WebSocket 都要登录;按 IP 限制输错次数 - 班级管理页:建班、改名、删除(有学生时不能删);上传 CSV 名单导入(兼容 GBK / UTF-8, 识别表头,标出被 Excel 变成科学计数法的学号),密码留空生成 6 位数字;查看、重置密码, 导出带密码的名单 - 学生作品自动保存到服务器(按账号和实验),显示保存状态;离开页面前立即保存 - 前端加路由:登录页、实验页、班级管理页 - 部署:TEACHER_PASSWORD 必填,数据库放在数据卷 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a00622be15
commit
7cec273d58
31 files changed
+1776
-382
No files matched your search
@@ -0,0 +1,27 @@
|
||||
// 页面路由:登录页、实验页(学生和老师都用)、老师的班级管理页。
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { apiHooks } from './api'
|
||||
import { currentUser } from './auth'
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{ path: '/login', component: () => import('./pages/LoginPage.vue'), meta: { public: true } },
|
||||
{ path: '/', component: () => import('./pages/WorkbenchPage.vue') },
|
||||
{ path: '/teacher', component: () => import('./pages/TeacherPage.vue'), meta: { teacher: true } },
|
||||
{ path: '/:rest(.*)*', redirect: '/' },
|
||||
],
|
||||
})
|
||||
|
||||
router.beforeEach((to) => {
|
||||
if (to.meta.public) return currentUser.value && to.path === '/login' ? '/' : true
|
||||
if (!currentUser.value) return { path: '/login', query: to.fullPath === '/' ? {} : { redirect: to.fullPath } }
|
||||
if (to.meta.teacher && currentUser.value.role !== 'teacher') return '/'
|
||||
return true
|
||||
})
|
||||
|
||||
// 登录过期(比如老师重置了密码):回到登录页
|
||||
apiHooks.onUnauthorized = () => {
|
||||
currentUser.value = null
|
||||
if (router.currentRoute.value.path !== '/login') void router.push('/login')
|
||||
}
|
||||
Reference in new issue
Block a user