diff --git a/frontend/src/api/notification.ts b/frontend/src/api/notification.ts index 275a43d..ea9eb59 100644 --- a/frontend/src/api/notification.ts +++ b/frontend/src/api/notification.ts @@ -19,6 +19,7 @@ export interface NotificationChannel { enabled: boolean config: Record hasPassword: boolean + hasSecret: boolean createdAt: string updatedAt: string } diff --git a/frontend/src/components/NotificationConfigPanel.vue b/frontend/src/components/NotificationConfigPanel.vue index bc307d8..565a167 100644 --- a/frontend/src/components/NotificationConfigPanel.vue +++ b/frontend/src/components/NotificationConfigPanel.vue @@ -69,6 +69,7 @@ const testingId = ref(null) const showChannelModal = ref(false) const editingId = ref(null) const editingHasPassword = ref(false) +const editingHasSecret = ref(false) const savingChannel = ref(false) const channelForm = ref(emptyChannelForm()) @@ -85,6 +86,8 @@ function emptyChannelForm() { password: '', to: '', ssl: true, + webhookUrl: '', + secret: '', } } @@ -107,16 +110,18 @@ const weekOptions = computed(() => [ // 每月提醒日限制在 1-28,避免不同月份天数差异 const monthDayOptions = computed(() => Array.from({ length: 28 }, (_, i) => ({ label: String(i + 1), value: i + 1 }))) -// 前端开放 Server 酱与邮箱两种推送渠道 +// 前端开放 Server 酱、邮箱与飞书两种推送渠道 const channelTypeOptions = computed(() => [ { label: t('notification.typeServerChan'), value: 'SERVERCHAN' }, { label: t('notification.typeEmail'), value: 'EMAIL' }, + { label: t('notification.typeFeishu'), value: 'FEISHU' }, ]) /** 渠道类型显示名 */ function channelTypeLabel(type: string | null) { if (type === 'SERVERCHAN') return t('notification.typeServerChan') if (type === 'EMAIL') return t('notification.typeEmail') + if (type === 'FEISHU') return t('notification.typeFeishu') return type || '' } @@ -124,6 +129,7 @@ function channelTypeLabel(type: string | null) { function channelTagType(type: string) { if (type === 'SERVERCHAN') return 'success' if (type === 'EMAIL') return 'warning' + if (type === 'FEISHU') return 'primary' return 'info' } @@ -193,6 +199,7 @@ async function handleSaveConfig() { function openChannelModal(channel: NotificationChannel | null) { editingId.value = channel ? channel.id : null editingHasPassword.value = !!channel?.hasPassword + editingHasSecret.value = !!channel?.hasSecret const base = emptyChannelForm() if (channel) { const cfg = channel.config || {} @@ -207,6 +214,8 @@ function openChannelModal(channel: NotificationChannel | null) { password: '', to: cfg.to || '', ssl: cfg.ssl !== false, + webhookUrl: cfg.webhookUrl || '', + secret: '', }) } channelForm.value = base @@ -219,6 +228,13 @@ function buildChannelConfig() { if (form.type === 'SERVERCHAN') { return { sendKey: form.sendKey.trim() } } + if (form.type === 'FEISHU') { + const config: Record = { webhookUrl: form.webhookUrl.trim() } + if (form.secret) { + config.secret = form.secret + } + return config + } const config: Record = { smtpHost: form.smtpHost.trim(), smtpPort: form.smtpPort, @@ -240,6 +256,11 @@ async function handleSaveChannel() { message.error(t('notification.sendKeyRequired')) return } + } else if (form.type === 'FEISHU') { + if (!form.webhookUrl.trim()) { + message.error(t('notification.webhookUrlRequired')) + return + } } else if (!form.username.trim()) { message.error(t('notification.usernameRequired')) return @@ -475,6 +496,20 @@ onMounted(loadAll) + +