mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-05 23:47:50 +08:00
feat: UI: add user page: useIframeShowMail && mailboxSplitSize (#184)
This commit is contained in:
@@ -33,7 +33,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { themeSwitch } = useGlobalState()
|
const { themeSwitch, mailboxSplitSize, useIframeShowMail } = useGlobalState()
|
||||||
const autoRefresh = ref(false)
|
const autoRefresh = ref(false)
|
||||||
const autoRefreshInterval = ref(30)
|
const autoRefreshInterval = ref(30)
|
||||||
const data = ref([])
|
const data = ref([])
|
||||||
@@ -146,6 +146,10 @@ const deleteMail = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSpiltSizeChange = (size) => {
|
||||||
|
mailboxSplitSize.value = size;
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await refresh();
|
await refresh();
|
||||||
});
|
});
|
||||||
@@ -157,7 +161,8 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<n-split class="left" v-if="!isMobile" direction="horizontal" :max="0.75" :min="0.25" :default-size="0.3">
|
<n-split class="left" v-if="!isMobile" direction="horizontal" :max="0.75" :min="0.25"
|
||||||
|
:default-size="mailboxSplitSize" :on-update:size="onSpiltSizeChange">
|
||||||
<template #1>
|
<template #1>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
|
<div style="display: inline-block; margin-top: 10px; margin-bottom: 10px;">
|
||||||
@@ -230,8 +235,10 @@ onBeforeUnmount(() => {
|
|||||||
{{ t('downloadMail') }}
|
{{ t('downloadMail') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
<div v-html="curMail.message" style="margin-top: 10px;"></div>
|
<iframe v-if="useIframeShowMail" :srcdoc="curMail.message"
|
||||||
<!-- <iframe :srcdoc="curMail.message" style="width: 100%; height: 100%;"></iframe> -->
|
style="margin-top: 10px;width: 100%; height: 100%;">
|
||||||
|
</iframe>
|
||||||
|
<div v-else v-html="curMail.message" style="margin-top: 10px;"></div>
|
||||||
</n-card>
|
</n-card>
|
||||||
<n-card class="mail-item" v-else>
|
<n-card class="mail-item" v-else>
|
||||||
<n-result status="info" :title="t('pleaseSelectMail')">
|
<n-result status="info" :title="t('pleaseSelectMail')">
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import Index from '../views/Index.vue'
|
import Index from '../views/Index.vue'
|
||||||
import Settings from '../views/Settings.vue'
|
import User from '../views/User.vue'
|
||||||
import SendMail from '../views/send/SendMail.vue'
|
import SendMail from '../views/send/SendMail.vue'
|
||||||
import Admin from '../views/Admin.vue'
|
import Admin from '../views/Admin.vue'
|
||||||
import SendBox from '../views/send/SendBox.vue'
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
@@ -13,17 +12,13 @@ const router = createRouter({
|
|||||||
component: Index
|
component: Index
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/user',
|
||||||
component: Settings
|
component: User
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/send',
|
path: '/send',
|
||||||
component: SendMail
|
component: SendMail
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/sendbox',
|
|
||||||
component: SendBox
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/admin',
|
path: '/admin',
|
||||||
component: Admin
|
component: Admin
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const useGlobalState = createGlobalState(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const showAuth = ref(false);
|
const showAuth = ref(false);
|
||||||
|
const showPassword = ref(false);
|
||||||
const showAdminAuth = ref(false);
|
const showAdminAuth = ref(false);
|
||||||
const auth = useStorage('auth', '');
|
const auth = useStorage('auth', '');
|
||||||
const adminAuth = useStorage('adminAuth', '');
|
const adminAuth = useStorage('adminAuth', '');
|
||||||
@@ -40,6 +41,8 @@ export const useGlobalState = createGlobalState(
|
|||||||
const adminTab = ref("account");
|
const adminTab = ref("account");
|
||||||
const adminMailTabAddress = ref("");
|
const adminMailTabAddress = ref("");
|
||||||
const adminSendBoxTabAddress = ref("");
|
const adminSendBoxTabAddress = ref("");
|
||||||
|
const mailboxSplitSize = useStorage('mailboxSplitSize', 0.25);
|
||||||
|
const useIframeShowMail = useStorage('useIframeShowMail', false);
|
||||||
return {
|
return {
|
||||||
isDark,
|
isDark,
|
||||||
toggleDark,
|
toggleDark,
|
||||||
@@ -47,6 +50,7 @@ export const useGlobalState = createGlobalState(
|
|||||||
settings,
|
settings,
|
||||||
openSettings,
|
openSettings,
|
||||||
showAuth,
|
showAuth,
|
||||||
|
showPassword,
|
||||||
auth,
|
auth,
|
||||||
jwt,
|
jwt,
|
||||||
localeCache,
|
localeCache,
|
||||||
@@ -56,6 +60,8 @@ export const useGlobalState = createGlobalState(
|
|||||||
adminTab,
|
adminTab,
|
||||||
adminMailTabAddress,
|
adminMailTabAddress,
|
||||||
adminSendBoxTabAddress,
|
adminSendBoxTabAddress,
|
||||||
|
mailboxSplitSize,
|
||||||
|
useIframeShowMail,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import { ref, h, computed, onMounted } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useIsMobile } from '../utils/composables'
|
import { useIsMobile } from '../utils/composables'
|
||||||
import { DarkModeFilled, LightModeFilled, MenuFilled, AdminPanelSettingsFilled, SendFilled } from '@vicons/material'
|
import {
|
||||||
|
DarkModeFilled, LightModeFilled, MenuFilled,
|
||||||
|
AdminPanelSettingsFilled, SendFilled
|
||||||
|
} from '@vicons/material'
|
||||||
import { GithubAlt, Language, User, Home, Copy } from '@vicons/fa'
|
import { GithubAlt, Language, User, Home, Copy } from '@vicons/fa'
|
||||||
import AdminContact from './admin/AdminContact.vue'
|
|
||||||
|
import Login from './Login.vue'
|
||||||
|
|
||||||
import { useGlobalState } from '../store'
|
import { useGlobalState } from '../store'
|
||||||
import { api } from '../api'
|
import { api } from '../api'
|
||||||
@@ -14,42 +18,15 @@ const { toClipboard } = useClipboard()
|
|||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
jwt, localeCache, toggleDark, isDark,
|
jwt, localeCache, toggleDark, isDark, settings,
|
||||||
showAuth, adminAuth, auth, loading
|
showAuth, adminAuth, auth, loading
|
||||||
} = useGlobalState()
|
} = useGlobalState()
|
||||||
const { openSettings, settings } = useGlobalState()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
const isAdminRoute = computed(() => route.path.includes('admin'))
|
const isAdminRoute = computed(() => route.path.includes('admin'))
|
||||||
|
|
||||||
const showMobileMenu = ref(false)
|
const showMobileMenu = ref(false)
|
||||||
const tabValue = ref('signin')
|
|
||||||
const showLogout = ref(false)
|
|
||||||
const showDelteAccount = ref(false)
|
|
||||||
const password = ref('')
|
|
||||||
const showPassword = ref(false)
|
|
||||||
const emailName = ref("")
|
|
||||||
const emailDomain = ref("")
|
|
||||||
|
|
||||||
const login = async () => {
|
|
||||||
if (!password.value) {
|
|
||||||
message.error(t('passwordInput'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
jwt.value = password.value;
|
|
||||||
await api.getSettings()
|
|
||||||
location.reload()
|
|
||||||
} catch (error) {
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const logout = () => {
|
|
||||||
jwt.value = '';
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
const authFunc = async () => {
|
const authFunc = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -71,73 +48,35 @@ const { t } = useI18n({
|
|||||||
title: 'Cloudflare Temp Email',
|
title: 'Cloudflare Temp Email',
|
||||||
dark: 'Dark',
|
dark: 'Dark',
|
||||||
light: 'Light',
|
light: 'Light',
|
||||||
login: 'Login',
|
|
||||||
logout: 'Logout',
|
|
||||||
logoutConfirm: 'Are you sure to logout?',
|
|
||||||
delteAccount: "Delete Account",
|
|
||||||
delteAccountConfirm: "Are you sure to delete your account and all emails for this account?",
|
|
||||||
accessHeader: 'Access Password',
|
accessHeader: 'Access Password',
|
||||||
accessTip: 'Please enter the correct password',
|
accessTip: 'Please enter the correct password',
|
||||||
settings: 'Settings',
|
|
||||||
home: 'Home',
|
home: 'Home',
|
||||||
menu: 'Menu',
|
menu: 'Menu',
|
||||||
user: 'User',
|
user: 'User',
|
||||||
sendbox: 'Send Box',
|
|
||||||
sendMail: 'Send Mail',
|
sendMail: 'Send Mail',
|
||||||
pleaseGetNewEmail: 'Please login or click "Get New Email" button to get a new email address',
|
|
||||||
getNewEmail: 'Get New Email',
|
|
||||||
getNewEmailTip1: 'Please input the email you want to use. only allow ., a-z, A-Z and 0-9',
|
|
||||||
getNewEmailTip2: 'Levaing it blank will generate a random email address.',
|
|
||||||
getNewEmailTip3: 'You can choose a domain from the dropdown list.',
|
|
||||||
yourAddress: 'Your email address is',
|
yourAddress: 'Your email address is',
|
||||||
password: 'Password',
|
|
||||||
passwordTip: 'Please copy the password and you can use it to login to your email account.',
|
|
||||||
cancel: 'Cancel',
|
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
copy: 'Copy',
|
copy: 'Copy',
|
||||||
copied: 'Copied',
|
copied: 'Copied',
|
||||||
showPassword: 'Show Password',
|
|
||||||
fetchAddressError: 'Login password is invalid or account not exist, it may be network connection issue, please try again later.',
|
fetchAddressError: 'Login password is invalid or account not exist, it may be network connection issue, please try again later.',
|
||||||
mailV1Alert: 'You have some mails in v1, please click here to login and visit your history mails.',
|
mailV1Alert: 'You have some mails in v1, please click here to login and visit your history mails.',
|
||||||
generateName: 'Generate Fake Name',
|
|
||||||
help: 'Help',
|
|
||||||
passwordInput: 'Please input the password',
|
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
title: 'Cloudflare 临时邮件',
|
title: 'Cloudflare 临时邮件',
|
||||||
dark: '暗色',
|
dark: '暗色',
|
||||||
light: '亮色',
|
light: '亮色',
|
||||||
login: '登录',
|
|
||||||
logout: '退出登录',
|
|
||||||
logoutConfirm: '确定要退出登录吗?',
|
|
||||||
delteAccount: "删除账户",
|
|
||||||
delteAccountConfirm: "确定要删除你的账户和其中的所有邮件吗?",
|
|
||||||
accessHeader: '访问密码',
|
accessHeader: '访问密码',
|
||||||
accessTip: '请输入站点访问密码',
|
accessTip: '请输入站点访问密码',
|
||||||
settings: '设置',
|
|
||||||
home: '主页',
|
home: '主页',
|
||||||
menu: '菜单',
|
menu: '菜单',
|
||||||
user: '用户',
|
user: '用户',
|
||||||
sendbox: '发件箱',
|
|
||||||
sendMail: '发送邮件',
|
sendMail: '发送邮件',
|
||||||
pleaseGetNewEmail: '请"登录"或点击 "获取新邮箱" 按钮来获取一个新的邮箱地址',
|
|
||||||
getNewEmail: '注册新邮箱',
|
|
||||||
getNewEmailTip1: '请输入你想要使用的邮箱地址, 只允许 ., a-z, A-Z, 0-9',
|
|
||||||
getNewEmailTip2: '留空将会生成一个随机的邮箱地址。',
|
|
||||||
getNewEmailTip3: '你可以从下拉列表中选择一个域名。',
|
|
||||||
yourAddress: '你的邮箱地址是',
|
yourAddress: '你的邮箱地址是',
|
||||||
password: '密码',
|
|
||||||
passwordTip: '请复制密码,你可以使用它登录你的邮箱。',
|
|
||||||
cancel: '取消',
|
|
||||||
ok: '确定',
|
ok: '确定',
|
||||||
copy: '复制',
|
copy: '复制',
|
||||||
copied: '已复制',
|
copied: '已复制',
|
||||||
showPassword: '查看密码',
|
|
||||||
fetchAddressError: '登录密码无效或账号不存在,也可能是网络连接异常,请稍后再尝试。',
|
fetchAddressError: '登录密码无效或账号不存在,也可能是网络连接异常,请稍后再尝试。',
|
||||||
mailV1Alert: '你有一些 v1 版本的邮件,请点击此处登录查看。',
|
mailV1Alert: '你有一些 v1 版本的邮件,请点击此处登录查看。',
|
||||||
generateName: '生成随机名字',
|
|
||||||
help: '帮助',
|
|
||||||
passwordInput: '请输入密码',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -185,6 +124,7 @@ const menuOptions = computed(() => [
|
|||||||
text: true,
|
text: true,
|
||||||
size: "small",
|
size: "small",
|
||||||
style: "width: 100%",
|
style: "width: 100%",
|
||||||
|
onClick: () => { router.push("/user"); showMobileMenu.value = false; }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: () => t('user'),
|
default: () => t('user'),
|
||||||
@@ -193,75 +133,6 @@ const menuOptions = computed(() => [
|
|||||||
),
|
),
|
||||||
show: showUserMenu.value,
|
show: showUserMenu.value,
|
||||||
key: "user",
|
key: "user",
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: () => h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
size: "small",
|
|
||||||
style: "width: 100%",
|
|
||||||
onClick: () => { router.push('/sendbox'); showMobileMenu.value = false; }
|
|
||||||
},
|
|
||||||
{ default: () => t('sendbox') }
|
|
||||||
),
|
|
||||||
key: "sendbox"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
size: "small",
|
|
||||||
style: "width: 100%",
|
|
||||||
onClick: () => { showPassword.value = true; showMobileMenu.value = false; }
|
|
||||||
},
|
|
||||||
{ default: () => t('showPassword') }
|
|
||||||
),
|
|
||||||
key: "showPassword"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
size: "small",
|
|
||||||
style: "width: 100%",
|
|
||||||
onClick: () => { router.push('/settings'); showMobileMenu.value = false; }
|
|
||||||
},
|
|
||||||
{ default: () => t('settings') }
|
|
||||||
),
|
|
||||||
show: openSettings.value.enableAutoReply,
|
|
||||||
key: "settings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
size: "small",
|
|
||||||
style: "width: 100%",
|
|
||||||
onClick: () => { showLogout.value = true; showMobileMenu.value = false; }
|
|
||||||
},
|
|
||||||
{ default: () => t('logout') }
|
|
||||||
),
|
|
||||||
key: "logout"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
size: "small",
|
|
||||||
style: "width: 100%",
|
|
||||||
onClick: () => { showDelteAccount.value = true; showMobileMenu.value = false; }
|
|
||||||
},
|
|
||||||
{ default: () => t('delteAccount') }
|
|
||||||
),
|
|
||||||
show: openSettings.value.enableUserDeleteEmail,
|
|
||||||
key: "delte_account"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () => h(
|
label: () => h(
|
||||||
@@ -331,53 +202,8 @@ const copy = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateNameLoading = ref(false);
|
|
||||||
const generateName = async () => {
|
|
||||||
try {
|
|
||||||
generateNameLoading.value = true;
|
|
||||||
const { faker } = await import('https://esm.sh/@faker-js/faker');
|
|
||||||
emailName.value = faker.person
|
|
||||||
.fullName()
|
|
||||||
.replace(/\s+/g, '.')
|
|
||||||
.replace(/[^a-zA-Z0-9.]/g, '')
|
|
||||||
.toLowerCase();
|
|
||||||
} catch (error) {
|
|
||||||
message.error(error.message || "error");
|
|
||||||
} finally {
|
|
||||||
generateNameLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const newEmail = async () => {
|
|
||||||
try {
|
|
||||||
const res = await api.fetch(
|
|
||||||
`/api/new_address`
|
|
||||||
+ `?name=${emailName.value || ''}`
|
|
||||||
+ `&domain=${emailDomain.value || ''}`
|
|
||||||
);
|
|
||||||
jwt.value = res["jwt"];
|
|
||||||
await api.getSettings();
|
|
||||||
showPassword.value = true;
|
|
||||||
} catch (error) {
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteAccount = async () => {
|
|
||||||
try {
|
|
||||||
await api.fetch(`/api/delete_address`, {
|
|
||||||
method: 'DELETE'
|
|
||||||
});
|
|
||||||
jwt.value = '';
|
|
||||||
location.reload()
|
|
||||||
} catch (error) {
|
|
||||||
message.error(error.message || "error");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await api.getOpenSettings(message);
|
await api.getOpenSettings(message);
|
||||||
emailDomain.value = openSettings.value.domains ? openSettings.value.domains[0].value : "";
|
|
||||||
await api.getSettings();
|
await api.getSettings();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -439,84 +265,10 @@ onMounted(async () => {
|
|||||||
<n-alert v-if="jwt" type="warning" show-icon>
|
<n-alert v-if="jwt" type="warning" show-icon>
|
||||||
<span>{{ t('fetchAddressError') }}</span>
|
<span>{{ t('fetchAddressError') }}</span>
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-tabs v-model:value="tabValue" size="large" justify-content="space-evenly">
|
<Login />
|
||||||
<n-tab-pane name="signin" :tab="t('login')">
|
|
||||||
<n-form>
|
|
||||||
<n-form-item-row :label="t('password')" required>
|
|
||||||
<n-input v-model:value="password" type="textarea" :autosize="{ minRows: 3 }" />
|
|
||||||
</n-form-item-row>
|
|
||||||
<n-button @click="login" :loading="loading" type="primary" block secondary strong>
|
|
||||||
{{ t('login') }}
|
|
||||||
</n-button>
|
|
||||||
<n-button v-if="openSettings.enableUserCreateEmail" @click="tabValue = 'register'" block
|
|
||||||
secondary strong>
|
|
||||||
{{ t('getNewEmail') }}
|
|
||||||
</n-button>
|
|
||||||
</n-form>
|
|
||||||
</n-tab-pane>
|
|
||||||
<n-tab-pane v-if="openSettings.enableUserCreateEmail" name="register" :tab="t('getNewEmail')">
|
|
||||||
<n-spin :show="generateNameLoading">
|
|
||||||
<n-form>
|
|
||||||
<span>
|
|
||||||
<p>{{ t("getNewEmailTip1") }}</p>
|
|
||||||
<p>{{ t("getNewEmailTip2") }}</p>
|
|
||||||
<p>{{ t("getNewEmailTip3") }}</p>
|
|
||||||
</span>
|
|
||||||
<n-button @click="generateName" style="margin-bottom: 10px;">
|
|
||||||
{{ t('generateName') }}
|
|
||||||
</n-button>
|
|
||||||
<n-input-group>
|
|
||||||
<n-input-group-label v-if="openSettings.prefix">
|
|
||||||
{{ openSettings.prefix }}
|
|
||||||
</n-input-group-label>
|
|
||||||
<n-input v-model:value="emailName" />
|
|
||||||
<n-input-group-label>@</n-input-group-label>
|
|
||||||
<n-select v-model:value="emailDomain" :consistent-menu-width="false"
|
|
||||||
:options="openSettings.domains" />
|
|
||||||
</n-input-group>
|
|
||||||
<n-button type="primary" block secondary strong @click="newEmail"
|
|
||||||
:loading="loading">
|
|
||||||
{{ t('ok') }}
|
|
||||||
</n-button>
|
|
||||||
</n-form>
|
|
||||||
</n-spin>
|
|
||||||
</n-tab-pane>
|
|
||||||
<n-tab-pane name="help" :tab="t('help')">
|
|
||||||
<n-alert type="info" show-icon>
|
|
||||||
<span>{{ t('pleaseGetNewEmail') }}</span>
|
|
||||||
</n-alert>
|
|
||||||
<AdminContact />
|
|
||||||
</n-tab-pane>
|
|
||||||
</n-tabs>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<n-modal v-model:show="showPassword" preset="dialog" :title="t('password')">
|
|
||||||
<span>
|
|
||||||
<p>{{ t("passwordTip") }}</p>
|
|
||||||
</span>
|
|
||||||
<n-card>
|
|
||||||
<b>{{ jwt }}</b>
|
|
||||||
</n-card>
|
|
||||||
<template #action>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
<n-modal v-model:show="showLogout" preset="dialog" :title="t('logout')">
|
|
||||||
<p>{{ t('logoutConfirm') }}</p>
|
|
||||||
<template #action>
|
|
||||||
<n-button :loading="loading" @click="logout" size="small" tertiary type="primary">
|
|
||||||
{{ t('logout') }}
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
<n-modal v-model:show="showDelteAccount" preset="dialog" :title="t('delteAccount')">
|
|
||||||
<p>{{ t('delteAccountConfirm') }}</p>
|
|
||||||
<template #action>
|
|
||||||
<n-button :loading="loading" @click="deleteAccount" size="small" tertiary type="error">
|
|
||||||
{{ t('delteAccount') }}
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
</n-modal>
|
|
||||||
<n-modal v-model:show="showAuth" :closable="false" :closeOnEsc="false" :maskClosable="false" preset="dialog"
|
<n-modal v-model:show="showAuth" :closable="false" :closeOnEsc="false" :maskClosable="false" preset="dialog"
|
||||||
:title="t('accessHeader')">
|
:title="t('accessHeader')">
|
||||||
<p>{{ t('accessTip') }}</p>
|
<p>{{ t('accessTip') }}</p>
|
||||||
@@ -537,11 +289,6 @@ onMounted(async () => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-menu-button {
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.n-alert {
|
.n-alert {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import AdminContact from './admin/AdminContact.vue'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../store'
|
||||||
|
import { api } from '../api'
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const {
|
||||||
|
jwt, localeCache, loading, openSettings, showPassword
|
||||||
|
} = useGlobalState()
|
||||||
|
|
||||||
|
const tabValue = ref('signin')
|
||||||
|
const password = ref('')
|
||||||
|
const emailName = ref("")
|
||||||
|
const emailDomain = ref("")
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
if (!password.value) {
|
||||||
|
message.error(t('passwordInput'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
jwt.value = password.value;
|
||||||
|
await api.getSettings()
|
||||||
|
location.reload()
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
login: 'Login',
|
||||||
|
pleaseGetNewEmail: 'Please login or click "Get New Email" button to get a new email address',
|
||||||
|
getNewEmail: 'Get New Email',
|
||||||
|
getNewEmailTip1: 'Please input the email you want to use. only allow ., a-z, A-Z and 0-9',
|
||||||
|
getNewEmailTip2: 'Levaing it blank will generate a random email address.',
|
||||||
|
getNewEmailTip3: 'You can choose a domain from the dropdown list.',
|
||||||
|
password: 'Password',
|
||||||
|
ok: 'OK',
|
||||||
|
generateName: 'Generate Fake Name',
|
||||||
|
help: 'Help',
|
||||||
|
passwordInput: 'Please input the password',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
login: '登录',
|
||||||
|
pleaseGetNewEmail: '请"登录"或点击 "获取新邮箱" 按钮来获取一个新的邮箱地址',
|
||||||
|
getNewEmail: '注册新邮箱',
|
||||||
|
getNewEmailTip1: '请输入你想要使用的邮箱地址, 只允许 ., a-z, A-Z, 0-9',
|
||||||
|
getNewEmailTip2: '留空将会生成一个随机的邮箱地址。',
|
||||||
|
getNewEmailTip3: '你可以从下拉列表中选择一个域名。',
|
||||||
|
password: '密码',
|
||||||
|
ok: '确定',
|
||||||
|
generateName: '生成随机名字',
|
||||||
|
help: '帮助',
|
||||||
|
passwordInput: '请输入密码',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const generateNameLoading = ref(false);
|
||||||
|
const generateName = async () => {
|
||||||
|
try {
|
||||||
|
generateNameLoading.value = true;
|
||||||
|
const { faker } = await import('https://esm.sh/@faker-js/faker');
|
||||||
|
emailName.value = faker.person
|
||||||
|
.fullName()
|
||||||
|
.replace(/\s+/g, '.')
|
||||||
|
.replace(/[^a-zA-Z0-9.]/g, '')
|
||||||
|
.toLowerCase();
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
} finally {
|
||||||
|
generateNameLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const newEmail = async () => {
|
||||||
|
try {
|
||||||
|
const res = await api.fetch(
|
||||||
|
`/api/new_address`
|
||||||
|
+ `?name=${emailName.value || ''}`
|
||||||
|
+ `&domain=${emailDomain.value || ''}`
|
||||||
|
);
|
||||||
|
jwt.value = res["jwt"];
|
||||||
|
await api.getSettings();
|
||||||
|
showPassword.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
emailDomain.value = openSettings.value.domains ? openSettings.value.domains[0].value : "";
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-tabs v-model:value="tabValue" size="large" justify-content="space-evenly">
|
||||||
|
<n-tab-pane name="signin" :tab="t('login')">
|
||||||
|
<n-form>
|
||||||
|
<n-form-item-row :label="t('password')" required>
|
||||||
|
<n-input v-model:value="password" type="textarea" :autosize="{ minRows: 3 }" />
|
||||||
|
</n-form-item-row>
|
||||||
|
<n-button @click="login" :loading="loading" type="primary" block secondary strong>
|
||||||
|
{{ t('login') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button v-if="openSettings.enableUserCreateEmail" @click="tabValue = 'register'" block secondary
|
||||||
|
strong>
|
||||||
|
{{ t('getNewEmail') }}
|
||||||
|
</n-button>
|
||||||
|
</n-form>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane v-if="openSettings.enableUserCreateEmail" name="register" :tab="t('getNewEmail')">
|
||||||
|
<n-spin :show="generateNameLoading">
|
||||||
|
<n-form>
|
||||||
|
<span>
|
||||||
|
<p>{{ t("getNewEmailTip1") }}</p>
|
||||||
|
<p>{{ t("getNewEmailTip2") }}</p>
|
||||||
|
<p>{{ t("getNewEmailTip3") }}</p>
|
||||||
|
</span>
|
||||||
|
<n-button @click="generateName" style="margin-bottom: 10px;">
|
||||||
|
{{ t('generateName') }}
|
||||||
|
</n-button>
|
||||||
|
<n-input-group>
|
||||||
|
<n-input-group-label v-if="openSettings.prefix">
|
||||||
|
{{ openSettings.prefix }}
|
||||||
|
</n-input-group-label>
|
||||||
|
<n-input v-model:value="emailName" />
|
||||||
|
<n-input-group-label>@</n-input-group-label>
|
||||||
|
<n-select v-model:value="emailDomain" :consistent-menu-width="false"
|
||||||
|
:options="openSettings.domains" />
|
||||||
|
</n-input-group>
|
||||||
|
<n-button type="primary" block secondary strong @click="newEmail" :loading="loading">
|
||||||
|
{{ t('ok') }}
|
||||||
|
</n-button>
|
||||||
|
</n-form>
|
||||||
|
</n-spin>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="help" :tab="t('help')">
|
||||||
|
<n-alert type="info" show-icon>
|
||||||
|
<span>{{ t('pleaseGetNewEmail') }}</span>
|
||||||
|
</n-alert>
|
||||||
|
<AdminContact />
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.n-alert {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-form .n-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../store'
|
||||||
|
|
||||||
|
import AutoReply from './user/AutoReply.vue';
|
||||||
|
import SendBox from './send/SendBox.vue';
|
||||||
|
import Account from './user/Account.vue';
|
||||||
|
|
||||||
|
const { localeCache, settings, openSettings } = useGlobalState()
|
||||||
|
const userTab = useStorage('userTab', 'account')
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
sendbox: 'Send Box',
|
||||||
|
auto_reply: 'Auto Reply',
|
||||||
|
account: 'Account',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
sendbox: '发件箱',
|
||||||
|
auto_reply: '自动回复',
|
||||||
|
account: '账户',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="settings.address">
|
||||||
|
<n-tabs type="card" v-model:value="userTab">
|
||||||
|
<n-tab-pane name="account" :tab="t('account')">
|
||||||
|
<Account />
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="sendbox" :tab="t('sendbox')">
|
||||||
|
<SendBox />
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane v-if="openSettings.enableAutoReply" name="auto_reply" :tab="t('auto_reply')">
|
||||||
|
<AutoReply />
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import { useGlobalState } from '../../store'
|
||||||
|
|
||||||
|
const {
|
||||||
|
jwt, localeCache, settings, showPassword, mailboxSplitSize, useIframeShowMail
|
||||||
|
} = useGlobalState()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const showLogout = ref(false)
|
||||||
|
const showDelteAccount = ref(false)
|
||||||
|
const { t } = useI18n({
|
||||||
|
locale: localeCache.value || 'zh',
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
mailboxSplitSize: 'Mailbox Split Size',
|
||||||
|
useIframeShowMail: 'Use iframe Show Mail',
|
||||||
|
logout: "Logout",
|
||||||
|
delteAccount: "Delete Account",
|
||||||
|
showPassword: 'Show Password',
|
||||||
|
password: 'Password',
|
||||||
|
passwordTip: 'Please copy the password and you can use it to login to your email account.',
|
||||||
|
logoutConfirm: 'Are you sure to logout?',
|
||||||
|
delteAccount: "Delete Account",
|
||||||
|
delteAccountConfirm: "Are you sure to delete your account and all emails for this account?",
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
mailboxSplitSize: '邮箱界面分栏大小',
|
||||||
|
useIframeShowMail: '使用iframe显示邮件',
|
||||||
|
logout: '退出登录',
|
||||||
|
delteAccount: "删除账户",
|
||||||
|
showPassword: '查看密码',
|
||||||
|
password: '密码',
|
||||||
|
passwordTip: '请复制密码,你可以使用它登录你的邮箱。',
|
||||||
|
logoutConfirm: '确定要退出登录吗?',
|
||||||
|
delteAccount: "删除账户",
|
||||||
|
delteAccountConfirm: "确定要删除你的账户和其中的所有邮件吗?",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
jwt.value = '';
|
||||||
|
await router.push('/')
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAccount = async () => {
|
||||||
|
try {
|
||||||
|
await api.fetch(`/api/delete_address`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
jwt.value = '';
|
||||||
|
await router.push('/')
|
||||||
|
location.reload()
|
||||||
|
} catch (error) {
|
||||||
|
message.error(error.message || "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<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('useIframeShowMail')">
|
||||||
|
<n-switch v-model:value="useIframeShowMail" :round="false" />
|
||||||
|
</n-form-item-row>
|
||||||
|
</n-card>
|
||||||
|
<n-button @click="showPassword = true" type="primary" secondary block strong>
|
||||||
|
{{ t('showPassword') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="showLogout = true" secondary block strong>
|
||||||
|
{{ t('logout') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="showDelteAccount = true" type="error" secondary block strong>
|
||||||
|
{{ t('delteAccount') }}
|
||||||
|
</n-button>
|
||||||
|
</n-card>
|
||||||
|
<n-modal v-model:show="showPassword" preset="dialog" :title="t('password')">
|
||||||
|
<span>
|
||||||
|
<p>{{ t("passwordTip") }}</p>
|
||||||
|
</span>
|
||||||
|
<n-card>
|
||||||
|
<b>{{ jwt }}</b>
|
||||||
|
</n-card>
|
||||||
|
</n-modal>
|
||||||
|
<n-modal v-model:show="showLogout" preset="dialog" :title="t('logout')">
|
||||||
|
<p>{{ t('logoutConfirm') }}</p>
|
||||||
|
<template #action>
|
||||||
|
<n-button :loading="loading" @click="logout" size="small" tertiary type="primary">
|
||||||
|
{{ t('logout') }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
<n-modal v-model:show="showDelteAccount" preset="dialog" :title="t('delteAccount')">
|
||||||
|
<p>{{ t('delteAccountConfirm') }}</p>
|
||||||
|
<template #action>
|
||||||
|
<n-button :loading="loading" @click="deleteAccount" size="small" tertiary type="error">
|
||||||
|
{{ t('delteAccount') }}
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.n-card {
|
||||||
|
max-width: 800px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
import { useGlobalState } from '../store'
|
import { useGlobalState } from '../../store'
|
||||||
import { api } from '../api'
|
import { api } from '../../api'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const sourcePrefix = ref("")
|
const sourcePrefix = ref("")
|
||||||
Reference in New Issue
Block a user