mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-04 23:17:34 +08:00
feat: add extensible mail flags
This commit is contained in:
@@ -99,6 +99,8 @@ const getOpenSettings = async (message, notification) => {
|
||||
disableAnonymousUserCreateEmail: res["disableAnonymousUserCreateEmail"] || false,
|
||||
disableCustomAddressName: res["disableCustomAddressName"] || false,
|
||||
enableUserDeleteEmail: res["enableUserDeleteEmail"] || false,
|
||||
enableMailReadStatus: res["enableMailReadStatus"] === true,
|
||||
enableMailFlagged: res["enableMailFlagged"] === true,
|
||||
enableAutoReply: res["enableAutoReply"] || false,
|
||||
enableIndexAbout: res["enableIndexAbout"] || false,
|
||||
copyright: res["copyright"] || openSettings.value.copyright,
|
||||
|
||||
@@ -3,7 +3,10 @@ import { watch, onMounted, ref, onBeforeUnmount, computed } from "vue";
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { useScopedI18n } from '@/i18n/app'
|
||||
import { useGlobalState } from '../store'
|
||||
import { CloudDownloadRound, ArrowBackIosNewFilled, ArrowForwardIosFilled, InboxRound } from '@vicons/material'
|
||||
import {
|
||||
CloudDownloadRound, ArrowBackIosNewFilled, ArrowForwardIosFilled, InboxRound,
|
||||
StarBorderRound, StarRound
|
||||
} from '@vicons/material'
|
||||
import { useIsMobile } from '../utils/composables'
|
||||
import { processItem } from '../utils/email-parser'
|
||||
import { utcToLocalDate } from '../utils';
|
||||
@@ -55,9 +58,37 @@ const props = defineProps({
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
enableMailReadStatus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
enableMailFlagged: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
updateMailState: {
|
||||
type: Function,
|
||||
default: () => { },
|
||||
required: false
|
||||
},
|
||||
updateMailFlagged: {
|
||||
type: Function,
|
||||
default: () => { },
|
||||
required: false
|
||||
},
|
||||
fetchMailStates: {
|
||||
type: Function,
|
||||
default: () => ({ results: [] }),
|
||||
required: false
|
||||
},
|
||||
})
|
||||
|
||||
const localFilterKeyword = ref('')
|
||||
const mailStateFilter = ref(null)
|
||||
const flaggedOnly = ref(false)
|
||||
const mailStates = ref([])
|
||||
|
||||
const {
|
||||
isDark, mailboxSplitSize, mailListView, mailListPreviewLineClamp, indexTab, loading, useUTCDate,
|
||||
@@ -94,6 +125,73 @@ const data = computed(() => {
|
||||
});
|
||||
})
|
||||
|
||||
const isMailUnread = (mail) => {
|
||||
return props.enableMailReadStatus && mail?.unread === true
|
||||
}
|
||||
|
||||
const currentPageHasUnread = computed(() => rawData.value.some(isMailUnread))
|
||||
const mailStateFilterOptions = computed(() => mailStates.value.map(state => ({
|
||||
label: state.label || t(state.label_key),
|
||||
value: state.value,
|
||||
})))
|
||||
|
||||
const getReadStateValue = (unread) => {
|
||||
return mailStates.value.find(state => state.unread === unread)?.value
|
||||
}
|
||||
|
||||
const updateUnreadState = async (mails, state) => {
|
||||
if (mails.length === 0 || !state) return true
|
||||
try {
|
||||
const response = await props.updateMailState(mails.map(mail => mail.id), state)
|
||||
const results = response?.results ?? []
|
||||
const resultById = new Map(results.map(result => [result.id, result]))
|
||||
mails.forEach(mail => {
|
||||
const result = resultById.get(mail.id)
|
||||
if (result) mail.unread = result.unread
|
||||
})
|
||||
return true
|
||||
} catch (error) {
|
||||
message.error(error.message || "error")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const markMailsRead = async (mails) => {
|
||||
return await updateUnreadState(mails.filter(isMailUnread), getReadStateValue(false))
|
||||
}
|
||||
|
||||
const toggleCurrentMailUnread = async () => {
|
||||
if (!curMail.value) return
|
||||
await updateUnreadState([curMail.value], getReadStateValue(!curMail.value.unread))
|
||||
}
|
||||
|
||||
const toggleMailFlagged = async (mail) => {
|
||||
if (!mail) return
|
||||
try {
|
||||
const response = await props.updateMailFlagged([mail.id], !mail.flagged)
|
||||
const result = response?.results?.[0]
|
||||
if (result) mail.flagged = result.flagged
|
||||
if (flaggedOnly.value && !mail.flagged) await backFirstPageAndRefresh()
|
||||
} catch (error) {
|
||||
message.error(error.message || "error")
|
||||
}
|
||||
}
|
||||
|
||||
const toggleCurrentMailFlagged = async () => {
|
||||
await toggleMailFlagged(curMail.value)
|
||||
}
|
||||
|
||||
const openMail = async (mail) => {
|
||||
curMail.value = mail
|
||||
await markMailsRead([mail])
|
||||
}
|
||||
|
||||
const markCurrentPageRead = async () => {
|
||||
if (!await markMailsRead(rawData.value)) return
|
||||
message.success(t("success"))
|
||||
if (mailStateFilter.value === getReadStateValue(true)) await backFirstPageAndRefresh()
|
||||
}
|
||||
|
||||
const canGoPrevMail = computed(() => {
|
||||
if (!curMail.value) return false
|
||||
const currentIndex = data.value.findIndex(mail => mail.id === curMail.value.id)
|
||||
@@ -111,12 +209,12 @@ const prevMail = async () => {
|
||||
const currentIndex = data.value.findIndex(mail => mail.id === curMail.value.id)
|
||||
|
||||
if (currentIndex > 0) {
|
||||
curMail.value = data.value[currentIndex - 1]
|
||||
await openMail(data.value[currentIndex - 1])
|
||||
} else if (page.value > 1) {
|
||||
page.value--
|
||||
await refresh()
|
||||
if (data.value.length > 0) {
|
||||
curMail.value = data.value[data.value.length - 1]
|
||||
await openMail(data.value[data.value.length - 1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,12 +224,12 @@ const nextMail = async () => {
|
||||
const currentIndex = data.value.findIndex(mail => mail.id === curMail.value.id)
|
||||
|
||||
if (currentIndex < data.value.length - 1) {
|
||||
curMail.value = data.value[currentIndex + 1]
|
||||
await openMail(data.value[currentIndex + 1])
|
||||
} else if (count.value > page.value * pageSize.value) {
|
||||
page.value++
|
||||
await refresh()
|
||||
if (data.value.length > 0) {
|
||||
curMail.value = data.value[0]
|
||||
await openMail(data.value[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,6 +263,22 @@ const setupAutoRefresh = async (autoRefresh) => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadMailStates = async () => {
|
||||
if (!props.enableMailReadStatus) {
|
||||
mailStates.value = []
|
||||
mailStateFilter.value = null
|
||||
return
|
||||
}
|
||||
try {
|
||||
const { results = [] } = await props.fetchMailStates()
|
||||
mailStates.value = results
|
||||
mailStateFilter.value = results.find(state => state.default)?.value ?? results[0]?.value ?? null
|
||||
} catch (error) {
|
||||
mailStates.value = []
|
||||
message.error(error.message || "error")
|
||||
}
|
||||
}
|
||||
|
||||
watch(autoRefresh, async (autoRefresh, old) => {
|
||||
setupAutoRefresh(autoRefresh)
|
||||
}, { immediate: true })
|
||||
@@ -175,19 +289,32 @@ watch([page, pageSize], async ([page, pageSize], [oldPage, oldPageSize]) => {
|
||||
}
|
||||
})
|
||||
|
||||
watch(mailStateFilter, async (_value, oldValue) => {
|
||||
if (oldValue === null) return
|
||||
await backFirstPageAndRefresh()
|
||||
})
|
||||
|
||||
watch(flaggedOnly, async () => {
|
||||
await backFirstPageAndRefresh()
|
||||
})
|
||||
|
||||
watch(() => props.enableMailReadStatus, async (enabled, oldValue) => {
|
||||
if (enabled === oldValue) return
|
||||
await loadMailStates()
|
||||
})
|
||||
|
||||
const refresh = async () => {
|
||||
try {
|
||||
const { results, count: totalCount } = await props.fetchMailData(
|
||||
pageSize.value, (page.value - 1) * pageSize.value
|
||||
pageSize.value, (page.value - 1) * pageSize.value, mailStateFilter.value,
|
||||
flaggedOnly.value
|
||||
);
|
||||
loading.value = true;
|
||||
rawData.value = await Promise.all(results.map(async (item) => {
|
||||
item.checked = false;
|
||||
return await processItem(item);
|
||||
}));
|
||||
if (totalCount > 0) {
|
||||
count.value = totalCount;
|
||||
}
|
||||
if (page.value === 1) count.value = totalCount;
|
||||
curMail.value = null;
|
||||
if (!isMobile.value && !mailListView.value && data.value.length > 0) {
|
||||
curMail.value = data.value[0];
|
||||
@@ -215,7 +342,7 @@ const clickRow = async (row) => {
|
||||
curMail.value = null;
|
||||
return;
|
||||
}
|
||||
curMail.value = row;
|
||||
await openMail(row);
|
||||
};
|
||||
|
||||
|
||||
@@ -329,6 +456,7 @@ const multiActionDownload = async () => {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadMailStates()
|
||||
await refresh();
|
||||
});
|
||||
|
||||
@@ -381,6 +509,14 @@ onBeforeUnmount(() => {
|
||||
<n-button @click="backFirstPageAndRefresh" type="primary" tertiary>
|
||||
{{ t('refresh') }}
|
||||
</n-button>
|
||||
<n-button v-if="enableMailReadStatus && currentPageHasUnread" @click="markCurrentPageRead" tertiary>
|
||||
{{ t('markCurrentPageRead') }}
|
||||
</n-button>
|
||||
<n-select v-if="enableMailReadStatus" v-model:value="mailStateFilter" :options="mailStateFilterOptions"
|
||||
style="width: 120px" />
|
||||
<n-checkbox v-if="enableMailFlagged" v-model:checked="flaggedOnly">
|
||||
{{ t('flagged') }}
|
||||
</n-checkbox>
|
||||
<n-input v-if="showFilterInput" v-model:value="localFilterKeyword"
|
||||
:placeholder="t('keywordQueryTip')" style="width: 200px; display: flex; align-items: center;"
|
||||
clearable />
|
||||
@@ -397,12 +533,21 @@ onBeforeUnmount(() => {
|
||||
<div style="overflow: auto; min-height: 60vh; max-height: 100vh;">
|
||||
<n-list hoverable clickable>
|
||||
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)"
|
||||
:class="mailItemClass(row)">
|
||||
<template #prefix v-if="multiActionMode">
|
||||
<n-checkbox v-model:checked="row.checked" />
|
||||
:class="[mailItemClass(row), { 'mail-list-unread': isMailUnread(row) }]">
|
||||
<template #prefix>
|
||||
<n-checkbox v-if="multiActionMode" v-model:checked="row.checked" />
|
||||
<n-button v-else-if="enableMailFlagged" text circle type="warning" @click.stop="toggleMailFlagged(row)"
|
||||
:aria-label="row.flagged ? t('removeFlagged') : t('addFlagged')">
|
||||
<template #icon>
|
||||
<n-icon :component="row.flagged ? StarRound : StarBorderRound" />
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-thing :title="row.subject">
|
||||
<template #description>
|
||||
<n-tag v-if="isMailUnread(row)" type="warning">
|
||||
{{ t('unread') }}
|
||||
</n-tag>
|
||||
<n-tag type="info">
|
||||
ID: {{ row.id }}
|
||||
</n-tag>
|
||||
@@ -461,6 +606,9 @@ onBeforeUnmount(() => {
|
||||
style="overflow: auto; max-height: 100vh;">
|
||||
<MailContentRenderer :mail="curMail" :showEMailTo="showEMailTo"
|
||||
:enableUserDeleteEmail="enableUserDeleteEmail" :showReply="showReply" :showSaveS3="showSaveS3"
|
||||
:enableMailReadStatus="enableMailReadStatus" :enableMailFlagged="enableMailFlagged"
|
||||
:onToggleUnread="toggleCurrentMailUnread"
|
||||
:onToggleFlagged="toggleCurrentMailFlagged"
|
||||
:onDelete="deleteMail" :onReply="replyMail" :onForward="forwardMail" :onSaveToS3="saveToS3Proxy" />
|
||||
</n-card>
|
||||
<n-card :bordered="false" embedded class="mail-item" v-else>
|
||||
@@ -475,9 +623,15 @@ onBeforeUnmount(() => {
|
||||
<div v-else class="mail-list-scroll">
|
||||
<n-list hoverable clickable>
|
||||
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)"
|
||||
:class="mailItemClass(row)">
|
||||
<template #prefix v-if="multiActionMode">
|
||||
<n-checkbox v-model:checked="row.checked" />
|
||||
:class="[mailItemClass(row), { 'mail-list-unread': isMailUnread(row) }]">
|
||||
<template #prefix>
|
||||
<n-checkbox v-if="multiActionMode" v-model:checked="row.checked" />
|
||||
<n-button v-else-if="enableMailFlagged" text circle type="warning" @click.stop="toggleMailFlagged(row)"
|
||||
:aria-label="row.flagged ? t('removeFlagged') : t('addFlagged')">
|
||||
<template #icon>
|
||||
<n-icon :component="row.flagged ? StarRound : StarBorderRound" />
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-thing class="mail-list-thing">
|
||||
<template #header>
|
||||
@@ -487,6 +641,9 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="mail-list-meta">
|
||||
<n-tag v-if="isMailUnread(row)" type="warning">
|
||||
{{ t('unread') }}
|
||||
</n-tag>
|
||||
<n-tag type="info">
|
||||
ID: {{ row.id }}
|
||||
</n-tag>
|
||||
@@ -529,16 +686,38 @@ onBeforeUnmount(() => {
|
||||
<n-button @click="backFirstPageAndRefresh" tertiary size="small" type="primary">
|
||||
{{ t('refresh') }}
|
||||
</n-button>
|
||||
<n-button v-if="enableMailReadStatus && currentPageHasUnread" @click="markCurrentPageRead" tertiary size="small">
|
||||
{{ t('markCurrentPageRead') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<div v-if="showFilterInput" style="padding: 0 10px; margin-top: 8px; margin-bottom: 10px;">
|
||||
<n-input v-model:value="localFilterKeyword"
|
||||
:placeholder="t('keywordQueryTip')" size="small" clearable />
|
||||
</div>
|
||||
<div v-if="enableMailReadStatus || enableMailFlagged" style="padding: 0 10px; margin-bottom: 10px;">
|
||||
<n-select v-if="enableMailReadStatus" v-model:value="mailStateFilter" :options="mailStateFilterOptions"
|
||||
size="small" />
|
||||
<n-checkbox v-if="enableMailFlagged" v-model:checked="flaggedOnly" style="margin-top: 8px;">
|
||||
{{ t('flagged') }}
|
||||
</n-checkbox>
|
||||
</div>
|
||||
<div style="overflow: auto; min-height: 60vh; max-height: 100vh;">
|
||||
<n-list hoverable clickable>
|
||||
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)">
|
||||
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)"
|
||||
:class="{ 'mail-list-unread': isMailUnread(row) }">
|
||||
<template #prefix>
|
||||
<n-button v-if="enableMailFlagged" text circle type="warning" @click.stop="toggleMailFlagged(row)"
|
||||
:aria-label="row.flagged ? t('removeFlagged') : t('addFlagged')">
|
||||
<template #icon>
|
||||
<n-icon :component="row.flagged ? StarRound : StarBorderRound" />
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
<n-thing :title="row.subject">
|
||||
<template #description>
|
||||
<n-tag v-if="isMailUnread(row)" type="warning">
|
||||
{{ t('unread') }}
|
||||
</n-tag>
|
||||
<n-tag type="info">
|
||||
ID: {{ row.id }}
|
||||
</n-tag>
|
||||
@@ -568,6 +747,9 @@ onBeforeUnmount(() => {
|
||||
<MailContentRenderer :mail="curMail" :showEMailTo="showEMailTo"
|
||||
:enableUserDeleteEmail="enableUserDeleteEmail" :showReply="showReply" :showSaveS3="showSaveS3"
|
||||
:useUTCDate="useUTCDate" :onDelete="deleteMail" :onReply="replyMail" :onForward="forwardMail"
|
||||
:enableMailReadStatus="enableMailReadStatus" :enableMailFlagged="enableMailFlagged"
|
||||
:onToggleUnread="toggleCurrentMailUnread"
|
||||
:onToggleFlagged="toggleCurrentMailFlagged"
|
||||
:onSaveToS3="saveToS3Proxy" />
|
||||
</n-card>
|
||||
</n-drawer-content>
|
||||
@@ -676,6 +858,10 @@ onBeforeUnmount(() => {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mail-list-unread :deep(.n-thing-header__title) {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
|
||||
@@ -34,6 +34,14 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
enableMailReadStatus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
enableMailFlagged: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 回调函数 props
|
||||
onDelete: {
|
||||
type: Function,
|
||||
@@ -50,6 +58,14 @@ const props = defineProps({
|
||||
onSaveToS3: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
},
|
||||
onToggleUnread: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
},
|
||||
onToggleFlagged: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -146,6 +162,14 @@ const handleSaveToS3 = async (filename, blob) => {
|
||||
{{ t('downloadMail') }}
|
||||
</n-button>
|
||||
|
||||
<n-button v-if="enableMailReadStatus" size="small" tertiary type="info" @click="onToggleUnread">
|
||||
{{ mail.unread ? t('markRead') : t('markUnread') }}
|
||||
</n-button>
|
||||
|
||||
<n-button v-if="enableMailFlagged" size="small" tertiary type="warning" @click="onToggleFlagged">
|
||||
{{ mail.flagged ? t('removeFlagged') : t('addFlagged') }}
|
||||
</n-button>
|
||||
|
||||
<n-button v-if="showReply" size="small" tertiary type="info" @click="handleReply">
|
||||
<template #icon>
|
||||
<n-icon :component="ReplyFilled" />
|
||||
|
||||
@@ -34,6 +34,10 @@ export const MESSAGE_REGISTRY = {
|
||||
}
|
||||
},
|
||||
"components.MailBox": {
|
||||
"allMail": {
|
||||
"en": "All Mail",
|
||||
"zh": "全部邮件"
|
||||
},
|
||||
"attachments": {
|
||||
"en": "Show Attachments",
|
||||
"zh": "查看附件"
|
||||
@@ -70,10 +74,18 @@ export const MESSAGE_REGISTRY = {
|
||||
"en": "Forward",
|
||||
"zh": "转发"
|
||||
},
|
||||
"flagged": {
|
||||
"en": "Flagged",
|
||||
"zh": "星标邮件"
|
||||
},
|
||||
"keywordQueryTip": {
|
||||
"en": "Filter current page",
|
||||
"zh": "过滤当前页"
|
||||
},
|
||||
"markCurrentPageRead": {
|
||||
"en": "Mark This Page as Read",
|
||||
"zh": "本页全部已读"
|
||||
},
|
||||
"multiAction": {
|
||||
"en": "Multi Action",
|
||||
"zh": "多选"
|
||||
@@ -94,6 +106,10 @@ export const MESSAGE_REGISTRY = {
|
||||
"en": "Query",
|
||||
"zh": "查询"
|
||||
},
|
||||
"read": {
|
||||
"en": "Read",
|
||||
"zh": "已读"
|
||||
},
|
||||
"refresh": {
|
||||
"en": "Refresh",
|
||||
"zh": "刷新"
|
||||
@@ -129,6 +145,18 @@ export const MESSAGE_REGISTRY = {
|
||||
"unselectAll": {
|
||||
"en": "Unselect All",
|
||||
"zh": "取消全选"
|
||||
},
|
||||
"unread": {
|
||||
"en": "Unread",
|
||||
"zh": "未读"
|
||||
},
|
||||
"addFlagged": {
|
||||
"en": "Add Star",
|
||||
"zh": "添加星标"
|
||||
},
|
||||
"removeFlagged": {
|
||||
"en": "Remove Star",
|
||||
"zh": "取消星标"
|
||||
}
|
||||
},
|
||||
"components.AiExtractInfo": {
|
||||
@@ -170,6 +198,10 @@ export const MESSAGE_REGISTRY = {
|
||||
"en": "View Attachments",
|
||||
"zh": "查看附件"
|
||||
},
|
||||
"addFlagged": {
|
||||
"en": "Add Star",
|
||||
"zh": "添加星标"
|
||||
},
|
||||
"delete": {
|
||||
"en": "Delete",
|
||||
"zh": "删除"
|
||||
@@ -194,10 +226,22 @@ export const MESSAGE_REGISTRY = {
|
||||
"en": "Load Images",
|
||||
"zh": "加载图片"
|
||||
},
|
||||
"markRead": {
|
||||
"en": "Mark as Read",
|
||||
"zh": "标记已读"
|
||||
},
|
||||
"markUnread": {
|
||||
"en": "Mark as Unread",
|
||||
"zh": "标记未读"
|
||||
},
|
||||
"remoteImagesBlocked": {
|
||||
"en": "{count} remote resources blocked to protect your privacy",
|
||||
"zh": "已阻止 {count} 项外部资源以保护隐私"
|
||||
},
|
||||
"removeFlagged": {
|
||||
"en": "Remove Star",
|
||||
"zh": "取消星标"
|
||||
},
|
||||
"reply": {
|
||||
"en": "Reply",
|
||||
"zh": "回复"
|
||||
|
||||
@@ -24,6 +24,8 @@ export const useGlobalState = createGlobalState(
|
||||
disableAnonymousUserCreateEmail: false,
|
||||
disableCustomAddressName: false,
|
||||
enableUserDeleteEmail: false,
|
||||
enableMailReadStatus: false,
|
||||
enableMailFlagged: false,
|
||||
enableAutoReply: false,
|
||||
enableIndexAbout: false,
|
||||
/** @type {string[]} */
|
||||
|
||||
@@ -32,19 +32,41 @@ const SendMail = defineAsyncComponent(() => {
|
||||
|
||||
const { t } = useScopedI18n('views.Index')
|
||||
|
||||
const fetchMailData = async (limit, offset) => {
|
||||
const fetchMailData = async (limit, offset, mailState, flaggedOnly) => {
|
||||
if (mailIdQuery.value > 0) {
|
||||
const singleMail = await api.fetch(`/api/mail/${mailIdQuery.value}`);
|
||||
if (singleMail) return { results: [singleMail], count: 1 };
|
||||
return { results: [], count: 0 };
|
||||
}
|
||||
return await api.fetch(`/api/mails?limit=${limit}&offset=${offset}`);
|
||||
const mailStateQuery = mailState ? `&mail_state=${encodeURIComponent(mailState)}` : ''
|
||||
const flaggedQuery = flaggedOnly ? '&flagged=true' : ''
|
||||
return await api.fetch(
|
||||
`/api/mails?limit=${limit}&offset=${offset}${mailStateQuery}${flaggedQuery}`
|
||||
);
|
||||
};
|
||||
|
||||
const deleteMail = async (curMailId) => {
|
||||
await api.fetch(`/api/mails/${curMailId}`, { method: 'DELETE' });
|
||||
};
|
||||
|
||||
const updateMailState = async (ids, state) => {
|
||||
return await api.fetch(`/api/mails/state`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ ids, state })
|
||||
});
|
||||
};
|
||||
|
||||
const updateMailFlagged = async (ids, flagged) => {
|
||||
return await api.fetch(`/api/mails/flagged`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ ids, flagged })
|
||||
});
|
||||
};
|
||||
|
||||
const fetchMailStates = async () => {
|
||||
return await api.fetch(`/api/mail-states`)
|
||||
}
|
||||
|
||||
const deleteSenboxMail = async (curMailId) => {
|
||||
await api.fetch(`/api/sendbox/${curMailId}`, { method: 'DELETE' });
|
||||
};
|
||||
@@ -127,7 +149,10 @@ onMounted(() => {
|
||||
</div>
|
||||
<MailBox :key="mailBoxKey" :showEMailTo="false" :showReply="openSettings.enableSendMail" :showSaveS3="openSettings.isS3Enabled"
|
||||
:saveToS3="saveToS3" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
|
||||
:fetchMailData="fetchMailData" :deleteMail="deleteMail" :showFilterInput="true" />
|
||||
:fetchMailData="fetchMailData" :deleteMail="deleteMail" :showFilterInput="true"
|
||||
:enableMailReadStatus="openSettings.enableMailReadStatus"
|
||||
:enableMailFlagged="openSettings.enableMailFlagged" :updateMailState="updateMailState"
|
||||
:updateMailFlagged="updateMailFlagged" :fetchMailStates="fetchMailStates" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane v-if="openSettings.enableSendMail" name="sendbox" :tab="t('sendbox')">
|
||||
<SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
|
||||
|
||||
@@ -26,12 +26,17 @@ const message = useMessage()
|
||||
const currentPage = ref(1)
|
||||
const totalCount = ref(0)
|
||||
const currentMail = ref(null)
|
||||
const mailStates = ref([])
|
||||
const showAccountSettingsCard = ref(false)
|
||||
const currentAutoRefreshInterval = ref(60)
|
||||
const timer = ref(null)
|
||||
|
||||
const { t } = useScopedI18n('views.index.SimpleIndex')
|
||||
|
||||
const getReadStateValue = (unread) => {
|
||||
return mailStates.value.find(state => state.unread === unread)?.value
|
||||
}
|
||||
|
||||
// 复制地址
|
||||
const copyAddress = async () => {
|
||||
try {
|
||||
@@ -50,12 +55,58 @@ const fetchMails = async () => {
|
||||
totalCount.value = count > 0 ? count : totalCount.value;
|
||||
const rawMail = results && results.length > 0 ? results[0] : null
|
||||
currentMail.value = rawMail ? await processItem(rawMail) : null
|
||||
if (openSettings.value.enableMailReadStatus && rawMail?.unread) {
|
||||
const state = getReadStateValue(false)
|
||||
if (!state) return
|
||||
const response = await api.fetch(`/api/mails/state`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ ids: [rawMail.id], state })
|
||||
})
|
||||
const updatedMail = response.results?.[0]
|
||||
if (updatedMail) currentMail.value.unread = updatedMail.unread
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch mails:', error)
|
||||
message.error('获取邮件失败')
|
||||
}
|
||||
}
|
||||
|
||||
const toggleCurrentMailUnread = async () => {
|
||||
if (!currentMail.value || !openSettings.value.enableMailReadStatus) return
|
||||
try {
|
||||
const state = getReadStateValue(!currentMail.value.unread)
|
||||
if (!state) return
|
||||
const response = await api.fetch(`/api/mails/state`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
ids: [currentMail.value.id],
|
||||
state,
|
||||
})
|
||||
})
|
||||
const updatedMail = response.results?.[0]
|
||||
if (updatedMail) currentMail.value.unread = updatedMail.unread
|
||||
} catch (error) {
|
||||
message.error(error.message || 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const toggleCurrentMailFlagged = async () => {
|
||||
if (!currentMail.value || !openSettings.value.enableMailFlagged) return
|
||||
try {
|
||||
const response = await api.fetch(`/api/mails/flagged`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
ids: [currentMail.value.id],
|
||||
flagged: !currentMail.value.flagged,
|
||||
})
|
||||
})
|
||||
const updatedMail = response.results?.[0]
|
||||
if (updatedMail) currentMail.value.flagged = updatedMail.flagged
|
||||
} catch (error) {
|
||||
message.error(error.message || 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除邮件
|
||||
const deleteMail = async () => {
|
||||
if (!currentMail.value) return;
|
||||
@@ -106,6 +157,15 @@ watch(currentPage, () => {
|
||||
|
||||
onMounted(async () => {
|
||||
await api.getSettings()
|
||||
if (openSettings.value.enableMailReadStatus) {
|
||||
try {
|
||||
const { results = [] } = await api.fetch(`/api/mail-states`)
|
||||
mailStates.value = results
|
||||
} catch (error) {
|
||||
mailStates.value = []
|
||||
message.error(error.message || "error")
|
||||
}
|
||||
}
|
||||
await fetchMails()
|
||||
|
||||
// 启动自动刷新
|
||||
@@ -220,6 +280,10 @@ onBeforeUnmount(() => {
|
||||
<div style="margin-top: 16px;">
|
||||
<MailContentRenderer :mail="currentMail" :showEMailTo="false" :showReply="false"
|
||||
:enableUserDeleteEmail="openSettings.enableUserDeleteEmail" :showSaveS3="false"
|
||||
:enableMailReadStatus="openSettings.enableMailReadStatus"
|
||||
:enableMailFlagged="openSettings.enableMailFlagged"
|
||||
:onToggleUnread="toggleCurrentMailUnread"
|
||||
:onToggleFlagged="toggleCurrentMailFlagged"
|
||||
:onDelete="deleteMail" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,11 +20,13 @@ const queryMail = () => {
|
||||
mailBoxKey.value = Date.now();
|
||||
}
|
||||
|
||||
const fetchMailData = async (limit, offset) => {
|
||||
const fetchMailData = async (limit, offset, mailState, flaggedOnly) => {
|
||||
return await api.fetch(
|
||||
`/user_api/mails`
|
||||
+ `?limit=${limit}`
|
||||
+ `&offset=${offset}`
|
||||
+ (mailState ? `&mail_state=${encodeURIComponent(mailState)}` : '')
|
||||
+ (flaggedOnly ? '&flagged=true' : '')
|
||||
+ (addressFilter.value ? `&address=${addressFilter.value}` : '')
|
||||
);
|
||||
}
|
||||
@@ -50,6 +52,24 @@ const deleteMail = async (curMailId) => {
|
||||
await api.fetch(`/user_api/mails/${curMailId}`, { method: 'DELETE' });
|
||||
};
|
||||
|
||||
const updateMailState = async (ids, state) => {
|
||||
return await api.fetch(`/user_api/mails/state`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ ids, state })
|
||||
});
|
||||
};
|
||||
|
||||
const updateMailFlagged = async (ids, flagged) => {
|
||||
return await api.fetch(`/user_api/mails/flagged`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({ ids, flagged })
|
||||
});
|
||||
};
|
||||
|
||||
const fetchMailStates = async () => {
|
||||
return await api.fetch(`/user_api/mail-states`)
|
||||
}
|
||||
|
||||
watch(addressFilter, async (newValue) => {
|
||||
queryMail();
|
||||
});
|
||||
@@ -70,6 +90,10 @@ onMounted(() => {
|
||||
</n-input-group>
|
||||
<div style="margin-top: 10px;"></div>
|
||||
<MailBox :key="mailBoxKey" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail" :fetchMailData="fetchMailData"
|
||||
:deleteMail="deleteMail" :showFilterInput="true" />
|
||||
:deleteMail="deleteMail" :showFilterInput="true"
|
||||
:enableMailReadStatus="openSettings.enableMailReadStatus"
|
||||
:enableMailFlagged="openSettings.enableMailFlagged"
|
||||
:updateMailState="updateMailState" :updateMailFlagged="updateMailFlagged"
|
||||
:fetchMailStates="fetchMailStates" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user