@cqsjjb/scene-engine
v0.0.1-alpha.5
Published
场景引擎组件,通过 iframe 嵌入场景引擎功能
Maintainers
Readme
@cqsjjb/scene-engine
场景引擎组件库,通过 iframe 嵌入场景引擎功能,支持富文本编辑和内容渲染。
📦 安装
npm install @cqsjjb/scene-engine
# 或
yarn add @cqsjjb/scene-engine
# 或
pnpm add @cqsjjb/scene-engine🔧 依赖要求
该组件库需要以下 peer dependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0react-quill: ^1.3.0 || ^2.0.0
🚀 快速开始
SceneEngine - 场景引擎
场景引擎组件通过 iframe 嵌入场景引擎功能,支持新增和编辑场景。
import React, { useState } from 'react';
import { SceneEngine } from '@cqsjjb/scene-engine';
const App = () => {
const [visible, setVisible] = useState(false);
const [sceneId, setSceneId] = useState<string>();
const handleOpenEditor = (id?: string) => {
setSceneId(id);
setVisible(true);
};
const handleClose = (data: {
id: string;
contentTitle: string;
content: string;
settingConfig: {
[key: string]: any;
};
}) => {
console.log('场景数据:', data);
setVisible(false);
// 处理保存逻辑
};
return (
<div>
<button onClick={() => handleOpenEditor()}>新增场景</button>
<button onClick={() => handleOpenEditor('scene-123')}>编辑场景</button>
<SceneEngine
visible={visible}
id={sceneId}
host="http://localhost:5173"
onClose={handleClose}
style={{ zIndex: 9999 }}
/>
</div>
);
};ContentRenderer - 内容渲染器
内容渲染器组件用于渲染富文本内容,适用于预览场景内容。
import React from 'react';
import { ContentRenderer } from '@cqsjjb/scene-engine';
const Preview = () => {
const htmlContent = '<p>这是富文本内容</p>';
return (
<div>
<h2>场景预览</h2>
<ContentRenderer content={htmlContent} />
</div>
);
};📚 API 文档
SceneEngine
场景引擎组件,通过 iframe 嵌入场景引擎功能。
Props
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| style | React.CSSProperties | 否 | - | 自定义样式,默认组件使用固定定位全屏显示(z-index: 9999) |
| host | string | 否 | window.location.origin | 主机地址(协议 + 域名 + 端口),用于构建 iframe 的源地址 |
| src | string | 否 | host + '/article-layout' | iframe 的源地址,如果提供则优先级高于 host |
| id | string | 否 | - | 场景 ID,未传则认为是新增场景,传入则认为是编辑场景 |
| visible | boolean | 否 | true | 是否显示组件 |
| onClose | (data: CloseData) => void | 否 | - | 关闭事件回调 |
CloseData 类型
interface CloseData {
id: string; // 场景 ID
contentTitle: string; // 场景的标题
content: string; // 场景的内容(富文本 HTML)
settingConfig: {
[key: string]: any; // 场景的设置配置
};
}事件通信
组件通过 postMessage 与 iframe 内的编辑器进行通信:
打开编辑器: 组件会在 iframe 加载完成后发送
EVENT_OPEN_SCENE_EDITOR消息,消息格式:{ type: 'EVENT_OPEN_SCENE_EDITOR', id: string // 场景 ID,新增时为空字符串 }关闭编辑器: iframe 内编辑器发送
EVENT_CLOSE_SCENE_EDITOR消息时触发onClose回调,消息格式:{ type: 'EVENT_CLOSE_SCENE_EDITOR', id: string, contentTitle: string, content: string, settingConfig: Record<string, any> }
组件会验证消息来源,只处理来自 iframe 源地址的消息,确保通信安全。
ContentRenderer
内容渲染器组件,用于渲染富文本内容。组件会自动清洗 HTML 内容,确保内容以只读模式展示:
- 移除所有包含
ql-material-image--selected类的元素(清理图片选中状态) - 移除所有
contenteditable属性(确保内容不可编辑)
Props
| 属性名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| content | string | 否 | '' | 要渲染的 HTML 内容(富文本) |
💡 使用示例
完整示例
import React, { useState } from 'react';
import { SceneEngine, ContentRenderer } from '@cqsjjb/scene-engine';
interface SceneData {
id: string;
title: string;
content: string;
settingConfig: Record<string, any>;
}
const SceneManager = () => {
const [editorVisible, setEditorVisible] = useState(false);
const [editingSceneId, setEditingSceneId] = useState<string>();
const [scenes, setScenes] = useState<SceneData[]>([]);
const [previewContent, setPreviewContent] = useState<string>('');
// 打开编辑器
const handleOpenEditor = (sceneId?: string) => {
setEditingSceneId(sceneId);
setEditorVisible(true);
};
// 关闭编辑器并保存数据
const handleCloseEditor = (data: {
id: string;
contentTitle: string;
content: string;
settingConfig: Record<string, any>;
}) => {
const sceneData: SceneData = {
id: data.id,
title: data.contentTitle,
content: data.content,
settingConfig: data.settingConfig,
};
// 更新场景列表
const existingIndex = scenes.findIndex((s) => s.id === sceneData.id);
if (existingIndex >= 0) {
const updatedScenes = [...scenes];
updatedScenes[existingIndex] = sceneData;
setScenes(updatedScenes);
} else {
setScenes([...scenes, sceneData]);
}
setEditorVisible(false);
setEditingSceneId(undefined);
};
// 预览场景
const handlePreview = (scene: SceneData) => {
setPreviewContent(scene.content);
};
return (
<div>
<div>
<h2>场景列表</h2>
<button onClick={() => handleOpenEditor()}>新增场景</button>
{scenes.map((scene) => (
<div key={scene.id}>
<h3>{scene.title}</h3>
<button onClick={() => handleOpenEditor(scene.id)}>编辑</button>
<button onClick={() => handlePreview(scene)}>预览</button>
</div>
))}
</div>
{previewContent && (
<div>
<h2>预览</h2>
<ContentRenderer content={previewContent} />
</div>
)}
<SceneEngine
visible={editorVisible}
id={editingSceneId}
host="http://localhost:5173"
onClose={handleCloseEditor}
style={{ zIndex: 9999 }}
/>
</div>
);
};
export default SceneManager;自定义 iframe 地址
<SceneEngine
visible={true}
src="https://example.com/custom-editor-path"
onClose={handleClose}
/>自定义样式
<SceneEngine
visible={true}
style={{
zIndex: 10000,
backgroundColor: '#f5f5f5',
}}
onClose={handleClose}
/>🏗️ 构建
该组件库使用 Vite 构建,支持 ES Module 和 CommonJS 两种格式。
开发构建
npm run build构建产物将输出到 publish 目录,包括:
index.esm.js- ES Module 格式index.cjs.js- CommonJS 格式index.d.ts- TypeScript 类型定义index.css- 样式文件
发布
npm publish发布前会自动执行 prepublishOnly 脚本进行构建。
📝 注意事项
iframe 通信: 组件通过
postMessage与 iframe 内的编辑器通信,确保 iframe 的源地址与host或src配置一致。组件会验证消息来源,只处理来自 iframe 源的消息。样式隔离: 组件使用
createPortal将编辑器渲染到document.body,确保样式不受父组件影响。组件默认使用固定定位全屏显示(z-index: 9999)。富文本内容:
ContentRenderer组件使用dangerouslySetInnerHTML渲染 HTML 内容,请确保内容来源可信,避免 XSS 攻击。组件会自动清洗 HTML 内容:- 移除所有包含
ql-material-image--selected类的元素(清理图片选中状态) - 移除所有
contenteditable属性(确保内容不可编辑)
- 移除所有包含
react-quill 样式: 组件已引入
react-quill/dist/quill.snow.css和自定义样式,无需额外引入。浏览器兼容性: 组件依赖现代浏览器 API(如
postMessage、createPortal),建议在支持 ES2020 的浏览器中使用。默认路径: 如果未提供
src属性,组件会使用host + '/scene-engine'作为 iframe 的默认路径。
🔒 安全提示
- 使用
ContentRenderer渲染用户输入内容时,请确保对 HTML 内容进行安全过滤和转义 - 在生产环境中,建议使用 CSP (Content Security Policy) 限制 iframe 的源地址
