feat: 添加完整的前端管理系统 (VbenAdmin)

- 添加基于 VbenAdmin + Vue3 + Element Plus 的前端管理系统
- 包含完整的 UI 组件库和工具链
- 支持多应用架构 (web-ele, backend-mock, playground)
- 包含完整的开发规范和配置
- 修复 admin 目录的子模块问题,确保正确提交
This commit is contained in:
万物街
2025-08-23 13:24:04 +08:00
parent 43626e5bf2
commit dc6e9baec0
1406 changed files with 133197 additions and 1 deletions

View File

@@ -0,0 +1,905 @@
<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>