mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 添加SMTP邮件通知功能
新增功能: - 支持SMTP邮件通知平台,可通过邮件接收系统通知 - 支持配置SMTP服务器、端口、用户名、密码、发件人和收件人 - 支持TLS/SSL加密连接 - 提供美观的HTML邮件模板和纯文本备用格式 代码优化: - 重构邮件格式化逻辑,提取buildNotificationDetails减少重复代码 - 优化前端表单验证逻辑,提取validatePlatformForm统一验证 - 清理UI中的冗余提示信息和配置项 UI改进: - 移除SMTP配置说明文字 - 移除超时设置和忽略TLS证书验证选项 - 简化测试成功提示消息 - SMTP平台显示收件人邮箱而非URL 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -471,10 +471,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 space-y-1 text-sm">
|
||||
<div class="flex items-center text-gray-600 dark:text-gray-400">
|
||||
<div
|
||||
v-if="platform.type !== 'smtp'"
|
||||
class="flex items-center text-gray-600 dark:text-gray-400"
|
||||
>
|
||||
<i class="fas fa-link mr-2"></i>
|
||||
<span class="truncate">{{ platform.url }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="platform.type === 'smtp' && platform.to"
|
||||
class="flex items-center text-gray-600 dark:text-gray-400"
|
||||
>
|
||||
<i class="fas fa-envelope mr-2"></i>
|
||||
<span class="truncate">{{
|
||||
Array.isArray(platform.to) ? platform.to.join(', ') : platform.to
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="platform.enableSign"
|
||||
class="flex items-center text-gray-600 dark:text-gray-400"
|
||||
@@ -655,6 +667,7 @@
|
||||
<option value="slack">🟣 Slack</option>
|
||||
<option value="discord">🟪 Discord</option>
|
||||
<option value="bark">🔔 Bark</option>
|
||||
<option value="smtp">📧 邮件通知</option>
|
||||
<option value="custom">⚙️ 自定义</option>
|
||||
</select>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
@@ -684,8 +697,8 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Webhook URL (非Bark平台) -->
|
||||
<div v-if="platformForm.type !== 'bark'">
|
||||
<!-- Webhook URL (非Bark和SMTP平台) -->
|
||||
<div v-if="platformForm.type !== 'bark' && platformForm.type !== 'smtp'">
|
||||
<label
|
||||
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
@@ -836,6 +849,141 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SMTP 平台特有字段 -->
|
||||
<div v-if="platformForm.type === 'smtp'" class="space-y-5">
|
||||
<!-- SMTP 主机 -->
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<i class="fas fa-server mr-2 text-gray-400"></i>
|
||||
SMTP 服务器
|
||||
<span class="ml-1 text-xs text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="platformForm.host"
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
|
||||
placeholder="例如: smtp.gmail.com"
|
||||
required
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- SMTP 端口和安全设置 -->
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<i class="fas fa-plug mr-2 text-gray-400"></i>
|
||||
端口
|
||||
</label>
|
||||
<input
|
||||
v-model.number="platformForm.port"
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
max="65535"
|
||||
min="1"
|
||||
placeholder="587"
|
||||
type="number"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
默认: 587 (TLS) 或 465 (SSL)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<i class="fas fa-shield-alt mr-2 text-gray-400"></i>
|
||||
加密方式
|
||||
</label>
|
||||
<select
|
||||
v-model="platformForm.secure"
|
||||
class="w-full appearance-none rounded-xl border border-gray-300 bg-white px-4 py-3 pr-10 text-gray-900 shadow-sm transition-all focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option :value="false">STARTTLS (端口587)</option>
|
||||
<option :value="true">SSL/TLS (端口465)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户名 -->
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<i class="fas fa-user mr-2 text-gray-400"></i>
|
||||
用户名
|
||||
<span class="ml-1 text-xs text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="platformForm.user"
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
|
||||
placeholder="user@example.com"
|
||||
required
|
||||
type="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 密码 -->
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<i class="fas fa-lock mr-2 text-gray-400"></i>
|
||||
密码 / 应用密码
|
||||
<span class="ml-1 text-xs text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="platformForm.pass"
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
|
||||
placeholder="邮箱密码或应用专用密码"
|
||||
required
|
||||
type="password"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
建议使用应用专用密码,而非邮箱登录密码
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 发件人邮箱 -->
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<i class="fas fa-paper-plane mr-2 text-gray-400"></i>
|
||||
发件人邮箱
|
||||
<span class="ml-2 text-xs text-gray-500">(可选)</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="platformForm.from"
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
|
||||
placeholder="默认使用用户名邮箱"
|
||||
type="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 收件人邮箱 -->
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
<i class="fas fa-envelope mr-2 text-gray-400"></i>
|
||||
收件人邮箱
|
||||
<span class="ml-1 text-xs text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="platformForm.to"
|
||||
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
|
||||
placeholder="admin@example.com"
|
||||
required
|
||||
type="email"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">接收通知的邮箱地址</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 签名设置(钉钉/飞书) -->
|
||||
<div
|
||||
v-if="platformForm.type === 'dingtalk' || platformForm.type === 'feishu'"
|
||||
@@ -1008,7 +1156,23 @@ const platformForm = ref({
|
||||
name: '',
|
||||
url: '',
|
||||
enableSign: false,
|
||||
secret: ''
|
||||
secret: '',
|
||||
// Bark特有字段
|
||||
deviceKey: '',
|
||||
serverUrl: '',
|
||||
level: '',
|
||||
sound: '',
|
||||
group: '',
|
||||
// SMTP特有字段
|
||||
host: '',
|
||||
port: null,
|
||||
secure: false,
|
||||
user: '',
|
||||
pass: '',
|
||||
from: '',
|
||||
to: '',
|
||||
timeout: null,
|
||||
ignoreTLS: false
|
||||
})
|
||||
|
||||
// 监听activeSection变化,加载对应配置
|
||||
@@ -1030,17 +1194,48 @@ const platformTypeWatcher = watch(
|
||||
// 如果不是编辑模式,清空相关字段
|
||||
if (!editingPlatform.value) {
|
||||
if (newType === 'bark') {
|
||||
// 切换到Bark时,清空URL相关字段
|
||||
// 切换到Bark时,清空URL和SMTP相关字段
|
||||
platformForm.value.url = ''
|
||||
platformForm.value.enableSign = false
|
||||
platformForm.value.secret = ''
|
||||
} else {
|
||||
// 切换到其他平台时,清空Bark相关字段
|
||||
// 清空SMTP字段
|
||||
platformForm.value.host = ''
|
||||
platformForm.value.port = null
|
||||
platformForm.value.secure = false
|
||||
platformForm.value.user = ''
|
||||
platformForm.value.pass = ''
|
||||
platformForm.value.from = ''
|
||||
platformForm.value.to = ''
|
||||
platformForm.value.timeout = null
|
||||
platformForm.value.ignoreTLS = false
|
||||
} else if (newType === 'smtp') {
|
||||
// 切换到SMTP时,清空URL和Bark相关字段
|
||||
platformForm.value.url = ''
|
||||
platformForm.value.enableSign = false
|
||||
platformForm.value.secret = ''
|
||||
// 清空Bark字段
|
||||
platformForm.value.deviceKey = ''
|
||||
platformForm.value.serverUrl = ''
|
||||
platformForm.value.level = ''
|
||||
platformForm.value.sound = ''
|
||||
platformForm.value.group = ''
|
||||
} else {
|
||||
// 切换到其他平台时,清空Bark和SMTP相关字段
|
||||
platformForm.value.deviceKey = ''
|
||||
platformForm.value.serverUrl = ''
|
||||
platformForm.value.level = ''
|
||||
platformForm.value.sound = ''
|
||||
platformForm.value.group = ''
|
||||
// SMTP 字段
|
||||
platformForm.value.host = ''
|
||||
platformForm.value.port = null
|
||||
platformForm.value.secure = false
|
||||
platformForm.value.user = ''
|
||||
platformForm.value.pass = ''
|
||||
platformForm.value.from = ''
|
||||
platformForm.value.to = ''
|
||||
platformForm.value.timeout = null
|
||||
platformForm.value.ignoreTLS = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1051,6 +1246,14 @@ const isPlatformFormValid = computed(() => {
|
||||
if (platformForm.value.type === 'bark') {
|
||||
// Bark平台需要deviceKey
|
||||
return !!platformForm.value.deviceKey
|
||||
} else if (platformForm.value.type === 'smtp') {
|
||||
// SMTP平台需要必要的配置
|
||||
return !!(
|
||||
platformForm.value.host &&
|
||||
platformForm.value.user &&
|
||||
platformForm.value.pass &&
|
||||
platformForm.value.to
|
||||
)
|
||||
} else {
|
||||
// 其他平台需要URL且URL格式正确
|
||||
return !!platformForm.value.url && !urlError.value
|
||||
@@ -1134,8 +1337,8 @@ const saveWebhookConfig = async () => {
|
||||
|
||||
// 验证 URL
|
||||
const validateUrl = () => {
|
||||
// Bark平台不需要验证URL
|
||||
if (platformForm.value.type === 'bark') {
|
||||
// Bark和SMTP平台不需要验证URL
|
||||
if (platformForm.value.type === 'bark' || platformForm.value.type === 'smtp') {
|
||||
urlError.value = false
|
||||
urlValid.value = false
|
||||
return
|
||||
@@ -1163,27 +1366,46 @@ const validateUrl = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加/更新平台
|
||||
const savePlatform = async () => {
|
||||
if (!isMounted.value) return
|
||||
|
||||
// Bark平台只需要deviceKey,其他平台需要URL
|
||||
// 验证平台配置
|
||||
const validatePlatformForm = () => {
|
||||
if (platformForm.value.type === 'bark') {
|
||||
if (!platformForm.value.deviceKey) {
|
||||
showToast('请输入Bark设备密钥', 'error')
|
||||
return
|
||||
return false
|
||||
}
|
||||
} else if (platformForm.value.type === 'smtp') {
|
||||
const requiredFields = [
|
||||
{ field: 'host', message: 'SMTP服务器' },
|
||||
{ field: 'user', message: '用户名' },
|
||||
{ field: 'pass', message: '密码' },
|
||||
{ field: 'to', message: '收件人邮箱' }
|
||||
]
|
||||
|
||||
for (const { field, message } of requiredFields) {
|
||||
if (!platformForm.value[field]) {
|
||||
showToast(`请输入${message}`, 'error')
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!platformForm.value.url) {
|
||||
showToast('请输入Webhook URL', 'error')
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
if (urlError.value) {
|
||||
showToast('请输入有效的Webhook URL', 'error')
|
||||
return
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 添加/更新平台
|
||||
const savePlatform = async () => {
|
||||
if (!isMounted.value) return
|
||||
|
||||
// 验证表单
|
||||
if (!validatePlatformForm()) return
|
||||
|
||||
savingPlatform.value = true
|
||||
try {
|
||||
@@ -1300,7 +1522,7 @@ const testPlatform = async (platform) => {
|
||||
signal: abortController.value.signal
|
||||
})
|
||||
if (response.success && isMounted.value) {
|
||||
showToast('测试成功,webhook连接正常', 'success')
|
||||
showToast('测试成功', 'success')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return
|
||||
@@ -1314,24 +1536,8 @@ const testPlatform = async (platform) => {
|
||||
const testPlatformForm = async () => {
|
||||
if (!isMounted.value) return
|
||||
|
||||
// Bark平台验证
|
||||
if (platformForm.value.type === 'bark') {
|
||||
if (!platformForm.value.deviceKey) {
|
||||
showToast('请先输入Bark设备密钥', 'error')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 其他平台验证URL
|
||||
if (!platformForm.value.url) {
|
||||
showToast('请先输入Webhook URL', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
if (urlError.value) {
|
||||
showToast('请输入有效的Webhook URL', 'error')
|
||||
return
|
||||
}
|
||||
}
|
||||
// 验证表单
|
||||
if (!validatePlatformForm()) return
|
||||
|
||||
testingConnection.value = true
|
||||
try {
|
||||
@@ -1339,7 +1545,7 @@ const testPlatformForm = async () => {
|
||||
signal: abortController.value.signal
|
||||
})
|
||||
if (response.success && isMounted.value) {
|
||||
showToast('测试成功,webhook连接正常', 'success')
|
||||
showToast('测试成功', 'success')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return
|
||||
@@ -1397,7 +1603,17 @@ const closePlatformModal = () => {
|
||||
serverUrl: '',
|
||||
level: '',
|
||||
sound: '',
|
||||
group: ''
|
||||
group: '',
|
||||
// SMTP特有字段
|
||||
host: '',
|
||||
port: null,
|
||||
secure: false,
|
||||
user: '',
|
||||
pass: '',
|
||||
from: '',
|
||||
to: '',
|
||||
timeout: null,
|
||||
ignoreTLS: false
|
||||
}
|
||||
urlError.value = false
|
||||
urlValid.value = false
|
||||
@@ -1415,6 +1631,7 @@ const getPlatformName = (type) => {
|
||||
slack: 'Slack',
|
||||
discord: 'Discord',
|
||||
bark: 'Bark',
|
||||
smtp: '邮件通知',
|
||||
custom: '自定义'
|
||||
}
|
||||
return names[type] || type
|
||||
@@ -1428,6 +1645,7 @@ const getPlatformIcon = (type) => {
|
||||
slack: 'fab fa-slack text-purple-600',
|
||||
discord: 'fab fa-discord text-indigo-600',
|
||||
bark: 'fas fa-bell text-orange-500',
|
||||
smtp: 'fas fa-envelope text-blue-600',
|
||||
custom: 'fas fa-webhook text-gray-600'
|
||||
}
|
||||
return icons[type] || 'fas fa-bell'
|
||||
@@ -1441,6 +1659,7 @@ const getWebhookHint = (type) => {
|
||||
slack: '请在Slack应用的Incoming Webhooks中获取地址',
|
||||
discord: '请在Discord服务器的集成设置中创建Webhook',
|
||||
bark: '请在Bark App中查看您的设备密钥',
|
||||
smtp: '请配置SMTP服务器信息,支持Gmail、QQ邮箱等',
|
||||
custom: '请输入完整的Webhook接收地址'
|
||||
}
|
||||
return hints[type] || ''
|
||||
|
||||
Reference in New Issue
Block a user