mirror of
https://github.com/eoao/cloud-mail.git
synced 2026-05-06 13:41:43 +08:00
优化邮件列表改为虚拟滚动列表
This commit is contained in:
@@ -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",
|
||||
|
||||
40
mail-vue/pnpm-lock.yaml
generated
40
mail-vue/pnpm-lock.yaml
generated
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
@@ -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",
|
||||
|
||||
@@ -54,7 +54,7 @@ const zh = {
|
||||
botVerifyFail: '人机验证失败,请重试',
|
||||
authExpired: '身份认证失效,请重新登录',
|
||||
unauthorized: '权限不足',
|
||||
bannedSend: '你已被禁止发送邮件',
|
||||
bannedSend: '你没有发送邮件权限',
|
||||
initSuccess: '初始化成功',
|
||||
noDomainPermAdd: '你没有权限添加该域名邮箱',
|
||||
noDomainPermReg: '你没有权限注册该域名邮箱',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user