kindergarten_java/reading-platform-frontend/src/views/office/WebOffice.vue
zhonghua 459fa434ac feat: KidsMode 文档/视频预览调整,player 增强
- KidsMode: 视频对接 player 组件,文档对接 WebOffice 组件
- WebOffice: 新增 noPage 嵌入模式,支持 props 传入 url/fileName
- player: 参考 VideoPlayer 增强功能(title、emit、键盘快捷键、加载遮罩、唯一ID)

Made-with: Cursor
2026-03-17 14:43:08 +08:00

221 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="!expire" ref="containerRef" class="!w-full !h-full z-999" :class="noPage ? 'absolute top-0 left-0' : 'pos-fixed top-0 left-0'"></div>
<div v-else class="flex justify-center items-center w-full h-full">
<div class="my-60px text-center">
链接已失效!
<span v-if="!noPage" class="cursor-pointer color-#0085FF ml-10px" @click="home">返回首页</span>
<span v-else class="block mt-10px color-#999">文档预览加载失败</span>
</div>
</div>
<!-- <Modal ref="modalRef" class="max-w-80%" width="1340px" v-model:open="open" :footer="null" title="在线资源">
<div class="flex min-h-600px bg-#f5f5f5 flex-col ">
<div class="bg-white p-10px">
<a-input-search allowClear class="w-full max-w-360px ml-5px" v-model:value="searchName" placeholder="请输入作者"
enter-button="搜索" size="large" @search="getImgs" />
</div>
<div class="flex flex-grow flex-wrap pt-15px pl-15px">
<div v-for="item in dataSource"
class="mr-15px mb-15px w-200px max-w-200px max-h-260px bg-white p-6px flex flex-col rounded-10px">
<div class="">{{ item.name }}</div>
<img class=" object-contain my-auto cursor-pointer" alt="资源过期了" :src="item.url + img_resize"
@click="setImgPreview(item.url)" />
<div class="flex justify-around mt-5px" style="">
<a-button class="mx-auto my-5px" type="primary" @click="setImgPreview(item.url)"> 查看</a-button>
<a-button class="mx-auto my-5px" type="primary" @click="addImg(item)" :loading="loading_add">
使用</a-button>
</div>
</div>
</div>
<div class="flex mx-15px mb-15px bg-white py-10px" v-if="pagination">
<div style="margin-left: auto;margin-right: 12px;">
<Pagination v-model:current="pagination.current" @change="paginationChange" @showSizeChange="pageSizeChange"
:showSizeChanger="true" :total="pagination.total" v-model:pageSize="pagination.pageSize" />
</div>
</div>
</div>
</Modal> -->
</template>
<!-- 阿里云IMM weboffice -->
<script lang="ts" name="WebOffice" setup>
import { onMounted, ref, nextTick, onUnmounted, onBeforeUnmount } from 'vue';
import {
generateWebofficeToken,
generateWebofficeTokenReadOnly,
refreshWebofficeToken,
} from '@/api/imm.api';
import { useRoute, useRouter } from 'vue-router';
import { getTemItem, TemObj } from './temObjs';
const props = defineProps<{
/** 嵌入模式:与页面共存,使用 absolute 定位 */
noPage?: boolean;
/** 文档 URL嵌入模式必传 */
url?: string;
/** 文件名(嵌入模式,用于 IMM */
fileName?: string;
/** 文件 ID嵌入模式可选 */
fileId?: string;
}>();
const containerRef = ref<HTMLElement | null>(null);
const route = useRoute();
const expire = ref(false);
const router = useRouter();
let updateSizeInterval: any;
function getTemObjFromProps(): TemObj | null {
if (!props.url) return null;
const ext = props.url.split('.').pop()?.split('?')[0] || 'pdf';
const name = props.fileName?.includes('.') ? props.fileName : `${props.fileName || 'document'}.${ext}`;
return {
id: props.fileId || '',
isEdit: false,
name,
url: encodeURIComponent(props.url),
};
}
onMounted(() => {
nextTick(() => {
init(containerRef.value);
});
});
const _temObj = ref<TemObj>({
id: '',
isEdit: false,
name: '',
url: '',
});
const onUnmountedUpdateSize = ref(true);
const updateSize = async () => {
if (!onUnmountedUpdateSize.value) {
return;
}
}
onBeforeUnmount(() => {
updateSize();
onUnmountedUpdateSize.value = false;
clearInterval(updateSizeInterval);
clearTimeout(timer.value);
})
const timer = ref<any>(null);
const debouncedFn = (time = 1000) => {
clearTimeout(timer.value);
timer.value = setTimeout(() => {
updateSize();
}, time);
};
onUnmounted(() => {
window.removeEventListener('beforeunload', updateSize);
})
window.addEventListener('beforeunload', updateSize);
function home() {
router.replace("/datas");
}
const baseInstance = ref<any>(null);
async function init(mount: HTMLElement | null) {
if (!mount) {
console.error('确保挂载节点元素存在。 一般在 onMounted 钩子中调用。');
return;
}
let temObj: TemObj | null;
if (props.noPage && props.url) {
temObj = getTemObjFromProps();
} else {
temObj = getTemItem(route.query._t as string);
}
if (!temObj) {
expire.value = true;
return;
}
_temObj.value = temObj;
const url = decodeURIComponent(`oss://lesingle-kid-course${new URL(decodeURIComponent(temObj.url)).pathname}`);
let tokenInfo = await getTokenFun(url, temObj);
const instance = (window as any).aliyun.config({
mount,
url: tokenInfo.webofficeURL,
refreshToken: () => {
// timeout过期时刷新 token
// return props.refreshTokenFun(tokenInfo).then((data) => {
return refreshTokenFun(tokenInfo).then((data) => {
Object.assign(tokenInfo, data);
return {
token: tokenInfo.accessToken,
timeout: 10 * 60 * 1000,
};
});
},
});
baseInstance.value = instance;
instance.setToken({
token: tokenInfo.accessToken,
timeout: 10 * 60 * 1000,
});
await instance.ready();
// const imgurl = 'http://image.activity.lesingle.com/activitymaterial/poster/%E5%8A%A8%E7%89%A9%E7%BB%98%E6%9C%AC_1736908271102.jpg';
// const imgurl = 'https://img0.baidu.com/it/u=3217812679,2585737758&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500';
// const app = instance.Application;
// if (app && app.ActiveDocument) {
// await insertWordImage(instance, imgurl);
// } else if (app && app.ActivePresentation) {
// await insertPPTImage(instance, imgurl);
// }
// try {
// if (temObj.isEdit) {
// const app = instance.Application;
// // 获取 InsertTab 标签页下的所有控件
// const controls = await app.CommandBars('InsertTab').Controls;
// const newButton = await controls.Add(1);//添加一个按钮控件其中1表示按钮类型。
// newButton.Caption = '在线图片';//:设置按钮的标题。
// newButton.OnAction = () => open.value = true;//:设置按钮点击事件的回调函数。
// newButton.Picture = imgstr;
// }
// } catch (error) {
// console.error('下拉框', error)
// }
instance.on('fileStatus', () => {
debouncedFn(5000);
});
instance.ApiEvent.AddApiEventListener('error', (err: unknown) => {
console.log('发生错误:', err);
})
}
/**
* 获取IMM凭证信息
*/
async function getTokenFun(url: any, temObj: TemObj) {
let res = temObj.isEdit
? await generateWebofficeToken({ url: url, name: temObj.name })
: await generateWebofficeTokenReadOnly({ url: url, name: temObj.name });
return res
}
/**
* 刷新IMM凭证信息
*/
async function refreshTokenFun(tokenInfo: any) {
console.log('refreshWebofficeToken is called');
let res = await refreshWebofficeToken({ accessToken: tokenInfo.accessToken, refreshToken: tokenInfo.refreshToken });
return res;
}
</script>
<style scoped></style>