Files
tougao_web/src/components/page/mailboxMouldDetail.vue
2026-03-24 13:38:11 +08:00

417 lines
15 KiB
Vue

<template>
<div class="detail-container">
<header class="action-bar">
<div class="left">
<el-button type="text" icon="el-icon-back" @click="goBack" class="back-btn">{{ $t('mailboxMouldDetail.back') }}</el-button>
<el-divider direction="vertical"></el-divider>
<span class="page-title">{{ isEditMode ? $t('mailboxMouldDetail.editTemplate') : $t('mailboxMouldDetail.createTemplate') }}</span>
</div>
<div class="right">
<el-button size="mini" @click="goBack">{{ $t('mailboxMouldDetail.cancel') }}</el-button>
<el-button
type="primary"
size="mini"
icon="el-icon-document-checked"
@click="handleSave"
:loading="saveLoading"
:disabled="saveLoading || journalLoading"
:loading-text="$t('mailboxMouldDetail.loading')"
>
{{ $t('mailboxMouldDetail.save') }}
</el-button>
</div>
</header>
<main class="editor-layout" v-loading="journalLoading" :element-loading-text="$t('mailboxMouldDetail.loading')">
<aside class="config-aside scroll-panel" v-show="!journalLoading">
<el-form ref="detailForm" label-position="top" :model="form" :rules="rules" size="mini" class="compact-form">
<div class="section-title"><i class="el-icon-setting"></i> {{ $t('mailboxMouldDetail.basicInfo') }}</div>
<el-form-item prop="title" :label="$t('mailboxMouldDetail.templateTitle')">
<el-input v-model="form.title" :placeholder="$t('mailboxMouldDetail.templateTitlePlaceholder')"></el-input>
</el-form-item>
<el-form-item prop="journalId" :label="$t('mailboxMouldDetail.journal')">
<el-select v-model="form.journalId" filterable style="width: 100%">
<el-option v-for="j in journalList" :key="j.journal_id" :label="j.title" :value="String(j.journal_id)" />
</el-select>
</el-form-item>
<el-form-item prop="scene" :label="$t('mailboxMouldDetail.templateType')">
<el-select v-model="form.scene" style="width: 100%">
<el-option :label="$t('mailboxMouldDetail.sceneInviteSubmission')" value="invite_submission"></el-option>
<el-option :label="$t('mailboxMouldDetail.scenePromoteCitation')" value="promote_citation"></el-option>
<el-option :label="$t('mailboxMouldDetail.sceneGeneralThanks')" value="general_thanks"></el-option>
</el-select>
</el-form-item>
<el-row :gutter="8">
<el-col :span="14">
<el-form-item prop="lang" :label="$t('mailboxMouldDetail.languageConfig')">
<el-radio-group v-model="form.lang" class="full-width-radio">
<el-radio-button label="en">EN</el-radio-button>
<el-radio-button label="zh">ZH</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item prop="version" :label="$t('mailboxMouldDetail.version')">
<el-input v-model="form.version" :placeholder="$t('mailboxMouldDetail.versionPlaceholder')"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-divider></el-divider>
<div class="section-title"><i class="el-icon-cpu"></i> {{ $t('mailboxMouldDetail.variablesJson') }}</div>
<el-form-item class="no-m-b">
<el-input
type="textarea"
:rows="6"
v-model="form.variables"
class="dark-json-input"
:placeholder="$t('mailboxMouldDetail.variablesPlaceholder')"
></el-input>
</el-form-item>
<div class="status-box">
<span>{{ $t('mailboxMouldDetail.activeStatus') }}</span>
<el-switch v-model="form.is_active" :active-value="1" :inactive-value="0"></el-switch>
</div>
</el-form>
</aside>
<section class="main-editor" v-show="!journalLoading">
<el-card shadow="never" class="editor-card">
<div class="subject-input-wrapper">
<div class="subject-label">{{ $t('mailboxMouldDetail.emailSubject') }}:</div>
<el-input
v-model="form.subject"
size="small"
:placeholder="$t('mailboxMouldDetail.emailSubjectPlaceholder')"
class="subject-inner-input"
></el-input>
</div>
<div class="body-editor-container">
<div class="subject-label" style="margin-bottom: 10px;">{{ $t('mailboxMouldDetail.emailBody') }}:</div>
<TmrEmailEditor
v-model="form.body"
placeholder=""
/>
<!-- <CkeditorMail v-model="form.body" /> -->
</div>
</el-card>
</section>
</main>
</div>
</template>
<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',
saveTemplate: 'api/mail_template/saveTemplate'
};
export default {
name: 'mailboxMouldDetail',
components: { CkeditorMail,TmrEmailEditor },
data() {
return {
journalLoading: true,
journalList: [],
saveLoading: false,
// 防抖:避免重复点击“保存”导致多次请求
saveDebounceTimer: null,
rules: {
journalId: [{ required: true, message: this.$t('mailboxMouldDetail.rulesJournal'), trigger: 'change' }],
scene: [{ required: true, message: this.$t('mailboxMouldDetail.rulesScene'), trigger: 'change' }],
lang: [{ required: true, message: this.$t('mailboxMouldDetail.rulesLanguage'), trigger: 'change' }],
title: [{ required: true, message: this.$t('mailboxMouldDetail.rulesTitle'), trigger: 'blur' }],
version: [{ required: true, message: this.$t('mailboxMouldDetail.rulesVersion'), trigger: 'blur' }]
},
form: {
journalId: '',
scene: 'invite_submission',
lang: 'en',
version: '1.0.0',
title: '',
subject: '',
body: '',
variables: '',
is_active: 1
}
};
},
computed: {
isEditMode() {
const q = this.$route && this.$route.query ? this.$route.query : {};
return !!(q.template_id || q.id);
}
},
created() {
this.loadJournals();
},
beforeDestroy() {
if (this.saveDebounceTimer) {
clearTimeout(this.saveDebounceTimer);
this.saveDebounceTimer = null;
}
},
methods: {
loadJournals() {
this.journalLoading = true;
const q = this.$route && this.$route.query ? this.$route.query : {};
const fromRouteJournalId = q.journal_id || q.journalId || '';
this.$api
.post(API.getAllJournal, { username: localStorage.getItem('U_name') })
.then(res => {
const list = res || [];
const mapped = (Array.isArray(list) ? list : []).map(j => ({
journal_id: j.journal_id || j.id,
title: j.title || j.name || ''
}));
this.journalList = mapped;
if (fromRouteJournalId) {
const has = mapped.some(j => String(j.journal_id) === String(fromRouteJournalId));
if (has) this.form.journalId = String(fromRouteJournalId);
}
if (!this.form.journalId && mapped.length > 0) this.form.journalId = String(mapped[0].journal_id);
})
.catch(() => {
this.journalList = [];
})
.then(() => {
const q = this.$route && this.$route.query ? this.$route.query : {};
const templateId = q.template_id || q.id || '';
if (templateId) {
return this.loadTemplate(String(templateId));
}
return null;
})
.finally(() => {
this.journalLoading = false;
});
},
loadTemplate(templateId) {
return this.$api.post(API.getTemplate, { template_id: String(templateId) }).then(res => {
if (!res || res.code !== 0) return;
const data = (res && res.data) || {};
const t = data.template || data.detail || data || {};
if (t.journal_id != null) this.form.journalId = String(t.journal_id);
if (t.scene != null) this.form.scene = String(t.scene);
if (t.language != null) this.form.lang = String(t.language).toLowerCase();
if (t.title != null) this.form.title = String(t.title);
if (t.subject != null) this.form.subject = String(t.subject);
if (t.variables_json != null) this.form.variables = String(t.variables_json);
if (t.version != null) this.form.version = String(t.version);
if (t.is_active != null) this.form.is_active = Number(t.is_active) === 1 ? 1 : 0;
const bodyHtml = t.body_html != null ? t.body_html : (t.body != null ? t.body : '');
this.form.body = String(bodyHtml || '');
});
},
goBack() {
const q = (this.$route && this.$route.query) || {};
if (q.from_auto_promotion === '1') {
this.$router.push({ path: '/autoPromotion' });
return;
}
this.$router.push({ path: '/mailboxMould' });
},
handleSave() {
// 若已在保存中,直接忽略重复点击
if (this.saveLoading) return;
// 防抖:连续点击只触发最后一次
if (this.saveDebounceTimer) {
clearTimeout(this.saveDebounceTimer);
}
this.saveDebounceTimer = setTimeout(() => {
this.saveDebounceTimer = null;
this._doSave();
}, 600);
}
,
async _doSave() {
const formRef = this.$refs.detailForm;
const validatePromise =
formRef && formRef.validate
? new Promise(resolve => formRef.validate(ok => resolve(ok)))
: Promise.resolve(true);
const ok = await validatePromise;
if (!ok) return;
if (!this.form.subject) {
this.$message.warning(this.$t('mailboxMouldDetail.rulesSubject'));
return;
}
if (!this.form.body) {
this.$message.warning(this.$t('mailboxMouldDetail.rulesBody'));
return;
}
this.saveLoading = true;
try {
const q = this.$route.query;
const templateId = q.template_id || q.id || '';
const bodyHtml = this.form.body || '';
const bodyText = bodyHtml.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
const params = {
journal_id: String(this.form.journalId || ''),
scene: String(this.form.scene || 'invite_submission'),
language: String(this.form.lang || 'en'),
title: String(this.form.title || ''),
subject: String(this.form.subject || ''),
body_html: bodyHtml,
body_text: bodyText || '',
variables_json: String(this.form.variables || ''),
version: String(this.form.version || '1.0.0'),
is_active: this.form.is_active === 1 ? '1' : '0'
};
if (templateId) params.template_id = String(templateId);
const res = await this.$api.post(API.saveTemplate, params);
if (res && res.code === 0) {
this.$message.success(this.$t('mailboxMouldDetail.saveSuccess'));
this.goBack();
} else {
this.$message.error((res && res.msg) || this.$t('mailboxMouldDetail.saveFail'));
}
} catch (e) {
this.$message.error(this.$t('mailboxMouldDetail.saveFail'));
} finally {
this.saveLoading = false;
}
}
}
};
</script>
<style scoped>
/* 容器锁定 */
.detail-container { height: 100vh; display: flex; flex-direction: column; background: #f0f2f5; overflow: hidden; }
/* 顶部栏高度压缩 */
.action-bar {
height: 40px;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
border-bottom: 1px solid #dcdfe6;
flex-shrink: 0;
}
.action-bar .left { display: flex; align-items: center; min-width: 0; }
.page-title { font-size: 13px; font-weight: bold; color: #333; }
.page-subject {
margin-left: 10px;
font-size: 12px;
color: #666;
max-width: 520px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.editor-layout { flex: 1; display: flex; overflow: hidden; background: #f0f2f5; }
/* 左侧边栏 - 占比 2 (约 20%-25%) */
.config-aside {
width: 280px;
background: #fff;
border-right: 1px solid #dcdfe6;
padding: 15px;
flex-shrink: 0;
display: flex;
flex-direction: column;
}
.section-title { font-size: 12px; font-weight: bold; color: #409EFF; margin-bottom: 12px; display: flex; align-items: center; gap: 5px; }
.el-divider--horizontal { margin: 15px 0; }
/* 右侧编辑区 - 占比 8 */
.main-editor {
flex: 1;
padding: 12px;
display: flex;
flex-direction: column;
min-width: 0;
}
.editor-card {
height: 100%;
display: flex;
flex-direction: column;
border: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.editor-card /deep/ .el-card__body {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 !important;
overflow: hidden;
}
/* 邮件主题行优化 */
.subject-input-wrapper {
display: flex;
align-items: center;
padding: 10px 15px;
background: #fafafa;
border-bottom: 1px solid #eee;
gap: 10px;
}
.subject-label { font-size: 12px; font-weight: bold; color: #666; white-space: nowrap; }
.subject-inner-input{
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.subject-inner-input /deep/ .el-input__inner { border: transparent; background: transparent; font-weight: 500; font-size: 14px; }
.subject-inner-input /deep/ .el-input__inner:focus { background: #fff; border-color: #dcdfe6; }
/* 编辑器容器撑满 */
.body-editor-container { flex: 1; overflow: hidden; padding: 10px 15px; }
.body-editor-container /deep/ .ck-editor { height: 100%; display: flex; flex-direction: column; }
.body-editor-container /deep/ .ck-editor__main { flex: 1; overflow: auto; }
/* 状态切换栏 */
.status-box {
margin-top: auto;
padding-top: 15px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #666;
}
/* 黑色背景 JSON 输入框 */
.dark-json-input /deep/ .el-textarea__inner {
background: #1e1e1e;
color: #9cdcfe;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 12px;
line-height: 1.5;
border: 1px solid #333;
}
/* 滚动条 */
.scroll-panel { overflow-y: auto; }
.scroll-panel::-webkit-scrollbar { width: 4px; }
.scroll-panel::-webkit-scrollbar-thumb { background: #ccc; border-radius: 2px; }
/* 表单紧凑微调 */
.detail-container /deep/ .el-form-item--mini.el-form-item { margin-bottom: 12px; }
.detail-container /deep/ .el-form--label-top .el-form-item__label { padding: 0 0 4px; font-size: 12px; color: #999; }
</style>