Files
qiming/qiming-mobile/components/page-preview-iframe/page-preview-iframe.uvue

157 lines
3.7 KiB
Plaintext

<template>
<drawer-popup
ref="popupPreviewPage"
:title="headerTitle"
direction="bottom"
height="90%"
@update-visible="updateVisible"
>
<iframe
class="h-full"
ref="iframeRef"
:src="iframeConfig.uri"
@load="onIframeLoad"
></iframe>
</drawer-popup>
</template>
<script setup lang="uts">
import TurndownService from "turndown"; // markdown转html
import { apiAgentComponentPageResultUpdate } from "@/servers/agentDev";
import { useI18n } from "@/utils/i18n";
/**
* 页面预览组件
* 用于在弹窗中预览网页内容
*/
// 定义组件props
interface PreviewData {
/** 页面 URI */
uri: string;
/** 请求方法 */
method?: "browser_navigate_page" | "browser_open_page";
/** 数据类型 */
data_type?: "html" | "markdown";
/** 请求 ID */
request_id?: string;
}
// 预览页面弹窗
const popupPreviewPage = ref<any>(null);
// iframe引用
const iframeRef = ref<HTMLIFrameElement>(null);
const emit = defineEmits(["update-visible"]);
const updateVisible = (value: boolean) => {
emit("update-visible", value);
};
// iframe配置
const iframeConfig = reactive<PreviewData>({
uri: "",
method: "browser_open_page",
data_type: "html",
request_id: "",
});
// 预览页面标题
const headerTitle = ref<string>("");
const { t } = useI18n();
/**
* 处理页面打开
* @param data 预览数据
*/
const handlePageOpen = (data: PreviewData) => {
Object.assign(iframeConfig, data);
popupPreviewPage.value?.open();
};
/**
* iframe加载完成事件
*/
const onIframeLoad = () => {
console.log("onIframeLoad");
const iframe = iframeRef.value;
if (!iframe || !iframeConfig.uri) return;
// iframe.src = iframeConfig.uri;
onIframeDOMChange(iframe);
};
/**
* 监听iframe DOM变化
* @param iframe iframe元素
*/
const onIframeDOMChange = (iframe: HTMLIFrameElement) => {
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
if (!iframeDoc) return;
const turndownService = new TurndownService();
let timer: NodeJS.Timeout;
// 监听 iframe 内部 DOM 变化
const observer = new MutationObserver(() => {
// 每次变化后延迟 500ms 再检测,确保渲染稳定
clearTimeout(timer);
timer = setTimeout(async () => {
// 获取 head 中的 title 内容
const title =
iframeDoc.querySelector("head > title")?.textContent ||
t("Mobile.PagePreview.defaultTitle");
headerTitle.value = title;
const html = iframeDoc.body.innerHTML;
if (!iframeConfig.method) return;
// 获取 iframe 内容
let str = "";
// 如果是 html
if (iframeConfig.data_type === "html") {
str = html;
}
// 如果是 markdown
if (iframeConfig.data_type === "markdown") {
str = turndownService.turndown(html);
}
if (!str) {
return;
}
if (iframeConfig.method === "browser_navigate_page") {
const params = {
requestId: iframeConfig.request_id || "",
html: str,
};
await apiAgentComponentPageResultUpdate(params);
}
}, 500);
});
observer.observe(iframeDoc.body, {
childList: true,
subtree: true,
characterData: true,
});
// 清理
return () => {
observer.disconnect();
clearTimeout(timer);
};
};
// 必须暴露出来,否则父组件访问不到
defineExpose({
handlePageOpen,
popupPreviewPage,
});
</script>
<style lang="scss" scoped>
.h-full {
border: 2rpx solid #ddd;
}
</style>