Files
workspace/code/email/Email-Vue/src/components/email-shadow-renderer.vue
T
2025-06-10 21:27:20 +08:00

94 lines
2.0 KiB
Vue

<template>
<div ref="sandboxRef" class="email-renderer"></div>
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from "vue";
import { useRouterStore } from "@/store/modules/router";
const routerStore = useRouterStore();
const props = defineProps<{
html: string;
}>();
const sandboxRef = ref<HTMLDivElement | null>(null);
let shadowRoot: ShadowRoot | null = null;
function sanitizeHtml(html: string): string {
// 简单去除 <script> 标签及其内容
return html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
}
watch(
() => props.html,
async (newHtml) => {
if (!sandboxRef.value) return;
await nextTick();
if (!shadowRoot) {
shadowRoot = sandboxRef.value.attachShadow({ mode: "open" });
// 监听shadowRoot里的点击事件,事件委托拦截a标签
shadowRoot.addEventListener("click", (e) => {
const target = e.target as HTMLElement;
const anchor = target.closest("a");
if (anchor && anchor.getAttribute("href")?.startsWith("mailto:")) {
e.preventDefault();
const email = anchor.getAttribute("href")!.replace("mailto:", "");
if (email) {
routerStore.navigateTo({
path: "/write",
query: {
toEmail: email
}
});
}
}
});
}
// 先清空
shadowRoot.innerHTML = "";
// 安全过滤
const safeHtml = sanitizeHtml(newHtml);
// 直接插入,保留里面的 <style> 标签
shadowRoot.innerHTML = `
<style>
html, body {
margin: 0;
padding: 0;
}
img {
max-width: 100%;
height: auto;
display: block;
}
table {
max-width: 100%;
overflow-wrap: break-word;
}
.wrapper {
box-sizing: border-box;
}
</style>
<div class="wrapper">
${safeHtml}
</div>
`;
},
{ immediate: true }
);
</script>
<style scoped>
.email-renderer {
position: relative;
width: 100%;
overflow-x: auto;
}
</style>