Commit 4d6dd087 authored by liujiangnan's avatar liujiangnan

feat: 全功能PPT

parent caae00fd
.error-message[data-v-123e5f17]{display:flex;justify-content:center;align-items:center;height:100%;font-size:16px;color:#666}.page-buttons[data-v-123e5f17]{position:fixed;right:20px;bottom:20px;z-index:1000;display:flex;gap:10px}.arrow-button[data-v-123e5f17]{width:40px;height:40px;border-radius:50%;border:none;background-color:#409eff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .3s}.arrow-button[data-v-123e5f17]:hover{background-color:#66b1ff}.arrow-button[data-v-123e5f17]:disabled{background-color:#c0c4cc;cursor:not-allowed}.arrow[data-v-123e5f17]{border:solid white;border-width:0 2px 2px 0;display:inline-block;padding:4px}.left[data-v-123e5f17]{transform:rotate(135deg);margin-left:4px}.right[data-v-123e5f17]{transform:rotate(-45deg);margin-right:4px}
.thumbnail-container{position:fixed;bottom:-150px;left:0;right:0;height:140px;background-color:#505050e6;transition:bottom .3s ease;z-index:999}.thumbnail-container.show{bottom:0}.thumbnail-list{display:flex;overflow-x:auto;padding:10px;gap:10px;height:100%}.thumbnail-item{flex:0 0 auto;width:160px;height:120px;position:relative;cursor:pointer;transition:all .2s ease;padding:2px;border-radius:4px;background-color:transparent}.thumbnail-item img{width:100%;height:100%;object-fit:cover;border-radius:4px;border:2px solid transparent;transition:all .2s ease}.thumbnail-item.active{background-color:#ffffff4d;transform:scale(1.05);box-shadow:0 0 10px #ffffff80}.thumbnail-item.active img{border-color:#fff}.thumbnail-item:hover:not(.active){transform:scale(1.05);background-color:#ffffff1a}.thumbnail-item .page-number{position:absolute;bottom:5px;left:50%;transform:translate(-50%);background-color:#000000b3;color:#fff;padding:2px 8px;border-radius:10px;font-size:12px}.thumbnail-list::-webkit-scrollbar{height:8px}.thumbnail-list::-webkit-scrollbar-track{background:#0000001a;border-radius:4px}.thumbnail-list::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:4px}.thumbnail-list::-webkit-scrollbar-thumb:hover{background:#ffffff80}.error-message[data-v-5e90ea40]{display:flex;justify-content:center;align-items:center;height:100%;font-size:16px;color:#666}.page-buttons-container[data-v-5e90ea40]{position:fixed;bottom:20px;right:20px;display:flex;gap:15px;z-index:1000}.page-button[data-v-5e90ea40]{width:50px;height:50px;background-color:#50505099;border-radius:25px;border:1px solid rgba(255,255,255,.3);color:#ffffffe6;font-size:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .3s ease;box-shadow:0 2px 6px #00000026}.page-button[data-v-5e90ea40]:hover{background-color:#3c3c3ccc;transform:scale(1.1)}.page-button[data-v-5e90ea40]:active{transform:scale(.95)}.arrow[data-v-5e90ea40]{border:solid white;border-width:0 2px 2px 0;display:inline-block;padding:4px}.left[data-v-5e90ea40]{transform:rotate(135deg);margin-left:4px}.right[data-v-5e90ea40]{transform:rotate(-45deg);margin-right:4px}.menu-icon[data-v-5e90ea40]{width:18px;height:14px;position:relative;border-top:2px solid white;border-bottom:2px solid white}.menu-icon[data-v-5e90ea40]:before{content:"";position:absolute;top:50%;left:0;width:100%;height:2px;background-color:#fff;transform:translateY(-50%)}.thumbnail-container[data-v-5e90ea40]{position:fixed;bottom:-150px;left:0;right:0;height:140px;background-color:#505050e6;transition:bottom .3s ease;z-index:999}.thumbnail-container.show[data-v-5e90ea40]{bottom:0}.thumbnail-list[data-v-5e90ea40]{display:flex;overflow-x:auto;padding:10px;gap:10px;height:100%}.thumbnail-item[data-v-5e90ea40]{flex:0 0 auto;width:100px;height:120px;position:relative;cursor:pointer;transition:all .2s ease;padding:2px;border-radius:4px}.thumbnail-item img[data-v-5e90ea40]{width:100%;height:100%;object-fit:cover;border-radius:4px;border:2px solid transparent;transition:all .2s ease}.thumbnail-item.active[data-v-5e90ea40]{background-color:#ffffff4d;transform:scale(1.05);box-shadow:0 0 10px #ffffff80}.thumbnail-item.active img[data-v-5e90ea40]{border-color:#fff}.thumbnail-item .page-number[data-v-5e90ea40]{position:absolute;bottom:5px;left:50%;transform:translate(-50%);background-color:#000000b3;color:#fff;padding:2px 8px;border-radius:10px;font-size:12px}.thumbnail-item[data-v-5e90ea40]:hover:not(.active){transform:scale(1.05);background-color:#ffffff1a}.thumbnail-list[data-v-5e90ea40]::-webkit-scrollbar{height:8px}.thumbnail-list[data-v-5e90ea40]::-webkit-scrollbar-track{background:#0000001a;border-radius:4px}.thumbnail-list[data-v-5e90ea40]::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:4px}.thumbnail-list[data-v-5e90ea40]::-webkit-scrollbar-thumb:hover{background:#ffffff80}
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="//staging-teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script> <script type="text/javascript" src="//staging-teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script>
<script type="module" crossorigin src="assets/index-B3XZ3Y4E.js"></script> <script type="module" crossorigin src="assets/index-D6v_bxeY.js"></script>
<link rel="stylesheet" crossorigin href="assets/index-B3npc05E.css"> <link rel="stylesheet" crossorigin href="assets/index-D5frsKoS.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
<script setup> <script setup>
import { ref } from 'vue' import { ref, watch } from 'vue'
import VueOfficePdf from "@vue-office/pdf"; import VueOfficePdf from "@vue-office/pdf";
import VueOfficeDocx from "@vue-office/docx"; import VueOfficeDocx from "@vue-office/docx";
import '@/assets/thumbnail.css'; // 添加这行导入缩略图样式
const url = ref(''); const url = ref('');
const ppt_html = ref(''); const ppt_html = ref('');
const ppt_iframe = ref(null);
// 添加一个状态来控制错误提示 // 添加一个状态来控制错误提示
const showErrorMsg = ref(false); const showErrorMsg = ref(false);
const currentPage = ref(1);
const totalPages = ref(0);
function toggleThumbnailContainer() {
const container = document.querySelector('.thumbnail-container');
container.classList.toggle('show');
// 如果是显示缩略图列表,则滚动到当前页
if (container.classList.contains('show')) {
// 获取当前页码
const currentPg = ppt_iframe.value.contentWindow.GetCurPg();
// 找到当前页的缩略图
const currentThumb = document.querySelector(`.thumbnail-item:nth-child(${currentPg + 1})`);
if (currentThumb) {
// 使用 scrollIntoView 滚动到当前缩略图的位置
setTimeout(() => {
currentThumb.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}, 100); // 给一个小延时确保动画效果正常
}
}
}
function calculateTotalPages() {
try {
if (ppt_iframe.value) {
const mainDiv = ppt_iframe.value.contentDocument.getElementById('main');
if (mainDiv) {
totalPages.value = mainDiv.children.length;
generateThumbnails();
}
}
} catch (error) {
console.error('无法访问 iframe 内容:', error);
}
}
function generateThumbnails() {
if (!ppt_html.value) return;
const baseUrl = ppt_html.value.replace('index.html', 'thum/');
const list = document.querySelector('.thumbnail-list');
list.innerHTML = '';
// 获取当前页码
const currentPg = ppt_iframe.value.contentWindow.GetCurPg() || 0;
// 如果是第一次加载(当前页是1),则需要调用 syncExec
if (currentPg === 0) {
ppt_iframe.value.contentWindow.syncExec(0, -1, -1, false);
}
for (let i = 1; i <= totalPages.value; i++) {
const thumbnailItem = document.createElement('div');
thumbnailItem.className = `thumbnail-item ${i === currentPg + 1 ? 'active' : ''}`;
thumbnailItem.innerHTML = `
<img src="${baseUrl}${i}.jpg" alt="Page ${i}">
<div class="page-number">${i}</div>
`;
thumbnailItem.addEventListener('click', () => {
jumpToPage(i);
});
list.appendChild(thumbnailItem);
}
// 更新当前页码状态,注意页码转换
updateActiveThumbnail(currentPg + 1);
}
function updateActiveThumbnail(pageNumber) {
currentPage.value = pageNumber;
const thumbnails = document.querySelectorAll('.thumbnail-item');
thumbnails.forEach((thumb, index) => {
if (index + 1 === pageNumber) {
thumb.classList.add('active');
} else {
thumb.classList.remove('active');
}
});
}
function jumpToPage(pageNumber) {
const tempCp = ppt_iframe.value.contentWindow.GetCurPg();
if (tempCp != pageNumber-1) {
ppt_iframe.value.contentWindow.resetPage(tempCp);
ppt_iframe.value.contentWindow.syncExec(pageNumber-1, -1, -1, false);
updateActiveThumbnail(pageNumber);
}
toggleThumbnailContainer();
}
function prevPage() {
if (ppt_iframe.value) {
ppt_iframe.value.contentWindow.ExecGoBack();
// 获取当前页码并更新选中状态
const currentPg = ppt_iframe.value.contentWindow.GetCurPg();
updateActiveThumbnail(currentPg+1);
}
}
function nextPage() {
if (ppt_iframe.value) {
ppt_iframe.value.contentWindow.ExecNext();
// 获取当前页码并更新选中状态
const currentPg = ppt_iframe.value.contentWindow.GetCurPg();
updateActiveThumbnail(currentPg+1);
}
}
// 在 iframe 加载完成后初始化缩略图
watch(ppt_iframe, (newValue) => {
if (newValue) {
// 给 iframe 一些加载时间
setTimeout(() => {
calculateTotalPages();
ppt_iframe.value.contentDocument.addEventListener('gopageEvent', () => {
const currentPg = ppt_iframe.value.contentWindow.GetCurPg();
updateActiveThumbnail(currentPg+1);
});
window.air.hideAirClassLoading('play_office', null);
}, 1000);
}
});
// 检查 URL 是否可访问 // 检查 URL 是否可访问
async function checkUrl(url) { async function checkUrl(url) {
try { try {
...@@ -40,46 +172,32 @@ window.courseware.getData(async (dt) => { ...@@ -40,46 +172,32 @@ window.courseware.getData(async (dt) => {
return; return;
} }
} }
window.air.hideAirClassLoading('play_office', null);
} }
}, 'play_office'); }, 'play_office');
function renderedHandler() { function renderedHandler() {
window.air.hideAirClassLoading('play_office', null); window.air.hideAirClassLoading('play_office', null);
} }
function errorHandler() { } function errorHandler() { }
const ppt_iframe = ref(null); const getProxiedUrl = (url) => {
function prevPage() { try {
if (ppt_iframe.value) { const urlObj = new URL(url);
ppt_iframe.value.contentWindow.ExecGoBack(); return `/ppt${urlObj.pathname}${urlObj.search}${urlObj.hash}`;
} } catch (e) {
} return url;
function nextPage() {
if (ppt_iframe.value) {
ppt_iframe.value.contentWindow.ExecNext();
} }
} }
// const getProxiedUrl = (url) => { // const getProxiedUrl = (url) => {
// try { // try {
// const urlObj = new URL(url); // const urlObj = new URL(url);
// return `/ppt${urlObj.pathname}${urlObj.search}${urlObj.hash}`; // return `${urlObj.pathname}${urlObj.search}${urlObj.hash}`;
// } catch (e) { // } catch (e) {
// return url; // return url;
// } // }
// } // }
const getProxiedUrl = (url) => {
try {
const urlObj = new URL(url);
return `${urlObj.pathname}${urlObj.search}${urlObj.hash}`;
} catch (e) {
return url;
}
}
</script> </script>
<template> <template>
...@@ -95,15 +213,24 @@ const getProxiedUrl = (url) => { ...@@ -95,15 +213,24 @@ const getProxiedUrl = (url) => {
<div v-if="showErrorMsg" class="error-message"> <div v-if="showErrorMsg" class="error-message">
课件正在生成中,请过一会儿再尝试 课件正在生成中,请过一会儿再尝试
</div> </div>
<iframe v-else ref="ppt_iframe" frameborder="0" width="100%" height="100%" :src="getProxiedUrl(ppt_html)"></iframe> <iframe v-else ref="ppt_iframe" frameborder="0" width="100%" height="100%"
<div class="page-buttons"> :src="getProxiedUrl(ppt_html)"></iframe>
<button class="arrow-button prev" @click="prevPage"> <div class="page-buttons-container">
<button class="page-button thumbnail-button" @click="toggleThumbnailContainer">
<span class="menu-icon"></span>
</button>
<button class="page-button prev-button" @click="prevPage">
<span class="arrow left"></span> <span class="arrow left"></span>
</button> </button>
<button class="arrow-button next" @click="nextPage"> <button class="page-button next-button" @click="nextPage">
<span class="arrow right"></span> <span class="arrow right"></span>
</button> </button>
</div> </div>
<div class="thumbnail-container">
<div class="thumbnail-list">
<!-- 缩略图将通过 JS 动态添加 -->
</div>
</div>
</div> </div>
</template> </template>
</template> </template>
...@@ -118,35 +245,53 @@ const getProxiedUrl = (url) => { ...@@ -118,35 +245,53 @@ const getProxiedUrl = (url) => {
color: #666; color: #666;
} }
.page-buttons { .page-buttons-container {
/* 固定在右下角 */
position: fixed; position: fixed;
right: 20px;
bottom: 20px; bottom: 20px;
z-index: 1000; right: 20px;
/* 横向排列 */
display: flex; display: flex;
gap: 10px; gap: 15px;
/* 按钮之间的间距 */
/* 确保按钮容器在最上层 */
z-index: 1000;
} }
.arrow-button { .page-button {
width: 40px; width: 50px;
height: 40px; height: 50px;
border-radius: 50%;
border: none; /* 深色半透明背景 */
background-color: #409EFF; background-color: rgba(80, 80, 80, 0.6);
cursor: pointer;
border-radius: 25px;
border: 1px solid rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.9);
font-size: 24px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.3s;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
} }
.arrow-button:hover { /* 悬停效果 */
background-color: #66b1ff; .page-button:hover {
background-color: rgba(60, 60, 60, 0.8);
transform: scale(1.1);
} }
.arrow-button:disabled { /* 点击效果 */
background-color: #c0c4cc; .page-button:active {
cursor: not-allowed; transform: scale(0.95);
} }
.arrow { .arrow {
...@@ -166,4 +311,111 @@ const getProxiedUrl = (url) => { ...@@ -166,4 +311,111 @@ const getProxiedUrl = (url) => {
margin-right: 4px; margin-right: 4px;
} }
/* 在现有样式基础上添加 */
.menu-icon {
width: 18px;
height: 14px;
position: relative;
border-top: 2px solid white;
border-bottom: 2px solid white;
}
.menu-icon::before {
content: '';
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 2px;
background-color: white;
transform: translateY(-50%);
}
.thumbnail-container {
position: fixed;
bottom: -150px;
left: 0;
right: 0;
height: 140px;
background-color: rgba(80, 80, 80, 0.9);
transition: bottom 0.3s ease;
z-index: 999;
}
.thumbnail-container.show {
bottom: 0;
}
.thumbnail-list {
display: flex;
overflow-x: auto;
padding: 10px;
gap: 10px;
height: 100%;
}
.thumbnail-item {
flex: 0 0 auto;
width: 100px;
height: 120px;
position: relative;
cursor: pointer;
transition: all 0.2s ease;
padding: 2px;
border-radius: 4px;
}
.thumbnail-item img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
border: 2px solid transparent;
transition: all 0.2s ease;
}
.thumbnail-item.active {
background-color: rgba(255, 255, 255, 0.3);
transform: scale(1.05);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.thumbnail-item.active img {
border-color: #fff;
}
.thumbnail-item .page-number {
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
}
.thumbnail-item:hover:not(.active) {
transform: scale(1.05);
background-color: rgba(255, 255, 255, 0.1);
}
.thumbnail-list::-webkit-scrollbar {
height: 8px;
}
.thumbnail-list::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.thumbnail-list::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
.thumbnail-list::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
</style> </style>
/* 缩略图容器样式 */
.thumbnail-container {
position: fixed;
bottom: -150px;
left: 0;
right: 0;
height: 140px;
background-color: rgba(80, 80, 80, 0.9);
transition: bottom 0.3s ease;
z-index: 999;
}
.thumbnail-container.show {
bottom: 0;
}
.thumbnail-list {
display: flex;
overflow-x: auto;
padding: 10px;
gap: 10px;
height: 100%;
}
.thumbnail-item {
flex: 0 0 auto;
width: 160px;
height: 120px;
position: relative;
cursor: pointer;
transition: all 0.2s ease;
padding: 2px;
border-radius: 4px;
background-color: transparent;
}
.thumbnail-item img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
border: 2px solid transparent;
transition: all 0.2s ease;
}
.thumbnail-item.active {
background-color: rgba(255, 255, 255, 0.3);
transform: scale(1.05);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.thumbnail-item.active img {
border-color: #fff;
}
.thumbnail-item:hover:not(.active) {
transform: scale(1.05);
background-color: rgba(255, 255, 255, 0.1);
}
.thumbnail-item .page-number {
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
}
/* 滚动条样式 */
.thumbnail-list::-webkit-scrollbar {
height: 8px;
}
.thumbnail-list::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.thumbnail-list::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
.thumbnail-list::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment