web-view-antd/src/views/infra/bookmark/index.vue

238 lines
6.5 KiB
Vue
Raw Normal View History

2025-03-05 17:13:23 +08:00
<script setup lang="ts">
2025-03-21 17:06:09 +08:00
import { onMounted, ref } from 'vue'
2025-03-21 11:51:27 +08:00
import { useEventListener } from '@vueuse/core'
2025-03-21 18:52:41 +08:00
import axios from 'axios'
2025-03-05 17:13:23 +08:00
import { IFrame } from '@/components/IFrame'
2025-03-21 17:06:09 +08:00
import { isInIframe } from '@/utils/iframe'
2025-03-21 18:52:41 +08:00
import { useMessage } from '@/hooks/web/useMessage'
2025-03-21 17:06:09 +08:00
import { uploadOneFile } from '@/api/base/upload'
2025-03-05 17:13:23 +08:00
import { initFilePreviewUrl } from '@/utils/file/preview'
2025-03-21 17:06:09 +08:00
import { getConfigKey } from '@/api/infra/config'
2025-03-21 18:52:41 +08:00
import type { FileDO } from '@/types/axios'
import { useGlobSetting } from '@/hooks/setting'
import { getAccessToken } from '@/utils/auth'
2025-03-21 17:06:09 +08:00
2025-03-05 17:13:23 +08:00
defineOptions({ name: 'BookmarkReplace' })
2025-03-21 17:06:09 +08:00
const isEmbed = isInIframe()
2025-03-21 18:52:41 +08:00
const globSetting = useGlobSetting()
2025-03-05 17:13:23 +08:00
const previewUrl = ref<string | undefined>(undefined)
2025-03-21 18:52:41 +08:00
const frameComponent = ref<{ frameRef: HTMLIFrameElement } | null>(null)
2025-03-05 17:13:23 +08:00
2025-03-21 18:52:41 +08:00
const { createMessage } = useMessage()
2025-03-05 17:13:23 +08:00
2025-03-21 17:06:09 +08:00
const wopi_client = ref(undefined)
const wopi_server = ref(undefined)
2025-03-05 17:13:23 +08:00
2025-03-21 18:52:41 +08:00
const editStatus = ref<{ Modified: boolean }>({ Modified: false })
const currentEditFile = ref<FileDO | undefined>(undefined)
2025-03-21 17:06:09 +08:00
onMounted(async () => {
wopi_client.value = await getConfigKey('wopi_client_addr')
wopi_server.value = await getConfigKey('wopi_server_ip_addr')
2025-03-05 17:13:23 +08:00
2025-03-21 18:52:41 +08:00
/**
* 接收消息
*/
2025-03-21 17:06:09 +08:00
useEventListener(window, 'message', async (e: MessageEvent) => {
logEvent(e)
2025-03-21 18:52:41 +08:00
let data: any = null
if (typeof e.data === 'string')
data = JSON.parse(e.data)
if (typeof e.data === 'object')
data = e.data
const ActionId = data.ActionId
const MessageId = data.MessageId
2025-03-05 18:12:55 +08:00
2025-03-21 17:06:09 +08:00
// 第三方调用发送的Event
switch (ActionId) {
2025-03-21 18:52:41 +08:00
case 'OPEN_FILE':
2025-03-21 17:06:09 +08:00
await handleFileBinary(e)
break
}
2025-03-21 18:52:41 +08:00
console.warn(`MessageId:${MessageId}`)
// WOPI Client发送消息
switch (MessageId) {
// 更新文档是否被编辑
case 'Doc_ModifiedStatus':
handleUpdateModifiedStatus(e)
break
// 用户执行了保存报错
case 'UI_Save':
console.warn('UI_Save')
handleUserSaveOp()
break
}
2025-03-21 17:06:09 +08:00
})
2025-03-05 18:12:55 +08:00
})
2025-03-21 18:52:41 +08:00
/**
* 更新是否编辑状态
* 保存后会更新是否编辑为否正常编辑后会更新是否编辑为是
* 用于区分是否要向第三方发送保存文件的回调
* @param e
*/
function handleUpdateModifiedStatus(e: MessageEvent) {
editStatus.value.Modified = JSON.parse(e.data).Values.Modified
}
let pollingInterval: NodeJS.Timeout | null = null
let lastCallTime: number | null = null
async function handleUserSaveOp() {
console.warn('handleUserSaveOp')
// 情况 1: 如果当前状态未修改,直接发送消息
if (!editStatus.value.Modified) {
// 下载文件并发送
downloadAndSendCurrentEditFile()
return
}
// 情况 2: 已处于修改状态,开始/重置等待流程
// 更新最后一次调用时间戳
lastCallTime = Date.now()
// 清除已有定时器(实现调用重置)
if (pollingInterval) {
clearInterval(pollingInterval)
pollingInterval = null
}
// 启动新的轮询检测
pollingInterval = setInterval(() => {
// 子情况 2a: 状态已恢复未修改
if (!editStatus.value.Modified) {
downloadAndSendCurrentEditFile()
cleanup()
return
}
// 子情况 2b: 检测超时10秒逻辑
const currentTime = Date.now()
if (lastCallTime && currentTime - lastCallTime >= 10000) {
createMessage.error('保存操作执行超时!')
cleanup()
}
}, 500) // 每 500ms 检测一次
}
// 清理函数
function cleanup() {
if (pollingInterval) {
clearInterval(pollingInterval)
pollingInterval = null
}
lastCallTime = null
}
/**
* 下载当前编辑的文件发送给调用者
*/
async function downloadAndSendCurrentEditFile() {
console.warn('downloadAndSendCurrentEditFile')
if (!currentEditFile.value)
return
try {
// 1. 发送请求(关键配置 responseType: 'arraybuffer'
const response = await axios.get(`${globSetting.apiUrl}/infra/file/download/${currentEditFile.value.id}`, {
responseType: 'arraybuffer', // [!code focus]
headers: {
Authorization: `Bearer ${getAccessToken()}`,
},
})
// 2. 验证响应状态
if (response.status !== 200)
createMessage.error(`请求失败,状态码:${response.status}`)
// 3. 获取 ArrayBuffer 数据
const arrayBuffer: ArrayBuffer = response.data
sendMessageToCaller({
ActionId: 'SAVE_FILE',
Payload: {
name: currentEditFile.value.name,
buffer: arrayBuffer,
},
})
}
catch (error) {
console.error('文件下载失败:', error)
createMessage.error('文件下载失败,请查看控制台日志')
throw error
}
}
/**
* 第三方上传文件
* @param e
*/
2025-03-21 17:06:09 +08:00
async function handleFileBinary(e: MessageEvent) {
const { name, type, buffer } = e.data.Payload
const blob = new Blob([buffer], { type })
const uploadResult = await uploadOneFile({
filename: name,
file: new File([blob], name, { type }),
})
2025-03-21 18:52:41 +08:00
currentEditFile.value = uploadResult.data.data as FileDO
previewUrl.value = await initFilePreviewUrl(currentEditFile.value.id, wopi_client.value, wopi_server.value)
}
/**
* 发送消息给WopiClient
* 用于执行书签相关操作
*/
// function sendMessageToWopiClient(data: any) {
// if (!frameComponent.value?.frameRef.contentWindow)
// createMessage.error('WOPI Client iframe 未就绪')
// try {
// frameComponent.value?.frameRef.contentWindow?.postMessage(data, '*')
// }
// catch (e) {
// console.error(e)
// createMessage.error(`消息发送失败,查看控制台日志!`)
// }
// }
/**
* 发送消息给第三方调用者
* 用于转发部分WopiClient回调
*/
function sendMessageToCaller(data: any) {
try {
const targetWindow = window.parent
targetWindow.postMessage(data, '*')
}
catch (e) {
console.error(e)
createMessage.error(`消息发送失败,查看控制台日志!`)
}
2025-03-21 17:06:09 +08:00
}
function logEvent(e: MessageEvent) {
console.log('=============receive message start=======')
console.log(e.data)
console.log(e.origin)
}
2025-03-05 17:13:23 +08:00
</script>
<template>
<div>
2025-03-21 17:06:09 +08:00
<div class="flex flex-col" :class="{ 'p-2 pt-4': !isEmbed }">
<div class="w-full flex flex-row" :class="{ 'h-[calc(100vh)]': isEmbed, 'h-[calc(100vh-105px)]': !isEmbed }">
<div class="w-full flex flex-row bg-white dark:bg-black">
2025-03-21 18:52:41 +08:00
<i-frame v-if="previewUrl" ref="frameComponent" class="w-full bg-white dark:bg-black" :src="previewUrl" :height="isEmbed ? 'calc(100vh)' : 'calc(100vh) - 105px'" />
2025-03-05 17:13:23 +08:00
</div>
</div>
</div>
</div>
</template>