邮件模版
This commit is contained in:
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user