新增“落地页全屏预览”模态窗及 iframe,配合 css的 98% 视窗自适应样式,实现接近全屏的预览
This commit is contained in:
@@ -350,6 +350,34 @@ iconify-icon {
|
|||||||
max-width: 760px;
|
max-width: 760px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-preview-modal {
|
||||||
|
width: 98vw;
|
||||||
|
height: 98vh;
|
||||||
|
max-width: 98vw;
|
||||||
|
max-height: 98vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-preview-body {
|
||||||
|
flex: 1;
|
||||||
|
background: #0f172a;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-preview-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 3px solid #000;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
.code-viewer {
|
.code-viewer {
|
||||||
background: #0f172a;
|
background: #0f172a;
|
||||||
color: #f8fafc;
|
color: #f8fafc;
|
||||||
|
|||||||
27
index.html
27
index.html
@@ -109,6 +109,9 @@
|
|||||||
<button id="export-image-btn" class="p-2 bg-green-500 text-white border-2 border-black hover:bg-green-600 transition-all" title="导出为图片">
|
<button id="export-image-btn" class="p-2 bg-green-500 text-white border-2 border-black hover:bg-green-600 transition-all" title="导出为图片">
|
||||||
<iconify-icon icon="mdi:image-outline" class="text-xl"></iconify-icon>
|
<iconify-icon icon="mdi:image-outline" class="text-xl"></iconify-icon>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="open-page-btn" class="p-2 bg-cyan-500 text-white border-2 border-black hover:bg-cyan-600 transition-all" title="预览落地页">
|
||||||
|
<iconify-icon icon="ph:monitor-play-bold" class="text-xl"></iconify-icon>
|
||||||
|
</button>
|
||||||
<button id="view-code-btn" class="p-2 bg-blue-500 text-white border-2 border-black hover:bg-blue-600 transition-all" title="查看代码">
|
<button id="view-code-btn" class="p-2 bg-blue-500 text-white border-2 border-black hover:bg-blue-600 transition-all" title="查看代码">
|
||||||
<iconify-icon icon="mdi:code-tags" class="text-xl"></iconify-icon>
|
<iconify-icon icon="mdi:code-tags" class="text-xl"></iconify-icon>
|
||||||
</button>
|
</button>
|
||||||
@@ -224,6 +227,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 页面预览模态窗 -->
|
||||||
|
<div id="page-preview-modal" class="modal-overlay">
|
||||||
|
<div class="modal-content page-preview-modal">
|
||||||
|
<div class="bg-gradient-to-r from-teal-500 to-cyan-500 p-3 border-b-4 border-black flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<iconify-icon icon="ph:monitor-play-fill" class="text-2xl text-white"></iconify-icon>
|
||||||
|
<h2 class="text-lg md:text-xl font-black text-white">落地页全屏预览</h2>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button id="page-preview-newtab-btn" class="px-3 py-1 bg-white text-teal-600 border-2 border-black font-semibold hover:bg-teal-50 transition-all flex items-center gap-1">
|
||||||
|
<iconify-icon icon="ph:arrow-square-out-bold" class="text-lg"></iconify-icon>
|
||||||
|
新窗口打开
|
||||||
|
</button>
|
||||||
|
<button id="close-page-preview-btn" class="text-white hover:bg-white/20 p-2 transition-all">
|
||||||
|
<iconify-icon icon="ph:x-bold" class="text-2xl"></iconify-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-preview-body">
|
||||||
|
<iframe id="page-preview-iframe" title="落地页全屏预览" sandbox="allow-scripts allow-same-origin allow-forms allow-pointer-lock" class="page-preview-iframe"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 引入JavaScript文件 -->
|
<!-- 引入JavaScript文件 -->
|
||||||
<script src="js/utils.js"></script>
|
<script src="js/utils.js"></script>
|
||||||
<script src="js/services/storage-service.js"></script>
|
<script src="js/services/storage-service.js"></script>
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
this.el.downloadSvgBtn = document.getElementById('download-svg-btn');
|
this.el.downloadSvgBtn = document.getElementById('download-svg-btn');
|
||||||
this.el.copyImageBtn = document.getElementById('copy-image-btn');
|
this.el.copyImageBtn = document.getElementById('copy-image-btn');
|
||||||
this.el.exportImageBtn = document.getElementById('export-image-btn');
|
this.el.exportImageBtn = document.getElementById('export-image-btn');
|
||||||
|
this.el.openPageBtn = document.getElementById('open-page-btn');
|
||||||
this.el.viewCodeBtn = document.getElementById('view-code-btn');
|
this.el.viewCodeBtn = document.getElementById('view-code-btn');
|
||||||
const toolbarContainer = this.el.viewCodeBtn?.parentElement;
|
const toolbarContainer = this.el.viewCodeBtn?.parentElement;
|
||||||
if (toolbarContainer) {
|
if (toolbarContainer) {
|
||||||
@@ -104,6 +105,14 @@
|
|||||||
this.el.codeContent = document.getElementById('code-content');
|
this.el.codeContent = document.getElementById('code-content');
|
||||||
this.el.copyCodeBtn = document.getElementById('copy-code-btn');
|
this.el.copyCodeBtn = document.getElementById('copy-code-btn');
|
||||||
this.el.closeCodeModalBtn = document.getElementById('close-code-modal-btn');
|
this.el.closeCodeModalBtn = document.getElementById('close-code-modal-btn');
|
||||||
|
this.el.pagePreviewModal = document.getElementById('page-preview-modal');
|
||||||
|
this.el.pagePreviewIframe = document.getElementById('page-preview-iframe');
|
||||||
|
this.el.closePagePreviewBtn = document.getElementById(
|
||||||
|
'close-page-preview-btn'
|
||||||
|
);
|
||||||
|
this.el.pagePreviewNewTabBtn = document.getElementById(
|
||||||
|
'page-preview-newtab-btn'
|
||||||
|
);
|
||||||
|
|
||||||
// 复制按钮可用性
|
// 复制按钮可用性
|
||||||
if (this.el.copyImageBtn && !this.copyClipboardSupported) {
|
if (this.el.copyImageBtn && !this.copyClipboardSupported) {
|
||||||
@@ -193,6 +202,11 @@
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (this.el.openPageBtn) {
|
||||||
|
this.el.openPageBtn.addEventListener('click', () =>
|
||||||
|
this.openPagePreview()
|
||||||
|
);
|
||||||
|
}
|
||||||
if (this.el.viewCodeBtn) {
|
if (this.el.viewCodeBtn) {
|
||||||
this.el.viewCodeBtn.addEventListener('click', () =>
|
this.el.viewCodeBtn.addEventListener('click', () =>
|
||||||
this.viewArtifactCode()
|
this.viewArtifactCode()
|
||||||
@@ -249,6 +263,23 @@
|
|||||||
this.closeCodeModal()
|
this.closeCodeModal()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (this.el.closePagePreviewBtn) {
|
||||||
|
this.el.closePagePreviewBtn.addEventListener('click', () =>
|
||||||
|
this.closePagePreview()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.el.pagePreviewModal) {
|
||||||
|
this.el.pagePreviewModal.addEventListener('click', (event) => {
|
||||||
|
if (event.target === this.el.pagePreviewModal) {
|
||||||
|
this.closePagePreview();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.el.pagePreviewNewTabBtn) {
|
||||||
|
this.el.pagePreviewNewTabBtn.addEventListener('click', () =>
|
||||||
|
this.openPageInNewTab()
|
||||||
|
);
|
||||||
|
}
|
||||||
if (this.el.codeModal) {
|
if (this.el.codeModal) {
|
||||||
this.el.codeModal.addEventListener('click', (event) => {
|
this.el.codeModal.addEventListener('click', (event) => {
|
||||||
if (event.target === this.el.codeModal) {
|
if (event.target === this.el.codeModal) {
|
||||||
@@ -337,6 +368,10 @@
|
|||||||
}
|
}
|
||||||
this.renderQuickActions(manifest.ui?.quickActions || []);
|
this.renderQuickActions(manifest.ui?.quickActions || []);
|
||||||
this.showViewerPlaceholder(manifest.ui?.placeholderText || '');
|
this.showViewerPlaceholder(manifest.ui?.placeholderText || '');
|
||||||
|
if (this.el.openPageBtn) {
|
||||||
|
const isHtmlModule = manifest.artifact?.type === 'html';
|
||||||
|
this.el.openPageBtn.classList.toggle('hidden', !isHtmlModule);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showViewerPlaceholder(text) {
|
showViewerPlaceholder(text) {
|
||||||
@@ -1956,6 +1991,79 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActiveHtmlArtifact(manifest = null) {
|
||||||
|
const targetManifest = manifest || this.getActiveManifest();
|
||||||
|
if (!targetManifest || targetManifest.artifact?.type !== 'html') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const state = this.runtime.getState(targetManifest.id);
|
||||||
|
if (!state?.currentArtifactId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return state.artifacts[state.currentArtifactId] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
openPagePreview() {
|
||||||
|
const manifest = this.getActiveManifest();
|
||||||
|
if (!manifest || manifest.artifact?.type !== 'html') {
|
||||||
|
alert('当前模块不支持页面预览,请切换到落地页生成模块。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const artifact = this.getActiveHtmlArtifact(manifest);
|
||||||
|
if (!artifact || !artifact.content) {
|
||||||
|
alert('尚未生成落地页内容,请先完成一次生成。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const htmlDoc = this.prepareHtmlDocument(artifact.content, {
|
||||||
|
partial: false
|
||||||
|
});
|
||||||
|
if (this.el.pagePreviewIframe) {
|
||||||
|
this.el.pagePreviewIframe.srcdoc = htmlDoc;
|
||||||
|
this.el.pagePreviewIframe.dataset.updatedAt =
|
||||||
|
new Date().toISOString();
|
||||||
|
}
|
||||||
|
if (this.el.pagePreviewModal) {
|
||||||
|
this.el.pagePreviewModal.classList.add('active');
|
||||||
|
this.el.pagePreviewModal.style.display = 'flex';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closePagePreview() {
|
||||||
|
if (!this.el.pagePreviewModal) return;
|
||||||
|
this.el.pagePreviewModal.classList.remove('active');
|
||||||
|
this.el.pagePreviewModal.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
openPageInNewTab() {
|
||||||
|
const manifest = this.getActiveManifest();
|
||||||
|
if (!manifest || manifest.artifact?.type !== 'html') {
|
||||||
|
alert('只能在落地页生成模块中打开新窗口预览。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const artifact = this.getActiveHtmlArtifact(manifest);
|
||||||
|
if (!artifact || !artifact.content) {
|
||||||
|
alert('当前落地页内容为空,请先生成或刷新内容。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const htmlDoc = this.prepareHtmlDocument(artifact.content, {
|
||||||
|
partial: false
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const blob = new Blob([htmlDoc], { type: 'text/html' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const opened = window.open(url, '_blank', 'noopener');
|
||||||
|
if (!opened) {
|
||||||
|
//alert('浏览器阻止了新窗口,请允许弹窗或手动复制代码。');
|
||||||
|
} else if (opened.focus) {
|
||||||
|
opened.focus();
|
||||||
|
}
|
||||||
|
setTimeout(() => URL.revokeObjectURL(url), 30_000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('打开新窗口失败:', error);
|
||||||
|
alert('打开新窗口失败,请检查浏览器设置或下载代码后手动打开。');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
cancelActiveStream() {
|
cancelActiveStream() {
|
||||||
this.manualAbortRequested = true;
|
this.manualAbortRequested = true;
|
||||||
@@ -2113,6 +2221,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.el.viewer.appendChild(wrapper);
|
this.el.viewer.appendChild(wrapper);
|
||||||
|
|
||||||
|
if (this.el.pagePreviewIframe && !partial) {
|
||||||
|
this.el.pagePreviewIframe.srcdoc = preparedHtml;
|
||||||
|
this.el.pagePreviewIframe.dataset.updatedAt =
|
||||||
|
new Date().toISOString();
|
||||||
|
} else if (this.el.pagePreviewIframe && partial) {
|
||||||
|
this.el.pagePreviewIframe.srcdoc = preparedHtml;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareHtmlDocument(htmlContent, options = {}) {
|
prepareHtmlDocument(htmlContent, options = {}) {
|
||||||
@@ -2167,13 +2283,6 @@
|
|||||||
|
|
||||||
ensureClosingTag('html', () => output.length);
|
ensureClosingTag('html', () => output.length);
|
||||||
|
|
||||||
if (partial) {
|
|
||||||
// 对流式场景追加最外层闭合,避免 iframe 在标签未闭合时渲染失败
|
|
||||||
if (!/</i.test(output.slice(-10))) {
|
|
||||||
output += '\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2496,6 +2605,10 @@
|
|||||||
this.el.editMermaidBtn.classList.toggle('hidden', !isMermaidManifest);
|
this.el.editMermaidBtn.classList.toggle('hidden', !isMermaidManifest);
|
||||||
this.el.editMermaidBtn.disabled = !hasArtifact || !isMermaidManifest;
|
this.el.editMermaidBtn.disabled = !hasArtifact || !isMermaidManifest;
|
||||||
}
|
}
|
||||||
|
if (this.el.openPageBtn) {
|
||||||
|
const isHtmlManifest = manifest.artifact?.type === 'html';
|
||||||
|
this.el.openPageBtn.disabled = !hasArtifact || !isHtmlManifest;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCurrentConversation() {
|
clearCurrentConversation() {
|
||||||
|
|||||||
Reference in New Issue
Block a user