Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

Commit dfc0f88

Browse files
committed
feat: add email testing functionality to health API and config form
- Introduced a new API method to send test emails, enhancing the health API. - Implemented a conditional field display logic in the config form to show fields based on sibling field values. - Added a button in the system settings tab to trigger the sending of test emails, improving user interaction. These changes aim to enhance the configuration options and provide a way to test email functionality directly from the settings interface. Signed-off-by: Innei <tukon479@gmail.com>
1 parent cf6f700 commit dfc0f88

4 files changed

Lines changed: 82 additions & 7 deletions

File tree

src/api/health.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,10 @@ export const healthApi = {
1717

1818
// 手动运行 Cron 任务
1919
runCron: (name: string) => request.post<void>(`/health/cron/run/${name}`),
20+
21+
// === 邮件测试 ===
22+
23+
// 发送测试邮件
24+
sendTestEmail: () =>
25+
request.get<{ message?: string; trace?: string }>('/health/email/test'),
2026
}

src/components/config-form/index.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,29 @@ import type { FormField } from './types'
88
import { SettingsItem } from '~/layouts/settings-layout'
99
import { uuid } from '~/utils'
1010

11+
/**
12+
* Check if a field should be shown based on showWhen conditions.
13+
* When the condition is not met, the field and all its nested children are hidden.
14+
*/
15+
function shouldShowField(
16+
field: FormField,
17+
formData: Ref<any>,
18+
sectionPrefix: string,
19+
): boolean {
20+
const { showWhen } = field.ui
21+
if (!showWhen) return true
22+
23+
for (const [key, expected] of Object.entries(showWhen)) {
24+
const actualValue = get(formData.value, `${sectionPrefix}.${key}`)
25+
if (Array.isArray(expected)) {
26+
if (!expected.includes(actualValue)) return false
27+
} else {
28+
if (actualValue !== expected) return false
29+
}
30+
}
31+
return true
32+
}
33+
1134
export const SectionFields = defineComponent({
1235
props: {
1336
fields: {
@@ -31,6 +54,7 @@ export const SectionFields = defineComponent({
3154
<>
3255
{fields
3356
.filter((field) => !field.ui.hidden)
57+
.filter((field) => shouldShowField(field, formData, dataKeyPrefix))
3458
.map((field) => {
3559
const fieldPath = `${dataKeyPrefix}.${field.key}`
3660

src/components/config-form/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ export interface UIConfig {
1313
hidden?: boolean
1414
placeholder?: string
1515
options?: Array<{ label: string; value: string | number }>
16+
/**
17+
* Conditionally show this field based on sibling field values.
18+
* When the condition is not met, the field and all its nested children are hidden.
19+
*/
20+
showWhen?: Record<string, string | string[]>
1621
}
1722

1823
export interface FormField {

src/views/setting/tabs/system.tsx

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { cloneDeep, isEmpty } from 'es-toolkit/compat'
2+
import { Mail as MailIcon } from 'lucide-vue-next'
3+
import { NButton } from 'naive-ui'
24
import {
35
computed,
46
defineComponent,
@@ -16,6 +18,7 @@ import type {
1618
} from '~/components/config-form/types'
1719
import type { PropType } from 'vue'
1820

21+
import { healthApi } from '~/api/health'
1922
import { optionsApi } from '~/api/options'
2023
import { SectionFields } from '~/components/config-form'
2124
import { SettingsSection } from '~/layouts/settings-layout'
@@ -169,6 +172,25 @@ export const TabSystem = defineComponent({
169172
saveAll,
170173
})
171174

175+
const isSendingTestEmail = ref(false)
176+
177+
const handleSendTestEmail = async () => {
178+
if (isSendingTestEmail.value) return
179+
isSendingTestEmail.value = true
180+
try {
181+
const result = await healthApi.sendTestEmail()
182+
if (result.message) {
183+
toast.error(`发送失败: ${result.message}`)
184+
} else {
185+
toast.success('测试邮件已发送,请检查收件箱')
186+
}
187+
} catch (error: any) {
188+
toast.error(error?.message || '发送测试邮件失败')
189+
} finally {
190+
isSendingTestEmail.value = false
191+
}
192+
}
193+
172194
return () => {
173195
const { activeGroup } = props
174196

@@ -191,13 +213,31 @@ export const TabSystem = defineComponent({
191213
/>
192214
) : (
193215
<SettingsSection key={section.key} title={section.title}>
194-
<div class="py-2">
195-
<SectionFields
196-
fields={section.fields}
197-
formData={computed(() => configs)}
198-
dataKeyPrefix={section.key}
199-
/>
200-
</div>
216+
{{
217+
default: () => (
218+
<div class="py-2">
219+
<SectionFields
220+
fields={section.fields}
221+
formData={computed(() => configs)}
222+
dataKeyPrefix={section.key}
223+
/>
224+
</div>
225+
),
226+
actions:
227+
section.key === 'mailOptions'
228+
? () => (
229+
<NButton
230+
size="small"
231+
secondary
232+
loading={isSendingTestEmail.value}
233+
onClick={handleSendTestEmail}
234+
renderIcon={() => <MailIcon size={14} />}
235+
>
236+
发送测试邮件
237+
</NButton>
238+
)
239+
: undefined,
240+
}}
201241
</SettingsSection>
202242
),
203243
)}

0 commit comments

Comments
 (0)