455 lines
11 KiB
Vue
455 lines
11 KiB
Vue
<script setup lang="ts">
|
||
import { globalStore, subjectStore, userStore } from '@/stores'
|
||
import { LogType, useLog, useSocket, useTeacherExplain } from '@/hooks'
|
||
import { $XXL, getSN, isStudyMachine, isWebEvn } from '@/utils'
|
||
import hud from '@/utils/hud'
|
||
import { useRouter } from 'vue-router'
|
||
import { storeToRefs } from 'pinia'
|
||
import sass from '@/utils/sass'
|
||
const OSS_URL = import.meta.env.VITE_OSS_URL
|
||
const lock_bg = `url(${OSS_URL}/urm/lock_bg.png) top left/cover no-repeat`
|
||
const logo = sass.getLogo()
|
||
|
||
const router = useRouter()
|
||
const { system, init, getDict, initDevTool, destroyDevTool } = globalStore()
|
||
const { getUserInfo } = userStore()
|
||
|
||
const { getSubjectAllList } = subjectStore()
|
||
const LOCK_HOME_PAGE_REFRESH_INTERVAL = 10_000
|
||
let lockHomePageTimer: ReturnType<typeof setInterval> | null = null
|
||
let lockHomePageRefreshing = false
|
||
|
||
if (!isWebEvn) {
|
||
// 注入安卓方法
|
||
window.webViewGoBack = () => {
|
||
const { system } = storeToRefs(globalStore())
|
||
if (system.value.fullPlay) {
|
||
system.value.fullPlay = false
|
||
return 1
|
||
}
|
||
if (router.currentRoute.value.path === '/') {
|
||
return 0
|
||
}
|
||
if (window.history.state?.position === 1 || !window.history.state.back) {
|
||
router.push('/')
|
||
return 0
|
||
} else {
|
||
const afterRoute = router.currentRoute.value.fullPath
|
||
router.back()
|
||
setTimeout(() => {
|
||
const nowRoute = router.currentRoute.value.fullPath
|
||
if (afterRoute === nowRoute) {
|
||
console.log('back后路由没变动,手动跳转')
|
||
router.replace(window.history.state.back)
|
||
}
|
||
}, 300)
|
||
return 1
|
||
}
|
||
}
|
||
window.initDevTool = initDevTool
|
||
window.destroyDevTool = destroyDevTool
|
||
window.goTo = (path = '/') => {
|
||
router.replace(path)
|
||
}
|
||
// 检查更新
|
||
// await checkUpdate(false).then(res => {
|
||
// if (!res) return;
|
||
// duration.value = 5;
|
||
// useTips({
|
||
// title: '更新提示',
|
||
// html: `<div style="text-align:center;"><p>新版本已经准备好,马上更新</p><p>当前版本:${getVersion() || ''}#${getVersion(1) || ''}</p><p style="color:#67C23A">最新版本:${system.latestVersion.apk?.versionNum || getVersion()}#${system.latestVersion.web?.versionNum || ''}</p></div>`,
|
||
// showCancel: false,
|
||
// showClose: false,
|
||
// zIndex: 10000,
|
||
// 'close-on-click-modal': false,
|
||
// 'close-on-press-escape': false,
|
||
// onOk: async () => {
|
||
// await apkUpdate();
|
||
// },
|
||
// });
|
||
// $XXL.closeLoading();
|
||
// });
|
||
}
|
||
|
||
init()
|
||
getDict()
|
||
getSubjectAllList()
|
||
|
||
const { init: socketInit } = useSocket()
|
||
socketInit()
|
||
|
||
// 初始化老师讲解 WebSocket 连接
|
||
const {
|
||
init: teacherExplainInit,
|
||
reconnect: teacherExplainReconnect,
|
||
onMessage: onTeacherMessage,
|
||
} = useTeacherExplain()
|
||
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
|
||
watch(
|
||
() => router.currentRoute.value.path,
|
||
(to, from) => {
|
||
if (from === '/login' && to !== '/login') {
|
||
teacherExplainReconnect()
|
||
}
|
||
},
|
||
)
|
||
|
||
watch(
|
||
() => system.isLock,
|
||
isLock => {
|
||
if (isLock) {
|
||
startLockHomePageRefresh()
|
||
return
|
||
}
|
||
|
||
stopLockHomePageRefresh()
|
||
},
|
||
{ immediate: true },
|
||
)
|
||
|
||
// 监听老师讲解 WebSocket 消息,收到 questionExplainStart 且 explaining 为 true 时跳转讲解页面
|
||
// 如果当前已在讲解页面则不跳转,由讲解页面内部根据 questionId 切换题目
|
||
onTeacherMessage(data => {
|
||
if (data.type === 'lockScreen') {
|
||
updateLockScreen(getLockFlag(data, 1))
|
||
return
|
||
}
|
||
|
||
if (data.type === 'unLockScreen') {
|
||
updateLockScreen(getLockFlag(data, 0))
|
||
return
|
||
}
|
||
|
||
if (data.type === 'questionExplainStart') {
|
||
let msg = data.msg
|
||
if (typeof msg === 'string') {
|
||
try {
|
||
msg = JSON.parse(msg)
|
||
} catch {
|
||
return
|
||
}
|
||
}
|
||
if (msg?.explaining === true) {
|
||
const currentPath = router.currentRoute.value.path
|
||
if (currentPath === '/subject/explain') return
|
||
hud.info('老师开始讲解啦,正在跳转...')
|
||
router.push({
|
||
path: '/subject/explain',
|
||
query: msg,
|
||
})
|
||
}
|
||
}
|
||
})
|
||
|
||
// 更新督学信息
|
||
// const { getInspectorInfo } = urgeStore()
|
||
// getInspectorInfo()
|
||
|
||
// if (system.debug || import.meta.env.VITE_INIT_VCONSOLE === '1') {
|
||
// initDevTool();
|
||
// }
|
||
// 不缓存的组件(存放的是组件名,defineOptions中的名字)
|
||
const excludeRouter = [
|
||
'SentenceSortStatistics', // 语感训练的统计界面
|
||
'SentenceSort', // 语感训练ing
|
||
'WordDetails',
|
||
'StudyAnalysis', // 学情分析
|
||
'RankingOtherDetail',
|
||
'NormalMode',
|
||
'HardMode',
|
||
'LearnWord',
|
||
'WordTest',
|
||
'WordTableTest',
|
||
'WordReview',
|
||
'WordTestRecords',
|
||
'TestList',
|
||
'WordTestRecords',
|
||
'Login',
|
||
'WordDictionaries',
|
||
'ShortRead',
|
||
'Homework', // 作业
|
||
'HomeWorkReports',
|
||
'HomeWorkDoWork',
|
||
'TextbookDetail', // 自由学书本详情
|
||
'DoWork', // 做题
|
||
'QuestionExplain', // 讲解
|
||
'Chat', // AI聊天
|
||
'StudentManage', // 学员管理
|
||
'CoinDetail', // 乐贝明细
|
||
'SelfCoinDetail', // 乐贝明细
|
||
'SetBook', // 教材设置
|
||
'SetTask', // 任务设置
|
||
'AddTask', // 添加任务
|
||
'TaskDetail', // 任务详情
|
||
'WordTable', // 单词书
|
||
'UserTaskRecords', // 导学任务列表
|
||
'UserWordBook', // 学员单词书
|
||
'Ranking',
|
||
]
|
||
const showLoad = ref(true)
|
||
function onLoaded() {
|
||
showLoad.value = false
|
||
}
|
||
// 播放点击音效
|
||
// function onPageClick() {
|
||
// if (!system.volume.effectsStatus) return;
|
||
// if (touchEffectsBlacklist.includes(route.path)) return;
|
||
// useAudio({
|
||
// url: touchEffects,
|
||
// autoplay: true,
|
||
// volume: system.volume.effectsVolume / 100,
|
||
// });
|
||
// }
|
||
|
||
// window.$xxl &&
|
||
// (window.$xxl.setAppsInfo = (res: any) => {
|
||
// info.value = res;
|
||
// console.log(res);
|
||
// });
|
||
// watch(
|
||
// () => system.isLock,
|
||
// newVal => {
|
||
// if (newVal) $XXL.closeLoading();
|
||
// },
|
||
// );
|
||
onMounted(() => {
|
||
$XXL.closeLoading()
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
stopLockHomePageRefresh()
|
||
})
|
||
</script>
|
||
<template>
|
||
<!-- @click="onPageClick" -->
|
||
<main>
|
||
<div v-if="system.isLock" class="lock">
|
||
<div class="title">
|
||
<div class="logo">
|
||
<img :src="logo" />
|
||
</div>
|
||
<span>解锁学习的快乐与自由</span>
|
||
</div>
|
||
<div class="lock-tips">
|
||
<Icon name="lock_screen" size="86" />
|
||
<div class="lock-tips-text">学习机已锁定,请联系家长解锁吧~</div>
|
||
</div>
|
||
</div>
|
||
<template v-else>
|
||
<UrmLoad v-if="showLoad" @loaded="onLoaded" />
|
||
<router-view v-slot="{ Component }">
|
||
<keep-alive :max="15" :exclude="excludeRouter">
|
||
<component :is="Component" />
|
||
</keep-alive>
|
||
</router-view>
|
||
</template>
|
||
<!-- <transition name="animate" mode="out-in" appear></transition> -->
|
||
<div id="x-audio"></div>
|
||
</main>
|
||
</template>
|
||
<style lang="scss">
|
||
@font-face {
|
||
font-family: 'AlimamaShuHeiTi';
|
||
src: url(https://lxx-web-1313840333.cos.ap-chengdu.myqcloud.com/font/AlimamaShuHeiTi.otf)
|
||
format('opentype');
|
||
font-weight: normal;
|
||
font-style: normal;
|
||
}
|
||
@font-face {
|
||
font-family: 'AlibabaHealth';
|
||
src: url(https://lxx-web-1313840333.cos.ap-chengdu.myqcloud.com/font/AlibabaHealth.ttf)
|
||
format('truetype');
|
||
font-weight: normal;
|
||
font-style: normal;
|
||
}
|
||
main {
|
||
width: 1920px;
|
||
height: 100vh;
|
||
max-width: 100%;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
#x-audio,
|
||
#learn-audio {
|
||
display: none;
|
||
visibility: hidden;
|
||
}
|
||
.component {
|
||
width: 100%;
|
||
height: 100%;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.lock {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: v-bind(lock_bg);
|
||
.title {
|
||
position: fixed;
|
||
bottom: 3.67vh;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
text-align: center;
|
||
.logo {
|
||
margin-bottom: $space-2_h;
|
||
img {
|
||
background: #fff;
|
||
border-radius: 0.83vh;
|
||
width: 56px;
|
||
height: 4.67vh;
|
||
}
|
||
}
|
||
span {
|
||
color: #282b53;
|
||
text-align: center;
|
||
font-family: $font-cn;
|
||
font-size: $font-size-6;
|
||
line-height: 1.5em;
|
||
position: relative;
|
||
|
||
&::after,
|
||
&::before {
|
||
position: absolute;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
content: '';
|
||
display: block;
|
||
width: 48px;
|
||
height: 0.33vh;
|
||
background: #282b53;
|
||
}
|
||
|
||
&::after {
|
||
left: -64px;
|
||
}
|
||
&::before {
|
||
right: -64px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.lock-tips {
|
||
z-index: 2;
|
||
text-align: center;
|
||
position: relative;
|
||
top: 10vh;
|
||
&-text {
|
||
margin-top: $space-6_h;
|
||
color: #fff;
|
||
text-align: center;
|
||
font-size: $space-4;
|
||
}
|
||
}
|
||
|
||
&::after {
|
||
content: '';
|
||
display: block;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
z-index: 1;
|
||
}
|
||
}
|
||
}
|
||
.mask.videoReward {
|
||
z-index: 100;
|
||
}
|
||
|
||
.animate-enter-active {
|
||
animation: zoomIn 0.3s;
|
||
// transform-origin: left bottom;
|
||
}
|
||
.animate-leave-active {
|
||
animation: zoomIn 0.3s reverse;
|
||
// transform-origin: left bottom;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
from {
|
||
transform: scale3d(1, 1, 1);
|
||
}
|
||
|
||
50% {
|
||
transform: scale3d(1.01, 1.01, 1.01);
|
||
}
|
||
|
||
to {
|
||
transform: scale3d(1, 1, 1);
|
||
}
|
||
}
|
||
|
||
@keyframes rotateInUpLeft {
|
||
from {
|
||
transform: rotate3d(0, 0, 1, 45deg);
|
||
opacity: 0;
|
||
}
|
||
|
||
to {
|
||
transform: translate3d(0, 0, 0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
@keyframes zoomIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: scale3d(0.9, 0.9, 0.9);
|
||
}
|
||
|
||
50% {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
</style>
|