邮件模版

This commit is contained in:
2026-03-13 10:20:53 +08:00
parent 3c27591fc7
commit 213ca978b7
17 changed files with 21350 additions and 744 deletions

View File

@@ -1,21 +1,28 @@
<template>
<div class="mail-container">
<div class="mail-sidebar">
<div class="p-20">
<el-button type="primary" icon="el-icon-plus" class="write-btn" @click="handleWrite">
{{ $t('mailboxCollect.writeBtn') }}
</el-button>
<div class="p-10" style="padding-left: 0px;padding-right: 0px;">
<div class="sidebar-top-actions">
<el-button type="primary" icon="el-icon-plus" class="write-btn" @click="handleWrite">
{{ $t('mailboxCollect.writeBtn') }}
</el-button>
<el-button type="success" icon="el-icon-refresh" class="receive-btn" :loading="syncLoading" @click="handleSyncInbox">
{{ $t('mailboxCollect.receiveBtn') }}
</el-button>
</div>
</div>
<ul class="folder-list">
<li :class="{ active: currentFolder === 'inbox' }" @click="switchFolder('inbox')">
<i class="el-icon-message"></i> {{ $t('mailboxCollect.inboxTab') }}
<span class="badge" v-if="queryIn.num > 0">{{ queryIn.num }}</span>
</li>
<li :class="{ active: currentFolder === 'sent' }" @click="switchFolder('sent')">
<!-- <li :class="{ active: currentFolder === 'sent' }" @click="switchFolder('sent')">
<i class="el-icon-position"></i><span style="font-size: 14px;">{{ $t('mailboxCollect.outboxTab')}}</span>
</li>
<li @click="notImplemented"><i class="el-icon-document"></i> <span style="font-size: 14px;">{{ $t('mailboxCollect.draftsTab')}}</span> </li>
<li @click="notImplemented"><i class="el-icon-delete"></i> <span style="font-size: 14px;">{{ $t('mailboxCollect.deletedTab')}}</span> </li>
-->
</ul>
<div class="sidebar-footer">
@@ -34,17 +41,17 @@
<div class="mail-list-panel" :style="{ width: listWidth + 'px' }" v-if="selectedAccount">
<div class="panel-header">
<el-input
<el-input
v-model="searchKeyword"
prefix-icon="el-icon-search"
:placeholder="$t('mailboxCollect.searchPlaceholder')"
clearable
@change="handleSearch"
></el-input>
<el-button icon="el-icon-refresh" circle :loading="syncLoading" @click="handleSyncInbox" style="margin-left: 10px;"></el-button>
<!-- <el-button icon="el-icon-refresh" circle :loading="syncLoading" @click="handleSyncInbox" style="margin-left: 10px;"></el-button> -->
</div>
<div class="list-scroll-area">
<div ref="listScrollArea" class="list-scroll-area" @scroll="onListScroll">
<template v-if="displayList.length > 0">
<div
v-for="item in displayList"
@@ -70,8 +77,13 @@
</div>
</div>
</template>
<div v-else class="empty-list-container">
<div v-if="inboxLoadingMore" class="load-more-tip">
<i class="el-icon-loading"></i> {{ $t('mailboxCollect.loadingMore') || '加载更多...' }}
</div>
<div v-else-if="displayList.length > 0 && inboxPage >= inboxTotalPages" class="load-more-tip no-more">
{{ $t('mailboxCollect.noMore') || '没有更多了' }}
</div>
<div v-else-if="displayList.length === 0" class="empty-list-container">
<div class="empty-wrapper">
<i class="el-icon-message"></i>
<p>{{ $t('mailboxCollect.emptyText') }}</p>
@@ -101,10 +113,23 @@
</div>
</div>
<el-dialog :title="$t('mailboxCollect.selectAccountTitle')" :visible.sync="accountDialogVisible" width="600px" append-to-body>
<el-dialog
:title="$t('mailboxCollect.selectAccountTitle')"
:visible.sync="accountDialogVisible"
width="600px"
append-to-body
:close-on-click-modal="false"
:show-close="false"
:before-close="handleAccountDialogBeforeClose"
>
<el-form inline style="margin-bottom: 10px;">
<el-form-item :label="$t('mailboxCollect.journal')">
<el-select v-model="accountJournalId" style="width: 260px;" @change="loadAccountsForJournal">
<el-select
v-model="accountJournalId"
style="width: 260px;"
:loading="journalLoading"
@change="loadAccountsForJournal"
>
<el-option v-for="item in journalList" :key="item.journal_id" :label="item.title" :value="item.journal_id" />
</el-select>
</el-form-item>
@@ -128,7 +153,7 @@
const API = {
getInboxList: 'api/email_client/getInboxList',
getEmailDetail: 'api/email_client/getEmailDetail',
receiveMail: 'api/Mail/receiveMail',
syncInbox: 'api/email_client/syncInbox',
getAccounts: 'api/email_client/getAccounts',
getOneEmail: 'api/email_client/getOneEmail',
getAllJournal: 'api/Journal/getAllJournal',
@@ -145,12 +170,18 @@ export default {
tableData_out: [],
detailMail: {},
queryIn: { num: 0 },
inboxPage: 1,
inboxPerPage: 20,
inboxTotalPages: 1,
inboxTotal: 0,
inboxLoadingMore: false,
listWidth: 350,
minWidth: 260,
maxWidth: 600,
selectedAccount: null,
accountDialogVisible: false,
journalList: [],
journalLoading: false,
accountJournalId: null,
accountList: [],
accountLoading: false,
@@ -201,32 +232,73 @@ export default {
this.openAccountDialog();
}
},
loadDefaultAccount() {
this.$api.post(API.getAllJournal, {}).then(res => {
const journals = (res && res.data && res.data.journals) || res.data || [];
const list = (Array.isArray(journals) ? journals : []).map(i => ({
journal_id: i.journal_id || i.id,
title: i.title || i.name || ''
}));
this.journalList = list;
if (!list.length) {
this.openAccountDialog();
return;
}
const firstJournalId = list[0].journal_id;
this.accountJournalId = firstJournalId;
this.accountLoading = true;
this.$api.post(API.getAccounts, { journal_id: firstJournalId }).then(accRes => {
this.accountLoading = false;
const accounts = accRes && accRes.data ? accRes.data : [];
if (Array.isArray(accounts) && accounts.length > 0) {
this.selectedAccount = accounts[0];
this.fetchData();
} else {
this.accountList = accounts || [];
this.openAccountDialog();
}
}).catch(() => {
this.accountLoading = false;
this.openAccountDialog();
});
}).catch(() => {
this.openAccountDialog();
});
},
loadAccountById(jEmailId) {
this.$api.post(API.getOneEmail, { j_email_id: jEmailId }).then(res => {
const email = res.data.email;
if (res.code === 0 && email) {
this.selectedAccount = email;
this.fetchData();
} else {
this.openAccountDialog();
}
}).catch(() => this.openAccountDialog());
}).catch(() => {});
},
openAccountDialog() {
this.accountDialogVisible = true;
this.loadJournals();
if (this.selectedAccount.journal_id) {
if (this.selectedAccount && this.selectedAccount.journal_id) {
this.accountJournalId = this.selectedAccount.journal_id;
this.loadAccountsForJournal(this.selectedAccount.journal_id);
}
},
loadJournals() {
this.$api.post(API.getAllJournal, {}).then(res => {
this.journalList = (res.data.journals || res.data || []).map(i => ({
journal_id: i.journal_id || i.id,
title: i.title || i.name || ''
}));
});
this.journalLoading = true;
this.$api
.post(API.getAllJournal, {})
.then(res => {
this.journalList = (res.data.journals || res.data || []).map(i => ({
journal_id: i.journal_id || i.id,
title: i.title || i.name || ''
}));
if (!this.accountJournalId && this.journalList.length > 0) {
this.accountJournalId = this.journalList[0].journal_id;
this.loadAccountsForJournal(this.accountJournalId);
}
})
.finally(() => {
this.journalLoading = false;
});
},
loadAccountsForJournal(id) {
this.accountLoading = true;
@@ -239,28 +311,65 @@ export default {
this.selectedAccount = row;
this.accountDialogVisible = false;
this.closeDetail();
// 回填到地址栏
const q = Object.assign({}, this.$route.query, {
j_email_id: row.j_email_id,
journal_id: row.journal_id
});
this.$router.replace({ path: this.$route.path, query: q });
this.fetchData();
},
fetchData() {
handleAccountDialogBeforeClose(done) {
const hasAccount = this.selectedAccount || this.$route.query.j_email_id;
if (hasAccount) {
done();
}
// 没选账号时不允许关闭
},
// 拉取收件列表支持分页page=1 时替换列表page>1 时追加;接口返回 total、page、per_page、total_pages
fetchData(page) {
if (!this.selectedAccount) return;
const isFirstPage = page === 1 || page == null;
if (isFirstPage) {
this.inboxPage = 1;
}
const params = {
j_email_id: this.selectedAccount.j_email_id,
journal_id: this.selectedAccount.journal_id,
keyword: this.searchKeyword || ''
// keyword: this.searchKeyword || '',
page: isFirstPage ? 1 : page,
per_page: this.inboxPerPage
};
if (isFirstPage) {
// 第一页不显示 loadingMore仅翻页时显示
} else {
this.inboxLoadingMore = true;
}
this.$api.post(API.getInboxList, params).then(res => {
const list = (res && res.data && (res.data.list || res.data)) || [];
this.tableData_in = (Array.isArray(list) ? list : []).map(item => ({
const data = res && res.data ? res.data : {};
const list = Array.isArray(data.list) ? data.list : (Array.isArray(data) ? data : []);
const rows = list.map(item => ({
id: item.inbox_id || item.id,
inbox_id: item.inbox_id || item.id,
email: item.from_email,
from_name: item.from_name,
subject: item.subject,
email_date: item.email_date,
email_date: item.email_date,
content: item.content_html || item.content_text || '',
state: item.is_read === 1 ? 0 : 1
}));
this.queryIn.num = this.tableData_in.length;
if (isFirstPage) {
this.tableData_in = rows;
} else {
this.tableData_in = this.tableData_in.concat(rows);
}
this.inboxPage = data.page != null ? Number(data.page) : (isFirstPage ? 1 : this.inboxPage);
this.inboxTotalPages = data.total_pages != null ? Number(data.total_pages) : 1;
this.inboxTotal = data.total != null ? Number(data.total) : this.tableData_in.length;
this.queryIn.num = this.inboxTotal;
this.inboxLoadingMore = false;
}).catch(() => {
this.inboxLoadingMore = false;
});
},
switchFolder(f) { this.currentFolder = f; this.activeMailId = null; },
@@ -281,12 +390,26 @@ export default {
this.detailLoading = false;
});
},
// 同步收件箱api/email_client/syncInbox 参数 j_email_id、journal_id均为 String完成后重新拉取 displayList
handleSyncInbox() {
if (!this.selectedAccount) return;
this.syncLoading = true;
this.$api.post(API.receiveMail, { j_email_id: this.selectedAccount.j_email_id }).then(() => {
const params = {
j_email_id: String(this.selectedAccount.j_email_id),
journal_id: String(this.selectedAccount.journal_id),
};
this.$api.post(API.syncInbox, params).then((res) => {
this.syncLoading = false;
this.fetchData();
}).catch(() => this.syncLoading = false);
if (res && res.code === 0) {
this.$message.success(this.$t('mailboxCollect.syncSuccess'));
this.fetchData();
} else {
this.$message.error((res && res.msg) || this.$t('mailboxCollect.syncFail'));
}
}).catch(() => {
this.syncLoading = false;
this.$message.error(this.$t('mailboxCollect.syncFail'));
});
},
/**
* 格式化邮件显示时间
@@ -347,7 +470,16 @@ export default {
this.selectedAccount.j_email_id
);
},
handleSearch() { this.fetchData(); },
handleSearch() { this.fetchData(1); },
onListScroll(e) {
const el = e.target;
if (!el || this.currentFolder !== 'inbox' || this.inboxLoadingMore) return;
if (this.inboxPage >= this.inboxTotalPages) return;
const threshold = 80;
if (el.scrollHeight - el.scrollTop - el.clientHeight <= threshold) {
this.fetchData(this.inboxPage + 1);
}
},
notImplemented() { this.$message.info('开发中...'); }
}
};
@@ -370,8 +502,11 @@ export default {
display: flex;
flex-direction: column;
}
.p-10 { padding: 10px; }
.p-20 { padding: 20px; }
.write-btn { width: 100%; border-radius: 8px; font-weight: bold; }
.sidebar-top-actions { display: flex; gap: 10px; }
.write-btn { flex: 1; width: auto; border-radius: 8px; font-weight: bold;padding-left: 0px;padding-right: 0px; }
.receive-btn { flex: 1; width: auto; border-radius: 8px; font-weight: bold;margin-left: 0px;padding-left: 0px;padding-right: 0px; }
.folder-list { list-style: none; padding: 0; margin: 0; flex: 1; }
.folder-list li {
padding: 12px 20px;
@@ -413,7 +548,9 @@ export default {
.send-time { font-size: 12px; color: #909399; flex-shrink: 0; }
.row-two { margin-bottom: 4px; }
.mail-subject { font-size: 13px; color: #6a7282; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mail-excerpt { font-size: 12px; color: #606266; margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.mail-excerpt { font-size: 12px; color: #606266; margin: 0; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.load-more-tip { text-align: center; padding: 12px; font-size: 12px; color: #909399; }
.load-more-tip.no-more { color: #c0c4cc; }
/* 拖拽条 */
.list-resizer { width: 4px; cursor: col-resize; border-left: 1px solid #eaeaea; transition: background 0.2s; }