mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-06-28 02:42:44 +08:00
@@ -32,6 +32,7 @@
|
||||
- [x] email 转发使用 Cloudflare Email Routing
|
||||
- [x] 使用 password 重新登录之前的邮箱
|
||||
- [x] 获取自定义名字的邮箱
|
||||
- [x] 支持多语言
|
||||
- [ ] 免费版附件过大会造成 Exceeded CPU Limit 错误
|
||||
|
||||
---
|
||||
|
||||
@@ -14,6 +14,7 @@ This is a temporary email service that uses Cloudflare Workers to create a tempo
|
||||
- [x] Email forwarding using Cloudflare Email Routing
|
||||
- [x] Use password to login to the previous mailbox again.
|
||||
- [x] Get Custom Name Email
|
||||
- [x] Support multiple languages
|
||||
- [ ] Exceeded CPU Limit error caused by the free version of the attachment
|
||||
|
||||

|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"naive-ui": "^2.34.3",
|
||||
"vue": "^3.3.4",
|
||||
"vue-clipboard3": "^2.0.0"
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-i18n": "9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
|
||||
58
frontend/pnpm-lock.yaml
generated
58
frontend/pnpm-lock.yaml
generated
@@ -17,6 +17,9 @@ dependencies:
|
||||
vue-clipboard3:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
vue-i18n:
|
||||
specifier: '9'
|
||||
version: 9.3.0(vue@3.3.4)
|
||||
|
||||
devDependencies:
|
||||
'@vitejs/plugin-vue':
|
||||
@@ -276,6 +279,44 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/@intlify/core-base@9.3.0:
|
||||
resolution: {integrity: sha512-SRzn8TMnPZ6MY8OFrgouRq4DGaf01SHcJEF6FglYFYvRkgPzziEcQe+v2PD+O5lUp/rJafP4dabm1CmsVAA7rA==}
|
||||
engines: {node: '>= 16'}
|
||||
dependencies:
|
||||
'@intlify/devtools-if': 9.3.0
|
||||
'@intlify/message-compiler': 9.3.0
|
||||
'@intlify/shared': 9.3.0
|
||||
'@intlify/vue-devtools': 9.3.0
|
||||
dev: false
|
||||
|
||||
/@intlify/devtools-if@9.3.0:
|
||||
resolution: {integrity: sha512-5aKZnqj0Ff4dfwBX2Oo+MheVs00CBnC0RzWK26aT2M4AF0cxdFLOJAs51/eHT01jmzrxSvfBMjdArUWHwgetfg==}
|
||||
engines: {node: '>= 16'}
|
||||
dependencies:
|
||||
'@intlify/shared': 9.3.0
|
||||
dev: false
|
||||
|
||||
/@intlify/message-compiler@9.3.0:
|
||||
resolution: {integrity: sha512-D8tSJEhTCSFcCzkThjE4Sbk1tIdvzkYa1FaVIzUtZ8hKPATvokNrOiDw1i/h671m8A80l9Ywq594i/LPTB6EJA==}
|
||||
engines: {node: '>= 16'}
|
||||
dependencies:
|
||||
'@intlify/shared': 9.3.0
|
||||
source-map-js: 1.0.2
|
||||
dev: false
|
||||
|
||||
/@intlify/shared@9.3.0:
|
||||
resolution: {integrity: sha512-MMGRz6zWxtz7rHtxIIdnyb8SYOIaaseN1IvUhAEs9tOW4u77RD4DFp4qgPXesp2Gxo/5QitH9kwSs0jnxGUNEw==}
|
||||
engines: {node: '>= 16'}
|
||||
dev: false
|
||||
|
||||
/@intlify/vue-devtools@9.3.0:
|
||||
resolution: {integrity: sha512-kEaxIz1VEgsz2q5RhoS+fBGTkXr/4+pxmK9mN14+speVGb82HPRntKBmz0GO18I1JisD4Z0vAva+KCTHGeAqbQ==}
|
||||
engines: {node: '>= 16'}
|
||||
dependencies:
|
||||
'@intlify/core-base': 9.3.0
|
||||
'@intlify/shared': 9.3.0
|
||||
dev: false
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.4.15:
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
|
||||
@@ -346,6 +387,10 @@ packages:
|
||||
'@vue/compiler-dom': 3.3.4
|
||||
'@vue/shared': 3.3.4
|
||||
|
||||
/@vue/devtools-api@6.5.0:
|
||||
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
|
||||
dev: false
|
||||
|
||||
/@vue/reactivity-transform@3.3.4:
|
||||
resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
|
||||
dependencies:
|
||||
@@ -677,6 +722,19 @@ packages:
|
||||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-i18n@9.3.0(vue@3.3.4):
|
||||
resolution: {integrity: sha512-+2L+ae/e4+fixhjym3lgzGCGQG8wVGlGrDHzjfdgUudheHvbVHu5i6tn6FF+buH75UFA7T5ZO2ZO7zrh6CzuaA==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
dependencies:
|
||||
'@intlify/core-base': 9.3.0
|
||||
'@intlify/shared': 9.3.0
|
||||
'@intlify/vue-devtools': 9.3.0
|
||||
'@vue/devtools-api': 6.5.0
|
||||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue@3.3.4:
|
||||
resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==}
|
||||
dependencies:
|
||||
|
||||
@@ -2,16 +2,20 @@
|
||||
import { NMessageProvider, NGrid, NBackTop, NLayoutHeader, NInput } from 'naive-ui'
|
||||
import { NGi, NSpace, NButton, NConfigProvider, NSelect, NModal } from 'naive-ui'
|
||||
import { darkTheme, NSwitch, NGlobalStyle, NPopconfirm } from 'naive-ui'
|
||||
import { zhCN } from 'naive-ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Content from './Content.vue'
|
||||
|
||||
const jwt = useStorage('jwt')
|
||||
const localeCache = useStorage('locale', 'zhCN')
|
||||
const themeSwitch = useStorage('themeSwitch', false)
|
||||
const theme = computed(() => themeSwitch.value ? darkTheme : null)
|
||||
const showLogin = ref(false)
|
||||
const password = ref('')
|
||||
const localeConfig = computed(() => localeCache.value == 'zh' ? zhCN : null)
|
||||
|
||||
const login = () => {
|
||||
jwt.value = password.value;
|
||||
@@ -21,10 +25,37 @@ const logout = () => {
|
||||
jwt.value = '';
|
||||
location.reload()
|
||||
}
|
||||
const changeLocale = (locale) => {
|
||||
localeCache.value = locale;
|
||||
location.reload()
|
||||
}
|
||||
const { t, locale } = useI18n({
|
||||
useScope: 'global',
|
||||
locale: localeCache.value || 'zh',
|
||||
messages: {
|
||||
en: {
|
||||
title: 'Cloudflare Temp Email',
|
||||
dark: 'Dark',
|
||||
light: 'Light',
|
||||
login: 'Login',
|
||||
logout: 'Logout',
|
||||
logoutConfirm: 'Are you sure to logout?',
|
||||
},
|
||||
zh: {
|
||||
title: 'Cloudflare 临时邮件',
|
||||
dark: '暗色',
|
||||
light: '亮色',
|
||||
login: '登录',
|
||||
logout: '登出',
|
||||
logoutConfirm: '确定要登出吗?',
|
||||
}
|
||||
}
|
||||
});
|
||||
locale.value = localeCache.value;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :theme="theme">
|
||||
<n-config-provider :locale="localeConfig" :theme="theme">
|
||||
<n-global-style />
|
||||
<n-message-provider>
|
||||
<n-grid x-gap="12" :cols="8">
|
||||
@@ -34,29 +65,33 @@ const logout = () => {
|
||||
<n-space vertical>
|
||||
<n-layout-header>
|
||||
<div>
|
||||
<h2>Cloudflare Temp Email </h2>
|
||||
<h2>{{ t('title') }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<n-button v-if="localeCache == 'zh'" @click="changeLocale('en')">English</n-button>
|
||||
<n-button v-else @click="changeLocale('zh')">中文</n-button>
|
||||
<n-switch v-model:value="themeSwitch">
|
||||
<template #checked>
|
||||
Dark
|
||||
{{ t('dark') }}
|
||||
</template>
|
||||
<template #unchecked>
|
||||
Light
|
||||
{{ t('light') }}
|
||||
</template>
|
||||
</n-switch>
|
||||
<n-popconfirm v-if="jwt" @positive-click="logout">
|
||||
<template #trigger>
|
||||
<n-button tertiary round type="primary">
|
||||
Logout
|
||||
{{ t('logout') }}
|
||||
</n-button>
|
||||
</template>
|
||||
<template #default>
|
||||
<span>Are you sure to logout?</span>
|
||||
<span>
|
||||
{{ t('logoutConfirm') }}
|
||||
</span>
|
||||
</template>
|
||||
</n-popconfirm>
|
||||
<n-button v-else tertiary @click="showLogin = true" round type="primary">
|
||||
Login
|
||||
{{ t('login') }}
|
||||
</n-button>
|
||||
<n-button tag="a" target="_blank" tertiary type="primary" round
|
||||
href="https://github.com/dreamhunter2333/cloudflare_temp_email">Star on Github
|
||||
@@ -72,14 +107,14 @@ const logout = () => {
|
||||
<n-back-top :right="100" />
|
||||
<n-modal v-model:show="showLogin" preset="dialog" title="Dialog">
|
||||
<template #header>
|
||||
<div>Login</div>
|
||||
<div>{{ t('login') }}</div>
|
||||
</template>
|
||||
<n-input v-model:value="password" type="textarea" :autosize="{
|
||||
minRows: 3
|
||||
}" />
|
||||
<template #action>
|
||||
<n-button @click="login" size="small" tertiary round type="primary">
|
||||
Login
|
||||
{{ t('login') }}
|
||||
</n-button>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { watch, onMounted, ref } from "vue";
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { toClipboard } = useClipboard()
|
||||
const message = useMessage()
|
||||
@@ -25,6 +26,41 @@ const openSettings = ref({
|
||||
domain: 'test.com'
|
||||
})
|
||||
|
||||
const { t, locale } = useI18n({
|
||||
useScope: 'global',
|
||||
locale: 'zh',
|
||||
messages: {
|
||||
en: {
|
||||
yourAddress: 'Your email address is',
|
||||
getNewEmail: 'Get New Email',
|
||||
getNewEmailTip1: 'Please input the email you want to use.',
|
||||
getNewEmailTip2: 'Levaing it blank will generate a random email address.',
|
||||
cancel: 'Cancel',
|
||||
ok: 'OK',
|
||||
copy: 'Copy',
|
||||
showPassword: 'Show Password',
|
||||
autoRefresh: 'Auto Refresh',
|
||||
refresh: 'Refresh',
|
||||
password: 'Password',
|
||||
passwordTip: 'Please copy the password and you can use it to login to your email account.',
|
||||
},
|
||||
zh: {
|
||||
yourAddress: '你的邮箱地址是',
|
||||
getNewEmail: '获取新邮箱',
|
||||
getNewEmailTip1: '请输入你想要使用的邮箱地址。',
|
||||
getNewEmailTip2: '留空将会生成一个随机的邮箱地址。',
|
||||
cancel: '取消',
|
||||
ok: '确定',
|
||||
copy: '复制',
|
||||
showPassword: '显示密码',
|
||||
autoRefresh: '自动刷新',
|
||||
refresh: '刷新',
|
||||
password: '密码',
|
||||
passwordTip: '请复制密码,你可以使用它登录你的邮箱。',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const setupAutoRefresh = async (autoRefresh) => {
|
||||
if (autoRefresh) {
|
||||
timer.value = setInterval(async () => {
|
||||
@@ -172,9 +208,9 @@ onMounted(async () => {
|
||||
<n-layout>
|
||||
<n-alert :type='address ? "info" : "warning"' show-icon>
|
||||
<span v-if="address">
|
||||
Your email address is <b>{{ address }}</b>
|
||||
{{ t('yourAddress') }} <b>{{ address }}</b>
|
||||
<n-button @click="copy" size="small" tertiary round type="primary">
|
||||
Copy
|
||||
{{ t('copy') }}
|
||||
</n-button>
|
||||
</span>
|
||||
<span v-else>
|
||||
@@ -182,20 +218,20 @@ onMounted(async () => {
|
||||
</span>
|
||||
</n-alert>
|
||||
<n-button v-if="address" class="center" @click="showPassword = true" tertiary round type="primary">
|
||||
Show Password
|
||||
{{ t('showPassword') }}
|
||||
</n-button>
|
||||
<n-button v-else class="center" @click="showNewEmail = true" tertiary round type="primary">
|
||||
Get New Email
|
||||
{{ t('getNewEmail') }}
|
||||
</n-button>
|
||||
<n-switch v-model:value="autoRefresh">
|
||||
<template #checked>
|
||||
Auto Refresh
|
||||
{{ t('autoRefresh') }}
|
||||
</template>
|
||||
<template #unchecked>
|
||||
Auto Refresh
|
||||
{{ t('autoRefresh') }}
|
||||
</template></n-switch>
|
||||
<n-button class="center" @click="refresh" round type="primary">
|
||||
Refresh
|
||||
{{ t('refresh') }}
|
||||
</n-button>
|
||||
<n-list hoverable clickable>
|
||||
<n-list-item v-for="row in data" v-bind:key="row.id">
|
||||
@@ -217,11 +253,11 @@ onMounted(async () => {
|
||||
</n-layout>
|
||||
<n-modal v-model:show="showNewEmail" preset="dialog" title="Dialog">
|
||||
<template #header>
|
||||
<div>Get New Email</div>
|
||||
<div>{{ t('getNewEmail') }}</div>
|
||||
</template>
|
||||
<span>
|
||||
<p>Please input the email you want to use.</p>
|
||||
<p>Levaing it blank will generate a random email address.</p>
|
||||
<p>{{ t("getNewEmailTip1") }}</p>
|
||||
<p>{{ t("getNewEmailTip2") }}</p>
|
||||
</span>
|
||||
<n-input-group>
|
||||
<n-input-group-label v-if="openSettings.prefix">
|
||||
@@ -234,19 +270,19 @@ onMounted(async () => {
|
||||
</n-input-group>
|
||||
<template #action>
|
||||
<n-button @click="showNewEmail = false">
|
||||
Cancel
|
||||
{{ t('cancel') }}
|
||||
</n-button>
|
||||
<n-button @click="newEmail" type="primary">
|
||||
OK
|
||||
{{ t('ok') }}
|
||||
</n-button>
|
||||
</template>
|
||||
</n-modal>
|
||||
<n-modal v-model:show="showPassword" preset="dialog" title="Dialog">
|
||||
<template #header>
|
||||
<div>Password</div>
|
||||
<div>{{ t("password") }}</div>
|
||||
</template>
|
||||
<span>
|
||||
Please copy the password and you can use it to login to your email account.
|
||||
<p>{{ t("passwordTip") }}</p>
|
||||
</span>
|
||||
<n-card>
|
||||
<b>{{ jwt }}</b>
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false, // you must set `false`, to use Composition API
|
||||
locale: 'zh', // set locale
|
||||
fallbackLocale: 'en', // set fallback locale
|
||||
'en': {
|
||||
messages: {}
|
||||
},
|
||||
'zhCN': {
|
||||
messages: {}
|
||||
}
|
||||
})
|
||||
const app = createApp(App)
|
||||
app.use(i18n)
|
||||
app.mount('#app')
|
||||
|
||||
Reference in New Issue
Block a user