Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 42 additions & 21 deletions mail-view/src/layout/account/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
class="search-input"
size="small"
clearable
@input="handleSearchInput"
@clear="handleSearchClear"
>
<template #prefix>
<Icon icon="mingcute:search-line" width="14" height="14" />
Expand All @@ -20,7 +22,7 @@
<div v-infinite-scroll="getAccountList" :infinite-scroll-distance="400" :infinite-scroll-immediate="false">
<!-- Account rows -->
<div
v-for="(item, index) in filteredAccounts"
v-for="(item, index) in accounts"
:key="item.accountId"
class="account-row"
:class="{ 'row-active': accountStore.currentAccountId === item.accountId }"
Expand Down Expand Up @@ -75,7 +77,7 @@
</div>

<!-- No search results -->
<div v-if="searchKeyword && filteredAccounts.length === 0 && !loading" class="empty-search">
<div v-if="searchKeyword.trim() && accounts.length === 0 && !loading" class="empty-search">
<Icon icon="mingcute:search-line" width="22" height="22" />
<span>{{ $t('noMessagesFound') }}</span>
</div>
Expand All @@ -95,7 +97,7 @@
</template>

<!-- Follow Loading Skeleton -->
<template v-if="accounts.length > 0 && !noLoading && !searchKeyword">
<template v-if="accounts.length > 0 && !noLoading">
<div class="skeleton-row">
<el-skeleton animated>
<template #template>
Expand All @@ -108,10 +110,10 @@
</div>
</template>

<div class="foot-tip" v-if="noLoading && accounts.length > 0 && !searchKeyword">
<div class="foot-tip" v-if="noLoading && accounts.length > 0">
{{ accounts.length }} {{ $t('accountTotal') }}
</div>
<div class="empty" v-if="noLoading && accounts.length === 0">
<div class="empty" v-if="!searchKeyword.trim() && noLoading && accounts.length === 0">
<el-empty :image-size="40" :description="$t('noMessagesFound')"/>
</div>
</div>
Expand Down Expand Up @@ -186,7 +188,7 @@

<script setup>
import { Icon } from "@iconify/vue";
import { nextTick, reactive, ref, computed, watch } from "vue";
import { nextTick, reactive, ref, watch, onUnmounted } from "vue";
import {
accountList,
accountAdd,
Expand Down Expand Up @@ -231,7 +233,6 @@ let turnstileId = null;
const botJsError = ref(false);
let verifyToken = '';
let verifyErrorCount = 0;
let first = true;
const addForm = reactive({
email: '',
suffix: settingStore.domainList[0]
Expand All @@ -243,16 +244,8 @@ const transferShow = ref(false);
const transferLoading = ref(false);
const transferAccount = ref(null);
const transferTargetId = ref('');

// Filtered accounts based on search keyword
const filteredAccounts = computed(() => {
if (!searchKeyword.value) return accounts;
const kw = searchKeyword.value.toLowerCase();
return accounts.filter(a =>
a.email.toLowerCase().includes(kw) ||
(a.name && a.name.toLowerCase().includes(kw))
);
});
let accountQuerySeq = 0;
let accountSearchTimer = null;

if (hasPerm('account:query')) {
getAccountList();
Expand All @@ -262,6 +255,10 @@ watch(() => accountStore.changeUserAccountName, () => {
if (accounts[0]) accounts[0].name = accountStore.changeUserAccountName;
});

onUnmounted(() => {
if (accountSearchTimer) clearTimeout(accountSearchTimer);
});


window.onTurnstileError = (e) => {
if (verifyErrorCount >= 4) return;
Expand Down Expand Up @@ -341,18 +338,39 @@ function remove(acc) {
}

function refresh() {
if (loading.value) return;
accountQuerySeq++;
if (accountSearchTimer) {
clearTimeout(accountSearchTimer);
accountSearchTimer = null;
}
loading.value = false;
followLoading.value = false;
noLoading.value = false;
queryParams.accountId = 0;
queryParams.lastSort = null;
getSkeletonRows();
scrollbarRef.value.setScrollTop(0);
scrollbarRef.value?.setScrollTop?.(0);
accounts.splice(0, accounts.length);
getAccountList();
}

function handleSearchInput() {
accountQuerySeq++;
loading.value = true;
followLoading.value = false;
noLoading.value = false;
if (accountSearchTimer) clearTimeout(accountSearchTimer);
accountSearchTimer = setTimeout(refresh, 300);
}

function handleSearchClear() {
if (accountSearchTimer) {
clearTimeout(accountSearchTimer);
accountSearchTimer = null;
}
refresh();
}

function changeAccount(acc) {
accountStore.currentAccountId = acc.accountId;
accountStore.currentAccount = acc;
Expand Down Expand Up @@ -385,18 +403,21 @@ function getAccountList() {
if (accounts.length === 0) { loading.value = true; }
else { followLoading.value = true; }
let start = Date.now();
const seq = accountQuerySeq;
const keyword = searchKeyword.value.trim();
const accountId = accounts.length > 0 ? accounts.at(-1).accountId : 0;
const lastSort = accounts.length > 0 ? accounts.at(-1).sort : null;
accountList(accountId, queryParams.size, lastSort).then(async list => {
accountList(accountId, queryParams.size, lastSort, keyword).then(async list => {
let duration = Date.now() - start;
if (duration < 200) await sleep(200 - duration);
if (seq !== accountQuerySeq) return;
if (list.length < queryParams.size) noLoading.value = true;
if (accounts.length === 0 && list[0]) accountStore.currentAccount = list[0];
accounts.push(...list);
loading.value = false;
followLoading.value = false;
first = false;
}).catch(() => {
if (seq !== accountQuerySeq) return;
loading.value = false;
followLoading.value = false;
});
Expand Down
6 changes: 3 additions & 3 deletions mail-view/src/request/account.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import http from '@/axios/index.js'

export function accountList(accountId, size, lastSort) {
return http.get('/account/list', {params: {accountId, size, lastSort}});
export function accountList(accountId, size, lastSort, email) {
return http.get('/account/list', {params: {accountId, size, lastSort, email}});
}

export function accountAdd(email,token) {
Expand All @@ -22,4 +22,4 @@ export function accountSetAllReceive(accountId) {

export function accountSetAsTop(accountId) {
return http.put('/account/setAsTop', {accountId})
}
}
67 changes: 57 additions & 10 deletions mail-view/src/views/transfer/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
v-model="createForm.accountId"
:placeholder="$t('selectAccount')"
filterable
remote
style="width:100%"
:loading="accountsLoading"
:remote-method="searchMyAccounts"
@change="handleAccountChange"
>
<el-option
v-for="acc in myAccounts"
Expand Down Expand Up @@ -152,7 +155,7 @@
</template>

<script setup>
import { ref, reactive, onMounted, defineOptions } from 'vue'
import { ref, reactive, onMounted, onUnmounted, defineOptions } from 'vue'
import { Icon } from '@iconify/vue'
import { useI18n } from 'vue-i18n'
import {
Expand Down Expand Up @@ -182,20 +185,61 @@ const myAccounts = ref([])
const accountsLoading = ref(false)
const createLoading = ref(false)
const createForm = reactive({ accountId: null, toDisplayId: '' })
const selectedAccount = ref(null)
const accountPageSize = 100
let accountSearchSeq = 0
let accountSearchTimer = null

function isTransferableAccount(account) {
const primaryAccountId = userStore.user.account?.accountId
const primaryEmail = userStore.user.email?.toLowerCase()
return account.accountId !== primaryAccountId && account.email?.toLowerCase() !== primaryEmail
}

async function loadMyAccounts() {
async function loadMyAccounts(keyword = '') {
const query = typeof keyword === 'string' ? keyword.trim() : ''
const seq = ++accountSearchSeq
accountsLoading.value = true
try {
const list = await accountList(0, 200, null)
const primaryAccountId = userStore.user.account?.accountId
const primaryEmail = userStore.user.email?.toLowerCase()
myAccounts.value = list.filter(account =>
account.accountId !== primaryAccountId && account.email?.toLowerCase() !== primaryEmail
)
if (!myAccounts.value.some(account => account.accountId === createForm.accountId)) {
const list = await accountList(0, accountPageSize, null, query)
if (seq !== accountSearchSeq) return
const nextAccounts = list.filter(isTransferableAccount)
const selectedInResult = createForm.accountId
? nextAccounts.some(account => account.accountId === createForm.accountId)
: false

if (query && createForm.accountId && !selectedInResult) {
createForm.accountId = null
selectedAccount.value = null
} else if (selectedAccount.value && !selectedInResult) {
nextAccounts.unshift(selectedAccount.value)
}

myAccounts.value = nextAccounts

if (!query && !createForm.accountId) {
createForm.accountId = myAccounts.value[0]?.accountId ?? null
selectedAccount.value = myAccounts.value[0] ?? null
} else if (createForm.accountId) {
selectedAccount.value = myAccounts.value.find(account => account.accountId === createForm.accountId) ?? selectedAccount.value
}
} finally { accountsLoading.value = false }
} finally {
if (seq === accountSearchSeq) accountsLoading.value = false
}
}

function searchMyAccounts(keyword) {
accountSearchSeq++
accountsLoading.value = true
if (accountSearchTimer) clearTimeout(accountSearchTimer)
accountSearchTimer = setTimeout(() => {
accountSearchTimer = null
loadMyAccounts(keyword)
}, 300)
}

function handleAccountChange(accountId) {
selectedAccount.value = myAccounts.value.find(account => account.accountId === accountId) ?? null
}

async function doCreate() {
Expand Down Expand Up @@ -249,6 +293,9 @@ async function doReject(item) {
}

onMounted(() => { loadAll(); loadMyAccounts() })
onUnmounted(() => {
if (accountSearchTimer) clearTimeout(accountSearchTimer)
})
</script>

<style lang="scss" scoped>
Expand Down
35 changes: 22 additions & 13 deletions mail-worker/src/service/account-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,12 @@ const accountService = {

list(c, params, userId) {

let { accountId, size, lastSort } = params;
let { accountId, size, lastSort, email } = params;

accountId = Number(accountId);
size = Number(size);
lastSort = Number(lastSort);
email = typeof email === 'string' ? email.trim() : '';

if (size > 100) {
size = 100;
Expand All @@ -148,18 +149,27 @@ const accountService = {
lastSort = 9999999999;
}

return orm(c).select().from(account).where(
and(
eq(account.userId, userId),
eq(account.isDel, isDel.NORMAL),
or(
lt(account.sort, lastSort),
and(
eq(account.sort, lastSort),
gt(account.accountId, accountId)
)
))
const conditions = [
eq(account.userId, userId),
eq(account.isDel, isDel.NORMAL),
or(
lt(account.sort, lastSort),
and(
eq(account.sort, lastSort),
gt(account.accountId, accountId)
)
)
];

if (email) {
const keyword = '%' + email + '%';
conditions.push(or(
sql`${account.email} COLLATE NOCASE LIKE ${keyword}`,
sql`${account.name} COLLATE NOCASE LIKE ${keyword}`
));
}

return orm(c).select().from(account).where(and(...conditions))
.orderBy(desc(account.sort), asc(account.accountId))
.limit(size)
.all();
Expand Down Expand Up @@ -284,7 +294,6 @@ const accountService = {

async setAsTop(c, params, userId) {
const { accountId } = params;
console.log(accountId);
const userRow = await userService.selectById(c, userId);
const mainAccountRow = await accountService.selectByEmailIncludeDel(c, userRow.email);
let mainSort = mainAccountRow.sort === 0 ? 2 : mainAccountRow.sort + 1;
Expand Down
Loading