diff --git a/playground/src/api/service/org/classroom-monitor.ts b/playground/src/api/service/org/classroom-monitor.ts index 555e9b9..68c19eb 100644 --- a/playground/src/api/service/org/classroom-monitor.ts +++ b/playground/src/api/service/org/classroom-monitor.ts @@ -5,6 +5,8 @@ import { get, post } from '#/api/http' export type ClassroomItem = { id: number | string name: string + /** 1 监控开启,0 关闭 */ + monitorState?: number } export type ClassMonitorCurrent = { @@ -25,6 +27,61 @@ export type ClassMonitorSnapshot = { studentName: string } +export type ClassMonitorStudentSnapshot = { + ts: number + url: string +} + +export type ClassMonitorSessionRow = { + classId: number | string + className: string + durationSec: number + endTime: string + intervalMs: number + schoolId: number | string + sessionId: number | string + snapshotCount: number + startTime: string + status: number + studentCount: number + teacherId: number | string + teacherName: string +} + +/** `/school/classMonitor/sessionDetail` 中 data 内单条学生(与列表快照字段接近,可能含 snapshotCount) */ +export type ClassMonitorSessionDetailStudent = { + avatar: string + latestTs: number + latestUrl: string + snapshotCount?: number + staleSeconds?: number + studentId: number | string + studentName: string +} + +/** `/school/classMonitor/sessionDetail` 返回的 data 对象 */ +export type ClassMonitorSessionDetail = { + classId: number | string + className: string + durationSec: number + endTime: string + intervalMs: number + schoolId: number | string + sessionId: number | string + snapshotCount: number + startTime: string + status: number + studentCount: number + students: ClassMonitorSessionDetailStudent[] + teacherId: number | string + teacherName: string +} + +/** 分页里的扩展字段 rainbow 暂不消费 */ +export type ClassMonitorSessionsPage = PageResponse & { + rainbow?: unknown[] +} + class ClassroomMonitorService { async getClassList() { return await get>('/org/class/list', { @@ -46,6 +103,41 @@ class ClassroomMonitorService { ) } + async getStudentSnapshots( + sessionId: number | string, + studentId: number | string, + ) { + return await get( + '/school/classMonitor/studentSnapshots', + { sessionId, studentId }, + ) + } + + async getSessions( + classId: number | string, + params?: { current?: number; size?: number }, + ) { + return await post('/school/classMonitor/sessions', { + classId, + current: params?.current ?? 1, + size: params?.size ?? 10, + }) + } + + async getSessionDetail( + sessionId: number | string, + classId?: number | string, + ) { + const params: Record = { sessionId } + if (classId !== undefined && classId !== '') { + params.classId = classId + } + return await get( + '/school/classMonitor/sessionDetail', + params, + ) + } + async updateMonitorStatus(classId: number | string, status: 0 | 1) { return await post('/school/classMonitor/control', { classId, diff --git a/playground/src/views/org/class-mgr/home.vue b/playground/src/views/org/class-mgr/home.vue index c4867ef..5c484e2 100644 --- a/playground/src/views/org/class-mgr/home.vue +++ b/playground/src/views/org/class-mgr/home.vue @@ -324,12 +324,20 @@ function updateScreenLock(row: any, status: 0 | 1) { v-bind="$attrs" > + + +
+ + + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+
+ + {{ selectedSnapshotStudent?.studentName?.slice(0, 1) || '学' }} + +
+
+ {{ selectedSnapshotStudent?.studentName || '-' }} +
+
+ 共 {{ studentSnapshots.length }} 张截图 +
+
+
+ +
+
+ +
+ 截图时间:{{ formatTime(item.ts) }} +
+
+
+ + +
+
@@ -277,6 +513,14 @@ function formatTime(time?: number | string) { } } + .class-row-info { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + min-width: 0; + } + .class-name { color: #111827; font-size: 16px; @@ -335,7 +579,15 @@ function formatTime(time?: number | string) { } .snapshot-card { + cursor: pointer; overflow: hidden; + transition: + border-color 0.2s, + box-shadow 0.2s; + + &:hover { + border-color: #409eff; + } } .snapshot-image, @@ -369,6 +621,50 @@ function formatTime(time?: number | string) { color: #6b7280; font-size: 12px; } + + .student-snapshot-dialog { + min-height: 240px; + } + + .student-snapshot-summary { + display: flex; + align-items: center; + gap: 12px; + padding-bottom: 16px; + margin-bottom: 16px; + border-bottom: 1px solid #edf0f5; + } + + .student-snapshot-list { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + max-height: 620px; + overflow-y: auto; + } + + .student-snapshot-item { + padding: 12px; + border: 1px solid #edf0f5; + border-radius: 8px; + } + + .student-snapshot-image { + width: 100%; + height: 180px; + border-radius: 8px; + } + + .sessions-table { + cursor: pointer; + width: 100%; + } + + .sessions-pagination { + margin-top: 16px; + display: flex; + justify-content: flex-end; + } } @media (max-width: 1400px) { @@ -376,6 +672,10 @@ function formatTime(time?: number | string) { .snapshot-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } + + .student-snapshot-list { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } } } @@ -408,6 +708,10 @@ function formatTime(time?: number | string) { .snapshot-grid { grid-template-columns: 1fr; } + + .student-snapshot-list { + grid-template-columns: 1fr; + } } } diff --git a/playground/src/views/org/classroom-monitor/routes.ts b/playground/src/views/org/classroom-monitor/routes.ts new file mode 100644 index 0000000..fd19960 --- /dev/null +++ b/playground/src/views/org/classroom-monitor/routes.ts @@ -0,0 +1,15 @@ +import type { RouteRecordRaw } from 'vue-router' + +const routes: RouteRecordRaw[] = [ + { + path: '/org/classroom-monitor/session/:sessionId', + name: 'ClassroomMonitorSessionDetail', + component: () => import('./session-detail.vue'), + meta: { + title: '历史监控会话', + hideInMenu: true, + }, + }, +] + +export default routes diff --git a/playground/src/views/org/classroom-monitor/session-detail.vue b/playground/src/views/org/classroom-monitor/session-detail.vue new file mode 100644 index 0000000..5c8b161 --- /dev/null +++ b/playground/src/views/org/classroom-monitor/session-detail.vue @@ -0,0 +1,417 @@ + + + + +