This commit is contained in:
2026-03-11 10:14:57 +08:00
parent 68f52bed67
commit 2c2ef4e6c4
10 changed files with 1694 additions and 231 deletions

View File

@@ -1,147 +1,432 @@
<template>
<div>
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-message"></i> Mailbox list
</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<el-tabs tab-position="left" style="height: 200px;">
<el-tab-pane :label="'Mail received ( '+queryIn.num+' )'">
<div style="margin: 5px 0 20px 10px;">
<el-input v-model="queryIn.username" placeholder="Please enter name or email" style="width: 300px;margin: 0 15px 0 0;"></el-input>
<el-button type="primary" icon="el-icon-search" @click="handleSearchIn" style="">Search</el-button>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd" style="float: right;">Write Letter</el-button>
</div>
<div style="margin: 0 0 0 10px;">
<el-table :data="tableData_in" border ref="multipleTable" header-cell-class-name="table-header" empty-text="New messages (0)">
<el-table-column label="Email">
<template slot-scope="scope">
<img v-if="scope.row.state==0" src="../../assets/img/mes_op.png" alt="" style="vertical-align: text-bottom;margin: 0 6px 0 2px;">
<img v-if="scope.row.state==1" src="../../assets/img/mes_ne.png" alt="" style="vertical-align: top;margin: 0 6px 0 0;">
{{scope.row.email}}
</template>
</el-table-column>
<el-table-column prop="account" label="Account"></el-table-column>
<el-table-column prop="ctime" label="Time" width="100"></el-table-column>
<el-table-column label=" " width="100" align="center">
<template slot-scope="scope">
<el-button plain type="primary" icon="el-icon-edit" @click="handleDetail(scope.row)">Look</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination background layout="total, prev, pager, next" :current-page="queryIn.pageIndex" :page-size="queryIn.pageSize"
:total="link_TotalIn" @current-change="handlePageChangeIn"></el-pagination>
</div>
</div>
</el-tab-pane>
<el-tab-pane :label="'Mail sent ( '+queryOut.num+' )'">
<div style="margin: 5px 0 20px 10px;">
<el-input v-model="queryOut.username" placeholder="Please enter name or email" style="width: 300px;margin: 0 15px 0 0;"></el-input>
<el-button type="primary" icon="el-icon-search" @click="handleSearchOut" style="">Search</el-button>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd" style="float: right;">Write Letter</el-button>
</div>
<div style="margin: 0 0 0 10px;">
<el-table :data="tableData_out" border ref="multipleTable" header-cell-class-name="table-header" empty-text="New messages (0)">
<el-table-column prop="email" label="Email"></el-table-column>
<el-table-column prop="account" label="Account"></el-table-column>
<el-table-column prop="ctime" label="Time" width="100"></el-table-column>
<el-table-column label=" " width="100" align="center">
<template slot-scope="scope">
<el-button plain type="primary" icon="el-icon-edit" @click="handleDetail(scope.row)">Look</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination background layout="total, prev, pager, next" :current-page="queryOut.pageIndex" :page-size="queryOut.pageSize"
:total="link_TotalOut" @current-change="handlePageChangeIn"></el-pagination>
</div>
</div>
<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>
<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')">
<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">
<div class="user-card">
<el-avatar size="small" icon="el-icon-user-solid" class="user-avatar"></el-avatar>
<div class="user-detail">
<div class="user-name">{{ selectedAccount ? (selectedAccount.smtp_from_name || 'Nova Demo') : '-' }}</div>
<div class="user-email" :title="selectedAccountEmail">{{ selectedAccountEmail }}</div>
</div>
</div>
<el-button size="mini" type="text" @click="openAccountDialog" class="switch-btn">
{{ $t('mailboxCollect.changeAccountBtn')}}
</el-button>
</div>
</div>
</el-tab-pane>
</el-tabs>
<div class="mail-list-panel" :style="{ width: listWidth + 'px' }" v-if="selectedAccount">
<div class="panel-header">
<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>
</div>
</div>
<div class="list-scroll-area">
<template v-if="displayList.length > 0">
<div
v-for="item in displayList"
:key="item.id"
:class="['mail-item-box', { active: activeMailId === item.id, unread: item.state === 1 }]"
@click="selectMail(item)"
>
<div class="item-left">
<el-avatar :size="32" icon="el-icon-user-solid" class="sender-avatar"></el-avatar>
</div>
<div class="item-right">
<div class="row-one">
<span class="sender-name">{{ item.from_name || item.email }}</span>
<span class="send-time">{{ formatDisplayTime(item.email_date) }}</span>
</div>
<div class="row-two">
<span class="mail-subject">{{ item.subject || $t('mailboxCollect.noSubject') }}</span>
</div>
<div class="row-three">
<p class="mail-excerpt">{{ stripHtml(item.content || '') }}</p>
</div>
</div>
</div>
</template>
<div v-else class="empty-list-container">
<div class="empty-wrapper">
<i class="el-icon-message"></i>
<p>{{ $t('mailboxCollect.emptyText') }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="list-resizer" @mousedown="initResize" v-if="selectedAccount"></div>
<div class="mail-content-panel" v-if="selectedAccount" v-loading="detailLoading">
<template v-if="activeMailId && !detailLoading">
<div class="mail-page">
<mail-detail
v-if="detailMail"
:mailData="detailMail"
@close="closeDetail"
/>
</div>
</template>
<div v-else class="empty-state">
<div class="empty-wrapper">
<i class="el-icon-message"></i>
<p>{{ $t('mailboxCollect.selectMailTip') }}</p>
</div>
</div>
</div>
<el-dialog :title="$t('mailboxCollect.selectAccountTitle')" :visible.sync="accountDialogVisible" width="600px" append-to-body>
<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-option v-for="item in journalList" :key="item.journal_id" :label="item.title" :value="item.journal_id" />
</el-select>
</el-form-item>
</el-form>
<el-table :data="accountList" v-loading="accountLoading" size="small">
<el-table-column prop="smtp_user" :label="$t('mailboxCollect.accountColumn')" />
<el-table-column prop="smtp_from_name" :label="$t('mailboxCollect.nameColumn')" />
<el-table-column :label="$t('mailboxCollect.operation')" width="100">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="chooseAccount(scope.row)">
{{ $t('mailboxCollect.useBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
baseUrl: this.Common.baseUrl,
mediaUrl: this.Common.mediaUrl,
tableData_in: [{
state:0,
email: '123456',
account: '123456',
ctime: '123456',
},{
state:1,
email: '123456',
account: '123456',
ctime: '123456',
}],
tableData_out: [],
queryIn: {
pageIndex: 1,
pageSize: 15,
username: '',
num: 10
},
queryOut: {
pageIndex: 1,
pageSize: 15,
username: '',
num: 30
},
link_TotalIn: 0,
link_TotalOut: 0,
};
},
created() {
this.getDate();
},
methods: {
// 获取编辑列表数据
getDate() {
const API = {
getInboxList: 'api/email_client/getInboxList',
getEmailDetail: 'api/email_client/getEmailDetail',
receiveMail: 'api/Mail/receiveMail',
getAccounts: 'api/email_client/getAccounts',
getOneEmail: 'api/email_client/getOneEmail',
getAllJournal: 'api/Journal/getAllJournal',
};
import MailDetail from '../../components/page/components/email/MailDetail.vue';
export default {
data() {
return {
currentFolder: 'inbox',
searchKeyword: '',
syncLoading: false,
activeMailId: null,
tableData_in: [],
tableData_out: [],
detailMail: {},
queryIn: { num: 0 },
listWidth: 350,
minWidth: 260,
maxWidth: 600,
selectedAccount: null,
accountDialogVisible: false,
journalList: [],
accountJournalId: null,
accountList: [],
accountLoading: false,
detailLoading: false,
};
},
components: { MailDetail },
computed: {
displayList() {
return this.currentFolder === 'inbox' ? this.tableData_in : this.tableData_out;
},
selectedAccountEmail() {
return this.selectedAccount ? this.selectedAccount.smtp_user : '';
}
},
created() {
this.initAccountSelection();
},
methods: {
closeDetail() {
this.activeMailId = null;
this.detailMail = {};
},
// 拖拽逻辑
initResize(e) {
const startX = e.clientX;
const startWidth = this.listWidth;
const doDrag = (moveEvent) => {
const newWidth = startWidth + (moveEvent.clientX - startX);
if (newWidth >= this.minWidth && newWidth <= this.maxWidth) {
this.listWidth = newWidth;
}
};
const stopDrag = () => {
document.removeEventListener('mousemove', doDrag);
document.removeEventListener('mouseup', stopDrag);
document.body.style.cursor = 'default';
};
document.addEventListener('mousemove', doDrag);
document.addEventListener('mouseup', stopDrag);
document.body.style.cursor = 'col-resize';
},
initAccountSelection() {
const q = this.$route.query;
if (q.j_email_id) {
this.loadAccountById(q.j_email_id);
} else {
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());
},
openAccountDialog() {
this.accountDialogVisible = true;
this.loadJournals();
if (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 || ''
}));
});
},
loadAccountsForJournal(id) {
this.accountLoading = true;
this.$api.post(API.getAccounts, { journal_id: id }).then(res => {
this.accountLoading = false;
this.accountList = res.data || [];
});
},
chooseAccount(row) {
this.selectedAccount = row;
this.accountDialogVisible = false;
this.closeDetail();
this.fetchData();
},
fetchData() {
if (!this.selectedAccount) return;
const params = {
j_email_id: this.selectedAccount.j_email_id,
journal_id: this.selectedAccount.journal_id,
keyword: this.searchKeyword || ''
};
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 => ({
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,
content: item.content_html || item.content_text || '',
state: item.is_read === 1 ? 0 : 1
}));
this.queryIn.num = this.tableData_in.length;
});
},
switchFolder(f) { this.currentFolder = f; this.activeMailId = null; },
selectMail(item) {
this.activeMailId = item.id;
this.detailLoading = true;
const inboxId = item.inbox_id || item.id;
this.$api.post(API.getEmailDetail, { inbox_id: inboxId }).then(res => {
this.detailLoading = false;
const d = res && res.data;
if (res && res.code === 0 && d) {
this.detailMail = {
...d
};
item.state = 0;
}
}).catch(() => {
this.detailLoading = false;
});
},
handleSyncInbox() {
this.syncLoading = true;
this.$api.post(API.receiveMail, { j_email_id: this.selectedAccount.j_email_id }).then(() => {
this.syncLoading = false;
this.fetchData();
}).catch(() => this.syncLoading = false);
},
/**
* 格式化邮件显示时间
* @param {Number|String} timestamp 后端返回的时间戳 (如: 1773110269)
* @returns {String} 格式化后的字符串 (如: 15:57, 昨天, 3月8日, 2025-12-05)
*/
formatDisplayTime(timestamp) {
if (!timestamp) return '';
},
// 搜索-收件箱
handleSearchIn() {
this.getDate();
},
// 搜索-发件箱
handleSearchOut() {
this.getDate();
},
// 写邮件
handleAdd() {
// 1. 处理 10 位秒级时间戳,转为毫秒并实例化 Date 对象
const date = new Date(Number(timestamp) * 1000);
const now = new Date();
// 提取日期信息用于比较
const dateYear = date.getFullYear();
const dateMonth = date.getMonth() + 1;
const dateDay = date.getDate();
const nowYear = now.getFullYear();
// 获取昨天日期
const yesterday = new Date(now);
yesterday.setDate(now.getDate() - 1);
},
// 邮箱信息
handleDetail(e) {
// 获取具体时分并补零 (例如 09:05)
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const timePart = `${hours}:${minutes}`;
},
// --- 按照图示规则判断返回 ---
// 分页导航-收件箱
handlePageChangeIn(val) {
this.$set(this.queryIn, 'pageIndex', val);
this.getDate();
},
// 分页导航-发件箱
handlePageChangeOut(val) {
this.$set(this.queryOut, 'pageIndex', val);
this.getDate();
},
}
};
// 1. 如果是今天:只显示时间,如 "15:57"
if (date.toDateString() === now.toDateString()) {
return timePart;
}
// 2. 如果是昨天:只显示 "昨天"
if (date.toDateString() === yesterday.toDateString()) {
return '昨天';
}
// 3. 如果是今年(非今天/昨天):显示 "月-日",如 "3月8日"
if (dateYear === nowYear) {
return `${dateMonth}${dateDay}`;
}
// 4. 如果是往年:显示完整年月日,如 "2025-12-05"
const fullMonth = dateMonth.toString().padStart(2, '0');
const fullDay = dateDay.toString().padStart(2, '0');
return `${dateYear}-${fullMonth}-${fullDay}`;
},
stripHtml(html) { return new DOMParser().parseFromString(html, 'text/html').body.textContent || ""; },
handleWrite() {
if (!this.selectedAccount) return;
this.$router.push(
'/mailboxSend?journal_id=' +
this.selectedAccount.journal_id +
'&j_email_id=' +
this.selectedAccount.j_email_id
);
},
handleSearch() { this.fetchData(); },
notImplemented() { this.$message.info('开发中...'); }
}
};
</script>
<style scoped>
.mail-container {
display: flex;
height: calc(100vh - 120px);
background: #fff;
overflow: hidden;
}
</style>
/* 1. 左侧栏 */
.mail-sidebar {
width: 220px;
background-color: #f8f9fa;
border-right: 1px solid #eaeaea;
flex-shrink: 0;
display: flex;
flex-direction: column;
}
.p-20 { padding: 20px; }
.write-btn { width: 100%; border-radius: 8px; font-weight: bold; }
.folder-list { list-style: none; padding: 0; margin: 0; flex: 1; }
.folder-list li {
padding: 12px 20px;
cursor: pointer;
color: #606266;
display: flex;
align-items: center;
}
.folder-list li i { margin-right: 12px; font-size: 18px; }
.folder-list li.active { background: #edeef0; color: #006699; font-weight: bold; border-right: 3px solid #006699; }
.badge { margin-left: auto; background: #ddd; padding: 2px 8px; border-radius: 10px; font-size: 12px; }
.sidebar-footer { padding: 15px 20px; border-top: 1px solid #eee; background: #f8f9fa; }
.user-card { display: flex; align-items: center; margin-bottom: 8px; overflow: hidden; }
.user-avatar { flex-shrink: 0; background: #ffeded; color: #f56c6c; }
.user-detail { margin-left: 10px; overflow: hidden; flex: 1; }
.user-name { font-size: 14px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-email { font-size: 12px; color: #909399; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.switch-btn { padding: 0; font-size: 12px; color: #409eff; }
/* 2. 中间列表栏 - 参考 image_8649db.png */
.mail-list-panel { display: flex; flex-direction: column; flex-shrink: 0; background: #fff; }
.panel-header { padding: 15px; border-bottom: 1px solid #f5f5f5; display: flex; }
.list-scroll-area { flex: 1; overflow-y: auto; }
.mail-item-box {
display: flex;
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
}
.mail-item-box:hover { background: #f5f7fa; }
.mail-item-box.active { background: #eef1f6; border-left: 3px solid #006699; }
.item-left { margin-right: 12px; display: flex; align-items: flex-start; padding-top: 4px; }
.sender-avatar { background: #c0c4cc; }
.item-right { flex: 1; overflow: hidden; }
.row-one { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.sender-name { font-size: 14px; color: #303133; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.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; }
/* 拖拽条 */
.list-resizer { width: 4px; cursor: col-resize; border-left: 1px solid #eaeaea; transition: background 0.2s; }
.list-resizer:hover { background: #409eff; }
/* 3. 右侧详情 */
.mail-content-panel { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.detail-toolbar { padding: 10px 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; }
.detail-content { padding: 30px 40px; overflow-y: auto; }
.mail-title { font-size: 24px; margin-bottom: 20px; }
.mail-info { display: flex; align-items: center; }
.info-main { margin-left: 15px; }
.mail-body { line-height: 1.6; font-size: 15px; }
.empty-state, .empty-list-container { flex: 1; display: flex; align-items: center; justify-content: center; color: #ccc; text-align: center; }
.empty-state i, .empty-list-container i { font-size: 48px; margin-bottom: 10px; }
</style>