feat: admin search mailbox && fix generateName multi dot && user jwt exp in 30 days && UI globalTabplacement && useSideMargin (#214)

* fix: generateName multi dot && user jwt exp in 30 days

* feat: support admin search mailbox

* fix: DELETE mail bug(should be raw_mails)

* feat: UI add globalTabplacement

* feat: UI add useSideMargin option
This commit is contained in:
Dream Hunter
2024-05-09 18:43:09 +08:00
committed by GitHub
parent 1fa56dfe98
commit b7308587c6
18 changed files with 231 additions and 147 deletions
+5 -4
View File
@@ -8,10 +8,11 @@ import Header from './views/Header.vue';
import Footer from './views/Footer.vue';
const { localeCache, isDark, loading } = useGlobalState()
const { localeCache, isDark, loading, useSideMargin } = useGlobalState()
const theme = computed(() => isDark.value ? darkTheme : null)
const localeConfig = computed(() => localeCache.value == 'zh' ? zhCN : null)
const isMobile = useIsMobile()
const showSideMargin = computed(() => !isMobile.value && !useSideMargin.value);
const { locale } = useI18n({
useScope: 'global',
@@ -39,8 +40,8 @@ onMounted(async () => {
<n-spin description="loading..." :show="loading">
<n-message-provider>
<n-grid x-gap="12" :cols="12">
<n-gi v-if="!isMobile" span="1"></n-gi>
<n-gi :span="isMobile ? 12 : 10">
<n-gi v-if="!showSideMargin" span="1"></n-gi>
<n-gi :span="showSideMargin ? 12 : 10">
<div class="main">
<n-space vertical>
<n-layout style="min-height: 80vh;">
@@ -51,7 +52,7 @@ onMounted(async () => {
</n-space>
</div>
</n-gi>
<n-gi v-if="!isMobile" span="1"></n-gi>
<n-gi v-if="!showSideMargin" span="1"></n-gi>
</n-grid>
<n-back-top />
</n-message-provider>
+1 -1
View File
@@ -10,7 +10,7 @@ const {
const instance = axios.create({
baseURL: API_BASE,
timeout: 10000
timeout: 30000
});
const apiFetch = async (path, options = {}) => {
+4
View File
@@ -54,6 +54,8 @@ export const useGlobalState = createGlobalState(
const userJwt = useStorage('userJwt', '');
const userTab = useStorage('userTab', 'user_settings');
const indexTab = useStorage('indexTab', 'mailbox');
const globalTabplacement = useStorage('globalTabplacement', 'top');
const useSideMargin = useStorage('useSideMargin', true);
const userOpenSettings = ref({
enable: false,
enableMailVerify: false,
@@ -91,6 +93,8 @@ export const useGlobalState = createGlobalState(
indexTab,
userOpenSettings,
userSettings,
globalTabplacement,
useSideMargin,
}
},
)
+13 -3
View File
@@ -15,9 +15,10 @@ import UserSettings from './admin/UserSettings.vue';
import Mails from './admin/Mails.vue';
import MailsUnknow from './admin/MailsUnknow.vue';
import Maintenance from './admin/Maintenance.vue';
import Appearance from './common/Appearance.vue';
const {
localeCache, adminAuth, showAdminAuth, adminTab, loading
localeCache, adminAuth, showAdminAuth, adminTab, loading, globalTabplacement
} = useGlobalState()
const message = useMessage()
@@ -44,7 +45,9 @@ const { t } = useI18n({
unknow: 'Mails with unknow receiver',
senderAccess: 'Sender Access Control',
sendBox: 'Send Box',
statistics: 'Statistics',
maintenance: 'Maintenance',
appearance: 'Appearance',
ok: 'OK',
},
zh: {
@@ -59,7 +62,9 @@ const { t } = useI18n({
unknow: '无收件人邮件',
senderAccess: '发件权限控制',
sendBox: '发件箱',
statistics: '统计',
maintenance: '维护',
appearance: '外观',
ok: '确定',
}
}
@@ -85,8 +90,7 @@ onMounted(async () => {
</n-button>
</template>
</n-modal>
<Statistics />
<n-tabs type="card" v-model:value="adminTab">
<n-tabs type="card" v-model:value="adminTab" :placement="globalTabplacement">
<n-tab-pane name="account" :tab="t('account')">
<Account />
</n-tab-pane>
@@ -114,9 +118,15 @@ onMounted(async () => {
<n-tab-pane name="sendBox" :tab="t('sendBox')">
<SendBox />
</n-tab-pane>
<n-tab-pane name="statistics" :tab="t('statistics')">
<Statistics />
</n-tab-pane>
<n-tab-pane name="maintenance" :tab="t('maintenance')">
<Maintenance />
</n-tab-pane>
<n-tab-pane name="appearance" :tab="t('appearance')">
<Appearance />
</n-tab-pane>
</n-tabs>
</div>
</template>
+2 -2
View File
@@ -11,7 +11,7 @@ import SendBox from './index/SendBox.vue';
import SendMail from './index/SendMail.vue';
import AccountSettings from './index/AccountSettings.vue';
const { localeCache, settings, openSettings, indexTab } = useGlobalState()
const { localeCache, settings, openSettings, indexTab, globalTabplacement } = useGlobalState()
const { t } = useI18n({
locale: localeCache.value || 'zh',
@@ -45,7 +45,7 @@ const deleteMail = async (curMailId) => {
<template>
<div>
<AddressBar />
<n-tabs v-if="settings.address" type="card" v-model:value="indexTab">
<n-tabs v-if="settings.address" type="card" v-model:value="indexTab" :placement="globalTabplacement">
<n-tab-pane name="mailbox" :tab="t('mailbox')">
<MailBox :showEMailTo="false" :showReply="true" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
+2 -2
View File
@@ -9,7 +9,7 @@ import UserBar from './user/UserBar.vue';
import BindAddress from './user/BindAddress.vue';
const {
localeCache, userTab, userOpenSettings, userSettings
localeCache, userTab, globalTabplacement, userSettings
} = useGlobalState()
const { t } = useI18n({
@@ -33,7 +33,7 @@ const { t } = useI18n({
<template>
<div>
<UserBar />
<n-tabs v-if="userSettings.user_email" type="card" v-model:value="userTab">
<n-tabs v-if="userSettings.user_email" type="card" v-model:value="userTab" :placement="globalTabplacement">
<n-tab-pane name="address_management" :tab="t('address_management')">
<AddressMangement />
</n-tab-pane>
+14 -4
View File
@@ -1,5 +1,5 @@
<script setup>
import { ref, onMounted } from 'vue';
import { ref, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n'
import { useGlobalState } from '../../store'
@@ -16,19 +16,27 @@ const { t } = useI18n({
messages: {
en: {
addressQueryTip: 'Leave blank to query all addresses',
keywordQueryTip: 'Leave blank to not query by keyword',
query: 'Query',
},
zh: {
addressQueryTip: '留空查询所有地址',
keywordQueryTip: '留空不按关键字查询',
query: '查询',
}
}
});
const mailBoxKey = ref("")
const mailKeyword = ref("")
const queryAddress = () => {
mailBoxKey.value = adminMailTabAddress.value;
watch([adminMailTabAddress, mailKeyword], () => {
adminMailTabAddress.value = adminMailTabAddress.value.trim();
mailKeyword.value = mailKeyword.value.trim();
});
const queryMail = () => {
mailBoxKey.value = Date.now();
}
const fetchMailData = async (limit, offset) => {
@@ -37,6 +45,7 @@ const fetchMailData = async (limit, offset) => {
+ `?limit=${limit}`
+ `&offset=${offset}`
+ (adminMailTabAddress.value ? `&address=${adminMailTabAddress.value}` : '')
+ (mailKeyword.value ? `&keyword=${mailKeyword.value}` : '')
);
}
@@ -52,7 +61,8 @@ onMounted(async () => {
<div>
<n-input-group>
<n-input v-model:value="adminMailTabAddress" :placeholder="t('addressQueryTip')" />
<n-button @click="queryAddress" type="primary" tertiary>
<n-input v-model:value="mailKeyword" :placeholder="t('keywordQueryTip')" />
<n-button @click="queryMail" type="primary" tertiary>
{{ t('query') }}
</n-button>
</n-input-group>
+9
View File
@@ -32,6 +32,7 @@ const { t } = useI18n({
autoCleanup: "Auto cleanup",
cleanupSuccess: "Cleanup success",
save: "Save",
cronTip: "Enable cron cleanup, need to configure [crons] in worker, please refer to the document",
},
zh: {
tip: '请输入清理天数',
@@ -43,6 +44,7 @@ const { t } = useI18n({
cleanupSuccess: "清理成功",
cleanupNow: "立即清理",
save: "保存",
cronTip: "启用定时清理, 需在 worker 配置 [crons] 参数, 请参考文档",
}
}
});
@@ -93,6 +95,9 @@ onMounted(async () => {
<template>
<div class="center">
<n-card>
<n-alert :show-icon="false">
<span>{{ t('cronTip') }}</span>
</n-alert>
<n-form :model="cleanupModel">
<n-form-item-row :label="t('mailBoxLabel')">
<n-checkbox v-model:checked="cleanupModel.enableMailsAutoCleanup">
@@ -162,6 +167,10 @@ onMounted(async () => {
justify-content: center;
}
.n-alert {
margin-bottom: 20px;
}
.item {
display: flex;
margin: 10px;
+82
View File
@@ -0,0 +1,82 @@
<script setup>
import { useI18n } from 'vue-i18n'
import { useGlobalState } from '../../store'
const {
localeCache, mailboxSplitSize, useIframeShowMail, preferShowTextMail,
globalTabplacement, useSideMargin
} = useGlobalState()
const { t } = useI18n({
locale: localeCache.value || 'zh',
messages: {
en: {
mailboxSplitSize: 'Mailbox Split Size',
useIframeShowMail: 'Use iframe Show HTML Mail',
preferShowTextMail: 'Display text Mail by default',
useSideMargin: 'Turn on the side margins on the left and right sides of the page',
globalTabplacement: 'Global Tab Placement',
left: 'left',
top: 'top',
right: 'right',
bottom: 'bottom',
},
zh: {
mailboxSplitSize: '邮箱界面分栏大小',
preferShowTextMail: '默认以文本显示邮件',
useIframeShowMail: '使用iframe显示HTML邮件',
globalTabplacement: '全局选项卡位置',
useSideMargin: '开启页面左右两侧侧边距',
left: '左侧',
top: '顶部',
right: '右侧',
bottom: '底部',
}
}
});
</script>
<template>
<div class="center">
<n-card>
<n-form-item-row :label="t('mailboxSplitSize')">
<n-slider v-model:value="mailboxSplitSize" :min="0.25" :max="0.75" :step="0.01" :marks="{
0.25: '0.25',
0.5: '0.5',
0.75: '0.75'
}" />
</n-form-item-row>
<n-form-item-row :label="t('preferShowTextMail')">
<n-switch v-model:value="preferShowTextMail" :round="false" />
</n-form-item-row>
<n-form-item-row :label="t('useIframeShowMail')">
<n-switch v-model:value="useIframeShowMail" :round="false" />
</n-form-item-row>
<n-form-item-row :label="t('useSideMargin')">
<n-switch v-model:value="useSideMargin" :round="false" />
</n-form-item-row>
<n-form-item-row :label="t('globalTabplacement')">
<n-radio-group v-model:value="globalTabplacement">
<n-radio-button value="top" :label="t('top')" />
<n-radio-button value="left" :label="t('left')" />
<n-radio-button value="right" :label="t('right')" />
<n-radio-button value="bottom" :label="t('bottom')" />
</n-radio-group>
</n-form-item-row>
</n-card>
</div>
</template>
<style scoped>
.center {
display: flex;
justify-content: center;
}
.n-card {
max-width: 800px;
text-align: left;
}
</style>
+3 -23
View File
@@ -5,10 +5,10 @@ import { useRouter } from 'vue-router'
import { useGlobalState } from '../../store'
import { api } from '../../api'
import Appearance from '../common/Appearance.vue'
const {
jwt, localeCache, settings, showAddressCredential, loading,
mailboxSplitSize, useIframeShowMail, preferShowTextMail
jwt, localeCache, settings, showAddressCredential, loading
} = useGlobalState()
const router = useRouter()
const message = useMessage()
@@ -19,9 +19,6 @@ const { t } = useI18n({
locale: localeCache.value || 'zh',
messages: {
en: {
mailboxSplitSize: 'Mailbox Split Size',
useIframeShowMail: 'Use iframe Show HTML Mail',
preferShowTextMail: 'Display text Mail by default',
logout: "Logout",
delteAccount: "Delete Account",
showAddressCredential: 'Show Address Credential',
@@ -30,9 +27,6 @@ const { t } = useI18n({
delteAccountConfirm: "Are you sure to delete your account and all emails for this account?",
},
zh: {
mailboxSplitSize: '邮箱界面分栏大小',
preferShowTextMail: '默认以文本显示邮件',
useIframeShowMail: '使用iframe显示HTML邮件',
logout: '退出登录',
delteAccount: "删除账户",
showAddressCredential: '查看邮箱地址凭证',
@@ -66,21 +60,7 @@ const deleteAccount = async () => {
<template>
<div class="center" v-if="settings.address">
<n-card>
<n-card>
<n-form-item-row :label="t('mailboxSplitSize')">
<n-slider v-model:value="mailboxSplitSize" :min="0.25" :max="0.75" :step="0.01" :marks="{
0.25: '0.25',
0.5: '0.5',
0.75: '0.75'
}" />
</n-form-item-row>
<n-form-item-row :label="t('preferShowTextMail')">
<n-switch v-model:value="preferShowTextMail" :round="false" />
</n-form-item-row>
<n-form-item-row :label="t('useIframeShowMail')">
<n-switch v-model:value="useIframeShowMail" :round="false" />
</n-form-item-row>
</n-card>
<Appearance />
<n-button @click="showAddressCredential = true" type="primary" secondary block strong>
{{ t('showAddressCredential') }}
</n-button>
+3 -2
View File
@@ -83,9 +83,10 @@ const generateName = async () => {
try {
generateNameLoading.value = true;
const { faker } = await import('https://esm.sh/@faker-js/faker');
emailName.value = faker.person
.fullName()
emailName.value = faker.internet.email()
.split('@')[0]
.replace(/\s+/g, '.')
.replace(/\.{2,}/g, '.')
.replace(/[^a-zA-Z0-9.]/g, '')
.toLowerCase();
} catch (error) {