unlayer-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIntegrate Unlayer Editor
集成Unlayer编辑器
Overview
概述
Unlayer provides official wrappers for React, Vue, and Angular, plus a plain JavaScript embed. All wrappers share the same underlying API — only the editor access pattern differs.
Unlayer为React、Vue和Angular提供了官方封装,同时也支持原生JavaScript嵌入。所有封装都基于相同的底层API——仅编辑器的访问方式有所不同。
Which Framework?
选择对应框架?
| Framework | Package | Install | Editor Access |
|---|---|---|---|
| React | | | |
| Vue | | | |
| Angular | | | |
| Plain JS | CDN script tag | | Global |
⚠️ Before installing any Unlayer package, verify the version exists on npm:bashnpm view react-email-editor version # check latest published versionNever pin a version number you haven't verified. Usewithout a version to get the latest, or runnpm install <package> --saveto see all available versions.npm view <package> versions --json
| 框架 | 包名 | 安装命令 | 编辑器访问方式 |
|---|---|---|---|
| React | | | |
| Vue | | | |
| Angular | | | |
| 原生JS | CDN脚本标签 | | 全局 |
⚠️ 在安装任何Unlayer包之前,请先验证该版本是否在npm上存在:bashnpm view react-email-editor version # 查看最新发布版本切勿锁定未验证的版本号。使用且不指定版本即可获取最新版,或运行npm install <package> --save查看所有可用版本。npm view <package> versions --json
React (Complete Working Example)
React(完整可用示例)
bash
npm install react-email-editor --savetsx
import React, { useRef, useState } from 'react';
import EmailEditor, { EditorRef, EmailEditorProps } from 'react-email-editor';
const EmailBuilder = () => {
const emailEditorRef = useRef<EditorRef>(null);
const [saving, setSaving] = useState(false);
// Save design JSON + export HTML to your backend
const handleSave = () => {
const unlayer = emailEditorRef.current?.editor;
if (!unlayer) return;
setSaving(true);
unlayer.exportHtml(async (data) => {
try {
const response = await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
design: data.design, // Save this — needed to edit later
html: data.html, // The rendered HTML output
}),
});
if (!response.ok) throw new Error('Save failed');
console.log('Saved successfully');
} catch (err) {
console.error('Save error:', err);
} finally {
setSaving(false);
}
});
};
// Load a saved design when editor is ready
const onReady: EmailEditorProps['onReady'] = async (unlayer) => {
try {
const response = await fetch('/api/templates/123');
if (response.ok) {
const saved = await response.json();
unlayer.loadDesign(saved.design); // Pass the saved design JSON
}
} catch (err) {
console.log('No saved design, starting blank');
}
};
return (
<div>
<button onClick={handleSave} disabled={saving}>
{saving ? 'Saving...' : 'Save'}
</button>
<EmailEditor
ref={emailEditorRef}
onReady={onReady}
options={{
projectId: 123456, // Dashboard > Project > Settings
displayMode: 'email',
}}
/>
</div>
);
};
export default EmailBuilder;Your backend should accept and return:
typescript
// POST /api/templates — save
{ design: object, html: string }
// GET /api/templates/:id — load
{ design: object, html: string, updatedAt: string }React Props:
| Prop | Type | Default | Description |
|---|---|---|---|
| | | All |
| | | Per-tool configuration |
| | | Theme and panel settings |
| | — | Called when editor is ready (receives |
| | — | Called when iframe loads (before ready) |
| | | Container inline styles |
| | | Minimum editor height |
bash
npm install react-email-editor --savetsx
import React, { useRef, useState } from 'react';
import EmailEditor, { EditorRef, EmailEditorProps } from 'react-email-editor';
const EmailBuilder = () => {
const emailEditorRef = useRef<EditorRef>(null);
const [saving, setSaving] = useState(false);
// 保存设计JSON并将HTML导出到后端
const handleSave = () => {
const unlayer = emailEditorRef.current?.editor;
if (!unlayer) return;
setSaving(true);
unlayer.exportHtml(async (data) => {
try {
const response = await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
design: data.design, // 保存该数据——后续编辑需要用到
html: data.html, // 渲染后的HTML输出
}),
});
if (!response.ok) throw new Error('保存失败');
console.log('保存成功');
} catch (err) {
console.error('保存错误:', err);
} finally {
setSaving(false);
}
});
};
// 编辑器就绪时加载已保存的设计
const onReady: EmailEditorProps['onReady'] = async (unlayer) => {
try {
const response = await fetch('/api/templates/123');
if (response.ok) {
const saved = await response.json();
unlayer.loadDesign(saved.design); // 传入已保存的设计JSON
}
} catch (err) {
console.log('无已保存设计,从零开始');
}
};
return (
<div>
<button onClick={handleSave} disabled={saving}>
{saving ? '保存中...' : '保存'}
</button>
<EmailEditor
ref={emailEditorRef}
onReady={onReady}
options={{
projectId: 123456, // 控制台 > 项目 > 设置
displayMode: 'email',
}}
/>
</div>
);
};
export default EmailBuilder;你的后端应支持接收和返回以下格式:
typescript
// POST /api/templates — 保存
{ design: object, html: string }
// GET /api/templates/:id — 加载
{ design: object, html: string, updatedAt: string }React属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| | | 所有 |
| | | 工具级配置 |
| | | 主题和面板设置 |
| | — | 编辑器就绪时调用(接收 |
| | — | iframe加载完成时调用(在就绪之前) |
| | | 容器内联样式 |
| | | 编辑器最小高度 |
Vue (Complete Working Example)
Vue(完整可用示例)
bash
npm install vue-email-editor --savevue
<template>
<div id="app">
<button v-on:click="handleSave" :disabled="saving">
{{ saving ? 'Saving...' : 'Save' }}
</button>
<EmailEditor ref="emailEditor" v-on:load="editorLoaded" />
</div>
</template>
<script>
import { EmailEditor } from 'vue-email-editor';
export default {
components: { EmailEditor },
data() {
return { saving: false };
},
methods: {
async editorLoaded() {
try {
const response = await fetch('/api/templates/123');
if (response.ok) {
const saved = await response.json();
this.$refs.emailEditor.editor.loadDesign(saved.design);
}
} catch (err) {
console.log('No saved design, starting blank');
}
},
handleSave() {
this.saving = true;
this.$refs.emailEditor.editor.exportHtml(async (data) => {
try {
await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: data.design, html: data.html }),
});
} catch (err) {
console.error('Save error:', err);
} finally {
this.saving = false;
}
});
},
},
};
</script>Props: , , , , , .
minHeightoptionstoolsappearancelocaleprojectIdbash
npm install vue-email-editor --savevue
<template>
<div id="app">
<button v-on:click="handleSave" :disabled="saving">
{{ saving ? '保存中...' : '保存' }}
</button>
<EmailEditor ref="emailEditor" v-on:load="editorLoaded" />
</div>
</template>
<script>
import { EmailEditor } from 'vue-email-editor';
export default {
components: { EmailEditor },
data() {
return { saving: false };
},
methods: {
async editorLoaded() {
try {
const response = await fetch('/api/templates/123');
if (response.ok) {
const saved = await response.json();
this.$refs.emailEditor.editor.loadDesign(saved.design);
}
} catch (err) {
console.log('无已保存设计,从零开始');
}
},
handleSave() {
this.saving = true;
this.$refs.emailEditor.editor.exportHtml(async (data) => {
try {
await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: data.design, html: data.html }),
});
} catch (err) {
console.error('保存错误:', err);
} finally {
this.saving = false;
}
});
},
},
};
</script>属性: 、、、、、。
minHeightoptionstoolsappearancelocaleprojectIdAngular (Complete Working Example)
Angular(完整可用示例)
bash
npm install angular-email-editor --saveModule (app.module.ts):
typescript
import { EmailEditorModule } from 'angular-email-editor';
@NgModule({ imports: [EmailEditorModule] })
export class AppModule {}Component (app.component.ts):
typescript
import { Component, ViewChild } from '@angular/core';
import { EmailEditorComponent } from 'angular-email-editor';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
@ViewChild(EmailEditorComponent)
private emailEditor: EmailEditorComponent;
saving = false;
async editorLoaded() {
try {
const response = await fetch('/api/templates/123');
if (response.ok) {
const saved = await response.json();
this.emailEditor.editor.loadDesign(saved.design);
}
} catch (err) {
console.log('No saved design');
}
}
handleSave() {
this.saving = true;
this.emailEditor.editor.exportHtml(async (data) => {
try {
await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: data.design, html: data.html }),
});
} catch (err) {
console.error('Save error:', err);
} finally {
this.saving = false;
}
});
}
}Template (app.component.html):
html
<div>
<button (click)="handleSave()" [disabled]="saving">
{{ saving ? 'Saving...' : 'Save' }}
</button>
<email-editor (loaded)="editorLoaded($event)"></email-editor>
</div>bash
npm install angular-email-editor --save模块(app.module.ts):
typescript
import { EmailEditorModule } from 'angular-email-editor';
@NgModule({ imports: [EmailEditorModule] })
export class AppModule {}组件(app.component.ts):
typescript
import { Component, ViewChild } from '@angular/core';
import { EmailEditorComponent } from 'angular-email-editor';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
@ViewChild(EmailEditorComponent)
private emailEditor: EmailEditorComponent;
saving = false;
async editorLoaded() {
try {
const response = await fetch('/api/templates/123');
if (response.ok) {
const saved = await response.json();
this.emailEditor.editor.loadDesign(saved.design);
}
} catch (err) {
console.log('无已保存设计');
}
}
handleSave() {
this.saving = true;
this.emailEditor.editor.exportHtml(async (data) => {
try {
await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: data.design, html: data.html }),
});
} catch (err) {
console.error('保存错误:', err);
} finally {
this.saving = false;
}
});
}
}模板(app.component.html):
html
<div>
<button (click)="handleSave()" [disabled]="saving">
{{ saving ? '保存中...' : '保存' }}
</button>
<email-editor (loaded)="editorLoaded($event)"></email-editor>
</div>Plain JavaScript
原生JavaScript
html
<script src="https://editor.unlayer.com/embed.js"></script>
<div id="editor-container" style="height: 700px;"></div>
<script>
unlayer.init({
id: 'editor-container',
projectId: 1234,
displayMode: 'email',
});
unlayer.addEventListener('editor:ready', async function () {
try {
const response = await fetch('/api/templates/123');
if (response.ok) {
const saved = await response.json();
unlayer.loadDesign(saved.design);
}
} catch (err) {
console.log('Starting blank');
}
});
function handleSave() {
unlayer.exportHtml(async function (data) {
await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: data.design, html: data.html }),
});
});
}
</script>html
<script src="https://editor.unlayer.com/embed.js"></script>
<div id="editor-container" style="height: 700px;"></div>
<script>
unlayer.init({
id: 'editor-container',
projectId: 1234,
displayMode: 'email',
});
unlayer.addEventListener('editor:ready', async function () {
try {
const response = await fetch('/api/templates/123');
if (response.ok) {
const saved = await response.json();
unlayer.loadDesign(saved.design);
}
} catch (err) {
console.log('从零开始');
}
});
function handleSave() {
unlayer.exportHtml(async function (data) {
await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: data.design, html: data.html }),
});
});
}
</script>Multiple Editor Instances
多编辑器实例
Use instead of :
createEditor()init()javascript
const editor1 = unlayer.createEditor({ id: 'email-editor', displayMode: 'email' });
const editor2 = unlayer.createEditor({ id: 'web-editor', displayMode: 'web' });
editor1.loadDesign(emailDesign);
editor2.loadDesign(webDesign);使用替代:
createEditor()init()javascript
const editor1 = unlayer.createEditor({ id: 'email-editor', displayMode: 'email' });
const editor2 = unlayer.createEditor({ id: 'web-editor', displayMode: 'web' });
editor1.loadDesign(emailDesign);
editor2.loadDesign(webDesign);Common Mistakes
常见错误
| Mistake | Fix |
|---|---|
| Container too small | Minimum 1024px wide x 700px tall. Editor fills its container. |
| Calling methods before ready | Always wait for |
Using | Use |
Missing | Get it from Dashboard > Project > Settings |
| Only saving HTML, not design JSON | Always save both — design JSON lets users edit later |
| No loading state while saving | Disable the save button to prevent double saves |
| Pinning a non-existent package version | Run |
| 错误 | 修复方案 |
|---|---|
| 容器尺寸过小 | 最小尺寸为1024px宽 x 700px高。编辑器会填充其所在容器。 |
| 在就绪前调用方法 | 始终等待 |
使用 | 改用 |
缺少 | 从控制台 > 项目 > 设置中获取 |
| 仅保存HTML,未保存设计JSON | 务必同时保存两者——设计JSON支持后续编辑 |
| 保存时无加载状态 | 禁用保存按钮以防止重复保存 |
| 锁定不存在的包版本 | 锁定前先运行 |
Troubleshooting
故障排除
| Error | Cause | Fix |
|---|---|---|
| Editor shows blank white space | Container div has 0 height | Set explicit height: |
| Script not loaded or wrong | Check |
| Accessed before mount | Only use inside |
| Design doesn't load | Malformed JSON | Validate JSON, check |
| 错误 | 原因 | 修复方案 |
|---|---|---|
| 编辑器显示空白 | 容器div高度为0 | 设置明确高度: |
| 脚本未加载或 | 检查 |
| 在挂载前访问 | 仅在 |
| 设计无法加载 | JSON格式错误 | 验证JSON,检查 |