This commit is contained in:
2026-03-24 13:38:11 +08:00
parent 12760aaf44
commit c6c262169d
9 changed files with 2924 additions and 773 deletions

View File

@@ -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: '/', //正式
});

View File

@@ -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'
}

View File

@@ -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: '请输入邮件内容'
}

View File

@@ -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

View File

@@ -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 '';

View 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">&times;</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>

View File

@@ -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,