自动化推广【约稿】

This commit is contained in:
2026-03-23 09:28:56 +08:00
parent f44b3910a4
commit 12760aaf44
21 changed files with 3482 additions and 559 deletions

View File

@@ -11,7 +11,7 @@
</el-breadcrumb>
</div>
<div class="container" style="padding-top: 0px;">
<div class="container" style="padding-top: 0px;" v-if="showSendEmail">
<div class="mail_shuru" style="position: relative; display: flex; align-items: center;">
<span class="mail_tit">{{ $t('mailboxSend.to') }}</span>
@@ -31,6 +31,7 @@
</div>
<el-autocomplete
v-if="!isToRecipientLimitReached()"
ref="autocompleteTo"
class="mail_inp"
v-model="toInput"
@@ -119,7 +120,14 @@
</div>
<div class="action-buttons">
<el-button type="primary" icon="el-icon-s-promotion" :loading="sendLoading" :disabled="sendLoading" @click="handleSend">
<el-button
type="primary"
icon="el-icon-s-promotion"
:loading="sendLoading"
:disabled="sendLoading"
:loading-text="$t('mailboxSend.sending')"
@click="handleSend"
>
{{ $t('mailboxSend.send') }}
</el-button>
<!-- <el-button size="medium" :loading="saveDraftLoading" :disabled="saveDraftLoading" @click="handleSaveDraft">{{ $t('mailboxSend.saveDraft') }}</el-button> -->
@@ -155,9 +163,11 @@
</div>
</el-dialog>
<template-selector-dialog v-if="showTemplateDialog"
<template-selector-dialog
v-if="showTemplateDialog"
:visible.sync="showTemplateDialog"
@confirm="handleTemplateApply"
@close-all-dialogs="closeAllDialogs"
/>
</div>
</template>
@@ -180,7 +190,8 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
sendnamelist: [],
sendtitle: '',
sendcc: '',
content: ''
content: '',
expert_id: ''
},
mail_List: [],
fileL_atta: [],
@@ -206,6 +217,8 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
sourceContent: '',
toInput: '',
// 默认只允许 1 个收件人,后续可按需动态修改
maxToRecipients: 1,
toSelecting: false,
nextToUid: 1,
ccList: [],
@@ -214,7 +227,21 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
nextCcUid: 1,
collapseValue: localStorage.getItem('collapse'),
sendLoading: false,
sendDebounceTimer: null,
saveDraftLoading: false,
selectedTemplateId: '',
selectedStyleId: '',
recipientSuggest: {
keyword: '',
pageIndex: 1,
pageSize: 10,
totalPages: 1,
loading: false,
options: [],
cb: null
},
recipientSuggestScrollHandler: null,
showSendEmail:true
};
},
components: {
@@ -225,7 +252,7 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
footerBarLeft() {
const collapsed = this.collapseValue === true || this.collapseValue === 'true';
return (collapsed ? 64: 260) + 'px';
},
}
},
watch: {
collapseValue: {
@@ -236,27 +263,164 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
},
},
created() {
this.showSendEmail=true
this.getDate();
},
activated(){
this.showSendEmail=true
},
mounted() {
bus.$on('collapse-content', (msg) => {
this.collapseValue = msg;
});
document.addEventListener('click', this.handleOutsideAutocompleteClick, true);
},
beforeDestroy() {
bus.$off('collapse-content');
this.detachRecipientSuggestScroll();
if (this.sendDebounceTimer) {
clearTimeout(this.sendDebounceTimer);
this.sendDebounceTimer = null;
}
document.removeEventListener('click', this.handleOutsideAutocompleteClick, true);
},
methods: {
handleTemplateApply(htmlContent) {
// 假设你使用的是 TinyMCE
if (window.tinymce && window.tinymce.activeEditor) {
// 建议:如果你想保留已有内容,用 insertContent
// 如果想彻底更换模板,用 setContent。
window.tinymce.activeEditor.setContent(htmlContent);
this.$message.success('Template applied successfully!');
}
},
closeAllDialogs() {
// 点击“去新增模板”后:关闭模板选择弹窗以及可能打开的其它弹窗
this.Librarybox = false;
this.showTemplateDialog = false;
},
handleOutsideAutocompleteClick(e) {
const target = e && e.target;
if (!target) return;
const inputRef = this.$refs.autocompleteTo;
const inputEl = inputRef && inputRef.$el ? inputRef.$el : null;
const inInput = inputEl && inputEl.contains(target);
const inSuggestion = typeof target.closest === 'function' && !!target.closest('.el-autocomplete-suggestion');
if (inInput || inSuggestion) return;
if (inputRef && typeof inputRef.close === 'function') {
inputRef.close();
}
if (inputRef && inputRef.$refs && inputRef.$refs.input && typeof inputRef.$refs.input.blur === 'function') {
inputRef.$refs.input.blur();
}
},
isToRecipientLimitReached() {
const max = Number(this.maxToRecipients || 0);
if (!max) return false;
return (this.queryMail.sendnamelist || []).length >= max;
},
showToRecipientLimitWarning() {
const max = Number(this.maxToRecipients || 1);
this.$message.warning(this.$t('mailboxSend.recipientLimit', { count: max }));
},
attachRecipientSuggestScroll() {
this.detachRecipientSuggestScroll();
this.$nextTick(() => {
const wraps = document.querySelectorAll('.el-autocomplete-suggestion__wrap');
if (!wraps || !wraps.length) return;
const wrap = wraps[wraps.length - 1];
const handler = () => {
if (this.recipientSuggest.loading) return;
if (this.recipientSuggest.pageIndex >= this.recipientSuggest.totalPages) return;
const threshold = 24;
const reachBottom = wrap.scrollTop + wrap.clientHeight >= wrap.scrollHeight - threshold;
if (reachBottom) {
this.loadMoreRecipientSuggestions();
}
};
wrap.addEventListener('scroll', handler);
this.recipientSuggestScrollHandler = { el: wrap, fn: handler };
});
},
detachRecipientSuggestScroll() {
const h = this.recipientSuggestScrollHandler;
if (h && h.el && h.fn) {
h.el.removeEventListener('scroll', h.fn);
}
this.recipientSuggestScrollHandler = null;
},
normalizeExpertList(list) {
if (!Array.isArray(list)) return [];
return list.map((u) => ({
...u,
value: u.email || '',
realname: u.name || u.realname || u.username || '',
expert_id: u.expert_id || u.id || ''
}));
},
async queryRecipientSuggestions(reset) {
if (this.recipientSuggest.loading) return;
const st = this.recipientSuggest;
if (!st.keyword) {
st.options = [];
if (st.cb) st.cb([]);
return;
}
if (reset) {
st.pageIndex = 1;
st.totalPages = 1;
st.options = [];
} else if (st.pageIndex >= st.totalPages) {
if (st.cb) st.cb(st.options);
return;
} else {
st.pageIndex += 1;
}
st.loading = true;
try {
const res = await this.$api.post('api/expert_manage/getList', {
major_id: '',
keyword: st.keyword,
pageIndex: st.pageIndex,
pageSize: st.pageSize
});
const data = (res && res.data) || {};
const list = this.normalizeExpertList(data.list || []);
st.totalPages = Number(data.totalPages || data.total_pages || 1) || 1;
st.options = reset ? list : st.options.concat(list);
if (st.cb) st.cb(st.options);
this.attachRecipientSuggestScroll();
} catch (e) {
if (st.cb) st.cb(st.options || []);
} finally {
st.loading = false;
}
},
loadMoreRecipientSuggestions() {
this.queryRecipientSuggestions(false);
},
handleTemplateApply(payload) {
// TemplateSelectorDialog 现在返回对象:{ html, journal_id, style_id, template_id, ... }
const html = payload && typeof payload === 'object' ? (payload.html || '') : String(payload || '');
const templateId = payload && typeof payload === 'object' ? (payload.template_id || '') : '';
const styleId = payload && typeof payload === 'object' ? (payload.style_id || '') : '';
const templateSubject = payload && typeof payload === 'object'
? ((payload.template && payload.template.subject) || payload.subject || '')
: '';
if (!html) {
this.$message.warning(this.$t('mailTemplate.noTemplateTip') || '未获取到模板内容');
return;
}
// 始终维护一份内容状态,确保源码模式/富文本模式都能回填
this.queryMail.content = html;
this.sourceContent = html;
// 优先回填 TinyMCE 实例
if (window.tinymce && window.tinymce.activeEditor && window.tinymce.activeEditor.setContent) {
window.tinymce.activeEditor.setContent(html);
}
this.selectedTemplateId = templateId ? String(templateId) : '';
this.selectedStyleId = styleId ? String(styleId) : '';
// 模板自带 subject仅当当前主题为空时自动回填
if (!this.queryMail.sendtitle && templateSubject) {
this.queryMail.sendtitle = String(templateSubject);
}
// this.$message.success(this.$t('mailboxSend.templateApplied') || 'Template applied successfully!');
},
// 切换富文本 / 源代码编辑模式(源码用 sourceContent 保留完整 HTML可自由来回切换
toggleSourceMode() {
if (this.isSourceMode) {
@@ -276,41 +440,34 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
}
this.isSourceMode = !this.isSourceMode;
},
// 返回收件箱(邮箱列表),带上 journal_id 和 j_email_id
// 返回收件箱(邮箱列表),账号信息从本地缓存读取
goBackInbox() {
const q = this.$route.query;
const query = {};
if (q.journal_id) query.journal_id = q.journal_id;
if (q.j_email_id) query.j_email_id = q.j_email_id;
this.$router.push({
path: '/mailboxCollect',
query,
this.showSendEmail=false
const currentPath = this.$route.fullPath;
// 先静默关闭当前标签,强制销毁 keep-alive 缓存,再执行跳转
bus.$emit('close_tag_by_path', {
path: currentPath,
silent: true
});
this.$nextTick(() => {
this.$router.push({ path: '/mailboxCollect' });
});
},
// 收件人自动补全 - 搜索用户
fetchUserSuggestions(queryString, cb) {
if (!queryString) {
if (this.isToRecipientLimitReached()) {
cb([]);
return;
}
this.$api
.post('api/Reviewer/researchUser', {
keywords: queryString
})
.then(res => {
if (res && res.code === 0 && res.data && res.data.list) {
const list = res.data.list.map(u => ({
...u,
value: u.email,
realname: u.realname || u.username || ''
}));
cb(list);
} else {
cb([]);
}
})
.catch(() => cb([]));
const keyword = (queryString || '').trim();
this.recipientSuggest.keyword = keyword;
this.recipientSuggest.cb = cb;
if (!keyword) {
this.detachRecipientSuggestScroll();
cb([]);
return;
}
this.queryRecipientSuggestions(true);
},
// 收件人输入框失焦:输入内容失去焦点则自动成为一条收件人数据(排除点击下拉项时)
handleToBlur(e) {
@@ -332,11 +489,17 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
}
const exists = (this.queryMail.sendnamelist || []).some(item => item.name === value);
if (!exists) {
if (this.isToRecipientLimitReached()) {
this.showToRecipientLimitWarning();
this.toInput = '';
return;
}
this.queryMail.sendnamelist.push({
id: null,
name: value,
_uid: this.nextToUid++,
});
this.queryMail.expert_id = '';
this.queryMail.sendname = (this.queryMail.sendnamelist || []).map(i => i.name);
}
this.toInput = '';
@@ -350,11 +513,17 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
// 去重
const exists = (this.queryMail.sendnamelist || []).some(item => item.name === email);
if (!exists) {
if (this.isToRecipientLimitReached()) {
this.showToRecipientLimitWarning();
this.toInput = '';
return;
}
this.queryMail.sendnamelist.push({
id: user.user_id || user.id || null,
id: user.expert_id || user.user_id || user.id || null,
name: email,
_uid: this.nextToUid++,
});
this.queryMail.expert_id = user.expert_id || user.user_id || user.id || '';
// 同步简单数组
this.queryMail.sendname = (this.queryMail.sendnamelist || []).map(i => i.name);
}
@@ -366,6 +535,9 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
if (index < 0) return;
this.queryMail.sendnamelist.splice(index, 1);
this.queryMail.sendname = (this.queryMail.sendnamelist || []).map(i => i.name);
if (!this.queryMail.sendnamelist.length) {
this.queryMail.expert_id = '';
}
},
// CC 输入框失焦:与 To 相同逻辑,输入失去焦点则自动成为一条数据(排除点击下拉项时)
handleCcBlur(e) {
@@ -415,9 +587,11 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
if (index < 0) return;
this.ccList.splice(index, 1);
},
// 根据路由 j_email_id 获取发件邮箱信息,用于展示发件人
// 根据本地缓存(优先)或路由参数获取发件邮箱信息,用于展示发件人
getDate() {
const jEmailId = this.$route.query.j_email_id;
// 1. 优先从本地缓存读取
const storedEmailId = localStorage.getItem('mailboxCollect_j_email_id');
const jEmailId = storedEmailId || this.$route.query.j_email_id;
if (!jEmailId) {
this.userMes = {};
return;
@@ -464,52 +638,82 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
});
},
// 发送邮件api/email_client/sendOneto_email 为多邮箱字符串拼接(逗号分隔),发送成功后关闭当前页并跳转收件箱
// 发送邮件api/email_client/sendTemplateStyleText
handleSend() {
// 防抖:快速连点只触发最后一次
if (this.sendLoading) return;
const toList = (this.queryMail.sendnamelist || []).map((item) => item.name).filter(Boolean);
if (!toList.length) {
this.$message.warning(this.$t('mailboxSend.validateTo'));
return;
}
if (!this.queryMail.sendtitle) {
this.$message.warning(this.$t('mailboxSend.validateSubject'));
return;
}
const journalId = this.$route.query.journal_id;
if (!journalId) {
this.$message.warning(this.$t('mailboxSend.needAccount'));
return;
}
this.sendLoading = true;
// 优先发送完整 HTMLsourceContent这样富文本编辑后也能保持 head/style/table 等结构(像阿里邮箱)
const bodyContent = this.sourceContent || (this.queryMail.content || '');
const params = {
journal_id: journalId,
to_email: toList.join(','),
subject: this.queryMail.sendtitle,
content: bodyContent,
};
const self = this;
this.$api.post('api/email_client/sendOne', params).then((res) => {
if (res && res.code === 0) {
self.$message.success(self.$t('mailboxSend.sendSuccess'));
self.queryMail.sendnamelist = [];
self.queryMail.sendtitle = '';
self.queryMail.sendcc = '';
self.ccList = [];
self.queryMail.content = '';
self.sourceContent = '';
self.fileL_atta = [];
self.goBackInbox();
} else {
self.$message.error((res && res.msg) || self.$t('mailboxSend.sendFail'));
if (this.sendDebounceTimer) clearTimeout(this.sendDebounceTimer);
this.sendDebounceTimer = setTimeout(() => {
this.sendDebounceTimer = null;
if (this.sendLoading) return;
const toList = (this.queryMail.sendnamelist || []).map((item) => item.name).filter(Boolean);
const expert_id_list = (this.queryMail.sendnamelist || []).map((item) => item.expert_id).filter(Boolean);
if (!toList.length) {
this.$message.warning(this.$t('mailboxSend.validateTo'));
return;
}
}).catch(() => {
self.$message.error(self.$t('mailboxSend.sendFail'));
}).finally(() => {
self.sendLoading = false;
});
if (!this.queryMail.sendtitle) {
this.$message.warning(this.$t('mailboxSend.validateSubject'));
return;
}
// journal_id 优先从本地缓存获取,兼容旧的路由参数
const storedJournalId = localStorage.getItem('mailboxCollect_journal_id');
const journalId = storedJournalId || this.$route.query.journal_id;
const storedEmailId = localStorage.getItem('mailboxCollect_j_email_id');
const jEmailId = storedEmailId || this.$route.query.j_email_id;
if (!journalId) {
this.$message.warning(this.$t('mailboxSend.needAccount'));
return;
}
if (!jEmailId) {
this.$message.warning(this.$t('mailboxSend.needAccount'));
return;
}
if (!this.selectedTemplateId || !this.selectedStyleId) {
this.$message.warning(this.$t('mailboxSend.selectTemplateStyleFirst'));
return;
}
this.sendLoading = true;
const params = {
journal_id: String(journalId),
template_id: String(this.selectedTemplateId),
to_email: toList.join(','),
style_id: String(this.selectedStyleId),
expert_id: expert_id_list.join(',') || '1',
j_email_id: String(jEmailId),
};
const self = this;
this.$api
.post('api/email_client/sendTemplateStyleTest', params)
.then((res) => {
if (res && res.code === 0) {
self.$message.success(self.$t('mailboxSend.sendSuccess'));
self.queryMail.sendnamelist = [];
self.queryMail.sendtitle = '';
self.queryMail.sendcc = '';
self.ccList = [];
self.queryMail.content = '';
self.sourceContent = '';
self.selectedTemplateId = '';
self.selectedStyleId = '';
self.fileL_atta = [];
self.goBackInbox();
} else {
self.$message.error((res && res.msg) || self.$t('mailboxSend.sendFail'));
}
})
.catch(() => {
self.$message.error(self.$t('mailboxSend.sendFail'));
})
.finally(() => {
self.sendLoading = false;
});
}, 600);
},
// 保存草稿(防抖:请求中禁用按钮)
handleSaveDraft() {
@@ -549,6 +753,10 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
},
// 保存通讯录
mailLibAdd(e) {
if (this.isToRecipientLimitReached()) {
this.showToRecipientLimitWarning();
return;
}
this.queryMail.sendname.push(e.email)
this.queryMail.sendnamelist.push({
id: null,
@@ -560,7 +768,15 @@ import TemplateSelectorDialog from '@/components/page/components/email/TemplateS
// 批量保存通讯录
mailLiyAll() {
console.log(this.LibrarySelection)
const max = Number(this.maxToRecipients || 0);
const current = (this.queryMail.sendnamelist || []).length;
const remain = max > 0 ? Math.max(0, max - current) : this.LibrarySelection.length;
if (remain <= 0) {
this.showToRecipientLimitWarning();
return;
}
for (let i = 0; i < this.LibrarySelection.length; i++) {
if (i >= remain) break;
this.queryMail.sendname.push(this.LibrarySelection[i].email)
this.queryMail.sendnamelist.push({
id: null,