Files
tougao_web/src/components/page/mailboxSend.vue
2026-03-17 09:19:35 +08:00

878 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div>
<div class="crumbs">
<el-button type="text" icon="el-icon-arrow-left" @click="goBackInbox" class="back-inbox-btn">
{{ $t('mailboxSend.backToInbox') }}
</el-button>
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-message"></i> {{ $t('mailboxSend.title') }}
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container" style="padding-top: 0px;">
<div class="mail_shuru" style="position: relative; display: flex; align-items: center;">
<span class="mail_tit">{{ $t('mailboxSend.to') }}</span>
<div style="flex: 1; display: flex; flex-wrap: wrap; align-items: center;">
<div class="selected-tags" v-if="queryMail.sendnamelist && queryMail.sendnamelist.length" style="display: inline-block;">
<el-tag
v-for="(item, index) in queryMail.sendnamelist"
:key="item._uid != null ? item._uid : 'to-' + index"
closable
@close="removeRecipient(index)"
type="info"
size="small"
style="margin-right: 6px;"
>
{{ item.name }}
</el-tag>
</div>
<el-autocomplete
ref="autocompleteTo"
class="mail_inp"
v-model="toInput"
:fetch-suggestions="fetchUserSuggestions"
:trigger-on-focus="false"
placeholder=""
@select="handleSelectUser"
@blur="handleToBlur"
style="width: 200px; flex: 1;"
>
<!-- <i slot="suffix" class="el-icon-plus" @click="handleSetLibrary" style="cursor: pointer; color: #006699; font-weight: bold; line-height: 40px; font-size: 16px;"></i> -->
<template slot-scope="{ item }">
<div class="name">
{{ item.value }} <span style="padding: 4px; color: #ccc;">|</span> {{ item.realname }}
</div>
</template>
</el-autocomplete>
</div>
<!-- <span class="sel_liby" @click="handleSetLibrary" style="position: static; margin-left: 10px;">
<i class="el-icon-plus"></i>{{ $t('mailboxSend.selectFromLibrary') }}
</span> -->
</div>
<div class="mail_shuru">
<span class="mail_tit">{{ $t('mailboxSend.subject') }}</span>
<el-input v-model="queryMail.sendtitle" class="mail_inp" ></el-input>
</div>
<!-- <div class="mail_shuru" style="position: relative; display: flex; align-items: center;">
<span class="mail_tit">{{ $t('mailboxSend.cc') }}</span>
<div style="flex: 1; display: flex; flex-wrap: wrap; align-items: center;">
<div class="selected-tags" v-if="ccList && ccList.length" style="display: inline-block;">
<el-tag
v-for="(item, index) in ccList"
:key="item._uid != null ? item._uid : 'cc-' + index"
closable
@close="removeCc(index)"
type="info"
size="small"
style="margin-right: 6px;"
>
{{ item.name }}
</el-tag>
</div>
<el-autocomplete
ref="autocompleteCc"
class="mail_inp"
v-model="ccInput"
:fetch-suggestions="fetchUserSuggestions"
:trigger-on-focus="false"
placeholder=""
@select="handleSelectCc"
@blur="handleCcBlur"
style="width: 200px; flex: 1;"
>
<template slot-scope="{ item }">
<div class="name">
{{ item.value }} <span style="padding: 4px; color: #ccc;">|</span> {{ item.realname }}
</div>
</template>
</el-autocomplete>
</div>
</div> -->
<div class="mail-editor-wrap" style="margin: 20px 0 0 0;">
<!-- 仿阿里邮箱工具栏中的 源代码编辑 / 退出源码编辑 -->
<emailCkeditor
ref="myTextEditor"
v-model="queryMail.content"
:is-source-mode="isSourceMode"
:source-content.sync="sourceContent"
:source-rows="16"
:source-placeholder="$t('mailboxSend.sourcePlaceholder')"
:show-select-template-button="true"
@onSelectTemplate="showTemplateDialog = true"
/>
</div>
<div class="mail-footer-bar" :style="{ left: footerBarLeft }">
<div class="sender-info">
<span class="sender-label">{{ $t('mailboxSend.sender') }}</span>
<span class="sender-content">
{{ userMes.realname }} &lt;{{ userMes.email }}&gt;
</span>
</div>
<div class="action-buttons">
<el-button type="primary" icon="el-icon-s-promotion" :loading="sendLoading" :disabled="sendLoading" @click="handleSend">
{{ $t('mailboxSend.send') }}
</el-button>
<!-- <el-button size="medium" :loading="saveDraftLoading" :disabled="saveDraftLoading" @click="handleSaveDraft">{{ $t('mailboxSend.saveDraft') }}</el-button> -->
</div>
</div>
</div>
<!-- 选择通讯录 -->
<el-dialog :title="$t('mailboxSend.selectUser')" :visible.sync="Librarybox" width="900px" :close-on-click-modal="false">
<el-button type="primary" style="margin: 0 0 15px 0;" icon="el-icon-plus" @click="mailLiyAll()" v-if="LibrarySelection!=''">
{{ $t('mailboxSend.batchSelection') }}
</el-button>
<el-table :data="mail_List" border ref="multipleTable" header-cell-class-name="table-header" @selection-change="handleSelectionChange" :empty-text="$t('mailboxSend.emptyText')">
<el-table-column type="selection" width="40" align="center" :selectable='checkboxSelect'></el-table-column>
<el-table-column :label="$t('mailboxSend.email')">
<template slot-scope="scope">
<b class="el-icon-check" v-if="scope.row.select_mark==1" style="color:#f74c4c;margin-right: 5px;font-weight: bold;"></b>
{{scope.row.email}}
</template>
</el-table-column>
<el-table-column prop="account" :label="$t('mailboxSend.account')"></el-table-column>
<el-table-column :label="$t('mailboxSend.operation')" width="110" align="center">
<template slot-scope="scope">
<el-button plain type="primary" icon="el-icon-plus" @click="mailLibAdd(scope.row)">
{{ $t('mailboxSend.selectBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination background layout="total, prev, pager, next" :current-page="queryLibry.pageIndex" :page-size="queryLibry.pageSize"
:total="link_TotalLibry" @current-change="handlePageChangeLibry"></el-pagination>
</div>
</el-dialog>
<template-selector-dialog v-if="showTemplateDialog"
:visible.sync="showTemplateDialog"
@confirm="handleTemplateApply"
/>
</div>
</template>
<script>
import emailCkeditor from '@/components/page/components/email/CkeditorMail.vue'
import TemplateSelectorDialog from '@/components/page/components/email/TemplateSelectorDialog.vue'
import 'multi-items-input'
import 'multi-items-input/dist/multi-items-input.css'
import bus from '../common/bus'
export default {
data() {
return {
baseUrl: this.Common.baseUrl,
mediaUrl: this.Common.mediaUrl,
that: this,
userMes: {},
queryMail: {
sendname: [],
sendnamelist: [],
sendtitle: '',
sendcc: '',
content: ''
},
mail_List: [],
fileL_atta: [],
fol_low: [{
value: '1',
label: 'Template 1'
}],
TempForm: {
board: 0
},
queryLibry: {
pageIndex: 1,
pageSize: 15,
username: ''
},
LibrForm: {},
LibrarySelection: [],
showTemplateDialog: false,
Librarybox: false,
link_TotalLibry: 0,
isSourceMode: false,
// 源码模式下的完整 HTML保留 DOCTYPE、html、行内样式等发送时若在源码模式则用此项
sourceContent: '',
toInput: '',
toSelecting: false,
nextToUid: 1,
ccList: [],
ccInput: '',
ccSelecting: false,
nextCcUid: 1,
collapseValue: localStorage.getItem('collapse'),
sendLoading: false,
saveDraftLoading: false,
};
},
components: {
emailCkeditor,
TemplateSelectorDialog,
},
computed: {
footerBarLeft() {
const collapsed = this.collapseValue === true || this.collapseValue === 'true';
return (collapsed ? 64: 260) + 'px';
},
},
watch: {
collapseValue: {
handler() {
// 监听 collapseValue 变化footerBarLeft 通过 computed 自动更新
},
immediate: true,
},
},
created() {
this.getDate();
},
mounted() {
bus.$on('collapse-content', (msg) => {
this.collapseValue = msg;
});
},
beforeDestroy() {
bus.$off('collapse-content');
},
methods: {
handleTemplateApply(htmlContent) {
// 假设你使用的是 TinyMCE
if (window.tinymce && window.tinymce.activeEditor) {
// 建议:如果你想保留已有内容,用 insertContent
// 如果想彻底更换模板,用 setContent。
window.tinymce.activeEditor.setContent(htmlContent);
this.$message.success('Template applied successfully!');
}
},
// 切换富文本 / 源代码编辑模式(源码用 sourceContent 保留完整 HTML可自由来回切换
toggleSourceMode() {
if (this.isSourceMode) {
// 退出源码 -> 先用 juice 做样式内联,再同步给富文本展示
if (this.$refs.myTextEditor && this.$refs.myTextEditor.handleInlining) {
const finalHtml = this.$refs.myTextEditor.handleInlining();
this.sourceContent = finalHtml || this.sourceContent || '';
this.queryMail.content = this.sourceContent;
} else {
this.queryMail.content = this.sourceContent || '';
}
} else {
// 进入源码:仅当源码区为空时才从富文本同步,避免覆盖你已编辑的完整 HTML
if (this.sourceContent === '' || this.sourceContent == null) {
this.sourceContent = this.queryMail.content || '';
}
}
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,
});
},
// 收件人自动补全 - 搜索用户
fetchUserSuggestions(queryString, cb) {
if (!queryString) {
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([]));
},
// 收件人输入框失焦:输入内容失去焦点则自动成为一条收件人数据(排除点击下拉项时)
handleToBlur(e) {
// 若失焦是因为点击了下拉建议区域,不执行任何逻辑,让 click 触发 select否则点击会选不中
const related = e && e.relatedTarget;
if (related && typeof related.closest === 'function' && related.closest('.el-autocomplete-suggestion')) {
return;
}
const value = (this.toInput || '').trim();
setTimeout(() => {
if (this.toSelecting) {
this.toSelecting = false;
return;
}
if (!value) return;
if (value.indexOf('@') === -1) {
this.toInput = '';
return;
}
const exists = (this.queryMail.sendnamelist || []).some(item => item.name === value);
if (!exists) {
this.queryMail.sendnamelist.push({
id: null,
name: value,
_uid: this.nextToUid++,
});
this.queryMail.sendname = (this.queryMail.sendnamelist || []).map(i => i.name);
}
this.toInput = '';
}, 150);
},
// 选中某个用户作为收件人
handleSelectUser(user) {
this.toSelecting = true;
if (!user || !user.email) return;
const email = user.email;
// 去重
const exists = (this.queryMail.sendnamelist || []).some(item => item.name === email);
if (!exists) {
this.queryMail.sendnamelist.push({
id: user.user_id || user.id || null,
name: email,
_uid: this.nextToUid++,
});
// 同步简单数组
this.queryMail.sendname = (this.queryMail.sendnamelist || []).map(i => i.name);
}
// 清空输入框,便于继续输入下一个
this.toInput = '';
},
// 删除已选收件人
removeRecipient(index) {
if (index < 0) return;
this.queryMail.sendnamelist.splice(index, 1);
this.queryMail.sendname = (this.queryMail.sendnamelist || []).map(i => i.name);
},
// CC 输入框失焦:与 To 相同逻辑,输入失去焦点则自动成为一条数据(排除点击下拉项时)
handleCcBlur(e) {
const related = e && e.relatedTarget;
if (related && typeof related.closest === 'function' && related.closest('.el-autocomplete-suggestion')) {
return;
}
const value = (this.ccInput || '').trim();
setTimeout(() => {
if (this.ccSelecting) {
this.ccSelecting = false;
return;
}
if (!value) return;
if (value.indexOf('@') === -1) {
this.ccInput = '';
return;
}
const exists = (this.ccList || []).some(item => item.name === value);
if (!exists) {
this.ccList.push({
id: null,
name: value,
_uid: this.nextCcUid++,
});
}
this.ccInput = '';
}, 150);
},
// 选中某个用户作为 CC与 To 相同逻辑)
handleSelectCc(user) {
this.ccSelecting = true;
if (!user || !user.email) return;
const email = user.email;
const exists = (this.ccList || []).some(item => item.name === email);
if (!exists) {
this.ccList.push({
id: user.user_id || user.id || null,
name: email,
_uid: this.nextCcUid++,
});
}
this.ccInput = '';
},
// 删除已选 CC
removeCc(index) {
if (index < 0) return;
this.ccList.splice(index, 1);
},
// 根据路由 j_email_id 获取发件邮箱信息,用于展示发件人
getDate() {
const jEmailId = this.$route.query.j_email_id;
if (!jEmailId) {
this.userMes = {};
return;
}
this.$api
.post('api/email_client/getOneEmail', { j_email_id: jEmailId })
.then(res => {
if (res && res.code === 0 && res.data && res.data.email) {
const email = res.data.email;
this.userMes = {
realname: email.smtp_from_name || email.smtp_user || '',
email: email.smtp_user || '',
};
} else {
this.userMes = {};
}
})
.catch(() => {
this.userMes = {};
});
},
// 获取通讯录数据
getLibary() {
this.$api
.post('api/Reviewer/researchUser', {keywords: this.queryLibry.username})
.then(res => {
if (res.code == 0) {
this.mail_List = res.data.list;
this.link_TotalLibry = res.data.list.length || 0;
for (let i = 0; i < this.mail_List.length; i++) {
this.mail_List[i].select_mark = 0
for (let j = 0; j < this.queryMail.sendnamelist.length; j++) {
if (this.queryMail.sendnamelist[j].name == this.mail_List[i].email) {
this.mail_List[i].select_mark = 1 // 禁用
}
}
}
} else {
this.$message.error(res.msg);
}
})
.catch(err => {
this.$message.error(err);
});
},
// 发送邮件api/email_client/sendOneto_email 为多邮箱字符串拼接(逗号分隔),发送成功后关闭当前页并跳转收件箱
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'));
}
}).catch(() => {
self.$message.error(self.$t('mailboxSend.sendFail'));
}).finally(() => {
self.sendLoading = false;
});
},
// 保存草稿(防抖:请求中禁用按钮)
handleSaveDraft() {
if (this.saveDraftLoading) return;
this.saveDraftLoading = true;
// TODO: 调用保存草稿接口,此处仅防抖占位
this.$nextTick(() => {
setTimeout(() => {
this.saveDraftLoading = false;
}, 300);
});
},
// 选择通讯录-弹出框
handleSetLibrary() {
this.Librarybox = true
console.log(this.LibrarySelection)
},
handleSelectionChange(val) {
this.LibrarySelection = val;
},
// 通讯录禁用
checkboxSelect(row, rowIndex) {
let mar_dis = 0
for (let i = 0; i < this.queryMail.sendnamelist.length; i++) {
if (this.queryMail.sendnamelist[i].name == row.email) {
mar_dis += 1 // 禁用
} else {
mar_dis += 0 // 不禁用
}
}
if (mar_dis == 1) {
return false
} else {
return true
}
},
// 保存通讯录
mailLibAdd(e) {
this.queryMail.sendname.push(e.email)
this.queryMail.sendnamelist.push({
id: null,
name: e.email,
_uid: this.nextToUid++,
})
this.getLibary();
},
// 批量保存通讯录
mailLiyAll() {
console.log(this.LibrarySelection)
for (let i = 0; i < this.LibrarySelection.length; i++) {
this.queryMail.sendname.push(this.LibrarySelection[i].email)
this.queryMail.sendnamelist.push({
id: null,
name: this.LibrarySelection[i].email,
_uid: this.nextToUid++,
})
}
this.getLibary();
},
// 回车方框输入框
blueHandle() {
console.log('456789')
},
// ip控制 - 回车确定
selectHandle(arr, obj) {
this.queryMail.sendname = []
for (let i = 0; i < arr.length; i++) {
this.queryMail.sendname.push(arr[i].name)
}
},
// ip控制 - backspace确定
deleteHandle(obj, arr) {
this.queryMail.sendname = []
for (let i = 0; i < arr.length; i++) {
this.queryMail.sendname.push(arr[i].name)
}
},
// 分页导航-通讯录
handlePageChangeLibry(val) {
this.$set(this.queryLibry, 'pageIndex', val);
this.getLibary();
},
// 富文本编辑器
onEditorChange({
editor,
html,
text
}) {
this.content = html;
},
// 上传成功
uploadSuccess(res) {
let quill = this.$refs.myTextEditor.quill;
// 获取光标所在位置
let length = quill.getSelection().index
// 插入图片 res.upurl为服务器返回的图片地址
quill.insertEmbed(length, 'image', this.baseUrl + res.data.icon)
// 调整光标到最后
quill.setSelection(length + 1)
},
// 上传文件
upSuccess_atta(res, file) {
if (res.code == 0) {
this.queryMail.attr = [];
for (var ii in fileList) {
var url = fileList[ii].response.upurl;
this.form.attr.push('picturesAndTables/' + url);
}
} else {
this.$message.error('service error' + res.msg);
}
},
beforeupload_atta(file) {
},
removefile_atta(file, fileList) {
this.queryMail.picturesAndTables = [];
for (var ii in fileList) {
var url = fileList[ii].response.upurl;
this.queryMail.picturesAndTables.push('picturesAndTables/' + url);
}
},
uperr_atta(err) {
this.$message.error('Upload error');
},
alertlimit() {
this.$message.error('The maximum number of uploaded files has been exceeded');
},
}
};
</script>
<style scoped>
.crumbs {
display: flex;
align-items: center;
margin-bottom: 16px;
}
.back-inbox-btn {
margin-right: 12px;
padding-left: 0;
}
.mail_shuru {
font-size: 14px;
padding: 0 0 5px 0;
margin: 0 0 10px 0;
border-bottom: 1px solid #ccc;
}
.mail_tit {
width: 60px;
display: inline-block;
}
.mail_inp {
display: inline-block;
width: 100%;
margin: 0 15px 0 0;
border: 0;
}
.mail_inp ::v-deep .el-input__inner {
border: 1px solid #fff !important;
}
.up_newstyle {
margin-left: 15px;
display: inline-block;
vertical-align: middle;
}
.up_newstyle ::v-deep .el-upload--text {
background-color: #006699;
border: 1px solid #006699;
padding: 5px 15px;
}
.up_newstyle ::v-deep .el-upload__text em {
color: #fff !important;
font-size: 12px;
}
.jw-container {
margin-top: 0;
background-color: #fff !important;
display: flex;
flex-wrap: wrap;
width: 75%;
display: inline-block;
vertical-align: middle;
}
.jw-container .jw__container {
margin-top: 0;
}
.jw-container .jw__container input {
font-size: 16px;
color: #606266;
}
.jw-container .pre-input {
width: 100%;
}
.jw-container .point-container {
margin: 2px 0 1px;
}
.jw-container .point-container .label {
height: 24px;
line-height: 24px;
background: #eef9ff;
padding: 0 7px;
}
.sel_liby {
position: absolute;
top: 10px;
right: 0;
color: #006699;
font-weight: bold;
letter-spacing: -0.5px;
}
.sel_moud {
font-size: 14px;
margin: -15px 0 0 0;
text-align: right;
color: #006699;
font-weight: bold;
letter-spacing: -0.5px;
}
.sel_moud:hover,
.sel_liby:hover {
text-decoration: underline;
cursor: pointer;
}
.sel_moud i,
.sel_liby i {
margin: 0 10px 0 0;
}
.avatar-uploader-mail {
height: 0;
}
.avatar-uploader-mail ::v-deep .el-upload--text {
height: 0 !important;
border: 0;
}
/* 让输入框的边框在平时不可见,只有在交互时体现,对齐邮件系统风格 */
.mail_inp ::v-deep .el-input__inner {
border: none !important;
border-bottom: 0px solid #fff !important;
padding-right: 30px;
}
.mail_inp ::v-deep .el-input__suffix {
right: 5px;
transition: all .3s;
}
.mail_inp ::v-deep .el-input__suffix:hover {
transform: scale(1.2);
}
/* 调整容器对齐 */
.mail_shuru {
display: flex;
align-items: flex-start; /* 考虑到多行标签,对齐顶部 */
min-height: 40px;
line-height: 40px;
}
/* 底部操作栏容器 */
.mail-footer-bar {
margin-top: 20px;
padding: 10px 15px;
background-color: #f8f9fa; /* 浅灰色背景类似图1 */
border: 1px solid #ddd;
border-radius: 4px;
display: flex;
justify-content: space-between; /* 左右分布 */
align-items: center;
}
/* 发件人信息样式 */
.sender-info {
font-size: 14px;
color: #666;
}
.sender-label {
font-weight: bold;
margin-right: 10px;
}
.sender-content {
color: #006699; /* 保持你代码中的蓝色 */
}
/* 按钮组样式 */
.action-buttons {
display: flex;
gap: 10px;
}
.mail-footer-bar {
position: fixed;
bottom: 0;
right: 0;
z-index: 10;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
}
/* Quill 工具栏“源代码编辑”按钮文案 */
/* 仿阿里邮箱:源代码编辑 工具栏条 */
.editor-toolbar-bar {
border: 1px solid #dcdfe6;
border-bottom: none;
background: #fafafa;
padding: 8px 12px;
border-radius: 4px 4px 0 0;
}
.toolbar-source-btn {
color: #409eff;
padding: 0 4px;
}
.toolbar-source-btn.exit {
color: #909399;
}
.source-editor-box {
border: 1px solid #dcdfe6;
border-radius: 0 0 4px 4px;
overflow: hidden;
}
.source-editor-box .el-textarea__inner {
border: none;
border-radius: 0;
font-family: Consolas, Monaco, monospace;
font-size: 13px;
}
</style>