mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-21 00:00:05 +08:00
feat: optimize email filtering with frontend-only search (#787)
* feat: optimize email filtering with frontend-only search - Remove backend keyword parameter from mail APIs (breaking change) - Implement frontend filtering on current page (20-100 items) - Add message_id database index for UPDATE performance - Support desktop and mobile responsive layouts - Update API documentation and CHANGELOG BREAKING CHANGE: /admin/mails and /user_api/mails no longer accept keyword parameter 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: restore Mail ID query input in Index.vue - Keep showMailIdQuery UI input for querying specific mail by ID - Triggered when URL contains mail_id parameter 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,20 +49,44 @@ const props = defineProps({
|
||||
default: (mail_id, filename, blob) => { },
|
||||
required: false
|
||||
},
|
||||
showFilterInput: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
})
|
||||
|
||||
const localFilterKeyword = ref('')
|
||||
|
||||
const {
|
||||
isDark, mailboxSplitSize, indexTab, loading, useUTCDate,
|
||||
autoRefresh, configAutoRefreshInterval, sendMailModel
|
||||
} = useGlobalState()
|
||||
const autoRefreshInterval = ref(configAutoRefreshInterval.value)
|
||||
const data = ref([])
|
||||
const rawData = ref([])
|
||||
const timer = ref(null)
|
||||
|
||||
const count = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
|
||||
// Computed property for filtered data (only filter current page)
|
||||
const data = computed(() => {
|
||||
if (!localFilterKeyword.value || localFilterKeyword.value.trim() === '') {
|
||||
return rawData.value;
|
||||
}
|
||||
const keyword = localFilterKeyword.value.toLowerCase();
|
||||
return rawData.value.filter(mail => {
|
||||
// Search in subject, text, message fields
|
||||
const searchFields = [
|
||||
mail.subject || '',
|
||||
mail.text || '',
|
||||
mail.message || ''
|
||||
].map(field => field.toLowerCase());
|
||||
return searchFields.some(field => field.includes(keyword));
|
||||
});
|
||||
})
|
||||
|
||||
const canGoPrevMail = computed(() => {
|
||||
if (!curMail.value) return false
|
||||
const currentIndex = data.value.findIndex(mail => mail.id === curMail.value.id)
|
||||
@@ -136,6 +160,8 @@ const { t } = useI18n({
|
||||
unselectAll: 'Unselect All',
|
||||
prevMail: 'Previous',
|
||||
nextMail: 'Next',
|
||||
keywordQueryTip: 'Filter current page',
|
||||
query: 'Query',
|
||||
},
|
||||
zh: {
|
||||
success: '成功',
|
||||
@@ -158,6 +184,8 @@ const { t } = useI18n({
|
||||
unselectAll: '取消全选',
|
||||
prevMail: '上一封',
|
||||
nextMail: '下一封',
|
||||
keywordQueryTip: '过滤当前页',
|
||||
query: '查询',
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -197,7 +225,7 @@ const refresh = async () => {
|
||||
pageSize.value, (page.value - 1) * pageSize.value
|
||||
);
|
||||
loading.value = true;
|
||||
data.value = await Promise.all(results.map(async (item) => {
|
||||
rawData.value = await Promise.all(results.map(async (item) => {
|
||||
item.checked = false;
|
||||
return await processItem(item);
|
||||
}));
|
||||
@@ -370,7 +398,7 @@ onBeforeUnmount(() => {
|
||||
<div>
|
||||
<div v-if="!isMobile" class="left">
|
||||
<div style="margin-bottom: 10px;">
|
||||
<n-space v-if="multiActionMode">
|
||||
<n-space v-if="multiActionMode" align="center">
|
||||
<n-button @click="multiActionModeClick(false)" tertiary>
|
||||
{{ t('cancelMultiAction') }}
|
||||
</n-button>
|
||||
@@ -393,7 +421,7 @@ onBeforeUnmount(() => {
|
||||
{{ t('downloadMail') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<n-space v-else>
|
||||
<n-space v-else align="center">
|
||||
<n-button @click="multiActionModeClick(true)" type="primary" tertiary>
|
||||
{{ t('multiAction') }}
|
||||
</n-button>
|
||||
@@ -410,6 +438,9 @@ onBeforeUnmount(() => {
|
||||
<n-button @click="backFirstPageAndRefresh" type="primary" tertiary>
|
||||
{{ t('refresh') }}
|
||||
</n-button>
|
||||
<n-input v-if="showFilterInput" v-model:value="localFilterKeyword"
|
||||
:placeholder="t('keywordQueryTip')" style="width: 200px; display: flex; align-items: center;"
|
||||
clearable />
|
||||
</n-space>
|
||||
</div>
|
||||
<n-split class="left" direction="horizontal" :max="0.75" :min="0.25" :default-size="mailboxSplitSize"
|
||||
@@ -482,10 +513,8 @@ onBeforeUnmount(() => {
|
||||
</n-split>
|
||||
</div>
|
||||
<div class="left" v-else>
|
||||
<n-space justify="center">
|
||||
<div style="display: inline-block;">
|
||||
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
|
||||
</div>
|
||||
<n-space justify="space-around" align="center" :wrap="false" style="display: flex; align-items: center;">
|
||||
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
|
||||
<n-switch v-model:value="autoRefresh" size="small" :round="false">
|
||||
<template #checked>
|
||||
{{ t('refreshAfter', { msg: autoRefreshInterval }) }}
|
||||
@@ -498,6 +527,10 @@ onBeforeUnmount(() => {
|
||||
{{ t('refresh') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<div v-if="showFilterInput" style="padding: 0 10px; margin-top: 8px;">
|
||||
<n-input v-model:value="localFilterKeyword"
|
||||
:placeholder="t('keywordQueryTip')" size="small" clearable />
|
||||
</div>
|
||||
<div style="overflow: auto; height: 80vh;">
|
||||
<n-list hoverable clickable>
|
||||
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)">
|
||||
|
||||
Reference in New Issue
Block a user