邮件列表加载动画优化和打包拆分
This commit is contained in:
@@ -12,13 +12,17 @@
|
||||
if (uiStoreStr) {
|
||||
const uiStore = JSON.parse(uiStoreStr)
|
||||
const root = document.documentElement
|
||||
root.style.background = uiStore.dark ? '#141414' : '#FFFFFF'
|
||||
root.setAttribute('class', uiStore.dark ? 'dark' : '');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<style>
|
||||
|
||||
html {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
.dark {
|
||||
background: #141414;
|
||||
}
|
||||
#loading-first {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
@@ -32,6 +36,12 @@
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.loading-hide {
|
||||
pointer-events: none;
|
||||
transition: all 200ms;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -89,7 +99,7 @@
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="loading-first">
|
||||
<div id="loading-first" >
|
||||
<div class="loading-icon">
|
||||
<svg class="circular" viewBox="0 0 20 20">
|
||||
<g class="loading-path">
|
||||
|
||||
7
mail-vue/package-lock.json
generated
7
mail-vue/package-lock.json
generated
@@ -18,6 +18,7 @@
|
||||
"echarts": "^5.6.0",
|
||||
"element-plus": "^2.9.11",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
@@ -2840,6 +2841,12 @@
|
||||
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/nprogress": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz",
|
||||
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/nypm/-/nypm-0.6.0.tgz",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"echarts": "^5.6.0",
|
||||
"element-plus": "^2.9.11",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^3.0.2",
|
||||
"pinia-plugin-persistedstate": "^4.2.0",
|
||||
|
||||
@@ -12,4 +12,4 @@ import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||
const { locale } = useI18n()
|
||||
locale.value = settingStore.lang
|
||||
watch(() => settingStore.lang, () => locale.value = settingStore.lang)
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div class="email-container">
|
||||
<div class="header-actions">
|
||||
<el-checkbox
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
:disabled="!emailList.length"
|
||||
@change="handleCheckAllChange"
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
:disabled="!emailList.length || loading"
|
||||
@change="handleCheckAllChange"
|
||||
>
|
||||
</el-checkbox>
|
||||
<div class="header-left" :style="'padding-left:' + actionLeft">
|
||||
@@ -18,22 +18,23 @@
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<span class="email-count" v-if="total">{{$t('emailCount', {total: total})}}</span>
|
||||
<span class="email-count" v-if="total">{{ $t('emailCount', {total: total}) }}</span>
|
||||
<Icon v-if="showAccountIcon" class="more-icon icon" width="16" height="16" icon="akar-icons:dot-grid-fill"
|
||||
@click="changeAccountShow"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="scroll" class="scroll">
|
||||
<el-scrollbar ref="scrollbarRef" @scroll="handleScroll">
|
||||
<el-scrollbar ref="scrollbarRef" style="height: 100%">
|
||||
<div class="scroll-box" :infinite-scroll-immediate="false" v-infinite-scroll="loadData"
|
||||
infinite-scroll-distance="600">
|
||||
<div v-for="item in emailList" :key="item.emailId">
|
||||
<div v-if="(skeleton && !loading)" v-for="item in emailList" :key="item.emailId">
|
||||
<div class="email-row"
|
||||
:data-checked="item.checked"
|
||||
@click="jumpDetails(item)"
|
||||
>
|
||||
<el-checkbox :class=" props.type === 'all-email' ? 'all-email-checkbox' : 'checkbox'" v-model="item.checked" @click.stop></el-checkbox>
|
||||
<el-checkbox :class=" props.type === 'all-email' ? 'all-email-checkbox' : 'checkbox'"
|
||||
v-model="item.checked" @click.stop></el-checkbox>
|
||||
<div @click.stop="starChange(item)" class="pc-star" v-if="showStar">
|
||||
<Icon v-if="item.isStar" icon="fluent-color:star-16" width="20" height="20"/>
|
||||
<Icon v-else icon="solar:star-line-duotone" width="18" height="18"/>
|
||||
@@ -43,62 +44,33 @@
|
||||
|
||||
<div class="email-sender" :style=" showStatus ? 'gap: 10px;' : ''">
|
||||
<div class="email-status" v-if="showStatus">
|
||||
<el-tooltip v-if="item.status === 0"
|
||||
effect="dark"
|
||||
:content="$t('received')"
|
||||
>
|
||||
<el-tooltip v-if="item.status === 0" effect="dark" :content="$t('received')">
|
||||
<Icon icon="ic:round-mark-email-read" style="color: #67C23A" 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: #67C23A" width="20" height="20"
|
||||
/>
|
||||
|
||||
<el-tooltip v-if="item.status === 1" effect="dark" :content="$t('sent')">
|
||||
<Icon icon="bi:send-arrow-up-fill" style="color: #67C23A" 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: #67C23A" width="20"
|
||||
height="20"/>
|
||||
|
||||
<el-tooltip v-if="item.status === 2" effect="dark" :content="$t('delivered')">
|
||||
<Icon icon="bi:send-check-fill" style="color: #67C23A" width="20" height="20"/>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-if="item.status === 3"
|
||||
effect="dark"
|
||||
:content="$t('bounced')"
|
||||
>
|
||||
<Icon icon="bi:send-x-fill" style="color: #F56C6C" width="20"
|
||||
height="20"/>
|
||||
|
||||
<el-tooltip v-if="item.status === 3" 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 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 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 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>
|
||||
<div class="del-status" v-if="item.isDel">
|
||||
<el-tooltip effect="dark" :content="$t('selectDeleted')">
|
||||
<Icon class="icon" icon="mdi:email-remove" width="20" height="20"/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
<span class="name">
|
||||
@@ -115,10 +87,10 @@
|
||||
<div class="email-text">
|
||||
<span class="email-subject">
|
||||
<slot name="subject" :email="item">
|
||||
{{ item.subject }}
|
||||
{{ item.subject || '\u200B' }}
|
||||
</slot>
|
||||
</span>
|
||||
<span class="email-content">{{ htmlToText(item) }}</span>
|
||||
<span class="email-content">{{ htmlToText(item) || '\u200B' }}</span>
|
||||
</div>
|
||||
<div class="user-info" v-if="showUserInfo">
|
||||
<div class="user">
|
||||
@@ -133,9 +105,6 @@
|
||||
</span>
|
||||
<span>{{ item.type === 0 ? item.toEmail : item.sendEmail }}</span>
|
||||
</div>
|
||||
<div class="del-status" v-if="item.isDel">
|
||||
<el-tag type="danger" size="small">{{$t('deleted')}}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,21 +113,47 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading" :class="loading ? 'loading-show' : 'loading-hide'" :style="firstLoad ? 'background: transparent' : ''">
|
||||
<Loading/>
|
||||
<template v-if="skeleton">
|
||||
<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"/>
|
||||
<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>
|
||||
</template>
|
||||
<div class="noLoading" v-if="noLoading && emailList.length > 0 && !(skeleton && loading)">
|
||||
<div>{{ $t('noMoreData') }}</div>
|
||||
</div>
|
||||
<div class="follow-loading" v-if="followLoading">
|
||||
<Loading/>
|
||||
</div>
|
||||
<div class="noLoading" v-if="noLoading && emailList.length > 0">
|
||||
<div>{{$t('noMoreData')}}</div>
|
||||
</div>
|
||||
<div class="empty" v-if="noLoading && emailList.length === 0">
|
||||
<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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -166,11 +161,13 @@
|
||||
<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 {useEmailStore} from "@/store/email.js";
|
||||
import {useUiStore} from "@/store/ui.js";
|
||||
import {useSettingStore} from "@/store/setting.js";
|
||||
import {sleep} from "@/utils/time-utils.js"
|
||||
import {fromNow} from "@/utils/day.js";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
@@ -212,12 +209,19 @@ const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
skeleton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showFirstLoading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const emit = defineEmits(['jump', 'refresh-before', 'delete-draft'])
|
||||
const { t } = useI18n()
|
||||
const {t} = useI18n()
|
||||
const settingStore = useSettingStore()
|
||||
const uiStore = useUiStore();
|
||||
const emailStore = useEmailStore();
|
||||
@@ -234,7 +238,8 @@ let scrollTop = 0
|
||||
const latestEmail = ref(null)
|
||||
const scrollbarRef = ref(null)
|
||||
let reqLock = false
|
||||
let isMobile = window.innerWidth < 1025
|
||||
let isMobile = innerWidth < 1025
|
||||
let skeletonRows = 0
|
||||
const queryParam = reactive({
|
||||
emailId: 0,
|
||||
size: 30,
|
||||
@@ -294,13 +299,16 @@ watch(() => emailStore.addStarEmailId, () => {
|
||||
})
|
||||
})
|
||||
|
||||
function getSkeletonRows() {
|
||||
if (emailList.length > 20) return skeletonRows = 20
|
||||
if (emailList.length === 0) return skeletonRows = 1
|
||||
skeletonRows = emailList.length
|
||||
}
|
||||
|
||||
const accountShow = computed(() => {
|
||||
return uiStore.accountShow && settingStore.settings.manyEmail === 0
|
||||
})
|
||||
|
||||
function handleScroll(e) {
|
||||
}
|
||||
|
||||
function htmlToText(email) {
|
||||
if (email.content) {
|
||||
|
||||
@@ -473,6 +481,7 @@ function getEmailList(refresh = false) {
|
||||
}
|
||||
|
||||
} else {
|
||||
getSkeletonRows()
|
||||
loading.value = true
|
||||
}
|
||||
|
||||
@@ -481,11 +490,13 @@ function getEmailList(refresh = false) {
|
||||
} else {
|
||||
followLoading.value = !refresh;
|
||||
}
|
||||
|
||||
|
||||
props.getEmailList(queryParam.emailId, queryParam.size).then(data => {
|
||||
|
||||
|
||||
let start = Date.now();
|
||||
props.getEmailList(queryParam.emailId, queryParam.size).then(async data => {
|
||||
let end = Date.now();
|
||||
let duration = end - start;
|
||||
if (duration < 500 && !queryParam.emailId) {
|
||||
await sleep(500 - duration)
|
||||
}
|
||||
firstLoad.value = false
|
||||
|
||||
let list = data.list.map(item => ({
|
||||
@@ -516,6 +527,9 @@ function getEmailList(refresh = false) {
|
||||
|
||||
function refresh() {
|
||||
emit('refresh-before')
|
||||
if (props.skeleton) {
|
||||
scrollbarRef.value.setScrollTop(0)
|
||||
}
|
||||
refreshList()
|
||||
}
|
||||
|
||||
@@ -531,7 +545,6 @@ function loadData() {
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.email-container {
|
||||
@@ -567,7 +580,7 @@ function loadData() {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
color: gray;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.follow-loading {
|
||||
@@ -597,12 +610,12 @@ function loadData() {
|
||||
|
||||
.loading-hide {
|
||||
pointer-events: none;
|
||||
transition: all 200ms;
|
||||
transition: var(--loading-hide-transition);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.email-row {
|
||||
:deep(.email-row) {
|
||||
display: flex;
|
||||
padding: 8px 0;
|
||||
justify-content: space-between;
|
||||
@@ -611,16 +624,17 @@ function loadData() {
|
||||
align-items: center;
|
||||
position: relative;
|
||||
transition: background 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||
@media (max-width: 1199px) {
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 10px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: 2px;
|
||||
color: var(--email-scroll-content-color);
|
||||
@media (max-width: 1199px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.user, .account {
|
||||
overflow: hidden;
|
||||
@@ -628,7 +642,12 @@ function loadData() {
|
||||
text-overflow: ellipsis;
|
||||
transition: all 300ms;
|
||||
line-height: 12px;
|
||||
max-width: 290px;
|
||||
max-width: 300px;
|
||||
min-width: 0;
|
||||
|
||||
@media (max-width: 1223px) {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
span:first-child {
|
||||
position: relative;
|
||||
@@ -663,7 +682,7 @@ function loadData() {
|
||||
}
|
||||
|
||||
.title-column {
|
||||
@media (max-width: 1200px) {
|
||||
@media (max-width: 1199px) {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 4px !important;
|
||||
}
|
||||
@@ -689,7 +708,12 @@ function loadData() {
|
||||
|
||||
.email-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
@media (max-width: 1199px) {
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
@@ -708,6 +732,14 @@ function loadData() {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.name-skeleton {
|
||||
width: 150px;
|
||||
height: 1rem;
|
||||
@media (max-width: 767px) {
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.phone-time {
|
||||
@@ -719,6 +751,30 @@ function loadData() {
|
||||
}
|
||||
}
|
||||
|
||||
.email-text-skeleton {
|
||||
.text-skeleton-one {
|
||||
width: 80%;
|
||||
height: 16px;
|
||||
@media (max-width: 1199px) {
|
||||
width: 40%;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
.text-skeleton-two {
|
||||
width: min(300px, 100%);
|
||||
height: 16px;
|
||||
@media (min-width: 1200px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 1199px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.email-text {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
@@ -762,6 +818,12 @@ function loadData() {
|
||||
}
|
||||
}
|
||||
|
||||
.email-right-skeleton {
|
||||
@media (max-width: 1199px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--email-hover-background);
|
||||
z-index: 0;
|
||||
@@ -812,6 +874,7 @@ function loadData() {
|
||||
gap: 15px;
|
||||
padding: 3px 15px;
|
||||
box-shadow: var(--header-actions-border);
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -828,6 +891,7 @@ function loadData() {
|
||||
align-items: start;
|
||||
height: 100%;
|
||||
color: var(--el-text-color-primary);;
|
||||
|
||||
.email-count {
|
||||
white-space: nowrap;
|
||||
margin-top: 6px;
|
||||
@@ -845,6 +909,15 @@ function loadData() {
|
||||
}
|
||||
}
|
||||
|
||||
.del-status {
|
||||
color: var(--el-color-danger);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
133
mail-vue/src/components/email-scroll/skeleton/index.vue
Normal file
133
mail-vue/src/components/email-scroll/skeleton/index.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div v-for="item in rows" style="background: var(--el-bg-color)">
|
||||
<div class="email-row">
|
||||
<el-checkbox disabled :class=" props.type === 'all-email' ? 'all-email-checkbox' : 'checkbox'"
|
||||
></el-checkbox>
|
||||
<div class="pc-star" v-if="showStar">
|
||||
<Icon style="color: var(--el-border-color)" icon="solar:star-line-duotone" width="18" height="18"/>
|
||||
</div>
|
||||
<div v-if="!showStar"></div>
|
||||
<div class="title" :class="accountShow ? 'title-column' : 'title-column'">
|
||||
|
||||
<div class="email-sender">
|
||||
<div class="email-status" v-if="showStatus">
|
||||
|
||||
</div>
|
||||
<div v-else></div>
|
||||
<span class="name">
|
||||
<span>
|
||||
<el-skeleton animated>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="text" class="name-skeleton"/>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</span>
|
||||
<span></span>
|
||||
</span>
|
||||
<span class="phone-time">
|
||||
<el-skeleton animated>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="text" style="width: 50px;height: 1rem;"/>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="email-text-skeleton">
|
||||
<el-skeleton animated>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="text" class="text-skeleton-one"/>
|
||||
<el-skeleton-item variant="text" class="text-skeleton-two"/>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
<div class="user-info" v-if="showUserInfo">
|
||||
<div class="user">
|
||||
<el-skeleton animated>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="text"
|
||||
style="width: 180px;margin-right: 15px;height: 1rem;margin-bottom: 4px;"/>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
<div class="account">
|
||||
<el-skeleton animated>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="text"
|
||||
style="width: 180px;margin-right: 15px;height: 1rem;margin-bottom: 4px;"/>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
<div class="del-status" v-if="item.isDel">
|
||||
<el-tag type="danger" size="small">{{ $t('deleted') }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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;"/>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
showStar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
accountShow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showStatus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showUserInfo: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
import {Icon} from "@iconify/vue";
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.phone-star {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pc-star {
|
||||
display: flex;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.pc-star {
|
||||
display: none;
|
||||
}
|
||||
.phone-star {
|
||||
display: block;
|
||||
align-self: end;
|
||||
padding-right: 16px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.star-pd {
|
||||
padding-top: 6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -106,4 +106,4 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -55,24 +55,33 @@ export async function init() {
|
||||
document.title = setting.title;
|
||||
}
|
||||
|
||||
const loading = document.getElementById('loading-first');
|
||||
|
||||
if (!setting.background) {
|
||||
loading.remove();
|
||||
removeLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
const img = new Image();
|
||||
img.src = cvtR2Url(setting.background);
|
||||
img.onload = () => {
|
||||
loading.remove();
|
||||
removeLoading();
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
|
||||
console.warn('背景图片加载失败:', img.src);
|
||||
loading.remove();
|
||||
removeLoading();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function removeLoading() {
|
||||
if (window.innerWidth < 1025) {
|
||||
document.documentElement.style.setProperty('--loading-hide-transition', 'none')
|
||||
}
|
||||
const doc = document.getElementById('loading-first');
|
||||
doc.classList.add('loading-hide')
|
||||
setTimeout(() => {
|
||||
doc.remove()
|
||||
},1000)
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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} <${email.sendEmail}> ${t('wrote')}:
|
||||
${formatDetailDate(email.createTime)} ${email.name} <${email.sendEmail}> ${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);
|
||||
|
||||
@@ -5,7 +5,8 @@ import './style.css';
|
||||
import { init } from '@/init/init.js';
|
||||
import { createPinia } from 'pinia';
|
||||
import piniaPersistedState from 'pinia-plugin-persistedstate';
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css';
|
||||
import 'nprogress/nprogress.css';
|
||||
import perm from "@/perm/perm.js";
|
||||
const pinia = createPinia().use(piniaPersistedState)
|
||||
import i18n from "@/i18n/index.js";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import NProgress from 'nprogress';
|
||||
import {useUiStore} from "@/store/ui.js";
|
||||
|
||||
const routes = [
|
||||
@@ -74,8 +75,25 @@ const router = createRouter({
|
||||
routes
|
||||
})
|
||||
|
||||
NProgress.configure({
|
||||
showSpinner: false, // 不显示旋转图标
|
||||
trickleSpeed: 50, // 自动递增速度
|
||||
minimum: 0.1 // 最小百分比
|
||||
});
|
||||
|
||||
let timer
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
|
||||
// 延迟 50ms 才启动进度条
|
||||
timer = setTimeout(() => {
|
||||
NProgress.start()
|
||||
}, 50)
|
||||
|
||||
const token = localStorage.getItem('token')
|
||||
|
||||
if (!token && to.name !== 'login') {
|
||||
@@ -99,6 +117,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
router.afterEach((to) => {
|
||||
|
||||
clearTimeout(timer)
|
||||
NProgress.done();
|
||||
|
||||
const uiStore = useUiStore()
|
||||
if (to.meta.menu) {
|
||||
if (['content', 'email', 'send'].includes(to.meta.name)) {
|
||||
|
||||
@@ -136,6 +136,7 @@ button, input, select, textarea {
|
||||
--base-border-color: #DCDFE6;
|
||||
--secondary-text-color: #909399;
|
||||
--message-block-color: rgba(0, 0, 0, 0);
|
||||
--loading-hide-transition: all 200ms;
|
||||
--vt-x: 50%;
|
||||
--vt-y: 50%;
|
||||
--vt-duration: 520ms;
|
||||
|
||||
@@ -205,8 +205,8 @@ function getEmailList(emailId, size) {
|
||||
}
|
||||
|
||||
.search-input {
|
||||
|
||||
width: min(280px,calc(100vw - 140px));
|
||||
width: 100%;
|
||||
max-width: 280px;
|
||||
height: 28px;
|
||||
.setting-icon {
|
||||
position: relative;
|
||||
|
||||
@@ -9,15 +9,16 @@
|
||||
@jump="jumpContent"
|
||||
actionLeft="6px"
|
||||
:show-account-icon="false"
|
||||
:show-first-loading="false"
|
||||
:showStar="false"
|
||||
@delete-draft="deleteDraft"
|
||||
:type="'draft'"
|
||||
>
|
||||
<template #name="props">
|
||||
<span class="send-email" >{{props.email.receiveEmail.join(',') || '('+$t('noRecipient')+')'}}</span>
|
||||
<span class="send-email">{{ props.email.receiveEmail.join(',') || '(' + $t('noRecipient') + ')' }}</span>
|
||||
</template>
|
||||
<template #subject="props" >
|
||||
{{props.email.subject || '('+$t('noSubject')+')'}}
|
||||
<template #subject="props">
|
||||
{{ props.email.subject || '(' + $t('noSubject') + ')' }}
|
||||
</template>
|
||||
</emailScroll>
|
||||
</template>
|
||||
@@ -53,19 +54,21 @@ watch(() => draftStore.setDraft, async () => {
|
||||
if (!draft.content && !draft.subject && !(draft.receiveEmail.length > 0)) {
|
||||
await db.value.draft.delete(draftId);
|
||||
await db.value.att.delete(draftId);
|
||||
scroll.value.refreshList();
|
||||
draftStore.refreshList++
|
||||
return;
|
||||
}
|
||||
|
||||
await db.value.draft.update(draftId, draft);
|
||||
await db.value.att.update(draftId, {attachments: attachments});
|
||||
scroll.value.refreshList();
|
||||
},{
|
||||
draftStore.refreshList++
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
watch(() => draftStore.refreshList,() => {
|
||||
scroll.value.refreshList();
|
||||
watch(() => draftStore.refreshList, async () => {
|
||||
const {list} = await getEmailList();
|
||||
scroll.value.emailList.length = 0
|
||||
scroll.value.emailList.push(...list)
|
||||
})
|
||||
|
||||
function getEmailList() {
|
||||
@@ -78,7 +81,7 @@ function getEmailList() {
|
||||
|
||||
async function deleteDraft(draftIds) {
|
||||
await db.value.draft.bulkDelete(draftIds);
|
||||
scroll.value.refreshList();
|
||||
draftStore.refreshList++
|
||||
}
|
||||
|
||||
async function jumpContent(email) {
|
||||
@@ -101,4 +104,4 @@ onMounted(() => {
|
||||
.send-email {
|
||||
font-weight: normal;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -503,7 +503,7 @@ function openAdd() {
|
||||
|
||||
.loading-hide {
|
||||
pointer-events: none;
|
||||
transition: all 200ms;
|
||||
transition: var(--loading-hide-transition);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -500,7 +500,7 @@ window.onresize = () => {
|
||||
|
||||
.loading-hide {
|
||||
pointer-events: none;
|
||||
transition: all 200ms;
|
||||
transition: var(--loading-hide-transition);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="settings-container">
|
||||
<div v-if="firstLoading" class="loading">
|
||||
<div class="loading" :class="firstLoading ? 'loading-show' : 'loading-hide'" >
|
||||
<loading/>
|
||||
</div>
|
||||
<el-scrollbar class="scroll" v-else>
|
||||
<el-scrollbar class="scroll" v-if="!firstLoading">
|
||||
<div class="scroll-body">
|
||||
<div class="card-grid">
|
||||
<!-- Website Settings Card -->
|
||||
@@ -660,7 +660,7 @@ defineOptions({
|
||||
name: 'sys-setting'
|
||||
})
|
||||
|
||||
const currentVersion = 'v1.7.0'
|
||||
const currentVersion = 'v1.8.0'
|
||||
const hasUpdate = ref(false)
|
||||
let getUpdateErrorCount = 1;
|
||||
const {t, locale} = useI18n();
|
||||
@@ -1172,12 +1172,28 @@ function editSetting(settingForm, refreshStatus = true) {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: var(--extra-light-fill) !important;
|
||||
|
||||
position: relative;
|
||||
.loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.loading-show {
|
||||
transition: all 200ms ease 200ms;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.loading-hide {
|
||||
transition: var(--loading-hide-transition);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -957,7 +957,7 @@ function adjustWidth() {
|
||||
|
||||
.loading-hide {
|
||||
pointer-events: none;
|
||||
transition: all 200ms;
|
||||
transition: var(--loading-hide-transition);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,15 +31,6 @@ export default defineConfig(({mode}) => {
|
||||
target: 'es2022',
|
||||
outDir: '../mail-worker/dist',
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
return 'main'
|
||||
},
|
||||
chunkFileNames: 'assets/[name]-[hash].js',
|
||||
entryFileNames: 'assets/[name]-[hash].js',
|
||||
}
|
||||
},
|
||||
assetsInclude: ['**/*.json'],
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user