Initial commit
This commit is contained in:
430
app/lib/email/templates/Template1.vue
Normal file
430
app/lib/email/templates/Template1.vue
Normal file
@@ -0,0 +1,430 @@
|
||||
<template>
|
||||
<div v-if="previewMode">
|
||||
<component :is="'style'" v-html="styles" />
|
||||
<div
|
||||
class="container py-6"
|
||||
:style="{
|
||||
minHeight: '100%',
|
||||
backgroundColor: 'hsl(0, 0%, 97%)',
|
||||
fontFamily: '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif',
|
||||
}"
|
||||
>
|
||||
<table
|
||||
role="presentation"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
:style="{
|
||||
width: '100%',
|
||||
maxWidth: '100%',
|
||||
margin: '0 auto',
|
||||
}"
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table
|
||||
role="presentation"
|
||||
class="email-container gmail-fix"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
:style="{
|
||||
margin: '0 auto',
|
||||
width: '100%',
|
||||
maxWidth: '680px',
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: '16px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
overflow: 'hidden',
|
||||
}"
|
||||
>
|
||||
<tbody>
|
||||
<tr class="header-row">
|
||||
<td :style="{ padding: '0' }">
|
||||
<div
|
||||
class="header-image"
|
||||
:style="{
|
||||
background: '#f3f4f6',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '425px',
|
||||
overflow: 'hidden',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="hasValidImage"
|
||||
:src="content.imageUrl"
|
||||
alt="image"
|
||||
:style="{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
display: 'block',
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="is-flex is-justify-content-center is-align-items-center"
|
||||
:style="{
|
||||
height: '100%',
|
||||
background: 'rgba(0, 0, 0, 0.3)',
|
||||
opacity: 0.15,
|
||||
}"
|
||||
>
|
||||
<SvgIcon v-bind="{ name: 'image.svg', type: 'black', size: 180 }" />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="content.message">
|
||||
<td class="content-padding" :style="{ paddingBottom: '20px', paddingTop: '30px' ,paddingRight: '25px', paddingLeft: '25px'}">
|
||||
<div
|
||||
:style="{
|
||||
border: '1px solid #e5e7eb',
|
||||
borderRadius: '12px',
|
||||
backgroundColor: '#ffffff',
|
||||
overflow: 'hidden',
|
||||
}"
|
||||
>
|
||||
<div :style="{ padding: '15px' }">
|
||||
<div
|
||||
class="message-content"
|
||||
:style="{
|
||||
fontSize: '16px',
|
||||
margin: '0',
|
||||
color: '#374151',
|
||||
lineHeight: '1.6',
|
||||
wordBreak: 'break-word',
|
||||
}"
|
||||
>
|
||||
<div class="content" v-html="processedMessage" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasValidLinks"
|
||||
:style="{
|
||||
padding: '15px',
|
||||
paddingLeft: '35px',
|
||||
borderTop: '1px solid #e5e7eb',
|
||||
backgroundColor: '#f9fafb',
|
||||
}"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
v-for="(link, index) in validLinks"
|
||||
:key="index"
|
||||
:style="{ width: '100%', marginBottom: '8px' }"
|
||||
>
|
||||
<ul :style="{ paddingLeft: '20px', margin: 0 }">
|
||||
<li :style="{ listStyleType: 'disc' }">
|
||||
<a
|
||||
:style="{
|
||||
color: '#2563eb!important',
|
||||
fontWeight: '600',
|
||||
textDecoration: 'underline',
|
||||
display: 'inline-block',
|
||||
wordBreak: 'break-word',
|
||||
fontSize: '14px',
|
||||
}"
|
||||
target="_blank"
|
||||
:href="link"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{{ getLinkText(index) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="company-padding" :style="{ paddingBottom: '20px' }">
|
||||
<div
|
||||
class="company-info"
|
||||
:style="{
|
||||
background: 'linear-gradient(to right, #000000, #0f9b0f)',
|
||||
color: '#ffffff',
|
||||
borderRadius: '12px',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="logo-container"
|
||||
:style="{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
marginBottom: '15px',
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: '8px',
|
||||
overflow: 'hidden',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
:src="imageUrl"
|
||||
alt="Utopia Footer"
|
||||
:style="{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
display: 'block',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- <tr>
|
||||
<td
|
||||
:style="{
|
||||
backgroundColor: '#f9fafb',
|
||||
padding: '20px',
|
||||
borderTop: '1px solid #e5e7eb',
|
||||
textAlign: 'center',
|
||||
}"
|
||||
>
|
||||
<p
|
||||
:style="{
|
||||
color: '#9ca3af',
|
||||
fontSize: '11px',
|
||||
margin: '0',
|
||||
lineHeight: '1.5',
|
||||
}"
|
||||
>
|
||||
©2025 Utopia. Tất cả các quyền được bảo lưu.
|
||||
</p>
|
||||
</td>
|
||||
</tr> -->
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { imageUrl } from '@/components/marketing/email/Email.utils';
|
||||
import { computed } from "vue";
|
||||
|
||||
interface KeywordItem {
|
||||
keyword: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Template1Content {
|
||||
subject?: string;
|
||||
message?: string;
|
||||
imageUrl?: string | null;
|
||||
linkUrl?: string[];
|
||||
textLinkUrl?: string[];
|
||||
keyword?: KeywordItem[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
content: Template1Content;
|
||||
previewMode?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
previewMode: false,
|
||||
});
|
||||
|
||||
const hasValidLinks = computed(() => {
|
||||
return props.content.linkUrl && props.content.linkUrl.some((link) => link && link.trim() !== "");
|
||||
});
|
||||
|
||||
const hasValidImage = computed(() => {
|
||||
return props.content.imageUrl && props.content.imageUrl.trim() !== "";
|
||||
});
|
||||
|
||||
const replaceKeywords = (content: string): string => {
|
||||
if (!content) return "";
|
||||
|
||||
let replacedContent = content;
|
||||
|
||||
if (props.content.keyword && Array.isArray(props.content.keyword)) {
|
||||
props.content.keyword.forEach((kw) => {
|
||||
if (typeof kw !== "string" && kw.keyword && kw.value) {
|
||||
const regex = new RegExp(`\\{\\{${kw.keyword}\\}\\}`, "g");
|
||||
replacedContent = replacedContent.replace(regex, kw.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return replacedContent;
|
||||
};
|
||||
|
||||
const processedMessage = computed(() => replaceKeywords(props.content.message || ""));
|
||||
|
||||
const validLinks = computed(() => {
|
||||
if (!props.content.linkUrl || props.content.linkUrl.length === 0) return [];
|
||||
return props.content.linkUrl.filter((link) => link && link.trim() !== "");
|
||||
});
|
||||
|
||||
const getLinkText = (index: number): string => {
|
||||
return props.content.textLinkUrl && props.content.textLinkUrl[index] && props.content.textLinkUrl[index].trim() !== ""
|
||||
? props.content.textLinkUrl[index]
|
||||
: `Link ${index + 1}`;
|
||||
};
|
||||
|
||||
const styles = `
|
||||
ol{
|
||||
list-style: decimal;
|
||||
}
|
||||
li{
|
||||
list-style: disc;
|
||||
}
|
||||
.greeting-padding{
|
||||
padding: 30px 25px 20px;
|
||||
}
|
||||
|
||||
.content-padding, .company-padding {
|
||||
padding: 0px 25px 20px;
|
||||
}
|
||||
|
||||
.message-content p {
|
||||
margin: 0 0 12px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.message-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-content strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.message-content em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.message-content u {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.message-content s {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.message-content ol,
|
||||
.message-content ul {
|
||||
margin: 12px 0;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.message-content ol li,
|
||||
.message-content ul li {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.message-content a {
|
||||
color: #2563eb;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.message-content h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin: 20px 0 12px 0;
|
||||
line-height: 1.3;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.message-content h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin: 18px 0 12px 0;
|
||||
line-height: 1.3;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.message-content h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin: 16px 0 12px 0;
|
||||
line-height: 1.3;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.message-content .ql-align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.message-content .ql-align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.message-content .ql-align-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.message-content .ql-indent-1 {
|
||||
padding-left: 3em;
|
||||
}
|
||||
|
||||
.message-content .ql-indent-2 {
|
||||
padding-left: 6em;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
|
||||
.email-container {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.greeting-padding{
|
||||
padding: 20px 0 0 0 !important;
|
||||
}
|
||||
|
||||
.content-padding {
|
||||
padding: 20px 0px !important;
|
||||
}
|
||||
.company-padding {
|
||||
padding: 0px 0px 20px 0px !important;
|
||||
}
|
||||
.company-info {
|
||||
padding: 20px 10px !important;
|
||||
}
|
||||
.greeting-title{
|
||||
padding: 10px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.header-image {
|
||||
width: 100% !important;
|
||||
height: 250px !important;
|
||||
}
|
||||
.greeting-text {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
.message-text {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
.logo-container {
|
||||
width: 150px !important;
|
||||
height: 45px !important;
|
||||
}
|
||||
.company-name {
|
||||
font-size: 12px !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.contact-info {
|
||||
font-size: 11px !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
</script>
|
||||
Reference in New Issue
Block a user