邮件列表加载动画优化和打包拆分

This commit is contained in:
eoao
2025-08-26 23:10:55 +08:00
parent de0bb288ec
commit 01cef2355d
119 changed files with 732 additions and 330 deletions

View File

@@ -4,7 +4,7 @@
<Icon v-perm="'account:add'" class="icon add" icon="ion:add-outline" width="23" height="23" @click="add"/>
<Icon class="icon refresh" icon="ion:reload" width="18" height="18" @click="refresh"/>
</div>
<el-scrollbar class="scrollbar">
<el-scrollbar class="scrollbar" ref="scrollbarRef">
<div v-infinite-scroll="getAccountList" :infinite-scroll-distance="600" :infinite-scroll-immediate="false">
<el-card class="item" :class="itemBg(item.accountId)" v-for="item in accounts" :key="item.accountId"
@click="changeAccount(item)">
@@ -37,7 +37,7 @@
<!-- Initial Loading Skeleton -->
<template v-if="loading">
<el-skeleton v-for="i in 3" :key="i" animated>
<el-skeleton v-for="i in skeletonRows" :key="i" animated>
<template #template>
<el-card class="item">
<el-skeleton-item variant="p" style="width: 70%; height: 20px; margin-bottom: 25px"/>
@@ -128,6 +128,7 @@
import {Icon} from "@iconify/vue";
import {nextTick, reactive, ref, watch} from "vue";
import {accountList, accountAdd, accountDelete, accountSetName} from "@/request/account.js";
import {sleep} from "@/utils/time-utils.js"
import {isEmail} from "@/utils/verify-utils.js";
import {useSettingStore} from "@/store/setting.js";
import {useAccountStore} from "@/store/account.js";
@@ -151,15 +152,18 @@ const setNameShow = ref(false)
const setNameLoading = ref(false)
const accountName = ref(null)
const addRef = ref({})
const scrollbarRef = ref({})
let account = null
let turnstileId = null
const botJsError = ref(false)
let verifyToken = ''
let verifyErrorCount = 0
let first = true
const addForm = reactive({
email: '',
suffix: settingStore.domainList[0]
})
let skeletonRows = 10
const queryParams = {
accountId: 0,
size: 20
@@ -201,6 +205,12 @@ window.onTurnstileSuccess = (token) => {
verifyToken = token;
};
function getSkeletonRows() {
if (accounts.length > 20) return skeletonRows = 20
if (accounts.length === 0) return skeletonRows = 1
skeletonRows = accounts.length
}
function setName() {
let name = accountName.value
@@ -281,6 +291,8 @@ function refresh() {
followLoading.value = false
noLoading.value = false
queryParams.accountId = 0
getSkeletonRows();
scrollbarRef.value.setScrollTop(0)
accounts.splice(0, accounts.length)
getAccountList()
}
@@ -325,7 +337,17 @@ function getAccountList() {
followLoading.value = true
}
accountList(queryParams.accountId, queryParams.size).then(list => {
let start = Date.now();
accountList(queryParams.accountId, queryParams.size).then(async list => {
let end = Date.now();
let duration = end - start;
if (duration < 500) {
await sleep(500 - duration)
}
if (list.length < queryParams.size) {
noLoading.value = true
}
@@ -337,6 +359,7 @@ function getAccountList() {
loading.value = false
followLoading.value = false
first = false
}).catch(() => {
loading.value = false
followLoading.value = false
@@ -363,6 +386,7 @@ function submit() {
})
return
}
if (!verifyToken && (settingStore.settings.addEmailVerify === 0 || (settingStore.settings.addEmailVerify === 2 && settingStore.settings.addVerifyOpen))) {
if (!verifyShow.value) {
verifyShow.value = true
@@ -477,7 +501,7 @@ path[fill="#ffdda1"] {
justify-content: center;
align-items: center;
padding: 10px 0;
color: gray;
color: var(--secondary-text-color);
}
}

View File

@@ -4,28 +4,30 @@
<div class="title">
<div class="title-left">
<span class="title-text">
<Icon icon="hugeicons:quill-write-01" width="28" height="28" />
<Icon icon="hugeicons:quill-write-01" width="28" height="28"/>
</span>
<span class="sender">{{$t('sender')}}:</span>
<span class="sender-name">{{form.name}}</span>
<span class="send-email"><{{form.sendEmail}}></span>
<span class="sender">{{ $t('sender') }}:</span>
<span class="sender-name">{{ form.name }}</span>
<span class="send-email"><{{ form.sendEmail }}></span>
</div>
<div @click="close" style="cursor: pointer;">
<Icon icon="material-symbols-light:close-rounded" width="22" height="22"/>
</div>
</div>
<div class="container">
<el-input-tag @add-tag="addTagChange" tag-type="primary" size="default" v-model="form.receiveEmail" :placeholder="$t('ruleEmailsInputDesc')" >
<el-input-tag @add-tag="addTagChange" tag-type="primary" size="default" v-model="form.receiveEmail"
:placeholder="$t('ruleEmailsInputDesc')">
<template #prefix>
<div class="item-title">{{$t('recipient')}} </div>
<div class="item-title">{{ $t('recipient') }}</div>
</template>
<template #suffix>
<span class="distribute" :class="form.manyType ? 'checked' : ''" @click.stop="checkDistribute" >{{$t('sendSeparately')}}</span>
<span class="distribute" :class="form.manyType ? 'checked' : ''"
@click.stop="checkDistribute">{{ $t('sendSeparately') }}</span>
</template>
</el-input-tag>
<el-input v-model="form.subject" :placeholder="$t('subjectInputDesc')">
<template #prefix>
<div class="item-title">{{$t('subject')}} </div>
<div class="item-title">{{ $t('subject') }}</div>
</template>
</el-input>
<tinyEditor :def-value="defValue" ref="editor" @change="change"/>
@@ -46,8 +48,8 @@
</div>
</div>
<div>
<el-button type="primary" @click="sendEmail" v-if="form.sendType === 'reply'">{{$t('reply')}}</el-button>
<el-button type="primary" @click="sendEmail" v-else >{{$t('send')}}</el-button>
<el-button type="primary" @click="sendEmail" v-if="form.sendType === 'reply'">{{ $t('reply') }}</el-button>
<el-button type="primary" @click="sendEmail" v-else>{{ $t('send') }}</el-button>
</div>
</div>
</div>
@@ -79,7 +81,7 @@ defineExpose({
openDraft
})
const { t } = useI18n()
const {t} = useI18n()
const draftStore = userDraftStore()
const settingStore = useSettingStore()
const emailStore = useEmailStore();
@@ -219,8 +221,8 @@ async function sendEmail() {
return
}
percentMessage = ElMessage({
message: () => h(sendPercent, { value: percent.value,desc: t('sending') }),
percentMessage = ElMessage({
message: () => h(sendPercent, {value: percent.value, desc: t('sending')}),
dangerouslyUseHTMLString: true,
plain: true,
duration: 0,
@@ -242,7 +244,7 @@ async function sendEmail() {
ElNotification({
title: t('sendSuccessMsg'),
type: "success",
message: h('span', { style: 'color: teal' }, email.subject),
message: h('span', {style: 'color: teal'}, email.subject),
position: 'bottom-right'
})
@@ -261,7 +263,7 @@ async function sendEmail() {
ElNotification({
title: t('sendFailMsg'),
type: e.code === 403 ? 'warning' : 'error',
message: h('span', { style: 'color: teal' }, e.message),
message: h('span', {style: 'color: teal'}, e.message),
position: 'bottom-right'
})
show.value = true
@@ -312,7 +314,7 @@ function openReply(email) {
<div></div>
<div>
<br>
${ formatDetailDate(email.createTime) } ${email.name} &lt${email.sendEmail}&gt ${t('wrote')}:
${formatDetailDate(email.createTime)} ${email.name} &lt${email.sendEmail}&gt ${t('wrote')}:
</div>
<blockquote class="mceNonEditable" style="margin: 0 0 0 0.8ex;border-left: 1px solid rgb(204,204,204);padding-left: 1ex;">
<articl>
@@ -334,7 +336,7 @@ function openReply(email) {
function formatImage(content) {
content = content || '';
const domain = settingStore.settings.r2Domain;
return content.replace(/{{domain}}/g, domain + '/');
return content.replace(/{{domain}}/g, domain + '/');
}
function open() {
@@ -352,7 +354,7 @@ function open() {
}
function openDraft(draft) {
Object.assign(form,{...draft})
Object.assign(form, {...draft})
defValue.value = ''
setTimeout(() => defValue.value = form.content)
show.value = true;
@@ -404,15 +406,16 @@ function close() {
cancelButtonText: t('cancel'),
type: 'warning',
distinguishCancelAndClose: true
}).then( async () => {
}).then(async () => {
const formData = {...toRaw(form)}
delete formData.draftId
delete formData.attachments
formData.createTime = dayjs().utc().format('YYYY-MM-DD HH:mm:ss');
const draftId = await db.value.draft.add({...formData})
db.value.att.add({draftId,attachments: toRaw(form.attachments)})
draftStore.refreshList ++
db.value.att.add({draftId, attachments: toRaw(form.attachments)})
draftStore.refreshList++
show.value = false
resetForm()
}).catch((action) => {
if (action === 'cancel') {
show.value = false
@@ -437,7 +440,7 @@ function close() {
.write-box {
background: var(--el-bg-color);
width: min(1200px,calc(100% - 80px));
width: min(1200px, calc(100% - 80px));
box-shadow: var(--el-box-shadow-light);
border: 1px solid var(--el-border-color-light);
transition: var(--el-transition-duration);
@@ -461,11 +464,13 @@ function close() {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.title-left {
align-items: center;
display: grid;
grid-template-columns: auto auto auto 1fr;
}
.title-text {
}
@@ -498,6 +503,7 @@ function close() {
display: grid;
grid-template-rows: auto auto 1fr auto;
gap: 15px;
.distribute {
color: var(--el-color-info);
background: var(--el-color-info-light-9);