增加通知模块
This commit is contained in:
@@ -0,0 +1,765 @@
|
||||
<template>
|
||||
<div class="notification-settings">
|
||||
<div class="settings-content">
|
||||
<el-tabs v-model="activeSubTab" tab-position="left" class="sub-tabs-container">
|
||||
|
||||
<!-- 邮箱 -->
|
||||
<el-tab-pane name="email">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><Message /></el-icon>
|
||||
<span>邮箱配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>邮箱通知配置</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.email" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.email.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发件人邮箱" required>
|
||||
<el-input v-model="formData.email.fromAddress" placeholder="例如:noreply@example.com" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="发件人名称">
|
||||
<el-input v-model="formData.email.fromName" placeholder="例如:官方网站" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="SMTP 服务器" required>
|
||||
<el-input v-model="formData.email.host" placeholder="例如:smtp.qq.com" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="SMTP 端口" required>
|
||||
<el-input v-model="formData.email.port" placeholder="例如:465 或 587" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="授权码/密码" required>
|
||||
<el-input v-model="formData.email.password" type="password" show-password placeholder="邮箱授权码或登录密码" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="加密方式">
|
||||
<el-radio-group v-model="formData.email.encryption">
|
||||
<el-radio label="ssl">SSL</el-radio>
|
||||
<el-radio label="tls">TLS</el-radio>
|
||||
<el-radio label="none">无加密</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="超时时间(秒)">
|
||||
<el-input-number v-model="formData.email.timeout" :min="1" :max="300" controls-position="right" />
|
||||
</el-form-item>
|
||||
<el-form-item label="测试收件邮箱">
|
||||
<el-input v-model="formData.email.testEmail" placeholder="输入邮箱地址用于测试发信" clearable />
|
||||
<div style="margin-top: 8px;">
|
||||
<el-button type="success" plain size="small" :loading="emailTestLoading" @click="handleSendEmailTest">
|
||||
发送测试邮件
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 短信 -->
|
||||
<el-tab-pane name="sms">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><Cellphone /></el-icon>
|
||||
<span>短信配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>短信配置</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.sms" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.sms.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="服务商">
|
||||
<el-radio-group v-model="formData.sms.provider">
|
||||
<el-radio label="aliyun">阿里云短信</el-radio>
|
||||
<el-radio label="tencent">腾讯云短信</el-radio>
|
||||
<el-radio label="custom">自定义</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 阿里云/腾讯云服务商配置 -->
|
||||
<template v-if="formData.sms.provider !== 'custom'">
|
||||
<el-form-item label="AccessKey ID">
|
||||
<el-input v-model="formData.sms.access_key" placeholder="请输入 AccessKey ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="AccessKey Secret">
|
||||
<el-input v-model="formData.sms.secret_key" type="password" show-password placeholder="请输入 AccessKey Secret" />
|
||||
</el-form-item>
|
||||
<el-form-item label="短信签名">
|
||||
<el-input v-model="formData.sms.sign_name" placeholder="例如:云泽科技" />
|
||||
</el-form-item>
|
||||
<el-form-item label="短信模板 Code">
|
||||
<el-input v-model="formData.sms.template_code" placeholder="例如:SMS_200300400" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 自定义网关配置 -->
|
||||
<template v-else>
|
||||
<el-divider content-position="left">自定义短信网关</el-divider>
|
||||
<el-form-item label="短信网关地址" required>
|
||||
<el-input
|
||||
v-model="formData.sms.backendUrl"
|
||||
placeholder="例如:https://yzsms.yunzer.cn"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="短信API KEY" required>
|
||||
<el-input
|
||||
v-model="formData.sms.apiKey"
|
||||
placeholder="请输入 API_KEY"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="测试手机号">
|
||||
<el-input
|
||||
v-model="formData.sms.testPhone"
|
||||
placeholder="国际格式号码,例如 +8613712345678"
|
||||
clearable
|
||||
/>
|
||||
<div style="margin-top: 8px;">
|
||||
<el-button type="success" plain size="small" :loading="testLoading" @click="handleSendTest">
|
||||
发送测试短信
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
|
||||
<!-- 站内信配置 -->
|
||||
<el-tab-pane name="sitemsg">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><Bell /></el-icon>
|
||||
<span>站内信配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>站内信通知配置</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.sitemsg" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.sitemsg.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="保留天数">
|
||||
<el-input-number v-model="formData.sitemsg.retention_days" :min="1" :max="365" controls-position="right" />
|
||||
<span class="form-tip-inline">天(过期消息将自动清理)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="自动标记已读">
|
||||
<el-switch v-model="formData.sitemsg.auto_read" />
|
||||
<div class="form-tip">用户打开消息中心列表时是否自动将所有消息标记为已读</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 钉钉配置 -->
|
||||
<el-tab-pane name="dingtalk">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><ChatDotSquare /></el-icon>
|
||||
<span>钉钉配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>钉钉群机器人配置</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.dingtalk" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.dingtalk.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Webhook 地址">
|
||||
<el-input v-model="formData.dingtalk.webhook_url" type="textarea" :rows="3" placeholder="请输入钉钉机器人的 Webhook 地址 (https://oapi.dingtalk.com/robot/send?access_token=...)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="加签密钥">
|
||||
<el-input v-model="formData.dingtalk.secret" type="password" show-password placeholder="请输入安全配置中加签的密钥(可选)" />
|
||||
<div class="form-tip">如果机器人的安全配置开启了“加签”,请在此处填写对应的秘钥</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Webhook配置 -->
|
||||
<el-tab-pane name="WebHook配置">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><Connection /></el-icon>
|
||||
<span>WebHook配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>HTTP Webhook 配置</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.webhook" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.webhook.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="推送 URL">
|
||||
<el-input v-model="formData.webhook.url" type="textarea" :rows="2" placeholder="请输入通知推送的 Webhook 目标 URL" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求方式">
|
||||
<el-select v-model="formData.webhook.method" style="width: 120px">
|
||||
<el-option label="POST" value="POST" />
|
||||
<el-option label="GET" value="GET" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="签名密钥 (Token)">
|
||||
<el-input v-model="formData.webhook.token" type="password" show-password placeholder="请输入用于验证的 Token 密钥" />
|
||||
<div class="form-tip">该 Token 将被包含在请求 Header 的 X-Webhook-Token 中以供接收方校验</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 飞书配置 -->
|
||||
<el-tab-pane name="feishu">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><Coordinate /></el-icon>
|
||||
<span>飞书配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>飞书机器人配置</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.feishu" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.feishu.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Webhook 地址">
|
||||
<el-input v-model="formData.feishu.webhook_url" type="textarea" :rows="3" placeholder="请输入飞书机器人的 Webhook 地址 (https://open.feishu.cn/open-apis/bot/v2/hook/...)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="加签密钥">
|
||||
<el-input v-model="formData.feishu.secret" type="password" show-password placeholder="请输入安全配置中加签的密钥(可选)" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Bark配置 -->
|
||||
<el-tab-pane name="Bark配置">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><ChatRound /></el-icon>
|
||||
<span>Bark配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>Bark 推送配置</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.bark" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.bark.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="推送服务器">
|
||||
<el-input v-model="formData.bark.server_url" placeholder="默认官方服务:https://api.day.app" />
|
||||
<div class="form-tip">支持自建 Bark 服务,如:https://bark.yourdomain.com</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备 Key">
|
||||
<el-input v-model="formData.bark.device_key" placeholder="请输入您的 Bark Device Key" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 微信配置 -->
|
||||
<el-tab-pane name="wechat">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><ChatDotRound /></el-icon>
|
||||
<span>微信配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>微信公众号/企业微信推送</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.wechat" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.wechat.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="AppID / CorpID">
|
||||
<el-input v-model="formData.wechat.app_id" placeholder="请输入公众号 AppID 或企业微信 CorpID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="AppSecret / Secret">
|
||||
<el-input v-model="formData.wechat.app_secret" type="password" show-password placeholder="请输入 AppSecret" />
|
||||
</el-form-item>
|
||||
<el-form-item label="模版ID / AgentID">
|
||||
<el-input v-model="formData.wechat.template_id" placeholder="请输入模版消息 ID 或企业微信应用 AgentID" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Telegram配置 -->
|
||||
<el-tab-pane name="Telegram配置">
|
||||
<template #label>
|
||||
<span class="tab-label-item">
|
||||
<el-icon><Promotion /></el-icon>
|
||||
<span>Telegram配置</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-pane-content">
|
||||
<el-card shadow="never" class="settings-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>Telegram Bot 推送</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="formData.telegram" label-width="140px" label-position="right">
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="formData.telegram.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Bot Token">
|
||||
<el-input v-model="formData.telegram.bot_token" type="password" show-password placeholder="例如:123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Chat ID">
|
||||
<el-input v-model="formData.telegram.chat_id" placeholder="例如:123456789 或 @my_channel" />
|
||||
<div class="form-tip">支持个人 Chat ID、群组 ID (通常为负数) 或公开频道 Username</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<div class="footer-actions">
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">
|
||||
保存配置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import {
|
||||
Message,
|
||||
Cellphone,
|
||||
Bell,
|
||||
ChatDotSquare,
|
||||
Connection,
|
||||
Coordinate,
|
||||
ChatRound,
|
||||
ChatDotRound,
|
||||
Promotion
|
||||
} from "@element-plus/icons-vue";
|
||||
import { getSmsInfo, editSmsInfo, sendTestSms } from "@/api/sms";
|
||||
import { getEmailInfo, editEmailInfo, sendTestEmail } from "@/api/email";
|
||||
|
||||
const STORAGE_KEY = "notification_settings_draft";
|
||||
const activeSubTab = ref("email");
|
||||
const submitting = ref(false);
|
||||
|
||||
const formData = reactive({
|
||||
email: {
|
||||
enabled: false,
|
||||
fromAddress: "",
|
||||
fromName: "",
|
||||
host: "",
|
||||
port: "",
|
||||
password: "",
|
||||
encryption: "ssl",
|
||||
timeout: 30,
|
||||
testEmail: "",
|
||||
},
|
||||
sms: {
|
||||
enabled: false,
|
||||
provider: "aliyun",
|
||||
access_key: "",
|
||||
secret_key: "",
|
||||
sign_name: "",
|
||||
template_code: "",
|
||||
backendUrl: "",
|
||||
apiKey: "",
|
||||
testPhone: "",
|
||||
},
|
||||
sitemsg: {
|
||||
enabled: false,
|
||||
retention_days: 30,
|
||||
auto_read: false,
|
||||
},
|
||||
dingtalk: {
|
||||
enabled: false,
|
||||
webhook_url: "",
|
||||
secret: "",
|
||||
},
|
||||
webhook: {
|
||||
enabled: false,
|
||||
url: "",
|
||||
method: "POST",
|
||||
token: "",
|
||||
},
|
||||
feishu: {
|
||||
enabled: false,
|
||||
webhook_url: "",
|
||||
secret: "",
|
||||
},
|
||||
bark: {
|
||||
enabled: false,
|
||||
server_url: "https://api.day.app",
|
||||
device_key: "",
|
||||
},
|
||||
wechat: {
|
||||
enabled: false,
|
||||
app_id: "",
|
||||
app_secret: "",
|
||||
template_id: "",
|
||||
},
|
||||
telegram: {
|
||||
enabled: false,
|
||||
bot_token: "",
|
||||
chat_id: "",
|
||||
}
|
||||
});
|
||||
|
||||
const testLoading = ref(false);
|
||||
|
||||
const emailTestLoading = ref(false);
|
||||
|
||||
const loadDraft = () => {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return;
|
||||
try {
|
||||
const data = JSON.parse(raw);
|
||||
for (const key in data) {
|
||||
if (formData[key] && typeof data[key] === 'object') {
|
||||
Object.assign(formData[key], data[key]);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("加载本地通知配置失败", e);
|
||||
}
|
||||
};
|
||||
|
||||
const loadEmailConfig = async () => {
|
||||
try {
|
||||
const res = await getEmailInfo();
|
||||
if (res.code === 200 && Array.isArray(res.data) && res.data.length > 0) {
|
||||
const item = res.data[0];
|
||||
formData.email.fromAddress = item.from_address || "";
|
||||
formData.email.fromName = item.from_name || "";
|
||||
formData.email.host = item.host || "";
|
||||
formData.email.port = item.port != null ? String(item.port) : "";
|
||||
formData.email.password = item.password || "";
|
||||
formData.email.encryption = item.encryption || "ssl";
|
||||
formData.email.timeout = item.timeout != null ? item.timeout : 30;
|
||||
formData.email.enabled = Number(item.enabled ?? 0) === 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载邮箱配置失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadSmsConfig = async () => {
|
||||
try {
|
||||
const res = await getSmsInfo();
|
||||
if (res.code === 200 && Array.isArray(res.data) && res.data.length > 0) {
|
||||
const item = res.data[0];
|
||||
formData.sms.backendUrl = item.backend_url || item.backendUrl || "";
|
||||
formData.sms.apiKey = item.api_key || item.apiKey || "";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载自定义短信配置失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const saveDraft = () => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(formData));
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
const defaults = {
|
||||
email: { enabled: false, fromAddress: "", fromName: "", host: "", port: "", password: "", encryption: "ssl", timeout: 30, testEmail: "" },
|
||||
sms: { enabled: false, provider: "aliyun", access_key: "", secret_key: "", sign_name: "", template_code: "", backendUrl: "", apiKey: "", testPhone: "" },
|
||||
sitemsg: { enabled: false, retention_days: 30, auto_read: false },
|
||||
dingtalk: { enabled: false, webhook_url: "", secret: "" },
|
||||
webhook: { enabled: false, url: "", method: "POST", token: "" },
|
||||
feishu: { enabled: false, webhook_url: "", secret: "" },
|
||||
bark: { enabled: false, server_url: "https://api.day.app", device_key: "" },
|
||||
wechat: { enabled: false, app_id: "", app_secret: "", template_id: "" },
|
||||
telegram: { enabled: false, bot_token: "", chat_id: "" },
|
||||
};
|
||||
|
||||
const currentTab = activeSubTab.value;
|
||||
Object.assign(formData[currentTab], defaults[currentTab]);
|
||||
saveDraft();
|
||||
ElMessage.success("已重置当前配置");
|
||||
};
|
||||
|
||||
const handleSendEmailTest = async () => {
|
||||
if (!formData.email.testEmail) {
|
||||
ElMessage.warning("请先输入测试收件邮箱");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
emailTestLoading.value = true;
|
||||
const payload = {
|
||||
fromAddress: formData.email.fromAddress,
|
||||
fromName: formData.email.fromName,
|
||||
host: formData.email.host,
|
||||
port: formData.email.port,
|
||||
password: formData.email.password,
|
||||
encryption: formData.email.encryption,
|
||||
timeout: formData.email.timeout,
|
||||
testEmail: formData.email.testEmail
|
||||
};
|
||||
const res = await sendTestEmail(payload);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("测试邮件发送成功");
|
||||
} else {
|
||||
ElMessage.error(res.msg || "测试邮件发送失败,请稍后重试");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || "测试邮件发送失败,请稍后重试");
|
||||
} finally {
|
||||
emailTestLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendTest = async () => {
|
||||
const phone = (formData.sms.testPhone || "").trim();
|
||||
if (!phone) {
|
||||
ElMessage.warning("请先输入测试收件手机号");
|
||||
return;
|
||||
}
|
||||
if (!/^\+\d{6,15}$/.test(phone)) {
|
||||
ElMessage.warning("请使用国际格式号码(以 + 开头,后面为数字)");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
testLoading.value = true;
|
||||
const res = await sendTestSms({
|
||||
backendUrl: formData.sms.backendUrl,
|
||||
apiKey: formData.sms.apiKey,
|
||||
phone
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg || "短信测试任务入队成功");
|
||||
} else {
|
||||
ElMessage.error(res.msg || "短信测试失败,请稍后重试");
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.message || "短信测试失败,请稍后重试");
|
||||
} finally {
|
||||
testLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
submitting.value = true;
|
||||
try {
|
||||
if (activeSubTab.value === 'email') {
|
||||
if (!formData.email.fromAddress || !formData.email.host || !formData.email.port) {
|
||||
ElMessage.error("发件人邮箱、SMTP 服务器和 SMTP 端口不能为空");
|
||||
submitting.value = false;
|
||||
return;
|
||||
}
|
||||
const res = await editEmailInfo({
|
||||
fromAddress: formData.email.fromAddress,
|
||||
fromName: formData.email.fromName,
|
||||
host: formData.email.host,
|
||||
port: formData.email.port,
|
||||
password: formData.email.password,
|
||||
encryption: formData.email.encryption,
|
||||
timeout: formData.email.timeout,
|
||||
enabled: formData.email.enabled ? 1 : 0
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("邮箱配置保存成功");
|
||||
} else {
|
||||
throw new Error(res.msg || "保存邮箱配置失败");
|
||||
}
|
||||
} else if (activeSubTab.value === 'sms' && formData.sms.provider === 'custom') {
|
||||
if (!formData.sms.backendUrl || !formData.sms.apiKey) {
|
||||
ElMessage.error("自定义短信网关地址和API KEY不能为空");
|
||||
submitting.value = false;
|
||||
return;
|
||||
}
|
||||
const res = await editSmsInfo({
|
||||
backendUrl: formData.sms.backendUrl,
|
||||
apiKey: formData.sms.apiKey
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success("自定义短信网关配置保存成功");
|
||||
} else {
|
||||
throw new Error(res.msg || "保存自定义短信配置失败");
|
||||
}
|
||||
}
|
||||
|
||||
saveDraft();
|
||||
ElMessage.success("保存配置成功");
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.message || "保存配置失败");
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadDraft();
|
||||
loadSmsConfig();
|
||||
loadEmailConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.notification-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sub-tabs-container {
|
||||
/* height: 520px; */
|
||||
}
|
||||
|
||||
/* Make tabs look extra sleek */
|
||||
:deep(.el-tabs--left .el-tabs__header.is-left) {
|
||||
margin-right: 0;
|
||||
border-right: 1px solid var(--el-border-color-light);
|
||||
background-color: var(--el-fill-color-blank);
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs--left .el-tabs__nav-wrap.is-left) {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
font-weight: 500;
|
||||
padding: 0 20px !important;
|
||||
color: var(--el-text-color-regular);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item:hover) {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item.is-active) {
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__active-bar) {
|
||||
width: 3px !important;
|
||||
}
|
||||
|
||||
.tab-label-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tab-label-item .el-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tab-pane-content {
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
border: none;
|
||||
}
|
||||
|
||||
:deep(.el-card__header) {
|
||||
padding: 0 0 16px 0;
|
||||
border-bottom: 1px dashed var(--el-border-color-light);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: var(--el-text-color-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.form-tip-inline {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -23,6 +23,14 @@
|
||||
v-if="activeTab === 'storage'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 通知配置 -->
|
||||
<el-tab-pane label="通知配置" name="notification">
|
||||
<notificationSettings
|
||||
ref="notificationSettingsRef"
|
||||
v-if="activeTab === 'notification'"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,6 +40,7 @@
|
||||
import { ref } from "vue";
|
||||
import platformSettings from "./components/platformSettings.vue";
|
||||
import storageSettings from "./components/storageSettings.vue";
|
||||
import notificationSettings from "./components/notificationSettings.vue";
|
||||
|
||||
const activeTab = ref("platform");
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user