kindergarten_java/reading-platform-frontend/src/views/teacher/lessons/BroadcastView.vue

235 lines
6.2 KiB
Vue
Raw Normal View History

<template>
<div class="broadcast-view">
<!-- 加载中 -->
<div v-if="loading" class="loading-container">
<a-spin size="large" />
<p>正在加载展播内容...</p>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="error-container">
<ExclamationCircleOutlined style="font-size: 48px; color: #ff4d4f;" />
<p>{{ error }}</p>
<a-button type="primary" @click="loadData">重新加载</a-button>
</div>
<!-- 展播内容 -->
<KidsMode
v-else-if="course && scripts.length > 0"
:course="course"
:scripts="scripts"
:activities="activities"
:current-step-index="currentStepIndex"
:timer-seconds="0"
@exit="handleExit"
@step-change="handleStepChange"
/>
<!-- 空状态 -->
<div v-else class="empty-container">
<InboxOutlined style="font-size: 48px; color: #ccc;" />
<p>暂无展播内容</p>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { useRoute } from 'vue-router';
import { ExclamationCircleOutlined, InboxOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import KidsMode from './components/KidsMode.vue';
import * as teacherApi from '@/api/teacher';
const route = useRoute();
// 状态
const loading = ref(true);
const error = ref('');
const course = ref<any>(null);
const scripts = ref<any[]>([]);
const activities = ref<any[]>([]);
const currentStepIndex = ref(0);
// 获取URL参数
const lessonId = route.params.id as string;
const initialStep = parseInt(route.query.step as string) || 0;
// 步骤类型映射
const stepTypeMap: Record<string, string> = {
'READING': '共读',
'DISCUSSION': '讨论',
'ACTIVITY': '活动',
'CREATION': '创作',
'SUMMARY': '总结',
'CUSTOM': '导入',
};
// 加载数据
const loadData = async () => {
loading.value = true;
error.value = '';
try {
const data = await teacherApi.getLesson(parseInt(lessonId));
// 解析课程中的路径数组
const parsePathArray = (paths: any) => {
if (!paths) return [];
if (typeof paths === 'string') {
try {
return JSON.parse(paths);
} catch {
return [];
}
}
return Array.isArray(paths) ? paths : [];
};
course.value = {
...data.course,
ebookPaths: parsePathArray(data.course?.ebookPaths),
audioPaths: parsePathArray(data.course?.audioPaths),
videoPaths: parsePathArray(data.course?.videoPaths),
posterPaths: parsePathArray(data.course?.posterPaths),
};
scripts.value = (data.course?.scripts || []).map((script: any) => ({
...script,
stepType: stepTypeMap[script.stepType] || script.stepType,
interactionPointsText: Array.isArray(script.interactionPoints)
? script.interactionPoints.join('、')
: script.interactionPoints,
resourceIds: typeof script.resourceIds === 'string' ? JSON.parse(script.resourceIds) : (script.resourceIds || []),
pages: (script.pages || []).map((page: any) => ({
...page,
resourceIds: typeof page.resourceIds === 'string' ? JSON.parse(page.resourceIds) : (page.resourceIds || []),
})),
}));
// 解析活动数据
if (data.course?.lessonPlanData) {
try {
const lessonPlan = typeof data.course.lessonPlanData === 'string'
? JSON.parse(data.course.lessonPlanData)
: data.course.lessonPlanData;
if (lessonPlan.activities && Array.isArray(lessonPlan.activities)) {
activities.value = lessonPlan.activities.map((activity: any, index: number) => ({
id: `activity-${index}`,
name: activity.name,
activityType: activity.type || 'OTHER',
duration: activity.duration,
objectives: activity.objectives,
activityGuide: activity.guide,
resourceUrl: activity.resourceUrl,
resourceType: activity.resourceType,
}));
}
} catch (parseErr) {
console.error('Failed to parse lesson plan:', parseErr);
}
}
// 设置初始环节
currentStepIndex.value = Math.min(initialStep, Math.max(0, scripts.value.length - 1));
} catch (err: any) {
console.error('Failed to load broadcast data:', err);
error.value = err.response?.data?.message || '加载失败,请重试';
} finally {
loading.value = false;
}
};
// 处理退出
const handleExit = () => {
message.info('展播模式已关闭,您可以关闭此标签页');
};
// 处理环节切换
const handleStepChange = (index: number) => {
currentStepIndex.value = index;
// 更新URL参数不刷新页面
const newUrl = `${window.location.pathname}?step=${index}`;
window.history.replaceState({}, '', newUrl);
};
// 键盘快捷键
const handleKeydown = (e: KeyboardEvent) => {
switch (e.key) {
case 'Escape':
message.info('按 ESC 退出全屏,关闭标签页请使用 Ctrl+W');
break;
case 'F11':
e.preventDefault();
toggleFullscreen();
break;
}
};
// 全屏切换
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
};
// 生命周期
onMounted(() => {
loadData();
document.addEventListener('keydown', handleKeydown);
// 自动进入全屏
setTimeout(() => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {
// 用户可能拒绝了全屏请求,忽略错误
});
}
}, 1000);
});
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown);
// 退出全屏
if (document.fullscreenElement) {
document.exitFullscreen().catch(() => {});
}
});
</script>
<style scoped lang="scss">
.broadcast-view {
width: 100vw;
height: 100vh;
overflow: hidden;
background: #1a1a2e;
}
.loading-container,
.error-container,
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
color: #fff;
gap: 20px;
p {
font-size: 18px;
opacity: 0.8;
}
}
.error-container {
p {
color: #ff4d4f;
}
}
</style>