fix: align user mail navigation

This commit is contained in:
dreamhunter2333
2026-08-23 19:28:06 +08:00
parent 43d9a9dfe9
commit 10760ccc44
17 changed files with 236 additions and 95 deletions
+1 -4
View File
@@ -653,8 +653,5 @@ export const deMessages = {
"components.AddressCredentialModal.username": "Benutzername",
"views.User.send_mail": "E-Mail senden",
"views.user.UserMailClient.noAddress": "Wähle eine verknüpfte E-Mail-Adresse aus",
"views.user.UserMailClient.selectAddress": "Absenderadresse",
"views.user.UserMailClient.selectAddressTip": "Wähle eine verknüpfte Adresse, um E-Mails zu schreiben und gesendete Nachrichten anzuzeigen",
"views.user.UserMailClient.sendbox": "Gesendet",
"views.user.UserMailClient.sendMail": "Verfassen"
"views.user.UserMailClient.sendbox": "Gesendet"
}
+1 -4
View File
@@ -653,8 +653,5 @@ export const esMessages = {
"components.AddressCredentialModal.username": "Usuario",
"views.User.send_mail": "Enviar correo",
"views.user.UserMailClient.noAddress": "Selecciona una dirección de correo vinculada",
"views.user.UserMailClient.selectAddress": "Dirección del remitente",
"views.user.UserMailClient.selectAddressTip": "Elige una dirección vinculada para redactar correos y ver los enviados",
"views.user.UserMailClient.sendbox": "Enviados",
"views.user.UserMailClient.sendMail": "Redactar"
"views.user.UserMailClient.sendbox": "Enviados"
}
+1 -4
View File
@@ -653,8 +653,5 @@ export const jaMessages = {
"components.AddressCredentialModal.username": "ユーザー名",
"views.User.send_mail": "メール送信",
"views.user.UserMailClient.noAddress": "紐付け済みのメールアドレスを選択してください",
"views.user.UserMailClient.selectAddress": "送信元アドレス",
"views.user.UserMailClient.selectAddressTip": "紐付け済みアドレスを選択して、メール作成と送信済みメールの確認ができます",
"views.user.UserMailClient.sendbox": "送信済み",
"views.user.UserMailClient.sendMail": "作成"
"views.user.UserMailClient.sendbox": "送信済み"
}
+1 -4
View File
@@ -653,8 +653,5 @@ export const ptBRMessages = {
"components.AddressCredentialModal.username": "Nome de usuário",
"views.User.send_mail": "Enviar e-mail",
"views.user.UserMailClient.noAddress": "Selecione um endereço de e-mail vinculado",
"views.user.UserMailClient.selectAddress": "Endereço do remetente",
"views.user.UserMailClient.selectAddressTip": "Escolha um endereço vinculado para escrever e ver e-mails enviados",
"views.user.UserMailClient.sendbox": "Enviados",
"views.user.UserMailClient.sendMail": "Escrever"
"views.user.UserMailClient.sendbox": "Enviados"
}
-12
View File
@@ -756,21 +756,9 @@ export const MESSAGE_REGISTRY = {
"en": "Select a bound email address to continue",
"zh": "请选择一个已绑定的邮箱地址"
},
"selectAddress": {
"en": "Sender address",
"zh": "发件邮箱"
},
"selectAddressTip": {
"en": "Choose a bound address to compose mail and view its sent items",
"zh": "选择已绑定邮箱后,可发送邮件并查看该邮箱的发件箱"
},
"sendbox": {
"en": "Sent",
"zh": "发件箱"
},
"sendMail": {
"en": "Compose",
"zh": "写邮件"
}
},
"views.user.UserLogin": {
+5 -1
View File
@@ -15,6 +15,7 @@ const {
} = useGlobalState()
const { t } = useScopedI18n('views.User')
const { t: userMailT } = useScopedI18n('views.user.UserMailClient')
</script>
@@ -28,8 +29,11 @@ const { t } = useScopedI18n('views.User')
<n-tab-pane name="user_mail_box_tab" :tab="t('user_mail_box_tab')">
<UserMailBox />
</n-tab-pane>
<n-tab-pane v-if="openSettings.enableSendMail" name="user_sendbox" :tab="userMailT('sendbox')">
<UserMailClient mode="sendbox" />
</n-tab-pane>
<n-tab-pane v-if="openSettings.enableSendMail" name="user_send_mail" :tab="t('send_mail')">
<UserMailClient />
<UserMailClient mode="send_mail" @sent="userTab = 'user_sendbox'" />
</n-tab-pane>
<n-tab-pane name="user_settings" :tab="t('user_settings')">
<UserSettingsPage />
+20 -4
View File
@@ -26,9 +26,21 @@ const props = defineProps({
type: Number,
default: 0,
},
userAddressMode: {
type: Boolean,
default: false,
},
addressOptions: {
type: Array,
default: () => [],
},
addressLoading: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['sent'])
const emit = defineEmits(['addressScroll', 'sent', 'update:addressId'])
const {
@@ -38,7 +50,7 @@ const {
const { t } = useScopedI18n('views.index.SendMail')
const isUserAddressMode = computed(() => props.addressId > 0)
const isUserAddressMode = computed(() => props.userAddressMode || props.addressId > 0)
const mailSettings = computed(() => (
isUserAddressMode.value ? userAddressSettings.value : settings.value
))
@@ -210,7 +222,7 @@ onMounted(async () => {
<template #header>
<div class="composer-title">
<h2>{{ t('composeMail') }}</h2>
<n-text depth="3">{{ mailSettings.address }}</n-text>
<n-text v-if="!isUserAddressMode" depth="3">{{ mailSettings.address }}</n-text>
</div>
</template>
<template #header-extra>
@@ -235,7 +247,11 @@ onMounted(async () => {
<n-grid cols="1 m:2" responsive="screen" :x-gap="16">
<n-grid-item>
<n-form-item :label="t('senderAddress')" :label-props="{ for: 'send-mail-sender-address' }">
<n-input :value="mailSettings.address" readonly
<n-select v-if="isUserAddressMode" class="address-picker-select" :value="addressId"
:options="addressOptions" :loading="addressLoading" filterable
@scroll="emit('addressScroll', $event)"
@update:value="emit('update:addressId', $event)" />
<n-input v-else :value="mailSettings.address" readonly
:input-props="{ id: 'send-mail-sender-address' }" />
</n-form-item>
</n-grid-item>
@@ -7,6 +7,7 @@ import { NBadge, NPopconfirm, NButton } from 'naive-ui'
import { useGlobalState } from '../../store'
import { api } from '../../api'
import { getRouterPathWithLang } from '../../utils'
import AddressCredentialModal from '../../components/AddressCredentialModal.vue'
import Login from '../common/Login.vue';
@@ -15,6 +16,7 @@ const message = useMessage()
const router = useRouter()
const { locale, t } = useScopedI18n('views.user.AddressManagement')
const { t: accountSettingsT } = useScopedI18n('views.index.AccountSettings')
const data = ref([])
const count = ref(0)
@@ -24,6 +26,20 @@ const showTranferAddress = ref(false)
const currentAddress = ref("")
const currentAddressId = ref(0)
const targetUserEmail = ref('')
const showAddressCredential = ref(false)
const currentAddressCredential = ref('')
const credentialAddress = ref('')
const showCredential = async (row) => {
try {
const { jwt: addressCredential } = await api.fetch(`/user_api/bind_address_jwt/${row.id}`)
currentAddressCredential.value = addressCredential
credentialAddress.value = row.name
showAddressCredential.value = true
} catch (error) {
message.error(error.message || "error")
}
}
const changeMailAddress = async (address_id) => {
try {
@@ -146,6 +162,14 @@ const columns = [
key: 'actions',
render(row) {
return h('div', [
h(NButton,
{
tertiary: true,
type: "primary",
onClick: () => showCredential(row)
},
{ default: () => accountSettingsT('showAddressCredential') }
),
h(NPopconfirm,
{
onPositiveClick: () => changeMailAddress(row.id)
@@ -204,6 +228,8 @@ watch([page, pageSize], async () => {
<template>
<div>
<AddressCredentialModal v-model:show="showAddressCredential" :address="credentialAddress"
:jwt="currentAddressCredential" />
<n-modal v-model:show="showTranferAddress" preset="dialog" :title="t('transferAddress')">
<span>
<p>{{ t("transferAddressTip") }}</p>
+53 -48
View File
@@ -1,5 +1,5 @@
<script setup>
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue'
import { useScopedI18n } from '@/i18n/app'
import { api } from '../../api'
@@ -10,17 +10,32 @@ const SendMail = defineAsyncComponent(() => import('../index/SendMail.vue'))
const ADDRESS_PAGE_SIZE = 100
const props = defineProps({
mode: {
type: String,
default: 'send_mail',
},
})
const emit = defineEmits(['sent'])
const message = useMessage()
const { openSettings } = useGlobalState()
const { t } = useScopedI18n('views.user.UserMailClient')
const { t: mailboxT } = useScopedI18n('views.user.UserMailBox')
const addressId = ref(null)
const selectedAddressId = ref(null)
const addressFilter = ref(null)
const addressOptions = ref([])
const addressCount = ref(0)
const addressLoading = ref(false)
const mailTab = ref('send_mail')
const sendboxKey = ref(0)
const hasMoreAddresses = computed(() => addressOptions.value.length < addressCount.value)
const addressFilterOptions = computed(() => addressOptions.value.map((address) => ({
label: address.label,
value: address.address,
})))
const fetchAddresses = async () => {
if (addressLoading.value || (!hasMoreAddresses.value && addressOptions.value.length > 0)) {
@@ -35,10 +50,14 @@ const fetchAddresses = async () => {
addressOptions.value.push(...results.map((address) => ({
label: address.name,
value: address.id,
address: address.name,
})))
if (offset === 0) {
addressCount.value = count
}
if (props.mode === 'send_mail' && !selectedAddressId.value && addressOptions.value.length > 0) {
selectedAddressId.value = addressOptions.value[0].value
}
} catch (error) {
message.error(error.message || 'error')
} finally {
@@ -56,46 +75,47 @@ const handleAddressScroll = async (event) => {
const fetchSendbox = async (limit, offset) => {
return await api.fetch(
`/user_api/address/${addressId.value}/sendbox?limit=${limit}&offset=${offset}`
`/user_api/sendbox?limit=${limit}&offset=${offset}`
+ (addressFilter.value ? `&address=${encodeURIComponent(addressFilter.value)}` : '')
)
}
const deleteSendboxMail = async (mailId) => {
await api.fetch(
`/user_api/address/${addressId.value}/sendbox/${mailId}`,
{ method: 'DELETE' }
)
await api.fetch(`/user_api/sendbox/${mailId}`, { method: 'DELETE' })
}
const querySendbox = () => {
sendboxKey.value = Date.now()
}
watch(addressFilter, querySendbox)
onMounted(fetchAddresses)
</script>
<template>
<div class="user-mail-client">
<n-card class="address-picker" :bordered="false" embedded size="small">
<n-flex align="center" justify="space-between" :wrap="true">
<div class="address-picker-copy">
<n-text strong>{{ t('selectAddress') }}</n-text>
<n-text depth="3">{{ t('selectAddressTip') }}</n-text>
</div>
<n-select v-model:value="addressId" class="address-picker-select" :options="addressOptions"
:loading="addressLoading" :placeholder="t('selectAddress')" filterable clearable
<template v-if="mode === 'send_mail'">
<n-empty v-if="!selectedAddressId" class="address-empty" :description="t('noAddress')" />
<SendMail v-else :key="selectedAddressId" user-address-mode :address-id="selectedAddressId"
:address-options="addressOptions" :address-loading="addressLoading"
@address-scroll="handleAddressScroll" @update:address-id="selectedAddressId = $event"
@sent="emit('sent')" />
</template>
<template v-else>
<n-input-group>
<n-select v-model:value="addressFilter" :options="addressFilterOptions" clearable
:loading="addressLoading" :placeholder="mailboxT('addressQueryTip')"
@scroll="handleAddressScroll" />
</n-flex>
</n-card>
<n-empty v-if="!addressId" class="address-empty" :description="t('noAddress')" />
<n-tabs v-else v-model:value="mailTab" type="line" animated>
<n-tab-pane name="send_mail" :tab="t('sendMail')" display-directive="show:lazy">
<SendMail :key="addressId" :address-id="addressId" @sent="mailTab = 'sendbox'" />
</n-tab-pane>
<n-tab-pane name="sendbox" :tab="t('sendbox')" display-directive="show:lazy">
<SendBox :key="addressId" :fetch-mail-data="fetchSendbox"
:enable-user-delete-email="openSettings.enableUserDeleteEmail"
:delete-mail="deleteSendboxMail" />
</n-tab-pane>
</n-tabs>
<n-button @click="querySendbox" type="primary" tertiary>
{{ mailboxT('query') }}
</n-button>
</n-input-group>
<div class="filter-spacing"></div>
<SendBox :key="sendboxKey" :fetch-mail-data="fetchSendbox" show-e-mail-from
:enable-user-delete-email="openSettings.enableUserDeleteEmail"
:delete-mail="deleteSendboxMail" />
</template>
</div>
</template>
@@ -105,27 +125,12 @@ onMounted(fetchAddresses)
text-align: left;
}
.address-picker {
margin-bottom: 10px;
}
.address-picker-copy {
display: flex;
flex-direction: column;
gap: 2px;
}
.address-picker-select {
width: min(420px, 100%);
.filter-spacing {
margin-top: 10px;
}
.address-empty {
padding: 72px 0;
}
@media (max-width: 640px) {
.address-picker-select {
width: 100%;
}
}
</style>