905 lines
29 KiB
Vue
905 lines
29 KiB
Vue
|
|
<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>
|