feat: add auto reply (#49)

* feat: add auto reply

* feat: add auto reply

* feat: update readme

* feat: add auto reply
This commit is contained in:
Dream Hunter
2023-12-18 21:41:15 +08:00
committed by GitHub
parent af78248145
commit 95ae65dc03
14 changed files with 349 additions and 33 deletions

View File

@@ -2,7 +2,8 @@ import { useGlobalState } from '../store'
import axios from 'axios'
const API_BASE = import.meta.env.VITE_API_BASE || "";
const { loading, auth, jwt, openSettings, showAuth, adminAuth, showAdminAuth } = useGlobalState();
const { loading, auth, jwt, settings, openSettings } = useGlobalState();
const { showAuth, adminAuth, showAdminAuth } = useGlobalState();
const instance = axios.create({
baseURL: API_BASE,
@@ -65,9 +66,11 @@ const getSettings = async () => {
if (typeof jwt.value != 'string' || jwt.value.trim() === '' || jwt.value === 'undefined') {
return "";
}
loading.value = true;
const res = await apiFetch("/api/settings");;
return res["address"];
settings.value = {
address: res["address"],
auto_reply: res["auto_reply"]
};
}
const adminShowPassword = async (id) => {

View File

@@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router'
import Index from '../views/Index.vue'
import Settings from '../views/Settings.vue'
import Admin from '../views/Admin.vue'
const router = createRouter({
@@ -9,6 +10,10 @@ const router = createRouter({
path: '/',
component: Index
},
{
path: '/settings',
component: Settings
},
{
path: '/admin',
component: Admin

View File

@@ -12,6 +12,16 @@ export const useGlobalState = createGlobalState(
value: 'test.com'
}]
})
const settings = ref({
address: '',
auto_reply: {
subject: '',
message: '',
enabled: false,
source_prefix: '',
name: '',
}
})
const showAuth = ref(false);
const showAdminAuth = ref(false);
const auth = useStorage('auth', '');
@@ -21,6 +31,7 @@ export const useGlobalState = createGlobalState(
const themeSwitch = useStorage('themeSwitch', false);
return {
loading,
settings,
openSettings,
showAuth,
auth,

View File

@@ -12,8 +12,7 @@ import { api } from '../api'
const { toClipboard } = useClipboard()
const message = useMessage()
const address = ref("")
const { jwt, openSettings } = useGlobalState()
const { jwt, settings, openSettings } = useGlobalState()
const autoRefresh = ref(false)
const data = ref([])
const timer = ref(null)
@@ -84,7 +83,7 @@ watch([page, pageSize], async ([page, pageSize], [oldPage, oldPageSize]) => {
})
const refresh = async () => {
if (typeof address.value != 'string' || address.value.trim() === '') {
if (typeof settings.value.address != 'string' || settings.value.address.trim() === '') {
return;
}
try {
@@ -105,7 +104,7 @@ const refresh = async () => {
const copy = async () => {
try {
await toClipboard(address.value)
await toClipboard(settings.value.address)
message.success('Copied');
} catch (e) {
message.error(e.message || "error");
@@ -120,7 +119,7 @@ const newEmail = async () => {
+ `&domain=${emailDomain.value || ''}`
);
jwt.value = res["jwt"];
address.value = await api.getSettings();
await api.getSettings();
await refresh();
showNewEmail.value = false;
showPassword.value = true;
@@ -132,7 +131,7 @@ const newEmail = async () => {
onMounted(async () => {
await api.getOpenSettings(message);
emailDomain.value = openSettings.value.domains ? openSettings.value.domains[0].value : "";
address.value = await api.getSettings();
await api.getSettings();
await refresh();
});
</script>
@@ -140,9 +139,9 @@ onMounted(async () => {
<template>
<div>
<n-layout>
<n-alert :type='address ? "info" : "warning"' show-icon>
<span v-if="address">
{{ t('yourAddress') }} <b>{{ address }}</b>
<n-alert :type='settings.address ? "info" : "warning"' show-icon>
<span v-if="settings.address">
{{ t('yourAddress') }} <b>{{ settings.address }}</b>
<n-button @click="copy" size="small" tertiary round type="primary">
{{ t('copy') }}
</n-button>
@@ -151,7 +150,7 @@ onMounted(async () => {
{{ t('pleaseGetNewEmail') }}
</span>
</n-alert>
<n-button v-if="address" class="center" @click="showPassword = true" tertiary round type="primary">
<n-button v-if="settings.address" class="center" @click="showPassword = true" tertiary round type="primary">
{{ t('showPassword') }}
</n-button>
<n-button v-else class="center" @click="showNewEmail = true" tertiary round type="primary">

View File

@@ -59,6 +59,8 @@ const { t } = useI18n({
logoutConfirm: 'Are you sure to logout?',
auth: 'Auth',
authTip: 'Please enter the correct auth code',
settings: 'Settings',
home: 'Home',
},
zh: {
title: 'Cloudflare 临时邮件',
@@ -69,12 +71,26 @@ const { t } = useI18n({
logoutConfirm: '确定要登出吗?',
auth: '授权',
authTip: '请输入正确的授权码',
settings: '设置',
home: '主页',
}
}
});
const menuOptions = computed(() => [
{
label: () => h(
NButton,
{
tertiary: true,
ghost: true,
onClick: () => router.push('/')
},
{ default: () => t('home') }
),
key: "home"
},
{
label: () => h(
NButton,
@@ -86,7 +102,7 @@ const menuOptions = computed(() => [
{ default: () => "Admin" }
),
show: !!adminAuth.value,
key: "home"
key: "admin"
},
{
label: () => h(
@@ -127,6 +143,18 @@ const menuOptions = computed(() => [
),
show: !!jwt.value,
key: "logout"
},
{
label: () => h(
NButton,
{
tertiary: true,
ghost: true,
onClick: () => { router.push('/settings') }
},
{ default: () => t('settings') }
),
key: "settings"
}
]);
@@ -186,7 +214,7 @@ const menuOptionsMobile = [
</template>
<p>{{ t('logoutConfirm') }}</p>
<template #action>
<n-button @click="login" size="small" tertiary round type="primary">
<n-button @click="logout" size="small" tertiary round type="primary">
{{ t('logout') }}
</n-button>
</template>

View File

@@ -0,0 +1,102 @@
<script setup>
import { NSpace, NFormItem, NInput, NSwitch, NButton } from 'naive-ui'
import { useMessage } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { onMounted, ref } from 'vue'
import Header from './Header.vue'
import { useGlobalState } from '../store'
import { api } from '../api'
const message = useMessage()
const sourcePrefix = ref("")
const enableAutoReply = ref(false)
const autoReplyMessage = ref("")
const subject = ref("")
const name = ref("")
const { settings } = useGlobalState()
const { t } = useI18n({
locale: 'zh',
messages: {
en: {
success: 'Success',
settings: 'Settings',
sourcePrefix: 'Source Mail Prefix',
name: 'Name',
enableAutoReply: 'Enable Auto Reply',
subject: 'Subject',
autoReply: 'Auto Reply',
save: 'Save',
},
zh: {
success: '成功',
settings: '设置',
sourcePrefix: '来源邮件前缀',
name: '名称',
enableAutoReply: '启用自动回复',
subject: '主题',
autoReply: '自动回复',
save: '保存',
}
}
});
const getSettings = async () => {
await api.getSettings()
sourcePrefix.value = settings.value.auto_reply.source_prefix || ""
enableAutoReply.value = settings.value.auto_reply.enabled || false
name.value = settings.value.auto_reply.name || ""
autoReplyMessage.value = settings.value.auto_reply.message || ""
subject.value = settings.value.auto_reply.subject || ""
}
const saveSettings = async () => {
try {
await api.fetch("/api/settings", {
method: "POST",
body: JSON.stringify({
auto_reply: {
enabled: enableAutoReply.value,
source_prefix: sourcePrefix.value,
name: name.value,
message: autoReplyMessage.value,
subject: subject.value,
}
})
})
message.success(t("success"))
} catch (error) {
message.error(error.message || "error");
}
}
onMounted(() => {
getSettings()
})
</script>
<template>
<n-space vertical>
<Header />
<h1>{{ t("settings") }}</h1>
<n-button type="primary" @click="saveSettings">{{ t('save') }}</n-button>
<n-form-item :label="t('enableAutoReply')" label-placement="left">
<n-switch v-model:value="enableAutoReply" />
</n-form-item>
<n-form-item :label="t('name')" label-placement="left">
<n-input :disabled="!enableAutoReply" v-model:value="name" />
</n-form-item>
<n-form-item :label="t('sourcePrefix')" label-placement="left">
<n-input :disabled="!enableAutoReply" v-model:value="sourcePrefix" />
</n-form-item>
<n-form-item :label="t('subject')" label-placement="left">
<n-input :disabled="!enableAutoReply" v-model:value="subject" />
</n-form-item>
<n-form-item :label="t('autoReply')" label-placement="left">
<n-input :disabled="!enableAutoReply" type="textarea" v-model:value="autoReplyMessage" />
</n-form-item>
</n-space>
</template>