fix(email): complete mail settings and templates
This commit is contained in:
1 parent
4f8ab94fac
commit
4833053a5c
29 files changed
+716
-426
No files matched your search
@@ -3,8 +3,6 @@ export const globalApiMethods = {
|
||||
* 全局&功能 (Global & Functions)
|
||||
* ================================================================ */
|
||||
|
||||
getMailSettings() { return this.request('/settings/mail') },
|
||||
saveMailSettings(data) { return this.request('/settings/mail', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
testMail(to) { return this.request('/mail/test', { method: 'POST', body: JSON.stringify({ to }) }) },
|
||||
|
||||
getFooterTabbarSettings() { return this.request('/settings/footer-tabbar') },
|
||||
|
||||
@@ -273,6 +273,17 @@
|
||||
v-html="schema.content || modelValue || ''"
|
||||
/>
|
||||
|
||||
<!-- Action button -->
|
||||
<n-button
|
||||
v-else-if="schema.type === 'button'"
|
||||
:type="schema.buttonType || 'default'"
|
||||
:loading="schema.loading"
|
||||
:disabled="schema.disabled"
|
||||
@click="emit('action', schema.key)"
|
||||
>
|
||||
{{ schema.buttonText || schema.label || '执行' }}
|
||||
</n-button>
|
||||
|
||||
<!-- Date picker -->
|
||||
<n-date-picker
|
||||
v-else-if="schema.type === 'date'"
|
||||
@@ -645,7 +656,7 @@ const props = defineProps({
|
||||
errorMessage: { type: String, default: '' },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(['update:modelValue', 'action'])
|
||||
|
||||
const secretMarkedForClear = computed(() => props.schema.secret && props.modelValue === '__AKSRT_CLEAR_SECRET__')
|
||||
const secretDisplayValue = computed(() => secretMarkedForClear.value ? '' : props.modelValue)
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
:model-value="getFieldValue(field.key)"
|
||||
:error-message="errors?.[field.key]"
|
||||
@update:model-value="setFieldValue(field.key, $event)"
|
||||
@action="emit('field-action', $event)"
|
||||
/>
|
||||
</slot>
|
||||
<AksField
|
||||
@@ -74,6 +75,7 @@
|
||||
:model-value="getFieldValue(field.key)"
|
||||
:error-message="errors?.[field.key]"
|
||||
@update:model-value="setFieldValue(field.key, $event)"
|
||||
@action="emit('field-action', $event)"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
@@ -161,7 +163,7 @@ const props = defineProps({
|
||||
errors: { type: Object, default: () => ({}) },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:formData', 'save', 'reload'])
|
||||
const emit = defineEmits(['update:formData', 'save', 'reload', 'field-action'])
|
||||
const slots = useSlots()
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
@update:form-data="formData = $event"
|
||||
@save="handleSave"
|
||||
@reload="handleReload"
|
||||
@field-action="handleFieldAction"
|
||||
>
|
||||
<template
|
||||
v-if="isPermissionsLogin"
|
||||
@@ -142,6 +143,7 @@ const captchaProviderTab = ref('tencent')
|
||||
/** Resolve domain/section from props or route meta (canonicalDomain + section, with legacy fallback) */
|
||||
const resolvedDomain = computed(() => normalizeSchemaDomain(props.domain || route.meta?.canonicalDomain || route.meta?.schemaDomain || resolveDomainFromPath(route.path)))
|
||||
const resolvedSection = computed(() => props.section || route.meta?.section || route.meta?.schemaSection || resolveSectionFromPath(route.path))
|
||||
const isEmailPage = computed(() => resolvedDomain.value === 'global' && resolvedSection.value === 'email')
|
||||
const isPermissionsLogin = computed(() => resolvedDomain.value === 'permissions' && resolvedSection.value === 'login')
|
||||
const isPermissionsModeration = computed(() => resolvedDomain.value === 'permissions' && resolvedSection.value === 'moderation')
|
||||
|
||||
@@ -158,7 +160,7 @@ function resolveSectionFromPath(path) {
|
||||
|
||||
/** Load schema from the Vue-side registry */
|
||||
const pageMeta = computed(() => {
|
||||
const schema = resolvedDomain.value === 'page_display'
|
||||
const schema = (isEmailPage.value || resolvedDomain.value === 'page_display')
|
||||
? serverSchema.value
|
||||
: getSchema(resolvedDomain.value, resolvedSection.value)
|
||||
if (schema && schema.groups && schema.groups.length > 0) {
|
||||
@@ -180,7 +182,7 @@ const pageMeta = computed(() => {
|
||||
|
||||
/** Default values from schema */
|
||||
const defaults = computed(() => {
|
||||
const schema = resolvedDomain.value === 'page_display'
|
||||
const schema = (isEmailPage.value || resolvedDomain.value === 'page_display')
|
||||
? serverSchema.value
|
||||
: getSchema(resolvedDomain.value, resolvedSection.value)
|
||||
if (!schema) return {}
|
||||
@@ -202,9 +204,7 @@ const {
|
||||
} = useSectionForm({
|
||||
fetch: async () => {
|
||||
const response = await api.getSchemaPage(resolvedDomain.value, resolvedSection.value)
|
||||
if (resolvedDomain.value === 'page_display') {
|
||||
serverSchema.value = response?.schema || null
|
||||
}
|
||||
serverSchema.value = response?.schema || serverSchema.value
|
||||
return response
|
||||
},
|
||||
submit: async (data) => {
|
||||
@@ -231,6 +231,25 @@ const {
|
||||
},
|
||||
})
|
||||
|
||||
async function handleFieldAction(key) {
|
||||
if (key !== 'email_test_send' || !isEmailPage.value) return
|
||||
const to = String(formData.value?.email_test_to || '').trim()
|
||||
if (!to) {
|
||||
message.warning('请先填写测试收件地址')
|
||||
return
|
||||
}
|
||||
if (dirty.value) {
|
||||
message.warning('请先保存当前配置,再发送测试邮件')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await api.testMail(to)
|
||||
message.success(response?.message || '测试邮件已发送')
|
||||
} catch (error) {
|
||||
message.error(error?.message || '测试邮件发送失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
try {
|
||||
await doSave()
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
export const emailSchema = {
|
||||
key: 'global.email',
|
||||
title: '邮件配置',
|
||||
icon: 'fa fas fa-envelope',
|
||||
description: 'SMTP 发信配置、邮件事件开关与模板自定义。',
|
||||
defaults: {
|
||||
email_footer_text: '',
|
||||
email_site_branding: true,
|
||||
email_template_mode: 'plain',
|
||||
email_reg_verify_link_enabled: true,
|
||||
email_reg_verify_code_enabled: true,
|
||||
email_reg_success_enabled: true,
|
||||
email_password_reset_enabled: true,
|
||||
email_change_code_enabled: true,
|
||||
email_change_notice_enabled: true,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'global',
|
||||
title: '全局设置',
|
||||
icon: 'fa fas fa-cog',
|
||||
fields: [
|
||||
{ key: 'email_footer_text', label: '邮件页脚文字', type: 'textarea', rows: 2, placeholder: '留空则使用站点名称', help: '附加在每封邮件底部的文字' },
|
||||
{ key: 'email_site_branding', label: '显示站点品牌', type: 'switch', default: true, help: '在邮件开头显示「[站点名]」前缀' },
|
||||
{ key: 'email_template_mode', label: '邮件格式', type: 'radio', default: 'plain', options: [
|
||||
{ label: '纯文本', value: 'plain' },
|
||||
{ label: 'HTML', value: 'html' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'branding',
|
||||
title: '邮件品牌',
|
||||
icon: 'fa fas fa-pen',
|
||||
fields: [
|
||||
{ key: 'mail_showname', label: '发件人名称', type: 'input', default: '', help: '覆盖系统默认发件人名称' },
|
||||
{ key: 'mail_title_prefix', label: '主题前缀', type: 'input', default: '', help: '自动添加在所有邮件主题前面,如【站点名】' },
|
||||
{ key: 'mail_description', label: '邮件底部说明', type: 'textarea', rows: 3, default: '', help: '附加在邮件页脚文字之后,留空不显示' },
|
||||
{ key: 'mail_more_content', label: '邮件附加内容', type: 'textarea', rows: 4, default: '', help: '附加在邮件内容末尾的补充说明' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'reg_verify_link',
|
||||
title: '注册验证链接',
|
||||
icon: 'fa fas fa-link',
|
||||
fields: [
|
||||
{ key: 'email_reg_verify_link_enabled', label: '启用', type: 'switch', default: true, help: '注册后发送验证链接邮件' },
|
||||
{ key: 'email_reg_verify_link_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '验证你的邮箱地址' },
|
||||
{ key: 'email_reg_verify_link_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {url}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{url} 验证链接' },
|
||||
{ key: 'email_reg_verify_link_html', label: 'HTML 格式', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'reg_verify_code',
|
||||
title: '注册验证码',
|
||||
icon: 'fa fas fa-keyboard',
|
||||
fields: [
|
||||
{ key: 'email_reg_verify_code_enabled', label: '启用', type: 'switch', default: true, help: '注册后发送 6 位验证码邮件' },
|
||||
{ key: 'email_reg_verify_code_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '邮箱验证码' },
|
||||
{ key: 'email_reg_verify_code_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {code}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{code} 验证码' },
|
||||
{ key: 'email_reg_verify_code_html', label: 'HTML 格式', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'reg_success',
|
||||
title: '注册成功通知',
|
||||
icon: 'fa fas fa-check-circle',
|
||||
fields: [
|
||||
{ key: 'email_reg_success_enabled', label: '启用', type: 'switch', default: true, help: '邮箱验证成功后发送通知邮件' },
|
||||
{ key: 'email_reg_success_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '邮箱验证成功' },
|
||||
{ key: 'email_reg_success_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name}', help: '可用占位符:{username} 用户名、{site_name} 站点名' },
|
||||
{ key: 'email_reg_success_html', label: 'HTML 格式', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'password_reset',
|
||||
title: '密码重置',
|
||||
icon: 'fa fas fa-unlock-alt',
|
||||
fields: [
|
||||
{ key: 'email_password_reset_enabled', label: '启用', type: 'switch', default: true, help: '发送密码重置链接邮件' },
|
||||
{ key: 'email_password_reset_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '重置密码' },
|
||||
{ key: 'email_password_reset_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {url}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{url} 重置链接' },
|
||||
{ key: 'email_password_reset_html', label: 'HTML 格式', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'change_code',
|
||||
title: '邮箱变更验证码',
|
||||
icon: 'fa fas fa-shield-alt',
|
||||
fields: [
|
||||
{ key: 'email_change_code_enabled', label: '启用', type: 'switch', default: true, help: '变更邮箱时发送验证码邮件' },
|
||||
{ key: 'email_change_code_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '邮箱验证码' },
|
||||
{ key: 'email_change_code_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {code}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{code} 验证码' },
|
||||
{ key: 'email_change_code_html', label: 'HTML 格式', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'change_notice',
|
||||
title: '邮箱变更通知',
|
||||
icon: 'fa fas fa-bell',
|
||||
fields: [
|
||||
{ key: 'email_change_notice_enabled', label: '启用', type: 'switch', default: true, help: '邮箱变更成功后向旧邮箱发送通知' },
|
||||
{ key: 'email_change_notice_subject', label: '主题(留空使用默认)', type: 'input', placeholder: '账号邮箱已变更' },
|
||||
{ key: 'email_change_notice_content', label: '内容模板(留空使用默认)', type: 'textarea', rows: 4, placeholder: '支持 {username} {site_name} {new_email}', help: '可用占位符:{username} 用户名、{site_name} 站点名、{new_email} 新邮箱' },
|
||||
{ key: 'email_change_notice_html', label: 'HTML 格式', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { logoSchema } from './logo.js'
|
||||
import { commonSchema } from './common.js'
|
||||
import { layoutSchema } from './layout.js'
|
||||
import { searchSchema } from './search.js'
|
||||
import { emailSchema } from './email.js'
|
||||
// Email schema is served by the PHP template catalog to keep defaults and fields canonical.
|
||||
import { tabbarSchema } from './tabbar.js'
|
||||
import { floatingSchema } from './floating.js'
|
||||
import { i18nSchema } from './i18n.js'
|
||||
@@ -21,7 +21,7 @@ export const globalSchemas = {
|
||||
common: commonSchema,
|
||||
layout: layoutSchema,
|
||||
search: searchSchema,
|
||||
email: emailSchema,
|
||||
email: null,
|
||||
tabbar: tabbarSchema,
|
||||
floating: floatingSchema,
|
||||
i18n: i18nSchema,
|
||||
|
||||
Reference in new issue
Block a user