tijiao
This commit is contained in:
@@ -19,8 +19,8 @@ const service = axios.create({
|
||||
// baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换
|
||||
// baseURL: 'http://www.tougao.com/', //测试本地 记得切换
|
||||
// baseURL: 'http://192.168.110.110/tougao/public/index.php/',
|
||||
baseURL: '/api', //本地
|
||||
// baseURL: '/', //正式
|
||||
// baseURL: '/api', //本地
|
||||
baseURL: '/', //正式
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -916,17 +916,38 @@ colTitle: 'Template title',
|
||||
startConfig: 'Start auto promotion configuration',
|
||||
notConfigured: 'Not configured',
|
||||
searchPlaceholder: 'Name / Email',
|
||||
statusAll: 'All status',
|
||||
searchBtn: 'Search',
|
||||
index: 'No.',
|
||||
taskName: 'Task Name',
|
||||
taskParams: 'Parameters',
|
||||
templateIdLabel: 'Template ID',
|
||||
styleIdLabel: 'Style ID',
|
||||
deliveryStats: 'Delivery Stats',
|
||||
totalCount: 'Total',
|
||||
sentCount: 'Sent',
|
||||
failCount: 'Failed',
|
||||
bounceCount: 'Bounce',
|
||||
noDeliveryIssue: 'No delivery issues',
|
||||
deliveryIssue: 'Delivery issues',
|
||||
completedText: 'Completed',
|
||||
expertInfo: 'Expert Info',
|
||||
templateStyle: 'Template / Style',
|
||||
stylePrefix: 'Style',
|
||||
runAt: 'Execution time',
|
||||
status: 'Status',
|
||||
state0: 'Draft',
|
||||
state1: 'Running',
|
||||
state2: 'Paused',
|
||||
state3: 'Completed',
|
||||
state4: 'Cancelled',
|
||||
state5: 'Preparing',
|
||||
paused: 'Paused',
|
||||
toRun: 'To run',
|
||||
operation: 'Operation',
|
||||
preview: 'Preview',
|
||||
previewAction: 'Preview',
|
||||
editAction: 'Edit',
|
||||
enable: 'Enable',
|
||||
pause: 'Pause',
|
||||
previewEditTitle: 'Preview and edit promotion email',
|
||||
@@ -943,9 +964,50 @@ colTitle: 'Template title',
|
||||
configUpdated: 'Configuration updated',
|
||||
pauseSuccess: 'Paused',
|
||||
enableSuccess: 'Enabled',
|
||||
operationFailed: 'Operation failed',
|
||||
selectTaskForLogs: 'Please select a task first (click the view icon in the list)',
|
||||
pushLogTitle: 'Task list',
|
||||
logRefresh: 'Refresh',
|
||||
taskLogStateFilter: 'Status',
|
||||
taskLogStateAll: 'All',
|
||||
taskLogState0: 'Pending',
|
||||
taskLogState1: 'Sent',
|
||||
taskLogState2: 'Failed',
|
||||
taskLogState3: 'Bounced',
|
||||
taskLogState4: 'Cancelled',
|
||||
logColExpert: 'Expert',
|
||||
logColSendTime: 'Sent at',
|
||||
logColPreparedAt: 'Prepared at',
|
||||
logColStatus: 'Status',
|
||||
logColAction: 'Action',
|
||||
emptyLogs: 'No logs',
|
||||
viewFailureReason: 'Reason',
|
||||
editLogTip: 'Edit',
|
||||
deleteLogTip: 'Delete',
|
||||
previewLogTip: 'Preview',
|
||||
logFieldAffiliation: 'Affiliation',
|
||||
logFieldSubject: 'Subject',
|
||||
logFieldSendTime: 'Sent at',
|
||||
logFieldExecutionTime: 'Executed at',
|
||||
saveDetail: 'Save',
|
||||
failureReasonTitle: 'Failure reason',
|
||||
saveDetailSuccess: 'Saved',
|
||||
saveDetailFailed: 'Save failed',
|
||||
logDetailLoadFailed: 'Failed to load detail',
|
||||
logIdMissing: 'Missing log id',
|
||||
logAlreadySent: 'This record is no longer pending; switched to preview',
|
||||
deleteLogConfirm: 'Delete this log entry?',
|
||||
tipTitle: 'Tip',
|
||||
deleteLogSuccess: 'Deleted',
|
||||
deleteLogFailed: 'Delete failed',
|
||||
noFailureReason: 'No failure reason',
|
||||
deletedSuccess: 'Deleted',
|
||||
mockPromotionSubject: 'Promotion for {journal}',
|
||||
mockPromotionContent: '<p>Dear {name},</p><p>Check out our latest journal updates...</p>'
|
||||
},
|
||||
tmrEmailEditor: {
|
||||
preview: 'Preview',
|
||||
placeholder: 'Please enter email content'
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -901,17 +901,38 @@ const zh = {
|
||||
startConfig: '立即开始期刊自动推广配置',
|
||||
notConfigured: '尚未配置',
|
||||
searchPlaceholder: '姓名 / 邮箱',
|
||||
statusAll: '全部状态',
|
||||
searchBtn: '搜索',
|
||||
index: '序号',
|
||||
taskName: '任务名称',
|
||||
taskParams: '参数内容',
|
||||
templateIdLabel: '模板ID',
|
||||
styleIdLabel: '风格ID',
|
||||
deliveryStats: '投递统计',
|
||||
totalCount: '总量',
|
||||
sentCount: '已发送',
|
||||
failCount: '失败',
|
||||
bounceCount: '退信',
|
||||
noDeliveryIssue: '无发送异常',
|
||||
deliveryIssue: '投递异常',
|
||||
completedText: '已完成',
|
||||
expertInfo: '专家信息',
|
||||
templateStyle: '模板 / 风格',
|
||||
stylePrefix: '风格',
|
||||
runAt: '执行时间',
|
||||
status: '状态',
|
||||
state0: '草稿',
|
||||
state1: '运行中',
|
||||
state2: '暂停',
|
||||
state3: '完成',
|
||||
state4: '取消',
|
||||
state5: '准备',
|
||||
paused: '已暂停',
|
||||
toRun: '待执行',
|
||||
operation: '操作',
|
||||
preview: '查看预览',
|
||||
previewAction: '预览',
|
||||
editAction: '编辑',
|
||||
enable: '开启',
|
||||
pause: '暂停',
|
||||
previewEditTitle: '预览并修改推广邮件',
|
||||
@@ -928,9 +949,50 @@ const zh = {
|
||||
configUpdated: '配置已更新',
|
||||
pauseSuccess: '已暂停',
|
||||
enableSuccess: '已开启',
|
||||
operationFailed: '操作失败',
|
||||
selectTaskForLogs: '请先选择一条任务(点击列表「查看」图标)',
|
||||
pushLogTitle: '任务列表',
|
||||
logRefresh: '刷新',
|
||||
taskLogStateFilter: '发送状态',
|
||||
taskLogStateAll: '全部',
|
||||
taskLogState0: '待发送',
|
||||
taskLogState1: '已发送',
|
||||
taskLogState2: '失败',
|
||||
taskLogState3: '退信',
|
||||
taskLogState4: '取消',
|
||||
logColExpert: '专家信息',
|
||||
logColSendTime: '发送时间',
|
||||
logColPreparedAt: '预处理完成时间',
|
||||
logColStatus: '状态',
|
||||
logColAction: '操作',
|
||||
emptyLogs: '暂无日志',
|
||||
viewFailureReason: '原因',
|
||||
editLogTip: '编辑',
|
||||
deleteLogTip: '删除',
|
||||
previewLogTip: '预览',
|
||||
logFieldAffiliation: '单位/机构',
|
||||
logFieldSubject: '主题',
|
||||
logFieldSendTime: '发送时间',
|
||||
logFieldExecutionTime: '执行时间',
|
||||
saveDetail: '保存',
|
||||
failureReasonTitle: '失败原因',
|
||||
saveDetailSuccess: '保存成功',
|
||||
saveDetailFailed: '保存失败',
|
||||
logDetailLoadFailed: '加载详情失败',
|
||||
logIdMissing: '缺少日志 ID',
|
||||
logAlreadySent: '该记录已非待发送状态,已切换为预览',
|
||||
deleteLogConfirm: '确定删除该条发送记录?',
|
||||
tipTitle: '提示',
|
||||
deleteLogSuccess: '删除成功',
|
||||
deleteLogFailed: '删除失败',
|
||||
noFailureReason: '暂无失败原因',
|
||||
deletedSuccess: '已删除',
|
||||
mockPromotionSubject: '自动推广:{journal}',
|
||||
mockPromotionContent: '<p>亲爱的 {name},</p><p>请查看我们最新的期刊更新...</p>'
|
||||
},
|
||||
tmrEmailEditor: {
|
||||
preview: '预览效果',
|
||||
placeholder: '请输入邮件内容'
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
:selectedStyleName="selectedStyleName"
|
||||
:saving="saving"
|
||||
:title="`${$t('autoPromotion.journalManage')}: ${wizardJournal ? wizardJournal.title : ''}`"
|
||||
@open-template-selector="showTemplateDialog = true"
|
||||
@open-template-selector="openTemplateSelector"
|
||||
@cancel="showWizardDialog = false"
|
||||
@confirm="saveWizardConfig"
|
||||
/>
|
||||
@@ -114,6 +114,8 @@
|
||||
:visible.sync="showTemplateDialog"
|
||||
:journalId="wizardJournal ? wizardJournal.journal_id : ''"
|
||||
:journalLabel="wizardJournal ? wizardJournal.title : ''"
|
||||
:initial-style-id="templateDialogInitialStyleId"
|
||||
:initial-template-id="templateDialogInitialTemplateId"
|
||||
:return-source="'autoPromotion'"
|
||||
@confirm="handleTemplateApply"
|
||||
@close-all-dialogs="closeAllDialogs"
|
||||
@@ -148,6 +150,8 @@ export default {
|
||||
selectedTemplateThumbHtml: '',
|
||||
selectedTemplateName: '',
|
||||
selectedStyleName: '',
|
||||
templateDialogInitialStyleId: '',
|
||||
templateDialogInitialTemplateId: '',
|
||||
templateNameMap: {},
|
||||
allJournals: []
|
||||
};
|
||||
@@ -156,6 +160,16 @@ export default {
|
||||
this.fetchPromotionJournals();
|
||||
},
|
||||
methods: {
|
||||
openTemplateSelector() {
|
||||
// 更换模板时优先回显当前已选 style/template;没有则由选择器回落到第一项
|
||||
this.templateDialogInitialStyleId = this.wizardConfig && this.wizardConfig.defaultStyleId
|
||||
? String(this.wizardConfig.defaultStyleId)
|
||||
: '';
|
||||
this.templateDialogInitialTemplateId = this.wizardConfig && this.wizardConfig.defaultTemplateId
|
||||
? String(this.wizardConfig.defaultTemplateId)
|
||||
: '';
|
||||
this.showTemplateDialog = true;
|
||||
},
|
||||
closeAllDialogs() {
|
||||
// 进入“新增模板/跳转列表页”前,先关闭当前页所有弹窗,减少卡顿
|
||||
this.showWizardDialog = false;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,9 @@ export default {
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
id: { type: String, default: () => 'tiny-' + +new Date() },
|
||||
showSelectTemplateButton: { type: Boolean, default: false }
|
||||
showSelectTemplateButton: { type: Boolean, default: false },
|
||||
// 仅在需要的页面开启“安全初始化内容”
|
||||
useSafeInitContent: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -125,7 +127,7 @@ autoInlineStyles(htmlContent) {
|
||||
height: 500,
|
||||
// 核心配置:允许所有 HTML 标签和样式,这对比邮件模板至关重要
|
||||
valid_children: '+body[tr],+body[thead],+body[tbody],+body[table],+body[style],+p[style],+div[style]',
|
||||
extended_valid_elements: 'style,meta,title',
|
||||
extended_valid_elements: 'div[*],table[*],tr[*],td[*],img[*],style',
|
||||
custom_elements: 'style,meta,title',
|
||||
verify_html: false, // 关闭 HTML 校验,防止自动删掉你的邮件结构
|
||||
forced_root_block: '', // 停止自动包裹 <p> 标签
|
||||
@@ -162,7 +164,10 @@ autoInlineStyles(htmlContent) {
|
||||
init_instance_callback: (editor) => {
|
||||
this.editor = editor;
|
||||
if (this.value) {
|
||||
editor.setContent(this.value);
|
||||
const initContent = this.useSafeInitContent
|
||||
? this.prepareContentForEditor(this.value)
|
||||
: this.value;
|
||||
editor.setContent(initContent);
|
||||
}
|
||||
|
||||
// 监听内容变化
|
||||
@@ -172,6 +177,21 @@ autoInlineStyles(htmlContent) {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 假设 rawHtml 是你那一大串完整的 HTML
|
||||
prepareContentForEditor(rawHtml) {
|
||||
// 1. 分离出 Head 及其之前的部分 (包含 doctype, html, head)
|
||||
const headMatch = rawHtml.match(/([\s\S]*?)<body([\s\S]*?)>/i);
|
||||
this.originalFullHead = headMatch ? headMatch[0] : ''; // 备份头部
|
||||
|
||||
// 2. 提取 Body 内部内容,并将 <body> 换成 <div>
|
||||
// 这样 TinyMCE 就不会因为 fullpage 插件失效而删掉它
|
||||
let bodyContent = rawHtml.replace(/([\s\S]*?)<body([\s\S]*?)>/i, '')
|
||||
.replace(/<\/body>([\s\S]*?)<\/html>/i, '');
|
||||
|
||||
// 返回给编辑器的内容:用一个特殊的 div 包裹
|
||||
return `<div id="mail-body-temp" style="margin:0;padding:0;">${bodyContent}</div>`;
|
||||
},
|
||||
// 获取经过行内样式转换后的内容
|
||||
getInlinedContent() {
|
||||
if (!this.editor) return '';
|
||||
|
||||
174
src/components/page/components/email/TmrEmailEditor.vue
Normal file
174
src/components/page/components/email/TmrEmailEditor.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div class="tmr-editor-container">
|
||||
<div class="editor-header">
|
||||
<span class="title"></span>
|
||||
<button @click="showModal = true" class="preview-trigger-btn">
|
||||
<i class="icon-eye"></i> {{ $t('tmrEmailEditor.preview') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
:value="plainText"
|
||||
@input="handleInput"
|
||||
:placeholder="resolvedPlaceholder"
|
||||
class="tmr-textarea"
|
||||
></textarea>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-if="showModal" class="tmr-modal-mask" @click.self="showModal = false">
|
||||
<div class="tmr-modal-container">
|
||||
<div class="modal-header">
|
||||
<span>{{ $t('tmrEmailEditor.preview') }}</span>
|
||||
<button class="close-btn" @click="showModal = false">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="common_tmr_email_box" v-html="htmlContentForPreview"></div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TmrEmailEditor',
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModal: false // 控制弹窗显示
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
resolvedPlaceholder() {
|
||||
return this.placeholder || this.$t('tmrEmailEditor.placeholder');
|
||||
},
|
||||
// 剥离外壳给 textarea 显示
|
||||
plainText() {
|
||||
if (!this.value) return '';
|
||||
const regex = /<div class="common_tmr_email_box">([\s\S]*?)<\/div>/i;
|
||||
const match = this.value.match(regex);
|
||||
const content = (match && match[1]) ? match[1] : this.value;
|
||||
return content.replace(/<br\s*\/?>/gi, '\n');
|
||||
},
|
||||
// 提取内部内容用于预览
|
||||
htmlContentForPreview() {
|
||||
const regex = /<div class="common_tmr_email_box">([\s\S]*?)<\/div>/i;
|
||||
const match = this.value.match(regex);
|
||||
return match ? match[1] : this.value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInput(e) {
|
||||
const rawValue = e.target.value;
|
||||
const htmlContent = rawValue.replace(/\n/g, '<br>');
|
||||
const finalResult = `<div class="common_tmr_email_box">${htmlContent}</div>`;
|
||||
this.$emit('input', finalResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 1. 基础布局 */
|
||||
.tmr-editor-container { width: 100%; position: relative; }
|
||||
.editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.preview-trigger-btn {
|
||||
background: #f0f2f5;
|
||||
border: 1px solid #dcdfe6;
|
||||
padding: 5px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
.preview-trigger-btn:hover { color: #409eff; border-color: #c6e2ff; background: #ecf5ff; }
|
||||
|
||||
.tmr-textarea {
|
||||
width: 100%;
|
||||
min-height: 70vh;
|
||||
padding: 15px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
box-sizing: border-box;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* 2. 弹窗动画 */
|
||||
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
|
||||
.fade-enter, .fade-leave-to { opacity: 0; }
|
||||
|
||||
/* 3. 弹窗样式 */
|
||||
.tmr-modal-mask {
|
||||
position: fixed;
|
||||
z-index: 9998;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tmr-modal-container {
|
||||
width: 1000px;
|
||||
max-width: 90%;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-btn { border: none; background: none; font-size: 20px; cursor: pointer; color: #909399; }
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
min-height: 60vh;
|
||||
overflow-y: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 10px 20px;
|
||||
border-top: 1px solid #eee;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 确保预览区域样式正确渲染 */
|
||||
.common_tmr_email_box {
|
||||
word-break: break-all;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
@@ -96,7 +96,11 @@
|
||||
|
||||
<div class="body-editor-container">
|
||||
<div class="subject-label" style="margin-bottom: 10px;">{{ $t('mailboxMouldDetail.emailBody') }}:</div>
|
||||
<CkeditorMail v-model="form.body" />
|
||||
<TmrEmailEditor
|
||||
v-model="form.body"
|
||||
placeholder=""
|
||||
/>
|
||||
<!-- <CkeditorMail v-model="form.body" /> -->
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
@@ -106,7 +110,7 @@
|
||||
|
||||
<script>
|
||||
import CkeditorMail from '@/components/page/components/email/CkeditorMail.vue';
|
||||
|
||||
import TmrEmailEditor from '@/components/page/components/email/TmrEmailEditor.vue';
|
||||
const API = {
|
||||
getAllJournal: 'api/Article/getJournal',
|
||||
getTemplate: 'api/mail_template/getTemplate',
|
||||
@@ -115,7 +119,7 @@ const API = {
|
||||
|
||||
export default {
|
||||
name: 'mailboxMouldDetail',
|
||||
components: { CkeditorMail },
|
||||
components: { CkeditorMail,TmrEmailEditor },
|
||||
data() {
|
||||
return {
|
||||
journalLoading: true,
|
||||
|
||||
Reference in New Issue
Block a user