告别邮件乱码:Vue-Email中PlainText渲染模式的深度实践指南
【免费下载链接】vue-email 💌 Write email templates with vue 项目地址: https://gitcode.com/gh_mirrors/vu/vue-email
为什么你的邮件在客户端显示一团糟?
当用户投诉"邮件在Outlook里全是HTML标签"或"手机邮件只有代码没有内容"时,90%的概率是PlainText渲染模式未正确配置。Vue-Email作为基于Vue的邮件模板引擎,提供了强大的HTML+PlainText双模式渲染能力,但开发者往往忽视了纯文本模式的精细化配置,导致在不支持HTML的邮件客户端中出现格式混乱、链接失效、图片占位符残留等问题。
本文将通过3个核心配置、5种实用场景和2套完整代码模板,帮助你彻底掌握Vue-Email中PlainText渲染的最佳实践,确保你的邮件在任何客户端都能完美呈现。
一、PlainText渲染的底层工作原理
Vue-Email的PlainText渲染基于html-to-text库实现,通过以下流程将Vue组件转换为纯文本:
核心实现位于packages/render/src/shared/render.ts:
// 关键代码片段
export async function render<T extends Component>(
component: T,
props?: ExtractComponentProps<T>,
options?: Options
) {
const App = createSSRApp(component, props || {})
const markup = await renderToString(App)
if (options?.plainText) {
// 启用PlainText模式时执行转换
return convert(markup, {
selectors: plainTextSelectors, // 应用选择器规则
...(options?.plainText === true ? options.htmlToTextOptions : {})
})
}
// HTML模式处理...
}
二、三大核心配置项完全解析
1. 基础启用配置
最简单的启用方式只需在渲染选项中添加plainText: true:
import { render } from '@vue-email/render'
import MyEmailTemplate from './MyEmailTemplate.vue'
const plainTextContent = await render(MyEmailTemplate, {
userName: 'John Doe'
}, {
plainText: true // 启用纯文本渲染
})
此配置会应用默认的选择器规则(定义在plain-text-selectors.ts):
// 默认选择器规则
export const plainTextSelectors: SelectorDefinition[] = [
{ selector: "img", format: "skip" }, // 跳过图片
{ selector: "#__vue-email-preview", format: "skip" }, // 跳过预览区域
{ selector: "a", options: { linkBrackets: false } } // 链接不显示括号
]
2. 自定义转换规则
通过htmlToTextOptions可以覆盖默认转换行为,常用配置包括:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| wordwrap | number | 80 | 每行最大字符数,超过自动换行 |
| selectors | SelectorDefinition[] | 内置规则 | 自定义标签处理方式 |
| formatters | Record<string, Formatter> | 内置格式化器 | 自定义元素格式化逻辑 |
| linkBrackets | boolean | false | 链接是否显示为 [文本](URL) 格式 |
示例:配置长文本自动换行和链接显示格式
const plainTextContent = await render(MyEmailTemplate,
{ /* props */ },
{
plainText: true,
htmlToTextOptions: {
wordwrap: 60, // 移动设备优化,每行60字符
selectors: [
...plainTextSelectors, // 继承默认规则
{
selector: 'a',
options: {
linkBrackets: true, // 链接显示为 [文本](url)
ignoreHref: false // 不忽略href属性
}
}
]
}
}
)
3. 高级选择器规则定制
通过自定义选择器,你可以精确控制每个HTML元素的转换方式。Vue-Email支持的选择器配置包括:
// 选择器规则结构
interface SelectorDefinition {
selector: string; // CSS选择器
format?: 'inline' | 'block' | 'skip' | 'empty'; // 格式化方式
options?: {
leadingLineBreaks?: number; // 前导换行符数量
trailingLineBreaks?: number; // 尾随换行符数量
baseUrl?: string; // 相对链接的基础URL
// 更多格式化选项...
};
}
实用示例:为标题和段落设置不同的行距
{
selectors: [
{ selector: 'h1', options: { leadingLineBreaks: 2, trailingLineBreaks: 1 } },
{ selector: 'h2', options: { leadingLineBreaks: 1, trailingLineBreaks: 1 } },
{ selector: 'p', options: { leadingLineBreaks: 1, trailingLineBreaks: 1 } },
{ selector: '.footer', options: { leadingLineBreaks: 2 } }
]
}
三、五种实战场景解决方案
1. 带图片的欢迎邮件
问题:默认配置会完全跳过图片,导致纯文本邮件中丢失图片描述
解决方案:使用alt属性作为图片替代文本
<!-- 邮件模板中 -->
<template>
<div>
<h1>Welcome to Our Service</h1>
<!-- 为图片添加有意义的alt属性 -->
<img src="/logo.png" alt="[Our Service Logo]" class="logo">
<p>Hello {{ userName }}, thanks for signing up!</p>
</div>
</template>
// 渲染配置
{
plainText: true,
htmlToTextOptions: {
selectors: [
...plainTextSelectors,
// 自定义图片处理规则
{
selector: 'img',
format: 'inline',
options: {
// 使用alt属性作为图片替代文本
img: { includeAltIfMissing: true }
}
}
]
}
}
纯文本输出效果:
Welcome to Our Service
[Our Service Logo]
Hello John Doe, thanks for signing up!
2. 包含按钮的行动召唤邮件
问题:HTML按钮在纯文本中无法显示,导致CTA失效
解决方案:为按钮添加专用类名并自定义转换规则
<template>
<div>
<p>Click the button below to verify your email:</p>
<a href="https://example.com/verify?token=xyz" class="btn-primary">
Verify Email
</a>
</div>
</template>
// 渲染配置
{
plainText: true,
htmlToTextOptions: {
selectors: [
...plainTextSelectors,
{
selector: 'a.btn-primary',
options: {
leadingLineBreaks: 2,
trailingLineBreaks: 2,
// 为按钮链接添加强调标记
linkBrackets: true,
format: (elem, walk, options) => {
const text = walk(elem.children, options);
const href = elem.attribs.href;
return `===== ${text} =====\n${href}\n`;
}
}
}
]
}
}
纯文本输出效果:
Click the button below to verify your email:
===== Verify Email =====
https://example.com/verify?token=xyz
3. 多列布局的产品推荐邮件
问题:HTML多列布局在纯文本中会错乱
解决方案:使用自定义分隔符和换行规则
// 渲染配置
{
plainText: true,
htmlToTextOptions: {
selectors: [
...plainTextSelectors,
{ selector: '.product-column', options: { leadingLineBreaks: 1 } },
{ selector: '.product-title', options: { bold: true } },
{ selector: '.product-price', options: { italic: true } },
// 添加列分隔符
{
selector: '.column-divider',
format: 'inline',
options: {
format: () => '\n------------------------\n'
}
}
]
}
}
4. 包含代码块的技术通知邮件
问题:代码块在纯文本中格式丢失
解决方案:保留缩进和使用等宽字体标记
{
plainText: true,
htmlToTextOptions: {
selectors: [
...plainTextSelectors,
{
selector: 'pre',
options: {
leadingLineBreaks: 2,
trailingLineBreaks: 2,
// 为代码块添加标记
format: (elem, walk) => {
const code = walk(elem.children, options);
return `\n[代码开始]\n${code}\n[代码结束]\n`;
}
}
},
{ selector: 'code', options: { preserveNewlines: true } }
]
}
}
5. 带表格数据的报表邮件
问题:HTML表格在纯文本中难以阅读
解决方案:使用等宽对齐和分隔线
{
plainText: true,
htmlToTextOptions: {
wordwrap: 120, // 增加行宽以容纳表格
selectors: [
...plainTextSelectors,
{ selector: 'table', options: { leadingLineBreaks: 2 } },
{ selector: 'th', options: { bold: true, padding: 2 } },
{ selector: 'tr', options: { trailingLineBreaks: 1 } },
{ selector: 'td', options: { padding: 2 } }
]
}
}
四、两套完整代码模板
模板一:交易确认邮件
Vue组件(TransactionEmail.vue):
<template>
<div>
<h1>Order Confirmation #{{ orderId }}</h1>
<section class="order-details">
<h2>Order Details</h2>
<p>Date: {{ orderDate }}</p>
<p>Total: ${{ totalAmount.toFixed(2) }}</p>
</section>
<section class="products">
<h2>Items Purchased</h2>
<div v-for="product in products" :key="product.id" class="product-item">
<p><strong>{{ product.name }}</strong> - ${{ product.price.toFixed(2) }}</p>
<p>Quantity: {{ product.quantity }}</p>
</div>
</section>
<section class="shipping">
<h2>Shipping Information</h2>
<p>{{ shippingAddress.street }}</p>
<p>{{ shippingAddress.city }}, {{ shippingAddress.state }} {{ shippingAddress.zip }}</p>
</section>
<a href="https://example.com/view-order?{{ orderId }}" class="btn-view-order">
View Order Details
</a>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
interface Product {
id: number
name: string
price: number
quantity: number
}
interface Address {
street: string
city: string
state: string
zip: string
}
defineProps<{
orderId: string
orderDate: string
totalAmount: number
products: Product[]
shippingAddress: Address
}>()
</script>
渲染代码(render-email.ts):
import { render } from '@vue-email/render'
import TransactionEmail from './TransactionEmail.vue'
import { plainTextSelectors } from '@vue-email/render/dist/shared/plain-text-selectors'
export async function generateTransactionEmail(orderData: any) {
// 生成HTML版本
const htmlContent = await render(TransactionEmail, orderData)
// 生成PlainText版本
const plainTextContent = await render(TransactionEmail, orderData, {
plainText: true,
htmlToTextOptions: {
wordwrap: 80,
selectors: [
...plainTextSelectors,
{ selector: 'h1', options: { leadingLineBreaks: 2, trailingLineBreaks: 1, bold: true } },
{ selector: 'h2', options: { leadingLineBreaks: 2, trailingLineBreaks: 1, bold: true } },
{ selector: '.product-item', options: { leadingLineBreaks: 1 } },
{
selector: '.btn-view-order',
options: {
leadingLineBreaks: 3,
trailingLineBreaks: 1,
linkBrackets: true,
format: (elem, walk) => {
const text = walk(elem.children);
const url = elem.attribs.href;
return `===== ${text} =====\n${url}`;
}
}
}
]
}
})
return { htmlContent, plainTextContent }
}
模板二:密码重置邮件
<template>
<div>
<h1>Password Reset Request</h1>
<p>Hello {{ userName }},</p>
<p>We received a request to reset your password. If you didn't make this request, you can safely ignore this email.</p>
<p>To reset your password, click the link below:</p>
<a :href="resetUrl" class="reset-link">Reset Password</a>
<p>This link will expire in {{ expiresIn }} minutes.</p>
<div class="support-info">
<p>Need help? Contact our support team at support@example.com</p>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue'
defineProps<{
userName: string
resetUrl: string
expiresIn: number
}>()
</script>
渲染配置:
{
plainText: true,
htmlToTextOptions: {
selectors: [
...plainTextSelectors,
{ selector: 'h1', options: { bold: true, leadingLineBreaks: 2, trailingLineBreaks: 2 } },
{ selector: '.reset-link', options: { leadingLineBreaks: 2, trailingLineBreaks: 2, linkBrackets: true } },
{ selector: '.support-info', options: { leadingLineBreaks: 3 } }
]
}
}
五、常见问题与解决方案
Q1: 如何在PlainText中保留表格结构?
A: 使用wordwrap和padding选项:
{
wordwrap: 120,
selectors: [
{ selector: 'table', options: { leadingLineBreaks: 2 } },
{ selector: 'td', options: { padding: [0, 2, 0, 2] } } // [上,右,下,左]
]
}
Q2: 如何处理复杂的嵌套列表?
A: 配置列表缩进:
{
selectors: [
{ selector: 'ul', options: { leadingLineBreaks: 1 } },
{ selector: 'ol', options: { leadingLineBreaks: 1 } },
{ selector: 'li', options: { bullet: '* ', indentation: 2 } }
]
}
Q3: 如何自定义链接显示格式?
A: 使用自定义格式化函数:
{
selector: 'a',
format: (elem, walk) => {
const text = walk(elem.children);
const url = elem.attribs.href;
return `${text}: ${url}`; // 输出 "文本: URL" 格式
}
}
六、最佳实践总结
- 始终提供双模式渲染 - 同时生成HTML和PlainText版本,确保所有客户端兼容性
- 自定义选择器规则 - 针对不同邮件类型创建专用的选择器配置
- 测试多种客户端 - 重点测试Gmail、Outlook、iOS邮件和Android邮件
- 保留关键信息 - 确保所有链接、按钮和重要数据在PlainText中可见
- 优化文本布局 - 使用空行和分隔符提高可读性
- 使用语义化HTML - 合理使用标题、段落和列表标签帮助转换器生成更好的结构
通过遵循这些指南,你将能够充分利用Vue-Email的PlainText渲染能力,构建出在任何邮件客户端都能完美显示的专业邮件。
附录:完整配置选项参考
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| plainText | boolean | false | 是否启用纯文本渲染 |
| htmlToTextOptions.wordwrap | number | 80 | 每行最大字符数 |
| htmlToTextOptions.selectors | SelectorDefinition[] | 默认规则 | 自定义元素处理规则 |
| htmlToTextOptions.preserveNewlines | boolean | false | 是否保留原始换行符 |
| htmlToTextOptions.baseUrl | string | undefined | 解析相对URL的基础地址 |
| htmlToTextOptions.linkBrackets | boolean | false | 是否用括号包裹链接 |
| htmlToTextOptions.formatters | Record<string, Formatter> | {} | 自定义格式化函数 |
要获取更多信息,请查看官方源代码:
【免费下载链接】vue-email 💌 Write email templates with vue 项目地址: https://gitcode.com/gh_mirrors/vu/vue-email
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



