software copyright
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
/**
|
||||
* 自然写互动课堂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
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user