feat: 监控、解绑设备、作业模式、锁屏等功能
This commit is contained in:
parent
3e5c5e4817
commit
436deb44dd
@ -1,7 +1,6 @@
|
|||||||
import str from '@/utils/str'
|
import str from '@/utils/str'
|
||||||
import { http } from './http'
|
import { http } from './http'
|
||||||
import { get } from './request'
|
import { get } from './request'
|
||||||
import { $XXL } from '@/utils'
|
|
||||||
|
|
||||||
export type Option = {
|
export type Option = {
|
||||||
value: any
|
value: any
|
||||||
@ -99,6 +98,11 @@ export interface UserData {
|
|||||||
smartUserIdentifier: string
|
smartUserIdentifier: string
|
||||||
schoolPersonType: number
|
schoolPersonType: number
|
||||||
lockFlag: number
|
lockFlag: number
|
||||||
|
screenLock?: 0 | 1
|
||||||
|
// 作业模式 0: 未开启 1: 已开启
|
||||||
|
workMode: number
|
||||||
|
// 监控状态 0: 未开启 1: 已开启
|
||||||
|
monitorState: number
|
||||||
isTeacher: boolean
|
isTeacher: boolean
|
||||||
// 是否为可以接受任务的学员
|
// 是否为可以接受任务的学员
|
||||||
isTaskStudent: boolean
|
isTaskStudent: boolean
|
||||||
@ -206,6 +210,14 @@ const globalApi = {
|
|||||||
data,
|
data,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// 上报课堂监控截图
|
||||||
|
reportSnapshot: (data: { sessionId: string; url: string; userId: string }) =>
|
||||||
|
http.request({
|
||||||
|
url: '/school/classMonitor/reportSnapshot',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
|
||||||
// 用户视频表-id查询
|
// 用户视频表-id查询
|
||||||
getUserVideoById: (id: any) =>
|
getUserVideoById: (id: any) =>
|
||||||
http.request({
|
http.request({
|
||||||
|
|||||||
161
src/hooks/useClassMonitor.ts
Normal file
161
src/hooks/useClassMonitor.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import html2canvas from 'html2canvas'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import globalApi from '@/api/global'
|
||||||
|
import { userStore } from '@/stores'
|
||||||
|
import { $XXL, sys_type, sys_type_study_machine } from '@/utils'
|
||||||
|
import sass from '@/utils/sass'
|
||||||
|
import { useTips } from './useTips'
|
||||||
|
|
||||||
|
let monitorTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
let monitorSessionId = ''
|
||||||
|
let isUploadingMonitorSnapshot = false
|
||||||
|
let monitorState: 0 | 1 = 0
|
||||||
|
|
||||||
|
const MONITOR_SNAPSHOT_INTERVAL = 10000
|
||||||
|
const MONITOR_MSG_TYPE = 'monitorStart'
|
||||||
|
|
||||||
|
const normalizeUploadPath = (res: any) => {
|
||||||
|
return String(
|
||||||
|
res?.data?.filePath || res?.data?.url || res?.filePath || res?.url || res?.data?.path || '',
|
||||||
|
)
|
||||||
|
.replace(/[`]/g, '')
|
||||||
|
.replace(/http:\/\//gi, 'https://')
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadNativeScreenshot = async () => {
|
||||||
|
const { token } = storeToRefs(userStore())
|
||||||
|
const { data: filePath } = $XXL.getScreenshot() || {}
|
||||||
|
if (!filePath) return ''
|
||||||
|
|
||||||
|
const res = await $XXL.uploadFile({
|
||||||
|
url: `${import.meta.env.VITE_SERVER_URL}/sysFileInfo/tenUploadAll`,
|
||||||
|
header: {
|
||||||
|
Authorization: token.value,
|
||||||
|
'X-Tenant-Id': sass.platform.tenantId,
|
||||||
|
},
|
||||||
|
filePath,
|
||||||
|
fileKey: 'file',
|
||||||
|
})
|
||||||
|
return normalizeUploadPath(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createWebScreenshotFile = async () => {
|
||||||
|
const canvas = await html2canvas(document.body, {
|
||||||
|
useCORS: true,
|
||||||
|
backgroundColor: null,
|
||||||
|
x: window.scrollX,
|
||||||
|
y: window.scrollY,
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
windowWidth: window.innerWidth,
|
||||||
|
windowHeight: window.innerHeight,
|
||||||
|
scale: window.devicePixelRatio || 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
return await new Promise<File | null>(resolve => {
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
if (!blob) return resolve(null)
|
||||||
|
resolve(new File([blob], `monitor-${Date.now()}.png`, { type: 'image/png' }))
|
||||||
|
}, 'image/png')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadWebScreenshot = async () => {
|
||||||
|
const file = await createWebScreenshotFile()
|
||||||
|
if (!file) return ''
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
const { data: res } = await globalApi.uploadFile(formData)
|
||||||
|
return normalizeUploadPath(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadMonitorScreenshot = async () => {
|
||||||
|
if (sys_type === sys_type_study_machine) {
|
||||||
|
return await uploadNativeScreenshot()
|
||||||
|
}
|
||||||
|
return await uploadWebScreenshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
const reportMonitorSnapshot = async (sessionId: string) => {
|
||||||
|
const { userInfo } = storeToRefs(userStore())
|
||||||
|
if (isUploadingMonitorSnapshot || monitorSessionId !== sessionId) return
|
||||||
|
|
||||||
|
isUploadingMonitorSnapshot = true
|
||||||
|
try {
|
||||||
|
const url = await uploadMonitorScreenshot()
|
||||||
|
if (!url || monitorSessionId !== sessionId) return
|
||||||
|
|
||||||
|
await globalApi.reportSnapshot({
|
||||||
|
sessionId,
|
||||||
|
url,
|
||||||
|
userId: userInfo.value.userId,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
} finally {
|
||||||
|
isUploadingMonitorSnapshot = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopClassMonitor = (showTip = false) => {
|
||||||
|
const isMonitoring = !!monitorTimer || !!monitorSessionId || monitorState === 1
|
||||||
|
if (monitorTimer) {
|
||||||
|
clearInterval(monitorTimer)
|
||||||
|
monitorTimer = null
|
||||||
|
}
|
||||||
|
if (showTip && isMonitoring) {
|
||||||
|
useTips({
|
||||||
|
tips: '已结束课堂监控',
|
||||||
|
showCancel: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
monitorSessionId = ''
|
||||||
|
monitorState = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const startClassMonitor = (msg: anyObj) => {
|
||||||
|
const sessionId = msg?.sessionId
|
||||||
|
if (!sessionId) return
|
||||||
|
if (monitorTimer && monitorSessionId === sessionId) return
|
||||||
|
|
||||||
|
stopClassMonitor()
|
||||||
|
monitorSessionId = sessionId
|
||||||
|
monitorState = 1
|
||||||
|
useTips({
|
||||||
|
tips: '已进入课堂监控状态',
|
||||||
|
showCancel: false,
|
||||||
|
})
|
||||||
|
reportMonitorSnapshot(sessionId)
|
||||||
|
monitorTimer = setInterval(() => reportMonitorSnapshot(sessionId), MONITOR_SNAPSHOT_INTERVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleClassMonitorMessage = (data: anyObj) => {
|
||||||
|
const messageType = data?.type || data?.msg?.type
|
||||||
|
if (messageType !== MONITOR_MSG_TYPE) return false
|
||||||
|
|
||||||
|
const msg = data?.msg || data
|
||||||
|
const monitoring = msg?.monitoring
|
||||||
|
if (monitoring === true || monitoring === 'true') {
|
||||||
|
startClassMonitor(msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (monitoring === false || monitoring === 'false') {
|
||||||
|
stopClassMonitor(true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const syncClassMonitorState = (state: unknown) => {
|
||||||
|
const nextState = Number(state)
|
||||||
|
if (nextState !== 0 && nextState !== 1) return
|
||||||
|
|
||||||
|
if (nextState === 0) {
|
||||||
|
stopClassMonitor(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
monitorState = 1
|
||||||
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import { useWebSocket } from '@vueuse/core'
|
import { useWebSocket } from '@vueuse/core'
|
||||||
import { userStore, globalStore, urgeStore } from '@/stores'
|
import { userStore, globalStore, urgeStore } from '@/stores'
|
||||||
import type { UseWebSocketReturn } from '@vueuse/core'
|
import type { UseWebSocketReturn } from '@vueuse/core'
|
||||||
import { getSN, $XXL, sys_type, sys_type_study_machine, isNotStudyMachine } from '@/utils'
|
import { getSN, $XXL, isNotStudyMachine } from '@/utils'
|
||||||
import { useLog, LogType, useTips } from '@/hooks'
|
import { useLog, LogType, useTips } from '@/hooks'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { handleClassMonitorMessage, stopClassMonitor } from './useClassMonitor'
|
||||||
|
|
||||||
let SOCKET: UseWebSocketReturn<any>
|
let SOCKET: UseWebSocketReturn<any>
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ enum SOCKET_MSG_TYPE {
|
|||||||
inspectorEnd = 'inspectorEnd',
|
inspectorEnd = 'inspectorEnd',
|
||||||
systemMessage = 'systemMessage',
|
systemMessage = 'systemMessage',
|
||||||
messageReceive = 'messageReceive',
|
messageReceive = 'messageReceive',
|
||||||
|
monitorStart = 'monitorStart',
|
||||||
}
|
}
|
||||||
|
|
||||||
const inspectorEnd = ref<anyObj>()
|
const inspectorEnd = ref<anyObj>()
|
||||||
@ -30,7 +32,6 @@ export const useSocket = () => {
|
|||||||
const SN = getSN()
|
const SN = getSN()
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
return
|
|
||||||
if (import.meta.env.MODE === 'test') return
|
if (import.meta.env.MODE === 'test') return
|
||||||
if (!token.value) return
|
if (!token.value) return
|
||||||
stop()
|
stop()
|
||||||
@ -79,8 +80,28 @@ export const useSocket = () => {
|
|||||||
// console.log(ws, event);
|
// console.log(ws, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getLockFlag = (data: anyObj, fallback: 0 | 1): 0 | 1 => {
|
||||||
|
const locked = data.msg?.locked
|
||||||
|
if (locked === true || locked === 'true') return 1
|
||||||
|
if (locked === false || locked === 'false') return 0
|
||||||
|
|
||||||
|
const lockFlag = Number(locked)
|
||||||
|
return lockFlag === 0 || lockFlag === 1 ? lockFlag : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateLockScreen = (lockFlag: 0 | 1) => {
|
||||||
|
if (isNotStudyMachine) return
|
||||||
|
|
||||||
|
system.value.isLock = lockFlag === 1
|
||||||
|
useLog(LogType.LockScreenTime, {
|
||||||
|
simSerialNumber: SN,
|
||||||
|
lockFlag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const onMessage = (ws: WebSocket, event: MessageEvent) => {
|
const onMessage = (ws: WebSocket, event: MessageEvent) => {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
|
const messageType = data.type || data.msg?.type
|
||||||
|
|
||||||
// 确认接收
|
// 确认接收
|
||||||
sendMsg({
|
sendMsg({
|
||||||
@ -89,7 +110,7 @@ export const useSocket = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 全局消息提醒
|
// 全局消息提醒
|
||||||
if (data.type === SOCKET_MSG_TYPE.systemMessage) {
|
if (messageType === SOCKET_MSG_TYPE.systemMessage) {
|
||||||
useTips({
|
useTips({
|
||||||
tips: data.msg,
|
tips: data.msg,
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
@ -98,29 +119,31 @@ export const useSocket = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if (isWeb) return;
|
// if (isWeb) return;
|
||||||
switch (data.type) {
|
if (
|
||||||
|
messageType !== SOCKET_MSG_TYPE.lockScreen &&
|
||||||
|
messageType !== SOCKET_MSG_TYPE.unLockScreen &&
|
||||||
|
typeof data.msg?.locked !== 'undefined'
|
||||||
|
) {
|
||||||
|
updateLockScreen(getLockFlag(data, 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (messageType) {
|
||||||
case SOCKET_MSG_TYPE.lockScreen:
|
case SOCKET_MSG_TYPE.lockScreen:
|
||||||
if (isNotStudyMachine) return
|
|
||||||
// 执行锁屏
|
// 执行锁屏
|
||||||
system.value.isLock = true
|
updateLockScreen(getLockFlag(data, 1))
|
||||||
useLog(LogType.LockScreenTime, {
|
|
||||||
simSerialNumber: SN,
|
|
||||||
lockFlag: 1,
|
|
||||||
})
|
|
||||||
break
|
break
|
||||||
case SOCKET_MSG_TYPE.unLockScreen:
|
case SOCKET_MSG_TYPE.unLockScreen:
|
||||||
if (isNotStudyMachine) return
|
|
||||||
// 执行解锁
|
// 执行解锁
|
||||||
system.value.isLock = false
|
updateLockScreen(getLockFlag(data, 0))
|
||||||
useLog(LogType.LockScreenTime, {
|
|
||||||
simSerialNumber: SN,
|
|
||||||
lockFlag: 0,
|
|
||||||
})
|
|
||||||
break
|
break
|
||||||
case SOCKET_MSG_TYPE.desktopPreview:
|
case SOCKET_MSG_TYPE.desktopPreview:
|
||||||
if (isNotStudyMachine) return
|
if (isNotStudyMachine) return
|
||||||
onDesktopPreview()
|
onDesktopPreview()
|
||||||
break
|
break
|
||||||
|
case SOCKET_MSG_TYPE.monitorStart:
|
||||||
|
handleClassMonitorMessage(data)
|
||||||
|
break
|
||||||
case SOCKET_MSG_TYPE.inspectorStart:
|
case SOCKET_MSG_TYPE.inspectorStart:
|
||||||
// 开始督学
|
// 开始督学
|
||||||
// console.log('开始督学');
|
// console.log('开始督学');
|
||||||
@ -197,6 +220,7 @@ export const useSocket = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
|
stopClassMonitor()
|
||||||
SOCKET?.close()
|
SOCKET?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { ref, shallowRef } from 'vue'
|
|||||||
import questionExplainApi from '@/api/subject/questionExplain'
|
import questionExplainApi from '@/api/subject/questionExplain'
|
||||||
import { getCache } from '@/utils'
|
import { getCache } from '@/utils'
|
||||||
import { userStore } from '@/stores'
|
import { userStore } from '@/stores'
|
||||||
|
import { handleClassMonitorMessage, stopClassMonitor } from './useClassMonitor'
|
||||||
|
|
||||||
// ======================== 常量配置 ========================
|
// ======================== 常量配置 ========================
|
||||||
|
|
||||||
@ -194,6 +195,8 @@ function handleMessage(data: TeacherWsMessage) {
|
|||||||
// 心跳响应不做业务处理
|
// 心跳响应不做业务处理
|
||||||
if (data.type === 'pong') return
|
if (data.type === 'pong') return
|
||||||
|
|
||||||
|
handleClassMonitorMessage(data)
|
||||||
|
|
||||||
// 调用所有已注册的外部消息回调
|
// 调用所有已注册的外部消息回调
|
||||||
messageHandlers.forEach(handler => {
|
messageHandlers.forEach(handler => {
|
||||||
try {
|
try {
|
||||||
@ -285,6 +288,7 @@ async function init() {
|
|||||||
function close() {
|
function close() {
|
||||||
isManualClose = true
|
isManualClose = true
|
||||||
stopHeartbeat()
|
stopHeartbeat()
|
||||||
|
stopClassMonitor()
|
||||||
clearReconnectTimer()
|
clearReconnectTimer()
|
||||||
stopVisibilityListener()
|
stopVisibilityListener()
|
||||||
reconnectAttempts = 0
|
reconnectAttempts = 0
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { globalStore, subjectStore } from '@/stores'
|
import { globalStore, subjectStore, userStore } from '@/stores'
|
||||||
import { useSocket, useTeacherExplain } from '@/hooks'
|
import { LogType, useLog, useSocket, useTeacherExplain } from '@/hooks'
|
||||||
import { $XXL, isWebEvn } from '@/utils'
|
import { $XXL, getSN, isStudyMachine, isWebEvn } from '@/utils'
|
||||||
import hud from '@/utils/hud'
|
import hud from '@/utils/hud'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
@ -12,8 +12,12 @@ const logo = sass.getLogo()
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { system, init, getDict, initDevTool, destroyDevTool } = globalStore()
|
const { system, init, getDict, initDevTool, destroyDevTool } = globalStore()
|
||||||
|
const { getUserInfo } = userStore()
|
||||||
|
|
||||||
const { getSubjectAllList } = subjectStore()
|
const { getSubjectAllList } = subjectStore()
|
||||||
|
const LOCK_HOME_PAGE_REFRESH_INTERVAL = 10_000
|
||||||
|
let lockHomePageTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
let lockHomePageRefreshing = false
|
||||||
|
|
||||||
if (!isWebEvn) {
|
if (!isWebEvn) {
|
||||||
// 注入安卓方法
|
// 注入安卓方法
|
||||||
@ -82,6 +86,52 @@ const {
|
|||||||
} = useTeacherExplain()
|
} = useTeacherExplain()
|
||||||
teacherExplainInit()
|
teacherExplainInit()
|
||||||
|
|
||||||
|
function getLockFlag(data: anyObj, fallback: 0 | 1): 0 | 1 {
|
||||||
|
const locked = data.msg?.locked
|
||||||
|
if (locked === true || locked === 'true') return 1
|
||||||
|
if (locked === false || locked === 'false') return 0
|
||||||
|
|
||||||
|
const lockFlag = Number(locked)
|
||||||
|
return lockFlag === 0 || lockFlag === 1 ? lockFlag : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLockScreen(lockFlag: 0 | 1) {
|
||||||
|
if (!isStudyMachine) return
|
||||||
|
|
||||||
|
system.isLock = lockFlag === 1
|
||||||
|
useLog(LogType.LockScreenTime, {
|
||||||
|
simSerialNumber: getSN(),
|
||||||
|
lockFlag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshLockHomePage() {
|
||||||
|
if (lockHomePageRefreshing) return
|
||||||
|
|
||||||
|
lockHomePageRefreshing = true
|
||||||
|
try {
|
||||||
|
await getUserInfo()
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('锁屏状态刷新首页信息失败', err)
|
||||||
|
} finally {
|
||||||
|
lockHomePageRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLockHomePageRefresh() {
|
||||||
|
if (lockHomePageTimer) return
|
||||||
|
|
||||||
|
refreshLockHomePage()
|
||||||
|
lockHomePageTimer = setInterval(refreshLockHomePage, LOCK_HOME_PAGE_REFRESH_INTERVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopLockHomePageRefresh() {
|
||||||
|
if (!lockHomePageTimer) return
|
||||||
|
|
||||||
|
clearInterval(lockHomePageTimer)
|
||||||
|
lockHomePageTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
// 重新登录后重连 WebSocket
|
// 重新登录后重连 WebSocket
|
||||||
watch(
|
watch(
|
||||||
() => router.currentRoute.value.path,
|
() => router.currentRoute.value.path,
|
||||||
@ -92,9 +142,32 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => system.isLock,
|
||||||
|
isLock => {
|
||||||
|
if (isLock) {
|
||||||
|
startLockHomePageRefresh()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stopLockHomePageRefresh()
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
// 监听老师讲解 WebSocket 消息,收到 questionExplainStart 且 explaining 为 true 时跳转讲解页面
|
// 监听老师讲解 WebSocket 消息,收到 questionExplainStart 且 explaining 为 true 时跳转讲解页面
|
||||||
// 如果当前已在讲解页面则不跳转,由讲解页面内部根据 questionId 切换题目
|
// 如果当前已在讲解页面则不跳转,由讲解页面内部根据 questionId 切换题目
|
||||||
onTeacherMessage(data => {
|
onTeacherMessage(data => {
|
||||||
|
if (data.type === 'lockScreen') {
|
||||||
|
updateLockScreen(getLockFlag(data, 1))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type === 'unLockScreen') {
|
||||||
|
updateLockScreen(getLockFlag(data, 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (data.type === 'questionExplainStart') {
|
if (data.type === 'questionExplainStart') {
|
||||||
let msg = data.msg
|
let msg = data.msg
|
||||||
if (typeof msg === 'string') {
|
if (typeof msg === 'string') {
|
||||||
@ -190,6 +263,10 @@ function onLoaded() {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$XXL.closeLoading()
|
$XXL.closeLoading()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopLockHomePageRefresh()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<!-- @click="onPageClick" -->
|
<!-- @click="onPageClick" -->
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { userStore, globalStore, taskStore, subjectStore } from '@/stores'
|
|||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import globalApi from '@/api/global'
|
import globalApi from '@/api/global'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useAudio } from '@/hooks'
|
import { useAudio, XMessage } from '@/hooks'
|
||||||
import {
|
import {
|
||||||
getLevelProgress,
|
getLevelProgress,
|
||||||
$XXL,
|
$XXL,
|
||||||
@ -43,6 +43,7 @@ const taskData = taskStore()
|
|||||||
const { userInfo, workToken } = storeToRefs(userStore())
|
const { userInfo, workToken } = storeToRefs(userStore())
|
||||||
const { getUserInfo, isFirstBind } = userStore()
|
const { getUserInfo, isFirstBind } = userStore()
|
||||||
const { system, checkUpdate } = globalStore()
|
const { system, checkUpdate } = globalStore()
|
||||||
|
const isHomePageLoading = ref(false)
|
||||||
|
|
||||||
const navMain = computed(() => {
|
const navMain = computed(() => {
|
||||||
// const { purchasedInspector, nextInspectorCourseDate, conductInspectorPlanId } =
|
// const { purchasedInspector, nextInspectorCourseDate, conductInspectorPlanId } =
|
||||||
@ -238,6 +239,8 @@ function getTaskRedPoint() {
|
|||||||
const { selectSubject } = subjectStore()
|
const { selectSubject } = subjectStore()
|
||||||
const handleNav = debounce(
|
const handleNav = debounce(
|
||||||
(data: anyObj) => {
|
(data: anyObj) => {
|
||||||
|
if (isHomePageLoading.value) return
|
||||||
|
|
||||||
if (data.key === 'homeWork' && userInfo.value.isTeacher) {
|
if (data.key === 'homeWork' && userInfo.value.isTeacher) {
|
||||||
const path = `${import.meta.env.VITE_TEACHER_ADMIN_URL}?token=${workToken.value.replace('Bearer ', '')}`
|
const path = `${import.meta.env.VITE_TEACHER_ADMIN_URL}?token=${workToken.value.replace('Bearer ', '')}`
|
||||||
if (isWebEvn) {
|
if (isWebEvn) {
|
||||||
@ -287,17 +290,36 @@ function handleKnown(data: anyObj) {
|
|||||||
|
|
||||||
// 个人中心
|
// 个人中心
|
||||||
function handleToUserPage() {
|
function handleToUserPage() {
|
||||||
|
if (isHomePageLoading.value) return
|
||||||
|
|
||||||
router.push({ path: '/system/user' })
|
router.push({ path: '/system/user' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击个人形象
|
// 点击个人形象
|
||||||
function clickPhoto() {
|
function clickPhoto() {
|
||||||
|
if (isHomePageLoading.value) return
|
||||||
|
|
||||||
if (isShufaAccount(userInfo.value.account)) {
|
if (isShufaAccount(userInfo.value.account)) {
|
||||||
// 书法小状元的账号
|
// 书法小状元的账号
|
||||||
router.replace(SHUFA_PATH)
|
router.replace(SHUFA_PATH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshHomePage() {
|
||||||
|
isHomePageLoading.value = true
|
||||||
|
try {
|
||||||
|
await getUserInfo()
|
||||||
|
if (Number(userInfo.value.workMode) === 1) {
|
||||||
|
XMessage.info('当前处于作业模式')
|
||||||
|
await router.replace('/system/homeWork')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
isHomePageLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let timer: any
|
let timer: any
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 是否首绑
|
// 是否首绑
|
||||||
@ -316,7 +338,8 @@ onActivated(async () => {
|
|||||||
getPoint()
|
getPoint()
|
||||||
getTaskRedPoint()
|
getTaskRedPoint()
|
||||||
|
|
||||||
await getUserInfo()
|
const isWorkMode = await refreshHomePage()
|
||||||
|
if (isWorkMode) return
|
||||||
|
|
||||||
// 获取徽章
|
// 获取徽章
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|||||||
@ -195,7 +195,7 @@ const startDoWork = () => {
|
|||||||
></Empty>
|
></Empty>
|
||||||
</div>
|
</div>
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<BackBar leftText="作业" />
|
<BackBar leftText="作业" index />
|
||||||
<div class="top-history">
|
<div class="top-history">
|
||||||
<!-- <mj-button type="success" tilt @click="to(`/system/homeWork/statistics`)"
|
<!-- <mj-button type="success" tilt @click="to(`/system/homeWork/statistics`)"
|
||||||
>统计分析</mj-button
|
>统计分析</mj-button
|
||||||
|
|||||||
@ -204,7 +204,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
// 全局的返回函数
|
// 全局的返回函数
|
||||||
export function globalBack(home = false) {
|
export function globalBack(home = false) {
|
||||||
if (window.history.state?.position === 1 || !window.history.state.back || home) {
|
if (window.history.state?.position === 1 || !window.history.state.back || home) {
|
||||||
router.push('/')
|
router.replace('/')
|
||||||
} else {
|
} else {
|
||||||
const afterRoute = router.currentRoute.value.fullPath
|
const afterRoute = router.currentRoute.value.fullPath
|
||||||
router.back()
|
router.back()
|
||||||
|
|||||||
@ -6,8 +6,9 @@ import loginApi from '@/api/login'
|
|||||||
import type { accountLoginType } from '@/api/login'
|
import type { accountLoginType } from '@/api/login'
|
||||||
import type { Option, UserData } from '@/api/global'
|
import type { Option, UserData } from '@/api/global'
|
||||||
import type { School } from '@/api/studentManage'
|
import type { School } from '@/api/studentManage'
|
||||||
import { setCache, clearCache, getSN, getManufacturer, isStudyMachine } from '@/utils'
|
import { setCache, clearCache, getSN, getManufacturer, isStudyMachine, isWebEvn } from '@/utils'
|
||||||
import { useSocket } from '@/hooks'
|
import { LogType, useLog, useSocket } from '@/hooks'
|
||||||
|
import { syncClassMonitorState } from '@/hooks/useClassMonitor'
|
||||||
import { globalStore } from '@/stores'
|
import { globalStore } from '@/stores'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import obj from '@/utils/obj'
|
import obj from '@/utils/obj'
|
||||||
@ -84,17 +85,42 @@ export const userStore = defineStore('user', () => {
|
|||||||
return userInfo.value.schoolClass?.[0]?.classList?.[0]
|
return userInfo.value.schoolClass?.[0]?.classList?.[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateScreenLock = (screenLock?: unknown) => {
|
||||||
|
const lockFlag = Number(screenLock)
|
||||||
|
if (lockFlag !== 0 && lockFlag !== 1) return
|
||||||
|
|
||||||
|
const { system } = storeToRefs(globalStore())
|
||||||
|
const isLock = lockFlag === 1
|
||||||
|
if (!isStudyMachine && isLock) return
|
||||||
|
if (system.value.isLock === isLock) return
|
||||||
|
|
||||||
|
system.value.isLock = isLock
|
||||||
|
if (!isStudyMachine) return
|
||||||
|
|
||||||
|
useLog(LogType.LockScreenTime, {
|
||||||
|
simSerialNumber: getSN(),
|
||||||
|
lockFlag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 更新用户信息
|
// 更新用户信息
|
||||||
const updateUserInfo = async (info: any) => {
|
const updateUserInfo = async (info: any) => {
|
||||||
if (!info) return
|
if (!info) return
|
||||||
|
const hasMonitorState = Object.prototype.hasOwnProperty.call(info, 'monitorState')
|
||||||
info = { ...userInfo.value, ...info }
|
info = { ...userInfo.value, ...info }
|
||||||
|
|
||||||
if (isStudyMachine) {
|
if (typeof info.screenLock !== 'undefined') {
|
||||||
|
updateScreenLock(info.screenLock)
|
||||||
|
} else if (isStudyMachine) {
|
||||||
// 锁屏
|
// 锁屏
|
||||||
const { system } = storeToRefs(globalStore())
|
const { system } = storeToRefs(globalStore())
|
||||||
system.value.isLock = info.lockFlag === 0
|
system.value.isLock = info.lockFlag === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasMonitorState) {
|
||||||
|
syncClassMonitorState(info.monitorState)
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof info.schoolClass === 'string') {
|
if (typeof info.schoolClass === 'string') {
|
||||||
info.schoolClass = JSON.parse(info.schoolClass)
|
info.schoolClass = JSON.parse(info.schoolClass)
|
||||||
if (info.schoolClass?.length) {
|
if (info.schoolClass?.length) {
|
||||||
@ -158,13 +184,25 @@ export const userStore = defineStore('user', () => {
|
|||||||
setCache('token', `Bearer ${tokenValue}`)
|
setCache('token', `Bearer ${tokenValue}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDeviceSerialNumber = () => {
|
||||||
|
const { system } = globalStore()
|
||||||
|
return system.device.SN || getSN()
|
||||||
|
}
|
||||||
|
|
||||||
// 账号登录
|
// 账号登录
|
||||||
const accountLogin = async (data: accountLoginType) => {
|
const accountLogin = async (data: accountLoginType) => {
|
||||||
const res = await loginApi.psdLogin({
|
const loginData: accountLoginType = {
|
||||||
...data,
|
...data,
|
||||||
simSerialNumber: getSN(),
|
|
||||||
model: getManufacturer(),
|
model: getManufacturer(),
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (isWebEvn) {
|
||||||
|
delete loginData.simSerialNumber
|
||||||
|
} else {
|
||||||
|
loginData.simSerialNumber = getDeviceSerialNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await loginApi.psdLogin(loginData)
|
||||||
await loginBefore()
|
await loginBefore()
|
||||||
setToken(res as any)
|
setToken(res as any)
|
||||||
await loginAfter()
|
await loginAfter()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user