fix preview
Some checks failed
Deploy / deploy (build, debian, 22) (push) Has been cancelled
Deploy / deploy (build:staging, school, 8822) (push) Has been cancelled

This commit is contained in:
2026-05-02 09:35:14 -06:00
parent 2e6e3aacec
commit 5fced6b4c2
3 changed files with 60 additions and 45 deletions

View File

@@ -42,18 +42,19 @@
</n-flex> </n-flex>
</n-flex> </n-flex>
<div class="iframe-wrapper" :style="iframeWrapperStyle"> <div class="iframe-wrapper" :style="iframeWrapperStyle">
<iframe class="iframe" ref="iframe"></iframe> <iframe class="iframe" :srcdoc="previewContent"></iframe>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { watchDebounced } from "@vueuse/core" import { watchDebounced } from "@vueuse/core"
import { computed, onMounted, useTemplateRef, ref } from "vue" import { computed, onMounted, ref } from "vue"
import { useRouter } from "vue-router" import { useRouter } from "vue-router"
import { Submission } from "../../api" import { Submission } from "../../api"
import { submission } from "../../store/submission" import { submission } from "../../store/submission"
import { useMessage } from "naive-ui" import { useMessage } from "naive-ui"
import { Icon } from "@iconify/vue" import { Icon } from "@iconify/vue"
import copy from "copy-text-to-clipboard" import copy from "copy-text-to-clipboard"
import { buildPreviewDocument } from "../../utils/previewDocument"
interface Props { interface Props {
html: string html: string
@@ -91,7 +92,9 @@ const layoutConfig: Record<
}, },
} }
const layoutIndex = ref(0) const layoutIndex = ref(0)
const layoutIcon = computed(() => layoutConfig[layouts[layoutIndex.value]!].icon) const layoutIcon = computed(
() => layoutConfig[layouts[layoutIndex.value]!].icon,
)
const layoutLabel = computed( const layoutLabel = computed(
() => layoutConfig[layouts[layoutIndex.value]!].label, () => layoutConfig[layouts[layoutIndex.value]!].label,
) )
@@ -105,35 +108,20 @@ function cycleLayout() {
const message = useMessage() const message = useMessage()
const router = useRouter() const router = useRouter()
const iframe = useTemplateRef<HTMLIFrameElement>("iframe")
const showDL = computed(() => props.html || props.css || props.js) const showDL = computed(() => props.html || props.css || props.js)
const previewContent = ref("")
function getContent() { function getContent() {
return `<!DOCTYPE html> return buildPreviewDocument({
<html lang="zh-Hans-CN"> html: props.html,
<head> css: props.css,
<meta charset="UTF-8" /> js: props.js,
<title>预览</title> assetBaseUrl: props.assetBaseUrl,
<meta name="viewport" content="width=device-width, initial-scale=1.0"> })
${props.assetBaseUrl ? `<base href="${props.assetBaseUrl}">` : ""}
<style>${props.css}</style>
<link rel="stylesheet" href="/normalize.min.css" />
</head>
<body>
${props.html}
<script>${props.js}<\/script>
</body>
</html>`
} }
function preview() { function preview() {
if (!iframe.value) return previewContent.value = getContent()
const doc = iframe.value.contentDocument
if (doc) {
doc.open()
doc.write(getContent())
doc.close()
}
} }
function download() { function download() {
@@ -155,7 +143,7 @@ function open() {
}) })
window.open(data.href, "_blank") window.open(data.href, "_blank")
} else { } else {
const newTab = window.open("/usercontent.html") const newTab = window.open("about:blank", "_blank")
if (!newTab) return if (!newTab) return
newTab.document.open() newTab.document.open()
newTab.document.write(getContent()) newTab.document.write(getContent())
@@ -183,10 +171,14 @@ async function updateScore(score: number) {
} }
} }
watchDebounced(() => [props.html, props.css, props.js], preview, { watchDebounced(
() => [props.html, props.css, props.js, props.assetBaseUrl],
preview,
{
debounce: 500, debounce: 500,
maxWait: 1000, maxWait: 1000,
}) },
)
onMounted(preview) onMounted(preview)
</script> </script>
<style scoped> <style scoped>

View File

@@ -2,6 +2,7 @@
import { onMounted, useTemplateRef } from "vue" import { onMounted, useTemplateRef } from "vue"
import { Submission } from "../api" import { Submission } from "../api"
import type { SubmissionAll } from "../utils/type" import type { SubmissionAll } from "../utils/type"
import { buildPreviewDocument } from "../utils/previewDocument"
interface Props { interface Props {
id: string id: string
@@ -18,20 +19,13 @@ async function init() {
const doc = iframe.value.contentDocument const doc = iframe.value.contentDocument
if (doc) { if (doc) {
doc.open() doc.open()
doc.write(`<!DOCTYPE html> doc.write(
<html lang="zh-Hans-CN"> buildPreviewDocument({
<head> html: submission.html,
<meta charset="UTF-8" /> css: submission.css,
<title>预览</title> js: submission.js,
<meta name="viewport" content="width=device-width, initial-scale=1.0"> }),
<style>${submission.css}</style> )
<link rel="stylesheet" href="/normalize.min.css" />
</head>
<body>
${submission.html}
<script>${submission.js}<\/script>
</body>
</html>`)
doc.close() doc.close()
} }
} }

View File

@@ -0,0 +1,29 @@
interface PreviewDocumentOptions {
html: string
css: string
js: string
assetBaseUrl?: string
}
export function buildPreviewDocument({
html,
css,
js,
assetBaseUrl,
}: PreviewDocumentOptions) {
return `<!DOCTYPE html>
<html lang="zh-Hans-CN">
<head>
<meta charset="UTF-8" />
<title>预览</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
${assetBaseUrl ? `<base href="${assetBaseUrl}">` : ""}
<style>${css}</style>
<link rel="stylesheet" href="/normalize.min.css" />
</head>
<body>
${html}
<script>${js}<\/script>
</body>
</html>`
}