Files
wwjcloud-nest-v1/admin/apps/web-ele/src/views/common/settings/notification/index.vue
万物街 dc6e9baec0 feat: 添加完整的前端管理系统 (VbenAdmin)
- 添加基于 VbenAdmin + Vue3 + Element Plus 的前端管理系统
- 包含完整的 UI 组件库和工具链
- 支持多应用架构 (web-ele, backend-mock, playground)
- 包含完整的开发规范和配置
- 修复 admin 目录的子模块问题,确保正确提交
2025-08-23 13:24:04 +08:00

905 lines
29 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<Page>
<el-card>
<template #header>
<div class="card-header">
<Icon icon="ep:bell" class="mr-2" />
<span>通知设置</span>
</div>
</template>
<el-tabs v-model="activeTab" type="border-card">
<!-- 邮件通知 -->
<el-tab-pane label="邮件通知" name="email">
<el-form
ref="emailFormRef"
:model="emailForm"
:rules="emailRules"
label-width="150px"
v-loading="loading"
>
<el-form-item label="启用邮件通知" prop="enabled">
<el-switch
v-model="emailForm.enabled"
active-text="启用"
inactive-text="禁用"
/>
<div class="form-item-tip">启用后系统将发送邮件通知</div>
</el-form-item>
<template v-if="emailForm.enabled">
<el-form-item label="通知类型" prop="types">
<el-checkbox-group v-model="emailForm.types">
<el-checkbox label="user_register">用户注册</el-checkbox>
<el-checkbox label="user_login">用户登录</el-checkbox>
<el-checkbox label="password_reset">密码重置</el-checkbox>
<el-checkbox label="order_created">订单创建</el-checkbox>
<el-checkbox label="order_paid">订单支付</el-checkbox>
<el-checkbox label="order_shipped">订单发货</el-checkbox>
<el-checkbox label="order_completed">订单完成</el-checkbox>
<el-checkbox label="system_error">系统错误</el-checkbox>
<el-checkbox label="security_alert">安全警报</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="管理员邮箱" prop="adminEmails">
<div class="email-list-container">
<div class="email-list">
<div v-for="(email, index) in emailForm.adminEmails" :key="index" class="email-item">
<el-input
v-model="emailForm.adminEmails[index]"
placeholder="请输入管理员邮箱"
clearable
/>
<el-button
type="danger"
text
@click="removeAdminEmail(index)"
class="ml-2"
>
<Icon icon="ep:delete" />
</el-button>
</div>
</div>
<el-button @click="addAdminEmail" type="primary" text class="mt-2">
<Icon icon="ep:plus" class="mr-1" />
添加邮箱
</el-button>
</div>
</el-form-item>
<el-form-item label="发送频率限制" prop="rateLimit">
<el-input-number
v-model="emailForm.rateLimit"
:min="1"
:max="100"
style="width: 200px"
/>
<span class="ml-2">/小时</span>
<div class="form-item-tip">限制每小时发送的邮件数量</div>
</el-form-item>
<el-form-item label="重试次数" prop="retryTimes">
<el-input-number
v-model="emailForm.retryTimes"
:min="0"
:max="5"
style="width: 200px"
/>
<span class="ml-2"></span>
</el-form-item>
<el-form-item label="队列延迟" prop="queueDelay">
<el-input-number
v-model="emailForm.queueDelay"
:min="0"
:max="3600"
style="width: 200px"
/>
<span class="ml-2"></span>
<div class="form-item-tip">邮件发送延迟时间</div>
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" @click="handleSaveEmail" :loading="saveLoading">
<Icon icon="ep:check" class="mr-1" />
保存设置
</el-button>
<el-button @click="handleResetEmail">
<Icon icon="ep:refresh" class="mr-1" />
重置
</el-button>
<el-button @click="handleTestEmail" :loading="testEmailLoading" v-if="emailForm.enabled">
<Icon icon="ep:message" class="mr-1" />
发送测试邮件
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 短信通知 -->
<el-tab-pane label="短信通知" name="sms">
<el-form
ref="smsFormRef"
:model="smsForm"
:rules="smsRules"
label-width="150px"
v-loading="loading"
>
<el-form-item label="启用短信通知" prop="enabled">
<el-switch
v-model="smsForm.enabled"
active-text="启用"
inactive-text="禁用"
/>
<div class="form-item-tip">启用后系统将发送短信通知</div>
</el-form-item>
<template v-if="smsForm.enabled">
<el-form-item label="通知类型" prop="types">
<el-checkbox-group v-model="smsForm.types">
<el-checkbox label="user_register">用户注册</el-checkbox>
<el-checkbox label="login_verify">登录验证</el-checkbox>
<el-checkbox label="password_reset">密码重置</el-checkbox>
<el-checkbox label="order_status">订单状态变更</el-checkbox>
<el-checkbox label="payment_notify">支付通知</el-checkbox>
<el-checkbox label="security_alert">安全警报</el-checkbox>
<el-checkbox label="marketing">营销推广</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="管理员手机" prop="adminPhones">
<div class="phone-list-container">
<div class="phone-list">
<div v-for="(phone, index) in smsForm.adminPhones" :key="index" class="phone-item">
<el-input
v-model="smsForm.adminPhones[index]"
placeholder="请输入管理员手机号"
clearable
/>
<el-button
type="danger"
text
@click="removeAdminPhone(index)"
class="ml-2"
>
<Icon icon="ep:delete" />
</el-button>
</div>
</div>
<el-button @click="addAdminPhone" type="primary" text class="mt-2">
<Icon icon="ep:plus" class="mr-1" />
添加手机号
</el-button>
</div>
</el-form-item>
<el-form-item label="发送时间限制">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="开始时间" prop="sendTimeStart" label-width="80px">
<el-time-picker
v-model="smsForm.sendTimeStart"
format="HH:mm"
value-format="HH:mm"
placeholder="选择开始时间"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束时间" prop="sendTimeEnd" label-width="80px">
<el-time-picker
v-model="smsForm.sendTimeEnd"
format="HH:mm"
value-format="HH:mm"
placeholder="选择结束时间"
/>
</el-form-item>
</el-col>
</el-row>
<div class="form-item-tip">限制短信发送的时间段避免打扰用户</div>
</el-form-item>
<el-form-item label="发送频率限制" prop="rateLimit">
<el-input-number
v-model="smsForm.rateLimit"
:min="1"
:max="1000"
style="width: 200px"
/>
<span class="ml-2">/小时</span>
</el-form-item>
<el-form-item label="同号码限制" prop="phoneLimit">
<el-input-number
v-model="smsForm.phoneLimit"
:min="1"
:max="10"
style="width: 200px"
/>
<span class="ml-2">/</span>
<div class="form-item-tip">限制同一手机号每天接收的短信数量</div>
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" @click="handleSaveSms" :loading="saveLoading">
<Icon icon="ep:check" class="mr-1" />
保存设置
</el-button>
<el-button @click="handleResetSms">
<Icon icon="ep:refresh" class="mr-1" />
重置
</el-button>
<el-button @click="handleTestSms" :loading="testSmsLoading" v-if="smsForm.enabled">
<Icon icon="ep:chat-dot-round" class="mr-1" />
发送测试短信
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 站内通知 -->
<el-tab-pane label="站内通知" name="system">
<el-form
ref="systemFormRef"
:model="systemForm"
:rules="systemRules"
label-width="150px"
v-loading="loading"
>
<el-form-item label="启用站内通知" prop="enabled">
<el-switch
v-model="systemForm.enabled"
active-text="启用"
inactive-text="禁用"
/>
<div class="form-item-tip">启用后系统将发送站内消息通知</div>
</el-form-item>
<template v-if="systemForm.enabled">
<el-form-item label="通知类型" prop="types">
<el-checkbox-group v-model="systemForm.types">
<el-checkbox label="user_register">用户注册</el-checkbox>
<el-checkbox label="order_created">订单创建</el-checkbox>
<el-checkbox label="order_paid">订单支付</el-checkbox>
<el-checkbox label="order_shipped">订单发货</el-checkbox>
<el-checkbox label="order_completed">订单完成</el-checkbox>
<el-checkbox label="order_refund">订单退款</el-checkbox>
<el-checkbox label="user_feedback">用户反馈</el-checkbox>
<el-checkbox label="system_maintenance">系统维护</el-checkbox>
<el-checkbox label="promotion">促销活动</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="消息保留期" prop="retentionDays">
<el-input-number
v-model="systemForm.retentionDays"
:min="7"
:max="365"
style="width: 200px"
/>
<span class="ml-2"></span>
<div class="form-item-tip">超过保留期的消息将被自动删除</div>
</el-form-item>
<el-form-item label="最大消息数" prop="maxMessages">
<el-input-number
v-model="systemForm.maxMessages"
:min="100"
:max="10000"
style="width: 200px"
/>
<span class="ml-2"></span>
<div class="form-item-tip">每个用户最多保留的消息数量</div>
</el-form-item>
<el-form-item label="自动标记已读" prop="autoMarkRead">
<el-switch
v-model="systemForm.autoMarkRead"
active-text="启用"
inactive-text="禁用"
/>
<div class="form-item-tip">用户查看消息后自动标记为已读</div>
</el-form-item>
<el-form-item label="推送到桌面" prop="desktopPush">
<el-switch
v-model="systemForm.desktopPush"
active-text="启用"
inactive-text="禁用"
/>
<div class="form-item-tip">支持浏览器桌面通知推送</div>
</el-form-item>
<el-form-item label="声音提醒" prop="soundAlert">
<el-switch
v-model="systemForm.soundAlert"
active-text="启用"
inactive-text="禁用"
/>
<div class="form-item-tip">新消息时播放提示音</div>
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" @click="handleSaveSystem" :loading="saveLoading">
<Icon icon="ep:check" class="mr-1" />
保存设置
</el-button>
<el-button @click="handleResetSystem">
<Icon icon="ep:refresh" class="mr-1" />
重置
</el-button>
<el-button @click="handleTestSystem" :loading="testSystemLoading" v-if="systemForm.enabled">
<Icon icon="ep:bell" class="mr-1" />
发送测试通知
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 微信通知 -->
<el-tab-pane label="微信通知" name="wechat">
<el-form
ref="wechatFormRef"
:model="wechatForm"
:rules="wechatRules"
label-width="150px"
v-loading="loading"
>
<el-form-item label="启用微信通知" prop="enabled">
<el-switch
v-model="wechatForm.enabled"
active-text="启用"
inactive-text="禁用"
/>
<div class="form-item-tip">启用后系统将发送微信模板消息</div>
</el-form-item>
<template v-if="wechatForm.enabled">
<el-form-item label="AppID" prop="appId">
<el-input
v-model="wechatForm.appId"
placeholder="请输入微信公众号AppID"
clearable
/>
</el-form-item>
<el-form-item label="AppSecret" prop="appSecret">
<el-input
v-model="wechatForm.appSecret"
type="password"
placeholder="请输入微信公众号AppSecret"
show-password
clearable
/>
</el-form-item>
<el-form-item label="通知类型" prop="types">
<el-checkbox-group v-model="wechatForm.types">
<el-checkbox label="order_created">订单创建</el-checkbox>
<el-checkbox label="order_paid">订单支付</el-checkbox>
<el-checkbox label="order_shipped">订单发货</el-checkbox>
<el-checkbox label="order_completed">订单完成</el-checkbox>
<el-checkbox label="order_refund">订单退款</el-checkbox>
<el-checkbox label="payment_success">支付成功</el-checkbox>
<el-checkbox label="account_change">账户变动</el-checkbox>
<el-checkbox label="service_notice">服务通知</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="模板消息配置">
<div class="template-config">
<div v-for="(template, key) in wechatForm.templates" :key="key" class="template-item">
<div class="template-label">{{ getTemplateLabel(key) }}</div>
<el-input
v-model="wechatForm.templates[key]"
placeholder="请输入模板ID"
clearable
/>
</div>
</div>
</el-form-item>
<el-form-item label="发送频率限制" prop="rateLimit">
<el-input-number
v-model="wechatForm.rateLimit"
:min="1"
:max="1000"
style="width: 200px"
/>
<span class="ml-2">/小时</span>
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" @click="handleSaveWechat" :loading="saveLoading">
<Icon icon="ep:check" class="mr-1" />
保存设置
</el-button>
<el-button @click="handleResetWechat">
<Icon icon="ep:refresh" class="mr-1" />
重置
</el-button>
<el-button @click="handleTestWechat" :loading="testWechatLoading" v-if="wechatForm.enabled">
<Icon icon="ep:chat-dot-round" class="mr-1" />
发送测试消息
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</Page>
</template>
<script lang="ts" setup>
// 1. Vue 相关导入
import { ref, reactive, onMounted } from 'vue';
import type { FormInstance } from 'element-plus';
// 2. Element Plus 组件导入
import {
ElButton,
ElCard,
ElCheckbox,
ElCheckboxGroup,
ElCol,
ElForm,
ElFormItem,
ElInput,
ElInputNumber,
ElMessage,
ElRow,
ElSwitch,
ElTabPane,
ElTabs,
ElTimePicker,
} from 'element-plus';
// 3. 图标组件导入
import { Icon } from '@iconify/vue';
// 4. Vben 组件导入
import { Page } from '@vben/common-ui';
// 5. 项目内部导入
import {
getNotificationSettingsApi,
updateNotificationSettingsApi,
resetNotificationSettingsApi,
testNotificationApi,
type NotificationSettings,
type UpdateNotificationSettingsParams,
} from '#/api/settings';
// 响应式数据
const loading = ref(false);
const saveLoading = ref(false);
const testEmailLoading = ref(false);
const testSmsLoading = ref(false);
const testSystemLoading = ref(false);
const testWechatLoading = ref(false);
const activeTab = ref('email');
const emailFormRef = ref<FormInstance>();
const smsFormRef = ref<FormInstance>();
const systemFormRef = ref<FormInstance>();
const wechatFormRef = ref<FormInstance>();
// 邮件通知表单
const emailForm = reactive({
enabled: true,
types: ['user_register', 'order_created', 'system_error'],
adminEmails: ['admin@example.com'],
rateLimit: 50,
retryTimes: 3,
queueDelay: 0,
});
// 短信通知表单
const smsForm = reactive({
enabled: false,
types: ['user_register', 'login_verify', 'password_reset'],
adminPhones: [''],
sendTimeStart: '08:00',
sendTimeEnd: '22:00',
rateLimit: 100,
phoneLimit: 5,
});
// 站内通知表单
const systemForm = reactive({
enabled: true,
types: ['user_register', 'order_created', 'order_paid'],
retentionDays: 30,
maxMessages: 1000,
autoMarkRead: true,
desktopPush: false,
soundAlert: true,
});
// 微信通知表单
const wechatForm = reactive({
enabled: false,
appId: '',
appSecret: '',
types: ['order_created', 'order_paid', 'payment_success'],
templates: {
order_created: '',
order_paid: '',
order_shipped: '',
order_completed: '',
order_refund: '',
payment_success: '',
account_change: '',
service_notice: '',
},
rateLimit: 100,
});
// 表单验证规则
const emailRules = {
adminEmails: [
{ required: true, message: '请输入管理员邮箱', trigger: 'blur' },
],
rateLimit: [
{ required: true, message: '请输入发送频率限制', trigger: 'blur' },
{ type: 'number', min: 1, max: 100, message: '发送频率范围为 1-100 封/小时', trigger: 'blur' },
],
};
const smsRules = {
adminPhones: [
{ required: true, message: '请输入管理员手机号', trigger: 'blur' },
],
rateLimit: [
{ required: true, message: '请输入发送频率限制', trigger: 'blur' },
{ type: 'number', min: 1, max: 1000, message: '发送频率范围为 1-1000 条/小时', trigger: 'blur' },
],
};
const systemRules = {
retentionDays: [
{ required: true, message: '请输入消息保留期', trigger: 'blur' },
{ type: 'number', min: 7, max: 365, message: '保留期范围为 7-365 天', trigger: 'blur' },
],
maxMessages: [
{ required: true, message: '请输入最大消息数', trigger: 'blur' },
{ type: 'number', min: 100, max: 10000, message: '消息数范围为 100-10000 条', trigger: 'blur' },
],
};
const wechatRules = {
appId: [
{ required: true, message: '请输入微信AppID', trigger: 'blur' },
],
appSecret: [
{ required: true, message: '请输入微信AppSecret', trigger: 'blur' },
],
};
// 方法
const addAdminEmail = () => {
emailForm.adminEmails.push('');
};
const removeAdminEmail = (index: number) => {
if (emailForm.adminEmails.length > 1) {
emailForm.adminEmails.splice(index, 1);
}
};
const addAdminPhone = () => {
smsForm.adminPhones.push('');
};
const removeAdminPhone = (index: number) => {
if (smsForm.adminPhones.length > 1) {
smsForm.adminPhones.splice(index, 1);
}
};
const getTemplateLabel = (key: string) => {
const labels: Record<string, string> = {
order_created: '订单创建',
order_paid: '订单支付',
order_shipped: '订单发货',
order_completed: '订单完成',
order_refund: '订单退款',
payment_success: '支付成功',
account_change: '账户变动',
service_notice: '服务通知',
};
return labels[key] || key;
};
const loadSettings = async () => {
loading.value = true;
try {
const settings = await getNotificationSettingsApi();
Object.assign(emailForm, settings.email || {});
Object.assign(smsForm, settings.sms || {});
Object.assign(systemForm, settings.system || {});
Object.assign(wechatForm, settings.wechat || {});
} catch (error) {
ElMessage.error('加载通知设置失败');
} finally {
loading.value = false;
}
};
const handleSaveEmail = async () => {
if (!emailFormRef.value) return;
try {
await emailFormRef.value.validate();
saveLoading.value = true;
// 过滤空的邮箱地址
const filteredEmails = emailForm.adminEmails.filter(email => email.trim());
const updateData: UpdateNotificationSettingsParams = {
type: 'email',
config: {
...emailForm,
adminEmails: filteredEmails,
},
};
await updateNotificationSettingsApi(updateData);
ElMessage.success('邮件通知设置保存成功');
} catch (error) {
ElMessage.error('保存失败');
} finally {
saveLoading.value = false;
}
};
const handleSaveSms = async () => {
if (!smsFormRef.value) return;
try {
await smsFormRef.value.validate();
saveLoading.value = true;
// 过滤空的手机号
const filteredPhones = smsForm.adminPhones.filter(phone => phone.trim());
const updateData: UpdateNotificationSettingsParams = {
type: 'sms',
config: {
...smsForm,
adminPhones: filteredPhones,
},
};
await updateNotificationSettingsApi(updateData);
ElMessage.success('短信通知设置保存成功');
} catch (error) {
ElMessage.error('保存失败');
} finally {
saveLoading.value = false;
}
};
const handleSaveSystem = async () => {
if (!systemFormRef.value) return;
try {
await systemFormRef.value.validate();
saveLoading.value = true;
const updateData: UpdateNotificationSettingsParams = {
type: 'system',
config: systemForm,
};
await updateNotificationSettingsApi(updateData);
ElMessage.success('站内通知设置保存成功');
} catch (error) {
ElMessage.error('保存失败');
} finally {
saveLoading.value = false;
}
};
const handleSaveWechat = async () => {
if (!wechatFormRef.value) return;
try {
await wechatFormRef.value.validate();
saveLoading.value = true;
const updateData: UpdateNotificationSettingsParams = {
type: 'wechat',
config: wechatForm,
};
await updateNotificationSettingsApi(updateData);
ElMessage.success('微信通知设置保存成功');
} catch (error) {
ElMessage.error('保存失败');
} finally {
saveLoading.value = false;
}
};
const handleResetEmail = async () => {
try {
await resetNotificationSettingsApi('email');
await loadSettings();
ElMessage.success('邮件通知设置已重置');
} catch (error) {
ElMessage.error('重置失败');
}
};
const handleResetSms = async () => {
try {
await resetNotificationSettingsApi('sms');
await loadSettings();
ElMessage.success('短信通知设置已重置');
} catch (error) {
ElMessage.error('重置失败');
}
};
const handleResetSystem = async () => {
try {
await resetNotificationSettingsApi('system');
await loadSettings();
ElMessage.success('站内通知设置已重置');
} catch (error) {
ElMessage.error('重置失败');
}
};
const handleResetWechat = async () => {
try {
await resetNotificationSettingsApi('wechat');
await loadSettings();
ElMessage.success('微信通知设置已重置');
} catch (error) {
ElMessage.error('重置失败');
}
};
const handleTestEmail = async () => {
testEmailLoading.value = true;
try {
await testNotificationApi('email');
ElMessage.success('测试邮件发送成功');
} catch (error) {
ElMessage.error('发送测试邮件失败');
} finally {
testEmailLoading.value = false;
}
};
const handleTestSms = async () => {
testSmsLoading.value = true;
try {
await testNotificationApi('sms');
ElMessage.success('测试短信发送成功');
} catch (error) {
ElMessage.error('发送测试短信失败');
} finally {
testSmsLoading.value = false;
}
};
const handleTestSystem = async () => {
testSystemLoading.value = true;
try {
await testNotificationApi('system');
ElMessage.success('测试通知发送成功');
} catch (error) {
ElMessage.error('发送测试通知失败');
} finally {
testSystemLoading.value = false;
}
};
const handleTestWechat = async () => {
testWechatLoading.value = true;
try {
await testNotificationApi('wechat');
ElMessage.success('测试微信消息发送成功');
} catch (error) {
ElMessage.error('发送测试微信消息失败');
} finally {
testWechatLoading.value = false;
}
};
// 生命周期
onMounted(() => {
loadSettings();
});
</script>
<style scoped>
.notification-settings-page {
padding: 20px;
}
.settings-container {
max-width: 1200px;
margin: 0 auto;
}
.settings-card {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
}
:deep(.el-tabs__content) {
padding: 20px;
}
:deep(.el-form-item__label) {
font-weight: 500;
}
.form-item-tip {
color: #999;
font-size: 12px;
margin-top: 4px;
line-height: 1.4;
}
.email-list-container,
.phone-list-container {
width: 100%;
}
.email-list,
.phone-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.email-item,
.phone-item {
display: flex;
align-items: center;
}
.template-config {
display: flex;
flex-direction: column;
gap: 12px;
}
.template-item {
display: flex;
align-items: center;
gap: 12px;
}
.template-label {
width: 80px;
font-size: 14px;
color: #666;
flex-shrink: 0;
}
:deep(.el-checkbox-group) {
display: flex;
flex-direction: column;
gap: 8px;
}
:deep(.el-checkbox-group .el-checkbox) {
margin-right: 0;
}
</style>