Files
2026-03-22 15:24:40 +08:00

345 lines
9.6 KiB
TypeScript
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.
/**
* 自然写互动课堂PC端应用软件 V1.0
*
* index.ts - Pinia状态管理(全局Store
*
* 功能说明:
* - 用户认证状态管理
* - 课堂状态管理(当前课堂/学生列表/笔迹数据)
* - 设备连接状态管理
* - 作业批改状态管理
* - WebSocket实时数据同步
* - 持久化存储(electron-store
*/
import { defineStore } from 'pinia';
import { ref, computed, reactive } from 'vue';
/* ======================== 类型定义 ======================== */
/** 应用视图模式 */
type ViewMode = 'prepare' | 'lesson' | 'grade' | 'report';
/** 设备信息 */
interface DeviceState {
id: string;
name: string;
type: 'usb' | 'ble';
status: 'connected' | 'disconnected' | 'error';
battery: number;
}
/** 学生在线状态 */
interface StudentOnlineState {
studentId: string;
name: string;
penId: string;
online: boolean;
lastActive: number;
strokeCount: number;
}
/** 课堂互动数据 */
interface ClassroomLiveData {
classroomId: string;
className: string;
startTime: number;
onlineStudents: StudentOnlineState[];
totalStrokes: number;
isRecording: boolean;
}
/** 批改任务 */
interface GradeTask {
assignmentId: string;
studentId: string;
studentName: string;
status: 'pending' | 'ai_graded' | 'reviewed' | 'completed';
aiScore: number;
teacherScore: number;
feedback: string;
}
/* ======================== 用户Store ======================== */
/**
* 用户认证与信息状态管理
*/
export const useUserStore = defineStore('user', () => {
/** 是否已登录 */
const isLoggedIn = ref(false);
/** 当前用户信息 */
const userInfo = ref<{
userId: string;
name: string;
role: string;
phone: string;
schoolId: string;
schoolName: string;
avatar: string;
} | null>(null);
/** 登录时间 */
const loginTime = ref(0);
/** Token过期时间 */
const tokenExpiresAt = ref(0);
/** 用户角色显示名 */
const roleLabel = computed(() => {
const roleMap: Record<string, string> = {
admin: '管理员',
teacher: '教师',
student: '学生',
parent: '家长'
};
return roleMap[userInfo.value?.role || ''] || '未知';
});
/**
* 登录成功后设置用户状态
*/
function setLoggedIn(user: typeof userInfo.value, expiresAt: number): void {
isLoggedIn.value = true;
userInfo.value = user;
loginTime.value = Date.now();
tokenExpiresAt.value = expiresAt;
console.log(`[Store] 用户登录: ${user?.name} (${user?.role})`);
}
/**
* 退出登录
*/
function logout(): void {
isLoggedIn.value = false;
userInfo.value = null;
loginTime.value = 0;
tokenExpiresAt.value = 0;
console.log('[Store] 用户已退出');
}
return { isLoggedIn, userInfo, loginTime, tokenExpiresAt, roleLabel, setLoggedIn, logout };
});
/* ======================== 课堂Store ======================== */
/**
* 课堂状态管理
* 管理当前课堂的实时数据
*/
export const useClassroomStore = defineStore('classroom', () => {
/** 当前视图模式 */
const viewMode = ref<ViewMode>('prepare');
/** 当前课堂数据 */
const liveData = ref<ClassroomLiveData | null>(null);
/** 是否在课堂中 */
const isInClass = ref(false);
/** WebSocket连接状态 */
const wsConnected = ref(false);
/** 在线学生数 */
const onlineCount = computed(() =>
liveData.value?.onlineStudents.filter(s => s.online).length || 0
);
/** 总学生数 */
const totalStudents = computed(() =>
liveData.value?.onlineStudents.length || 0
);
/** 在线率 */
const onlineRate = computed(() => {
const total = totalStudents.value;
return total > 0 ? Math.round((onlineCount.value / total) * 100) : 0;
});
/**
* 开始课堂
*/
function startClass(classroomId: string, className: string, students: StudentOnlineState[]): void {
liveData.value = {
classroomId,
className,
startTime: Date.now(),
onlineStudents: students,
totalStrokes: 0,
isRecording: false
};
isInClass.value = true;
viewMode.value = 'lesson';
console.log(`[Store] 课堂开始: ${className}, 学生${students.length}人`);
}
/**
* 结束课堂
*/
function endClass(): void {
const duration = liveData.value ? Date.now() - liveData.value.startTime : 0;
console.log(`[Store] 课堂结束, 时长=${Math.round(duration / 60000)}分钟, ` +
`笔迹=${liveData.value?.totalStrokes}`);
isInClass.value = false;
liveData.value = null;
}
/**
* 更新学生在线状态
*/
function updateStudentStatus(studentId: string, online: boolean): void {
const student = liveData.value?.onlineStudents.find(s => s.studentId === studentId);
if (student) {
student.online = online;
student.lastActive = Date.now();
}
}
/**
* 累加笔迹数据计数
*/
function addStrokeCount(count: number): void {
if (liveData.value) {
liveData.value.totalStrokes += count;
}
}
/**
* 切换视图模式
*/
function setViewMode(mode: ViewMode): void {
viewMode.value = mode;
console.log(`[Store] 视图切换: ${mode}`);
}
return {
viewMode, liveData, isInClass, wsConnected,
onlineCount, totalStudents, onlineRate,
startClass, endClass, updateStudentStatus, addStrokeCount, setViewMode
};
});
/* ======================== 设备Store ======================== */
/**
* 设备连接状态管理
*/
export const useDeviceStore = defineStore('device', () => {
/** 已连接设备列表 */
const devices = ref<DeviceState[]>([]);
/** 正在扫描BLE */
const isScanning = ref(false);
/** 已连接设备数 */
const connectedCount = computed(() =>
devices.value.filter(d => d.status === 'connected').length
);
/**
* 添加或更新设备
*/
function upsertDevice(device: DeviceState): void {
const idx = devices.value.findIndex(d => d.id === device.id);
if (idx >= 0) {
devices.value[idx] = device;
} else {
devices.value.push(device);
}
}
/**
* 移除设备
*/
function removeDevice(deviceId: string): void {
devices.value = devices.value.filter(d => d.id !== deviceId);
}
/**
* 更新设备电量
*/
function updateBattery(deviceId: string, battery: number): void {
const device = devices.value.find(d => d.id === deviceId);
if (device) {
device.battery = battery;
}
}
return { devices, isScanning, connectedCount, upsertDevice, removeDevice, updateBattery };
});
/* ======================== 批改Store ======================== */
/**
* 作业批改状态管理
*/
export const useGradeStore = defineStore('grade', () => {
/** 当前批改的作业ID */
const currentAssignmentId = ref('');
/** 批改任务列表 */
const gradeTasks = ref<GradeTask[]>([]);
/** 当前批改的学生索引 */
const currentTaskIndex = ref(0);
/** 待批改数 */
const pendingCount = computed(() =>
gradeTasks.value.filter(t => t.status === 'ai_graded' || t.status === 'pending').length
);
/** 已完成数 */
const completedCount = computed(() =>
gradeTasks.value.filter(t => t.status === 'completed' || t.status === 'reviewed').length
);
/** 总体进度百分比 */
const progressPercent = computed(() => {
const total = gradeTasks.value.length;
return total > 0 ? Math.round((completedCount.value / total) * 100) : 0;
});
/** 当前批改任务 */
const currentTask = computed(() => gradeTasks.value[currentTaskIndex.value] || null);
/**
* 加载批改任务列表
*/
function loadTasks(assignmentId: string, tasks: GradeTask[]): void {
currentAssignmentId.value = assignmentId;
gradeTasks.value = tasks;
currentTaskIndex.value = 0;
console.log(`[Store] 加载批改任务: ${tasks.length}份作业`);
}
/**
* 提交教师批改结果
*/
function submitGrade(studentId: string, score: number, feedback: string): void {
const task = gradeTasks.value.find(t => t.studentId === studentId);
if (task) {
task.teacherScore = score;
task.feedback = feedback;
task.status = 'reviewed';
console.log(`[Store] 批改完成: ${task.studentName}, 分数=${score}`);
}
}
/**
* 切换到下一个待批改任务
*/
function nextTask(): boolean {
for (let i = currentTaskIndex.value + 1; i < gradeTasks.value.length; i++) {
if (gradeTasks.value[i].status !== 'completed' && gradeTasks.value[i].status !== 'reviewed') {
currentTaskIndex.value = i;
return true;
}
}
return false;
}
/**
* 切换到上一个任务
*/
function prevTask(): boolean {
if (currentTaskIndex.value > 0) {
currentTaskIndex.value--;
return true;
}
return false;
}
return {
currentAssignmentId, gradeTasks, currentTaskIndex,
pendingCount, completedCount, progressPercent, currentTask,
loadTasks, submitGrade, nextTask, prevTask
};
});