优化邮件列表改为虚拟滚动列表

This commit is contained in:
eoao
2026-01-15 19:46:00 +08:00
parent 1cdca9107a
commit a5995947ac
13 changed files with 250 additions and 154 deletions

View File

@@ -12,6 +12,7 @@
},
"dependencies": {
"@iconify/vue": "^4.3.0",
"@vueuse/components": "^14.1.0",
"@vueuse/core": "^12.0.0",
"axios": "^1.7.8",
"compressorjs": "^1.2.1",

View File

@@ -11,6 +11,9 @@ importers:
'@iconify/vue':
specifier: ^4.3.0
version: 4.3.0(vue@3.5.20)
'@vueuse/components':
specifier: ^14.1.0
version: 14.1.0(vue@3.5.20)
'@vueuse/core':
specifier: ^12.0.0
version: 12.8.2
@@ -1136,21 +1139,39 @@ packages:
'@vue/shared@3.5.20':
resolution: {integrity: sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==}
'@vueuse/components@14.1.0':
resolution: {integrity: sha512-SDRJUAv3H7/PMh+KkYpq0d5KMzpKOfqx4qcV4xyN4mZOLPw8NkiWu+yDcfXwI8h1uCqhRNz2cdeaLa+IuaehFw==}
peerDependencies:
vue: ^3.5.0
'@vueuse/core@12.8.2':
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
'@vueuse/core@14.1.0':
resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==}
peerDependencies:
vue: ^3.5.0
'@vueuse/core@9.13.0':
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
'@vueuse/metadata@12.8.2':
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
'@vueuse/metadata@14.1.0':
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
'@vueuse/metadata@9.13.0':
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
'@vueuse/shared@12.8.2':
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
'@vueuse/shared@14.1.0':
resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
peerDependencies:
vue: ^3.5.0
'@vueuse/shared@9.13.0':
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
@@ -3584,6 +3605,12 @@ snapshots:
'@vue/shared@3.5.20': {}
'@vueuse/components@14.1.0(vue@3.5.20)':
dependencies:
'@vueuse/core': 14.1.0(vue@3.5.20)
'@vueuse/shared': 14.1.0(vue@3.5.20)
vue: 3.5.20
'@vueuse/core@12.8.2':
dependencies:
'@types/web-bluetooth': 0.0.21
@@ -3593,6 +3620,13 @@ snapshots:
transitivePeerDependencies:
- typescript
'@vueuse/core@14.1.0(vue@3.5.20)':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 14.1.0
'@vueuse/shared': 14.1.0(vue@3.5.20)
vue: 3.5.20
'@vueuse/core@9.13.0(vue@3.5.20)':
dependencies:
'@types/web-bluetooth': 0.0.16
@@ -3605,6 +3639,8 @@ snapshots:
'@vueuse/metadata@12.8.2': {}
'@vueuse/metadata@14.1.0': {}
'@vueuse/metadata@9.13.0': {}
'@vueuse/shared@12.8.2':
@@ -3613,6 +3649,10 @@ snapshots:
transitivePeerDependencies:
- typescript
'@vueuse/shared@14.1.0(vue@3.5.20)':
dependencies:
vue: 3.5.20
'@vueuse/shared@9.13.0(vue@3.5.20)':
dependencies:
vue-demi: 0.14.10(vue@3.5.20)

View File

@@ -28,13 +28,21 @@
</div>
<div ref="scroll" class="scroll">
<el-scrollbar ref="scrollbarRef" style="height: 100%">
<div class="scroll-box" :infinite-scroll-immediate="false" v-infinite-scroll="loadData"
infinite-scroll-distance="3000">
<div v-if="(skeleton && !loading)" v-for="item in emailList" :key="item.emailId">
<div class="email-row"
<UseVirtualList ref="scrollbarRef"
@scroll="onScroll"
:list="list"
:options="{ itemHeight: itemHeight, overscan: 15 }"
class="virtual"
style="height: 100%"
v-if="!loading && emailList.length > 0"
:key="keyCount"
>
<template #default="{ data: item, index }">
<div :class="'email-row ' + props.type"
:data-checked="item.checked"
@click="jumpDetails(item)"
v-if="!item.expand"
:key="item.emailId"
>
<el-checkbox :class=" props.type === 'all-email' ? 'all-email-checkbox' : 'checkbox'"
v-model="item.checked" @click.stop></el-checkbox>
@@ -47,29 +55,11 @@
<div class="email-sender" :style=" (showStatus ? 'gap: 10px;' : '') + ((item.unread === EmailUnreadEnum.UNREAD && showUnread) ? 'font-weight: bold' : '')">
<div class="email-status" v-if="showStatus">
<el-tooltip v-if="item.status === 0" effect="dark" :content="$t('received')">
<Icon icon="ic:round-mark-email-read" style="color: #51C76B" width="20" height="20"/>
</el-tooltip>
<el-tooltip v-if="item.status === 1" effect="dark" :content="$t('sent')">
<Icon icon="bi:send-arrow-up-fill" style="color: #51C76B" width="20" height="20"/>
</el-tooltip>
<el-tooltip v-if="item.status === 2" effect="dark" :content="$t('delivered')">
<Icon icon="bi:send-check-fill" style="color: #51C76B" width="20" height="20"/>
</el-tooltip>
<el-tooltip v-if="item.status === 3 || item.status === 8" effect="dark" :content="$t('bounced')">
<Icon icon="bi:send-x-fill" style="color: #F56C6C" width="20" height="20"/>
</el-tooltip>
<el-tooltip v-if="item.status === 4" effect="dark" :content="$t('complained')">
<Icon icon="bi:send-exclamation-fill" style="color:#FBBD08" width="20" height="20"/>
</el-tooltip>
<el-tooltip v-if="item.status === 5" effect="dark" :content="$t('delayed')">
<Icon icon="bi:send-arrow-up-fill" style="color:#FBBD08" width="20" height="20"/>
</el-tooltip>
<el-tooltip v-if="item.status === 7" effect="dark" :content="$t('noRecipient')">
<Icon icon="ic:round-mark-email-read" style="color:#FBBD08" width="20" height="20"/>
<el-tooltip effect="dark" :content="item.statusIcon.content">
<Icon :icon="item.statusIcon.icon" :style="`color: ${item.statusIcon.color}`" width="20" height="20"/>
</el-tooltip>
<div class="del-status" v-if="item.isDel">
<el-tooltip effect="dark" :content="$t('selectDeleted')">
<el-tooltip effect="dark" :content="item.isDelContent">
<Icon class="icon" icon="mdi:email-remove" width="20" height="20"/>
</el-tooltip>
</div>
@@ -84,7 +74,7 @@
<Icon v-if="item.isStar" icon="fluent-color:star-16" width="18" height="18"/>
</span>
</span>
<span class="phone-time">{{ fromNow(item.createTime) }}</span>
<span class="phone-time">{{ item.formatCreateTime }}</span>
</div>
<div>
<div class="email-text">
@@ -94,7 +84,7 @@
{{ item.subject || '\u200B' }}
</slot>
</span>
<span class="email-content">{{ htmlToText(item) || '\u200B' }}</span>
<span class="email-content">{{ item.formatText || '\u200B' }}</span>
</div>
<div class="user-info" v-if="showUserInfo">
<div class="user">
@@ -113,61 +103,46 @@
</div>
</div>
<div class="email-right" :style="showUserInfo ? 'align-self: start;':''">
<span class="email-time" :style="(item.unread === EmailUnreadEnum.UNREAD && showUnread) ? 'font-weight: bold' : ''">{{ fromNow(item.createTime) }}</span>
<span class="email-time" :style="(item.unread === EmailUnreadEnum.UNREAD && showUnread) ? 'font-weight: bold' : ''">{{ item.formatCreateTime }}</span>
</div>
</div>
</div>
<template v-if="skeleton">
<skeletonBlock v-if="firstLoad && showFirstLoading"
:rows="20"
<skeletonBlock v-else-if="item.expand === 'loading'"
:rows="1"
:showStar="showStar"
:accountShow="accountShow"
:showStatus="showStatus"
:showUserInfo="showUserInfo"
:type="type"/>
<skeletonBlock v-if="loading"
:rows="skeletonRows"
:showStar="showStar"
:accountShow="accountShow"
:showStatus="showStatus"
:showUserInfo="showUserInfo"
:type="type"/>
<skeletonBlock v-if="followLoading"
:rows="isMobile ? 1 : 2"
:showStar="showStar"
:accountShow="accountShow"
:showStatus="showStatus"
:showUserInfo="showUserInfo"
:type="type"/>
</template>
<template v-else>
<div></div>
<div class="loading" :class="loading ? 'loading-show' : 'loading-hide'"
:style="firstLoad ? 'background: transparent' : ''">
<Loading/>
</div>
<div class="follow-loading" v-if="followLoading">
<Loading/>
<div class="noLoading" v-else-if="item.expand === 'noMoreData'">
<div>{{ $t('noMoreData') }}</div>
</div>
</template>
<div class="noLoading" v-if="noLoading && emailList.length > 0 && !(skeleton && loading)">
<div>{{ $t('noMoreData') }}</div>
</div>
<div class="empty" v-if="noLoading && emailList.length === 0 && !(skeleton && loading)">
<el-empty :image-size="isMobile ? 120 : 0" :description="$t('noMessagesFound')"/>
</div>
</div>
</el-scrollbar>
</UseVirtualList>
<skeletonBlock v-if="firstLoad && showFirstLoading"
:rows="20"
:showStar="showStar"
:accountShow="accountShow"
:showStatus="showStatus"
:showUserInfo="showUserInfo"
:type="type"/>
<skeletonBlock v-if="loading"
:rows="skeletonRows"
:showStar="showStar"
:accountShow="accountShow"
:showStatus="showStatus"
:showUserInfo="showUserInfo"
:type="type"/>
<div class="empty" v-if="noLoading && emailList.length === 0 && !loading">
<el-empty :image-size="isMobile ? 120 : 0" :description="$t('noMessagesFound')"/>
</div>
</div>
</div>
</template>
<script setup>
import Loading from "@/components/loading/index.vue";
import {Icon} from "@iconify/vue";
import skeletonBlock from "@/components/email-scroll/skeleton/index.vue"
import {computed, onActivated, reactive, ref, watch} from "vue";
import {onBeforeRouteLeave} from "vue-router";
import {computed, onActivated, reactive, ref, watch, nextTick} from "vue";
import {useEmailStore} from "@/store/email.js";
import {useUiStore} from "@/store/ui.js";
import {useSettingStore} from "@/store/setting.js";
@@ -175,6 +150,8 @@ import {sleep} from "@/utils/time-utils.js"
import {fromNow} from "@/utils/day.js";
import {useI18n} from "vue-i18n";
import {EmailUnreadEnum} from "@/enums/email-enum.js";
import { UseVirtualList } from '@vueuse/components'
import { useScroll } from '@vueuse/core'
const props = defineProps({
getEmailList: Function,
@@ -216,10 +193,6 @@ const props = defineProps({
type: String,
default: ''
},
skeleton: {
type: Boolean,
default: true
},
showFirstLoading: {
type: Boolean,
default: true
@@ -239,6 +212,7 @@ const loading = ref(false);
const followLoading = ref(false);
const noLoading = ref(false);
const emailList = reactive([])
const expandList = reactive([])
const total = ref(0);
const checkAll = ref(false);
const isIndeterminate = ref(false);
@@ -250,6 +224,8 @@ const scrollbarRef = ref(null)
let reqLock = false
let isMobile = ref(innerWidth < 1367)
let skeletonRows = 0
const timePaddingRight = ref('');
const keyCount = ref(0)
const queryParam = reactive({
emailId: 0,
size: 50,
@@ -267,18 +243,83 @@ defineExpose({
})
onActivated(() => {
scroll.value.scrollTop = scrollTop
requestAnimationFrame(() => {
const index = scrollTop / itemHeight.value
scrollbarRef.value?.scrollTo(index);
})
})
getEmailList()
onBeforeRouteLeave(() => {
scrollTop = scroll.value.scrollTop
window.onresize = () => {
isMobile.value = innerWidth < 1367
}
function onScroll(e) {
scrollTop = e.target.scrollTop;
}
const { arrivedState } = useScroll(scrollbarRef, {
offset: { bottom: 1200 }
})
window.onresize = () => {
isMobile = innerWidth < 1367
}
const list = computed(() => {
return [...emailList, ...expandList]
})
const itemHeight = computed(() => {
if (props.type === 'all-email') {
return isMobile.value ? 132 : 65;
} else {
return isMobile.value ? 83 : 48;
}
})
watch(emailList, () => {
updateHasScrollbar();
})
watch(scrollbarRef, () => {
updateHasScrollbar();
})
// 强制刷新 (itemHeight 更改后虚拟滚动列表不会自己更新)
watch(itemHeight, () => {
keyCount.value ++
})
watch(followLoading, (isFollowLoading) => {
if (isFollowLoading) {
expandList.push({
emailId: 0,
expand: 'loading'
})
} else {
const index = expandList.findIndex(item => item.expand === 'loading')
expandList.splice(index, 1);
}
});
watch(noLoading, (isNoLoading) => {
if (isNoLoading) {
expandList.push({
emailId: 0,
expand: 'noMoreData'
})
} else {
const index = expandList.findIndex(item => item.expand === 'noMoreData')
expandList.splice(index, 1);
}
})
// 监听是否到达底部
watch(() => arrivedState.bottom, (isBottom) => {
if (isBottom && !loading.value) {
loadData();
}
});
watch(
() => emailList.map(item => item.checked),
@@ -313,6 +354,19 @@ watch(() => emailStore.addStarEmailId, () => {
})
})
function updateHasScrollbar() {
nextTick(() => {
const doc = document.querySelector('.virtual');
if (doc) {
if (doc.scrollHeight > doc.clientHeight) {
timePaddingRight.value = '5px';
} else {
timePaddingRight.value = '15px'
}
}
})
}
function getSkeletonRows() {
if (emailList.length > 20) return skeletonRows = 20
if (emailList.length === 0) return skeletonRows = 1
@@ -444,9 +498,13 @@ function addItem(email) {
return
}
email.formatText = htmlToText(email);
email.formatCreateTime = fromNow(email.formatCreateTime);
if (props.timeSort) {
if (noLoading.value) {
emailList.push(email)
handleList([email]);
emailList.push(email);
}
if (email.emailId > latestEmail.value.emailId) {
@@ -461,10 +519,12 @@ function addItem(email) {
const index = emailList.findIndex(item => item.emailId < email.emailId)
if (index !== -1) {
handleList([email]);
emailList.splice(index, 0, email);
} else {
if (noLoading.value) {
emailList.push(email)
handleList([email]);
emailList.push(email);
}
}
@@ -518,6 +578,7 @@ function getEmailList(refresh = false) {
} else {
getSkeletonRows()
loading.value = true
scrollTop = 0
}
if (emailList.length === 0) {
@@ -545,8 +606,9 @@ function getEmailList(refresh = false) {
}
latestEmail.value = data.latestEmail
emailList.push(...list);
handleList(list);
emailList.push(...list);
if (refresh) scrollbarRef.value?.setScrollTop(0);
noLoading.value = data.list.length < queryParam.size;
@@ -560,6 +622,26 @@ function getEmailList(refresh = false) {
})
}
function handleList(list) {
list.forEach(email => {
email.formatText = htmlToText(email)
email.formatCreateTime = fromNow(email.createTime);
email.test = t('received')
const statusIconMap = {
0: { icon: 'ic:round-mark-email-read', color: '#51C76B', content: t('received') },
1: { icon: 'bi:send-arrow-up-fill', color: '#51C76B', content: t('sent') },
2: { icon: 'bi:send-check-fill', color: '#51C76B', content: t('delivered') },
3: { icon: 'bi:send-x-fill', color: '#F56C6C', content: t('bounced') },
8: { icon: 'bi:send-x-fill', color: '#F56C6C', content: t('bounced') },
4: { icon: 'bi:send-exclamation-fill', color: '#FBBD08', content: t('complained') },
5: { icon: 'bi:send-arrow-up-fill', color: '#FBBD08', content: t('delayed') },
7: { icon: 'ic:round-mark-email-read', color: '#FBBD08', content: t('noRecipient') },
};
if (email.isDel) email.isdelContent = t('selectDeleted');
email.statusIcon = statusIconMap[email.status];
})
}
function refresh() {
emit('refresh-before')
if (props.skeleton) {
@@ -594,12 +676,11 @@ function loadData() {
.scroll {
margin: 0;
overflow: auto;
height: 100%;
position: relative;
overflow: hidden;
.scroll-box {
height: 100%;
.virtual {
will-change: scroll-position;
}
.empty {
@@ -614,7 +695,7 @@ function loadData() {
display: flex;
justify-content: center;
align-items: center;
padding: 15px 0;
padding: 15px 0 0 0;
color: var(--secondary-text-color);
}
@@ -659,7 +740,16 @@ function loadData() {
align-items: center;
position: relative;
transition: background 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
height: 48px;
@media (max-width: 1366px) {
height: 83px;
}
&.all-email {
height: 65px;
@media (max-width: 1366px) {
height: 132px;
}
}
.user-info {
display: flex;
flex-wrap: wrap;
@@ -712,7 +802,7 @@ function loadData() {
justify-content: start;
height: 100%;
align-self: start;
padding-top: 3px;
padding-bottom: 30px;
}
}
@@ -900,7 +990,7 @@ function loadData() {
}
.email-time {
padding-right: 16px !important;
padding-right: v-bind(timePaddingRight);
}
:deep(.el-scrollbar__view) {

View File

@@ -1,6 +1,6 @@
<template>
<div v-for="item in rows" style="background: var(--el-bg-color)">
<div class="email-row">
<div :class="'email-row ' + type ">
<el-checkbox disabled :class=" props.type === 'all-email' ? 'all-email-checkbox' : 'checkbox'"
></el-checkbox>
<div class="pc-star" v-if="showStar">
@@ -46,7 +46,7 @@
<el-skeleton animated>
<template #template>
<el-skeleton-item variant="text"
style="width: 180px;margin-right: 15px;height: 1rem;margin-bottom: 4px;"/>
style="width: 180px;margin-right: 5px;height: 1rem;margin-bottom: 4px;"/>
</template>
</el-skeleton>
</div>
@@ -54,7 +54,7 @@
<el-skeleton animated>
<template #template>
<el-skeleton-item variant="text"
style="width: 180px;margin-right: 15px;height: 1rem;margin-bottom: 4px;"/>
style="width: 180px;margin-right: 5px;height: 1rem;margin-bottom: 4px;"/>
</template>
</el-skeleton>
</div>
@@ -67,7 +67,7 @@
<div class="email-right-skeleton" :style="showUserInfo ? 'align-self: start;':''">
<el-skeleton animated>
<template #template>
<el-skeleton-item variant="text" style="width: 50px;margin-right: 15px;height: 1rem;"/>
<el-skeleton-item variant="text" style="width: 60px;margin-right: 15px;height: 1rem;"/>
</template>
</el-skeleton>
</div>
@@ -115,6 +115,11 @@ import {Icon} from "@iconify/vue";
width: 40px;
}
:deep(.el-skeleton__item) {
position: relative;
top: 2px;
}
@media (max-width: 1366px) {
.pc-star {
display: none;

View File

@@ -38,7 +38,7 @@
{{ userStore.user.email }}
</div>
<div class="detail-user-type">
<el-tag>{{ $t(userStore.user.role.name) }}</el-tag>
<el-tag>{{ userStore.user.role.name }}</el-tag>
</div>
<div class="action-info">
<div>

View File

@@ -2,6 +2,9 @@
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
scrollbar-width: thin;
scrollbar-color: var(--dark-border) transparent;
}
html, body {
@@ -34,10 +37,6 @@ html, body {
background: var(--el-bg-color);
}
* {
-webkit-tap-highlight-color: transparent;
}
ul, ol {
list-style: none;
}
@@ -172,28 +171,6 @@ button, input, select, textarea {
--el-input-text-color: var(--el-text-color-primary) !important;
}
@media (pointer: fine) and (hover: hover) {
/* 整个滚动条 */
::-webkit-scrollbar {
width: 6px; /* 垂直滚动条宽度 */
height: 6px; /* 水平滚动条高度 */
}
/* 滚动条轨道 */
::-webkit-scrollbar-track {
background: var(--el-bg-color);
border-radius: 10px;
}
/* 滚动条滑块 */
::-webkit-scrollbar-thumb {
background: var(--scrollbar-track-color);
border-radius: 10px;
cursor: pointer;
}
}
/* 视图过渡,基于径向剪裁 */
html.dark,
html:not(.dark) {

View File

@@ -11,6 +11,7 @@
actionLeft="4px"
:show-account-icon="false"
:time-sort="params.timeSort"
:item-height="65"
@jump="jumpContent"
@refresh-before="refreshBefore"
:type="'all-email'"

View File

@@ -754,7 +754,7 @@ defineOptions({
name: 'sys-setting'
})
const currentVersion = 'v2.6.0'
const currentVersion = 'v2.7.0'
const hasUpdate = ref(false)
let getUpdateErrorCount = 1;
const {t, locale} = useI18n();

View File

@@ -1,29 +1,11 @@
<template>
<el-scrollbar>
<div class="scrollbar-flex-content">
<p v-for="item in 1000" :key="item" class="scrollbar-demo-item">
{{ item }}
</p>
</div>
</el-scrollbar>
</template>
<style scoped>
.scrollbar-flex-content {
display: grid;
grid-template-columns: 200px 200px 200px 200px 200px 200px 200px 200px 200px 200px 200px;
width: 40px;
}
.scrollbar-demo-item {
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 50px;
marngin-bottom: 10px;
text-align: center;
border-radius: 4px;
background: var(--el-color-danger-light-9);
color: var(--el-color-danger);
}
<script setup>
</script>
<style>
</style>

View File

@@ -54,7 +54,7 @@ const en = {
botVerifyFail: 'Bot verification failed, please try again',
authExpired: 'Authentication has expired. Please sign in again',
unauthorized: 'Unauthorized',
bannedSend: 'You are banned from sending emails',
bannedSend: 'You do not have permission to send emails',
initSuccess: 'Successfully initialized',
noDomainPermAdd: "No permission to add this domain email",
noDomainPermReg: "No permission to register this domain email",

View File

@@ -54,7 +54,7 @@ const zh = {
botVerifyFail: '人机验证失败,请重试',
authExpired: '身份认证失效,请重新登录',
unauthorized: '权限不足',
bannedSend: '你已被禁止发送邮件',
bannedSend: '你没有发送邮件权限',
initSuccess: '初始化成功',
noDomainPermAdd: '你没有权限添加该域名邮箱',
noDomainPermReg: '你没有权限注册该域名邮箱',

View File

@@ -564,7 +564,7 @@ const init = {
INSERT INTO setting (
register, receive, add_email, many_email, title, auto_refresh, register_verify, add_email_verify
)
SELECT 0, 0, 0, 0, 'Cloud Mail', 1, 1, 1
SELECT 0, 0, 0, 0, 'Cloud Mail', 0, 1, 1
WHERE NOT EXISTS (SELECT 1 FROM setting)
`).run();
} catch (e) {

View File

@@ -539,12 +539,6 @@ const emailService = {
const conditions = [];
if (timeSort) {
conditions.push(gt(email.emailId, emailId));
} else {
conditions.push(lt(email.emailId, emailId));
}
if (type === 'send') {
conditions.push(eq(email.type, emailConst.type.SEND));
}
@@ -586,6 +580,12 @@ const emailService = {
const countConditions = [...conditions];
if (timeSort) {
conditions.unshift(gt(email.emailId, emailId));
} else {
conditions.unshift(lt(email.emailId, emailId));
}
const query = orm(c).select({ ...email, userEmail: user.email })
.from(email)
.leftJoin(user, eq(email.userId, user.userId))