|
@@ -275,6 +275,7 @@ const elementList = computed(() => {
|
|
|
|
|
|
// 检测当前幻灯片是否包含iframe元素
|
|
|
const currentSlideHasIframe = computed(() => {
|
|
|
+ console.log('elementList.value', elementList.value)
|
|
|
return elementList.value.some(element => element.type === ElementTypes.FRAME)
|
|
|
})
|
|
|
|
|
@@ -282,24 +283,43 @@ const currentSlideHasIframe = computed(() => {
|
|
|
const goToSlide = (index: number) => {
|
|
|
console.log('goToSlide 被调用,目标索引:', index)
|
|
|
console.log('当前索引:', slideIndex.value)
|
|
|
- slidesStore.updateSlideIndex(index)
|
|
|
- console.log('更新后的索引:', slideIndex.value)
|
|
|
+
|
|
|
+ if (index >= 0 && index < slides.value.length) {
|
|
|
+ slidesStore.updateSlideIndex(index)
|
|
|
+ console.log('更新后的索引:', slideIndex.value)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ console.warn('goToSlide: 无效的索引:', index)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 上一页
|
|
|
const previousSlide = () => {
|
|
|
if (slideIndex.value > 0) {
|
|
|
- slidesStore.updateSlideIndex(slideIndex.value - 1)
|
|
|
+ const newIndex = slideIndex.value - 1
|
|
|
+ console.log('上一页,从', slideIndex.value, '到', newIndex)
|
|
|
+ slidesStore.updateSlideIndex(newIndex)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 下一页
|
|
|
const nextSlide = () => {
|
|
|
if (slideIndex.value < slides.value.length - 1) {
|
|
|
- slidesStore.updateSlideIndex(slideIndex.value + 1)
|
|
|
+ const newIndex = slideIndex.value + 1
|
|
|
+ console.log('下一页,从', slideIndex.value, '到', newIndex)
|
|
|
+ slidesStore.updateSlideIndex(newIndex)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 监听slideIndex变化,调用getWork
|
|
|
+watch(() => slideIndex.value, (newIndex, oldIndex) => {
|
|
|
+ console.log('slideIndex变化,调用getWork', { newIndex, oldIndex })
|
|
|
+ if (newIndex !== oldIndex && typeof newIndex === 'number') {
|
|
|
+ console.log('触发getWork,当前幻灯片索引:', newIndex)
|
|
|
+ getWork()
|
|
|
+ }
|
|
|
+}, { immediate: false, deep: false })
|
|
|
+
|
|
|
// 全屏
|
|
|
const enterFullscreen = () => {
|
|
|
if (document.fullscreenElement) {
|
|
@@ -380,80 +400,98 @@ const findSlideIndexByIframeUrl = (iframeUrl: string): number => {
|
|
|
}
|
|
|
|
|
|
// 处理iframe链接,为包含workPage的iframe添加必要参数
|
|
|
-const processIframeLinks = () => {
|
|
|
+// 处理iframe链接,为包含workPage的iframe添加必要参数
|
|
|
+const processIframeLinks = async () => {
|
|
|
try {
|
|
|
console.log('开始处理iframe链接')
|
|
|
console.log('当前props:', { courseid: props.courseid, userid: props.userid })
|
|
|
|
|
|
// 从slides数据中查找包含iframe的元素
|
|
|
let hasIframe = false
|
|
|
- const updatedSlides = slides.value.map((slide, slideIndex) => {
|
|
|
- if (slide.elements && slide.elements.length > 0) {
|
|
|
- const updatedElements = slide.elements.map(element => {
|
|
|
- // 检查是否是iframe元素
|
|
|
- if (element.type === ElementTypes.FRAME && element.url) {
|
|
|
- const iframeSrc = element.url
|
|
|
-
|
|
|
- if (iframeSrc.includes('workPage')) {
|
|
|
- hasIframe = true
|
|
|
- console.log(`处理幻灯片 ${slideIndex + 1} 中的iframe链接:`, iframeSrc)
|
|
|
-
|
|
|
- try {
|
|
|
- // 解析URL,处理hash部分
|
|
|
- let baseUrl = iframeSrc
|
|
|
- let hashPart = ''
|
|
|
-
|
|
|
- // 分离base URL和hash部分
|
|
|
- if (iframeSrc.includes('#')) {
|
|
|
- const parts = iframeSrc.split('#')
|
|
|
- baseUrl = parts[0]
|
|
|
- hashPart = parts[1]
|
|
|
- }
|
|
|
-
|
|
|
- // 构建新的hash部分,添加参数
|
|
|
- // 使用当前幻灯片索引作为task参数
|
|
|
- let newHash = hashPart
|
|
|
- if (newHash.includes('?')) {
|
|
|
- // 如果hash中已经有查询参数,添加&
|
|
|
- newHash += `&courseid=${props.courseid || ''}&userid=${props.userid || ''}&stage=0&task=${slideIndex}&tool=0`
|
|
|
- }
|
|
|
- else {
|
|
|
- // 如果hash中没有查询参数,添加?
|
|
|
- newHash += `?courseid=${props.courseid || ''}&userid=${props.userid || ''}&stage=0&task=${slideIndex}&tool=0`
|
|
|
+ // 由于有异步操作,需整体用Promise.all处理
|
|
|
+ const updatedSlides = await Promise.all(
|
|
|
+ slides.value.map(async (slide, slideIndex) => {
|
|
|
+ if (slide.elements && slide.elements.length > 0) {
|
|
|
+ // 这里不能直接用async map,否则会导致类型不对
|
|
|
+ const updatedElements = await Promise.all(
|
|
|
+ slide.elements.map(async (element) => {
|
|
|
+ // 检查是否是iframe元素
|
|
|
+ if (element.type === ElementTypes.FRAME && element.url) {
|
|
|
+ const iframeSrc = element.url
|
|
|
+
|
|
|
+ if (iframeSrc.includes('workPage')) {
|
|
|
+ hasIframe = true
|
|
|
+ console.log(`处理幻灯片 ${slideIndex + 1} 中的iframe链接:`, iframeSrc)
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 解析URL,处理hash部分
|
|
|
+ let baseUrl = iframeSrc
|
|
|
+ let hashPart = ''
|
|
|
+
|
|
|
+ // 分离base URL和hash部分
|
|
|
+ if (iframeSrc.includes('#')) {
|
|
|
+ const parts = iframeSrc.split('#')
|
|
|
+ baseUrl = parts[0]
|
|
|
+ hashPart = parts[1]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建新的hash部分,添加参数
|
|
|
+ // 使用当前幻灯片索引作为task参数
|
|
|
+ let newHash = hashPart
|
|
|
+ if (newHash.includes('?')) {
|
|
|
+ // 如果hash中已经有查询参数,添加&
|
|
|
+ newHash += `&courseid=${props.courseid || ''}&userid=${props.userid || ''}&stage=0&task=${slideIndex}&tool=0`
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 如果hash中没有查询参数,添加?
|
|
|
+ newHash += `?courseid=${props.courseid || ''}&userid=${props.userid || ''}&stage=0&task=${slideIndex}&tool=0`
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建新的URL
|
|
|
+ const newUrl = `${baseUrl}#${newHash}`
|
|
|
+
|
|
|
+ console.log(`幻灯片 ${slideIndex + 1} 的iframe链接已更新:`, newUrl)
|
|
|
+
|
|
|
+ // 返回更新后的元素
|
|
|
+ return {
|
|
|
+ ...element,
|
|
|
+ url: newUrl
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error(`处理幻灯片 ${slideIndex + 1} 的iframe链接时出错:`, error)
|
|
|
+ return element
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (iframeSrc.includes('.html')) {
|
|
|
+ hasIframe = true
|
|
|
+ const html = await api.getHTML(iframeSrc)
|
|
|
+ console.log('html', html)
|
|
|
+ console.log(`处理幻灯片 ${slideIndex + 1} 中的iframe链接:`, iframeSrc)
|
|
|
+ return {
|
|
|
+ ...element,
|
|
|
+ isHTML: true,
|
|
|
+ url: html
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // 构建新的URL
|
|
|
- const newUrl = `${baseUrl}#${newHash}`
|
|
|
-
|
|
|
- console.log(`幻灯片 ${slideIndex + 1} 的iframe链接已更新:`, newUrl)
|
|
|
+ // 不是iframe元素或不需要处理,直接返回
|
|
|
+ return element
|
|
|
+ })
|
|
|
+ )
|
|
|
|
|
|
- // 返回更新后的元素
|
|
|
- return {
|
|
|
- ...element,
|
|
|
- url: newUrl
|
|
|
- }
|
|
|
- }
|
|
|
- catch (error) {
|
|
|
- console.error(`处理幻灯片 ${slideIndex + 1} 的iframe链接时出错:`, error)
|
|
|
- return element
|
|
|
- }
|
|
|
- }
|
|
|
+ // 返回更新后的幻灯片
|
|
|
+ return {
|
|
|
+ ...slide,
|
|
|
+ elements: updatedElements
|
|
|
}
|
|
|
-
|
|
|
- // 不是iframe元素或不需要处理,直接返回
|
|
|
- return element
|
|
|
- })
|
|
|
-
|
|
|
- // 返回更新后的幻灯片
|
|
|
- return {
|
|
|
- ...slide,
|
|
|
- elements: updatedElements
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- // 没有元素的幻灯片直接返回
|
|
|
- return slide
|
|
|
- })
|
|
|
+
|
|
|
+ // 没有元素的幻灯片直接返回
|
|
|
+ return slide
|
|
|
+ })
|
|
|
+ )
|
|
|
|
|
|
if (hasIframe) {
|
|
|
console.log('找到iframe元素,更新slides数据')
|
|
@@ -697,17 +735,31 @@ const handleHomeworkSubmit = async () => {
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
+ // message.warning('当前页面暂不支持作业提交')
|
|
|
console.log('尝试截图当前页面并提交')
|
|
|
- return
|
|
|
- /*
|
|
|
+ // return
|
|
|
try {
|
|
|
// 尝试使用html2canvas,对iframe支持更好
|
|
|
let imageData: string
|
|
|
-
|
|
|
+ const screenSlides = document.querySelectorAll('.viewer-canvas .screen-slide')
|
|
|
+ let iframe: HTMLElement | null = null
|
|
|
+ if (
|
|
|
+ screenSlides &&
|
|
|
+ screenSlides[slideIndex.value] &&
|
|
|
+ screenSlides[slideIndex.value].querySelector('iframe') &&
|
|
|
+ (screenSlides[slideIndex.value].querySelector('iframe') as HTMLIFrameElement).contentWindow &&
|
|
|
+ (screenSlides[slideIndex.value].querySelector('iframe') as HTMLIFrameElement).contentWindow!.document &&
|
|
|
+ (screenSlides[slideIndex.value].querySelector('iframe') as HTMLIFrameElement).contentWindow!.document.body
|
|
|
+ ) {
|
|
|
+ iframe = (screenSlides[slideIndex.value].querySelector('iframe') as HTMLIFrameElement).contentWindow!.document.body as HTMLElement
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ throw new Error('未能获取到iframe的body元素,无法截图')
|
|
|
+ }
|
|
|
try {
|
|
|
// 首先尝试使用html2canvas
|
|
|
const html2canvas = await import('html2canvas')
|
|
|
- const canvas = await html2canvas.default(document.querySelectorAll('.viewer-canvas .screen-slide')[slideIndex.value] as HTMLElement, {
|
|
|
+ const canvas = await html2canvas.default(iframe, {
|
|
|
useCORS: true,
|
|
|
allowTaint: true,
|
|
|
scale: 1,
|
|
@@ -725,7 +777,7 @@ const handleHomeworkSubmit = async () => {
|
|
|
|
|
|
// 回退到html-to-image
|
|
|
const { toPng } = await import('html-to-image')
|
|
|
- imageData = await toPng(document.querySelectorAll('.viewer-canvas .screen-slide')[slideIndex.value] as HTMLElement, {
|
|
|
+ imageData = await toPng(iframe, {
|
|
|
quality: 0.95,
|
|
|
backgroundColor: '#ffffff'
|
|
|
})
|
|
@@ -755,7 +807,7 @@ const handleHomeworkSubmit = async () => {
|
|
|
catch (error) {
|
|
|
console.error('截图提交失败:', error)
|
|
|
message.error('截图提交失败')
|
|
|
- }*/
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -882,6 +934,29 @@ const getCourseDetail = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+const getWork = async () => {
|
|
|
+ try {
|
|
|
+ console.log('getWork 开始执行,参数:', {
|
|
|
+ courseid: props.courseid,
|
|
|
+ slideIndex: slideIndex.value,
|
|
|
+ type: props.type
|
|
|
+ })
|
|
|
+
|
|
|
+ if (!props.courseid) {
|
|
|
+ console.warn('getWork: courseid 未提供,跳过执行')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await api.selectSWorks(props.courseid, '0', slideIndex.value.toString())
|
|
|
+ console.log('getWork 执行成功,结果:', res)
|
|
|
+ return res
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ console.error('getWork 执行失败:', error)
|
|
|
+ message.error('获取作业信息失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
document.addEventListener('keydown', handleKeydown)
|
|
|
|
|
@@ -902,6 +977,7 @@ onMounted(() => {
|
|
|
}
|
|
|
}
|
|
|
getCourseDetail()
|
|
|
+ getWork()
|
|
|
|
|
|
// 计算初始缩放比例
|
|
|
nextTick(() => {
|
|
@@ -1407,6 +1483,16 @@ onUnmounted(() => {
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
z-index: 1000;
|
|
|
+
|
|
|
+ .loading-spinner {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border: 4px solid #f3f3f3;
|
|
|
+ border-top: 4px solid #1890ff;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ margin: 0 auto 16px;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.loading-content {
|
|
@@ -1414,15 +1500,6 @@ onUnmounted(() => {
|
|
|
color: #666;
|
|
|
}
|
|
|
|
|
|
-.loading-spinner {
|
|
|
- width: 40px;
|
|
|
- height: 40px;
|
|
|
- border: 4px solid #f3f3f3;
|
|
|
- border-top: 4px solid #1890ff;
|
|
|
- border-radius: 50%;
|
|
|
- animation: spin 1s linear infinite;
|
|
|
- margin: 0 auto 16px;
|
|
|
-}
|
|
|
|
|
|
.loading-text {
|
|
|
font-size: 14px;
|