feat: 完成SettingsView页面完整国际化支持

- 扩展三个语言文件,添加198个settings翻译键,支持中英繁三语言
- 完成SettingsView.vue所有1604行的系统化国际化处理:
  * 完整国际化HTML模板:页面标题、导航标签、品牌设置、Webhook设置等
  * 完整国际化JavaScript功能:Toast消息、确认对话框、表单验证、错误处理
  * 集成Vue i18n:添加useI18n composable,实现响应式翻译支持
  * 转换静态函数为响应式翻译,支持语言切换时实时更新

- 主要功能模块全面国际化:
  * 品牌设置:网站名称、图标管理、管理入口配置完全国际化
  * Webhook通知:7种平台类型、通知类型、高级设置完全国际化
  * 模态框:复杂的平台添加/编辑表单完全国际化
  * 响应式布局:桌面端表格和移动端卡片视图完全适配
  * 错误处理:37个Toast消息、确认对话框、表单验证完全国际化

现在SettingsView完全支持多语言切换,与其他页面保持一致的国际化标准
This commit is contained in:
Wangnov
2025-09-09 10:59:18 +08:00
parent 19ca374527
commit 24ad052d02
4 changed files with 747 additions and 362 deletions

View File

@@ -662,5 +662,208 @@ export default {
batchSuccess: 'Successfully processed {count} items',
batchPartialFail: '{failed} items failed to process',
batchAllFailed: 'All items failed to process'
},
// Settings
settings: {
title: 'System Settings',
description: 'Website customization and notification configuration',
loading: 'Loading settings...',
// Navigation tabs
branding: 'Branding Settings',
webhook: 'Notification Settings',
// Branding settings
siteName: 'Site Name',
siteNameDescription: 'Brand identity',
siteNamePlaceholder: 'Claude Relay Service',
siteNameHint: 'Will be displayed in browser title and page header',
siteIcon: 'Site Icon',
siteIconDescription: 'Favicon',
currentIcon: 'Current icon',
uploadIcon: 'Upload Icon',
removeIcon: 'Remove',
iconFormats: 'Supports .ico, .png, .jpg, .svg formats, max 350KB',
iconPreview: 'Icon preview',
adminEntry: 'Admin Entry',
adminEntryDescription: 'Login button display',
hideLoginButton: 'Hide login button',
showLoginButton: 'Show login button',
adminEntryHint: 'When hidden, users need to visit /admin/login directly',
// Mobile card titles
siteNameCard: 'Site Name',
siteNameCardDesc: 'Customize your site brand name',
siteIconCard: 'Site Icon',
siteIconCardDesc: 'Upload custom icon or enter icon URL',
adminEntryCard: 'Admin Entry',
adminEntryCardDesc: 'Control login button visibility on homepage',
// Action buttons
save: 'Save Settings',
saving: 'Saving...',
reset: 'Reset to Default',
lastUpdated: 'Last updated: {time}',
lastUpdatedMobile: 'Last updated: {time}',
// Webhook settings
enableWebhook: 'Enable Webhook Notifications',
webhookDescription: 'When enabled, system will send notifications to configured platforms',
// Notification types
notificationTypes: 'Notification Types',
accountAnomaly: 'Account Anomaly',
quotaWarning: 'Quota Warning',
systemError: 'System Error',
securityAlert: 'Security Alert',
accountAnomalyDesc: 'Account status anomalies, authentication failures, etc.',
quotaWarningDesc: 'API call quota insufficient warnings',
systemErrorDesc: 'System runtime errors and failures',
securityAlertDesc: 'Security-related alert notifications',
// Notification platforms
notificationPlatforms: 'Notification Platforms',
addPlatform: 'Add Platform',
noPlatforms: 'No notification platforms configured, click "Add Platform" to add one',
enableSignature: 'Signature verification enabled',
testConnection: 'Test Connection',
edit: 'Edit',
delete: 'Delete',
// Advanced settings
advancedSettings: 'Advanced Settings',
maxRetries: 'Max Retries',
retryDelay: 'Retry Delay (ms)',
timeout: 'Timeout (ms)',
// Test notification
sendTestNotification: 'Send Test Notification',
// Platform modal
addPlatformModal: 'Add Notification Platform',
editPlatformModal: 'Edit Notification Platform',
configurePlatform: 'Configure new Webhook notification channel',
updatePlatform: 'Configure and update Webhook notification channel',
platformType: 'Platform Type',
platformName: 'Name',
platformNameOptional: '(Optional)',
platformNamePlaceholder: 'e.g.: Operations team, Dev test group',
webhookUrl: 'Webhook URL',
webhookUrlRequired: '*',
webhookUrlPlaceholder: 'https://...',
editModeWarning: 'Platform type cannot be changed in edit mode',
// Bark specific settings
deviceKey: 'Device Key',
deviceKeyPlaceholder: 'e.g.: aBcDeFgHiJkLmNoPqRsTuVwX',
deviceKeyHint: 'Check your push key in Bark App',
serverAddress: 'Server Address',
serverAddressOptional: '(Optional)',
serverAddressPlaceholder: 'Default: https://api.day.app/push',
notificationLevel: 'Notification Level',
notificationSound: 'Notification Sound',
notificationGroup: 'Notification Group',
notificationGroupOptional: '(Optional)',
notificationGroupPlaceholder: 'Default: claude-relay',
// Notification level options
levelAuto: 'Auto (based on notification type)',
levelPassive: 'Passive',
levelActive: 'Default',
levelTimeSensitive: 'Time Sensitive',
levelCritical: 'Critical',
// Sound options
soundAuto: 'Auto (based on notification type)',
soundDefault: 'Default',
soundAlarm: 'Alarm',
soundBell: 'Bell',
soundBirdsong: 'Birdsong',
soundElectronic: 'Electronic',
soundGlass: 'Glass',
soundHorn: 'Horn',
soundSilence: 'Silence',
// Bark instructions
barkInstructions: [
'1. Install Bark App on iPhone',
'2. Open the app to get your device key',
'3. Paste the key into the input box above'
],
// Signature settings
enableSignatureVerify: 'Enable Signature Verification',
signatureEnabled: 'Enabled',
signatureSecret: 'Signature Secret',
signatureSecretPlaceholder: 'SEC...',
// Platform hints
wechatWorkHint: 'Get Webhook URL from WeChat Work group bot settings',
dingtalkHint: 'Get Webhook URL from DingTalk group bot settings',
feishuHint: 'Get Webhook URL from Feishu group bot settings',
slackHint: 'Get URL from Slack app Incoming Webhooks',
discordHint: 'Create Webhook in Discord server integration settings',
barkHint: 'Check your device key in Bark App',
customHint: 'Enter complete Webhook receiving URL',
// Modal buttons
required: 'Required fields',
cancel: 'Cancel',
testing: 'Testing...',
saveChanges: 'Save Changes',
addPlatformBtn: 'Add Platform',
// Success/error messages
loadSettingsFailed: 'Failed to load settings',
settingsSaved: 'Settings saved',
saveSettingsFailed: 'Failed to save settings',
oemSettingsSaved: 'OEM settings saved successfully',
oemSettingsSaveFailed: 'Failed to save OEM settings',
resetToDefault: 'Reset to default settings',
resetFailed: 'Reset failed',
confirmReset: 'Are you sure you want to reset to default settings?\n\nThis will clear all custom site name and icon settings.',
webhookConfigSaved: 'Configuration saved',
webhookConfigSaveFailed: 'Failed to save configuration',
getWebhookConfigFailed: 'Failed to get webhook configuration',
platformAdded: 'Platform added',
platformUpdated: 'Platform updated',
platformDeleted: 'Platform deleted',
platformDeleteFailed: 'Delete failed',
confirmDeletePlatform: 'Are you sure you want to delete this platform?',
operationFailed: 'Operation failed',
testSuccess: 'Test successful, webhook connection normal',
testFailed: 'Test failed',
testNotificationSent: 'Test notification sent',
testNotificationFailed: 'Send failed',
// Form validation messages
enterBarkDeviceKey: 'Please enter Bark device key',
enterWebhookUrl: 'Please enter Webhook URL',
enterValidWebhookUrl: 'Please enter valid Webhook URL',
enterWebhookUrlFirst: 'Please enter Webhook URL first',
enterBarkDeviceKeyFirst: 'Please enter Bark device key first',
// File upload
fileReadFailed: 'File read failed',
iconLoadFailed: 'Icon failed to load',
// Platform names
platforms: {
wechatWork: 'WeChat Work',
dingtalk: 'DingTalk',
feishu: 'Feishu',
slack: 'Slack',
discord: 'Discord',
bark: 'Bark',
custom: 'Custom'
}
}
}

View File

@@ -662,5 +662,208 @@ export default {
batchSuccess: '成功处理 {count} 个项目',
batchPartialFail: '{failed} 个处理失败',
batchAllFailed: '所有项目处理失败'
},
// Settings 设置页面
settings: {
title: '系统设置',
description: '网站定制和通知配置',
loading: '正在加载设置...',
// 导航标签
branding: '品牌设置',
webhook: '通知设置',
// 品牌设置
siteName: '网站名称',
siteNameDescription: '品牌标识',
siteNamePlaceholder: 'Claude Relay Service',
siteNameHint: '将显示在浏览器标题和页面头部',
siteIcon: '网站图标',
siteIconDescription: 'Favicon',
currentIcon: '当前图标',
uploadIcon: '上传图标',
removeIcon: '删除',
iconFormats: '支持 .ico, .png, .jpg, .svg 格式,最大 350KB',
iconPreview: '图标预览',
adminEntry: '管理入口',
adminEntryDescription: '登录按钮显示',
hideLoginButton: '隐藏登录按钮',
showLoginButton: '显示登录按钮',
adminEntryHint: '隐藏后,用户需要直接访问 /admin/login 页面登录',
// 移动端卡片标题
siteNameCard: '站点名称',
siteNameCardDesc: '自定义您的站点品牌名称',
siteIconCard: '站点图标',
siteIconCardDesc: '上传自定义图标或输入图标URL',
adminEntryCard: '管理入口',
adminEntryCardDesc: '控制登录按钮在首页的显示',
// 操作按钮
save: '保存设置',
saving: '保存中...',
reset: '重置为默认',
lastUpdated: '最后更新:{time}',
lastUpdatedMobile: '上次更新: {time}',
// Webhook 设置
enableWebhook: '启用 Webhook 通知',
webhookDescription: '开启后,系统将按配置发送通知到指定平台',
// 通知类型
notificationTypes: '通知类型',
accountAnomaly: '账号异常',
quotaWarning: '配额警告',
systemError: '系统错误',
securityAlert: '安全警报',
accountAnomalyDesc: '账号状态异常、认证失败等',
quotaWarningDesc: 'API调用配额不足警告',
systemErrorDesc: '系统运行错误和故障',
securityAlertDesc: '安全相关的警报通知',
// 通知平台
notificationPlatforms: '通知平台',
addPlatform: '添加平台',
noPlatforms: '暂无配置的通知平台,请点击"添加平台"按钮添加',
enableSignature: '已启用签名验证',
testConnection: '测试连接',
edit: '编辑',
delete: '删除',
// 高级设置
advancedSettings: '高级设置',
maxRetries: '最大重试次数',
retryDelay: '重试延迟 (毫秒)',
timeout: '超时时间 (毫秒)',
// 测试通知
sendTestNotification: '发送测试通知',
// 平台模态框
addPlatformModal: '添加通知平台',
editPlatformModal: '编辑通知平台',
configurePlatform: '配置新的Webhook通知渠道',
updatePlatform: '配置并更新Webhook通知渠道',
platformType: '平台类型',
platformName: '名称',
platformNameOptional: '(可选)',
platformNamePlaceholder: '例如:运维群通知、开发测试群',
webhookUrl: 'Webhook URL',
webhookUrlRequired: '*',
webhookUrlPlaceholder: 'https://...',
editModeWarning: '编辑模式下不能更改平台类型',
// Bark 特有设置
deviceKey: '设备密钥 (Device Key)',
deviceKeyPlaceholder: '例如aBcDeFgHiJkLmNoPqRsTuVwX',
deviceKeyHint: '在Bark App中查看您的推送密钥',
serverAddress: '服务器地址',
serverAddressOptional: '(可选)',
serverAddressPlaceholder: '默认: https://api.day.app/push',
notificationLevel: '通知级别',
notificationSound: '通知声音',
notificationGroup: '通知分组',
notificationGroupOptional: '(可选)',
notificationGroupPlaceholder: '默认: claude-relay',
// 通知级别选项
levelAuto: '自动(根据通知类型)',
levelPassive: '被动',
levelActive: '默认',
levelTimeSensitive: '时效性',
levelCritical: '紧急',
// 声音选项
soundAuto: '自动(根据通知类型)',
soundDefault: '默认',
soundAlarm: '警报',
soundBell: '铃声',
soundBirdsong: '鸟鸣',
soundElectronic: '电子音',
soundGlass: '玻璃',
soundHorn: '喇叭',
soundSilence: '静音',
// Bark 提示信息
barkInstructions: [
'1. 在iPhone上安装Bark App',
'2. 打开App获取您的设备密钥',
'3. 将密钥粘贴到上方输入框'
],
// 签名设置
enableSignatureVerify: '启用签名验证',
signatureEnabled: '已启用',
signatureSecret: '签名密钥',
signatureSecretPlaceholder: 'SEC...',
// 平台提示信息
wechatWorkHint: '请在企业微信群机器人设置中获取Webhook地址',
dingtalkHint: '请在钉钉群机器人设置中获取Webhook地址',
feishuHint: '请在飞书群机器人设置中获取Webhook地址',
slackHint: '请在Slack应用的Incoming Webhooks中获取地址',
discordHint: '请在Discord服务器的集成设置中创建Webhook',
barkHint: '请在Bark App中查看您的设备密钥',
customHint: '请输入完整的Webhook接收地址',
// 模态框按钮
required: '必填项',
cancel: '取消',
testing: '测试中...',
saveChanges: '保存修改',
addPlatformBtn: '添加平台',
// 成功/错误消息
loadSettingsFailed: '加载设置失败',
settingsSaved: '设置已保存',
saveSettingsFailed: '保存设置失败',
oemSettingsSaved: 'OEM设置保存成功',
oemSettingsSaveFailed: '保存OEM设置失败',
resetToDefault: '已重置为默认设置',
resetFailed: '重置失败',
confirmReset: '确定要重置为默认设置吗?\n\n这将清除所有自定义的网站名称和图标设置。',
webhookConfigSaved: '配置已保存',
webhookConfigSaveFailed: '保存配置失败',
getWebhookConfigFailed: '获取webhook配置失败',
platformAdded: '平台已添加',
platformUpdated: '平台已更新',
platformDeleted: '平台已删除',
platformDeleteFailed: '删除失败',
confirmDeletePlatform: '确定要删除这个平台吗?',
operationFailed: '操作失败',
testSuccess: '测试成功webhook连接正常',
testFailed: '测试失败',
testNotificationSent: '测试通知已发送',
testNotificationFailed: '发送失败',
// 表单验证消息
enterBarkDeviceKey: '请输入Bark设备密钥',
enterWebhookUrl: '请输入Webhook URL',
enterValidWebhookUrl: '请输入有效的Webhook URL',
enterWebhookUrlFirst: '请先输入Webhook URL',
enterBarkDeviceKeyFirst: '请先输入Bark设备密钥',
// 文件上传
fileReadFailed: '文件读取失败',
iconLoadFailed: 'Icon failed to load',
// 平台名称
platforms: {
wechatWork: '企业微信',
dingtalk: '钉钉',
feishu: '飞书',
slack: 'Slack',
discord: 'Discord',
bark: 'Bark',
custom: '自定义'
}
}
}

View File

@@ -662,5 +662,208 @@ export default {
batchSuccess: '成功處理 {count} 個項目',
batchPartialFail: '{failed} 個處理失敗',
batchAllFailed: '所有項目處理失敗'
},
// Settings 設置頁面
settings: {
title: '系統設置',
description: '網站定制和通知配置',
loading: '正在載入設置...',
// 導航標籤
branding: '品牌設置',
webhook: '通知設置',
// 品牌設置
siteName: '網站名稱',
siteNameDescription: '品牌標識',
siteNamePlaceholder: 'Claude Relay Service',
siteNameHint: '將顯示在瀏覽器標題和頁面頭部',
siteIcon: '網站圖標',
siteIconDescription: 'Favicon',
currentIcon: '當前圖標',
uploadIcon: '上傳圖標',
removeIcon: '刪除',
iconFormats: '支援 .ico, .png, .jpg, .svg 格式,最大 350KB',
iconPreview: '圖標預覽',
adminEntry: '管理入口',
adminEntryDescription: '登入按鈕顯示',
hideLoginButton: '隱藏登入按鈕',
showLoginButton: '顯示登入按鈕',
adminEntryHint: '隱藏後,用戶需要直接訪問 /admin/login 頁面登入',
// 移動端卡片標題
siteNameCard: '站點名稱',
siteNameCardDesc: '自定義您的站點品牌名稱',
siteIconCard: '站點圖標',
siteIconCardDesc: '上傳自定義圖標或輸入圖標URL',
adminEntryCard: '管理入口',
adminEntryCardDesc: '控制登入按鈕在首頁的顯示',
// 操作按鈕
save: '保存設置',
saving: '保存中...',
reset: '重置為預設',
lastUpdated: '最後更新:{time}',
lastUpdatedMobile: '上次更新: {time}',
// Webhook 設置
enableWebhook: '啟用 Webhook 通知',
webhookDescription: '開啟後,系統將按配置發送通知到指定平台',
// 通知類型
notificationTypes: '通知類型',
accountAnomaly: '帳號異常',
quotaWarning: '配額警告',
systemError: '系統錯誤',
securityAlert: '安全警報',
accountAnomalyDesc: '帳號狀態異常、認證失敗等',
quotaWarningDesc: 'API調用配額不足警告',
systemErrorDesc: '系統運行錯誤和故障',
securityAlertDesc: '安全相關的警報通知',
// 通知平台
notificationPlatforms: '通知平台',
addPlatform: '新增平台',
noPlatforms: '暫無配置的通知平台,請點擊「新增平台」按鈕新增',
enableSignature: '已啟用簽名驗證',
testConnection: '測試連線',
edit: '編輯',
delete: '刪除',
// 高級設置
advancedSettings: '進階設置',
maxRetries: '最大重試次數',
retryDelay: '重試延遲 (毫秒)',
timeout: '逾時時間 (毫秒)',
// 測試通知
sendTestNotification: '發送測試通知',
// 平台模態框
addPlatformModal: '新增通知平台',
editPlatformModal: '編輯通知平台',
configurePlatform: '配置新的Webhook通知渠道',
updatePlatform: '配置並更新Webhook通知渠道',
platformType: '平台類型',
platformName: '名稱',
platformNameOptional: '(可選)',
platformNamePlaceholder: '例如:運維群通知、開發測試群',
webhookUrl: 'Webhook URL',
webhookUrlRequired: '*',
webhookUrlPlaceholder: 'https://...',
editModeWarning: '編輯模式下不能更改平台類型',
// Bark 特有設置
deviceKey: '設備密鑰 (Device Key)',
deviceKeyPlaceholder: '例如aBcDeFgHiJkLmNoPqRsTuVwX',
deviceKeyHint: '在Bark App中查看您的推送密鑰',
serverAddress: '伺服器地址',
serverAddressOptional: '(可選)',
serverAddressPlaceholder: '預設: https://api.day.app/push',
notificationLevel: '通知級別',
notificationSound: '通知聲音',
notificationGroup: '通知分組',
notificationGroupOptional: '(可選)',
notificationGroupPlaceholder: '預設: claude-relay',
// 通知級別選項
levelAuto: '自動(根據通知類型)',
levelPassive: '被動',
levelActive: '預設',
levelTimeSensitive: '時效性',
levelCritical: '緊急',
// 聲音選項
soundAuto: '自動(根據通知類型)',
soundDefault: '預設',
soundAlarm: '警報',
soundBell: '鈴聲',
soundBirdsong: '鳥鳴',
soundElectronic: '電子音',
soundGlass: '玻璃',
soundHorn: '喇叭',
soundSilence: '靜音',
// Bark 提示信息
barkInstructions: [
'1. 在iPhone上安裝Bark App',
'2. 打開App獲取您的設備密鑰',
'3. 將密鑰貼上到上方輸入框'
],
// 簽名設置
enableSignatureVerify: '啟用簽名驗證',
signatureEnabled: '已啟用',
signatureSecret: '簽名密鑰',
signatureSecretPlaceholder: 'SEC...',
// 平台提示信息
wechatWorkHint: '請在企業微信群機器人設置中獲取Webhook地址',
dingtalkHint: '請在釘釘群機器人設置中獲取Webhook地址',
feishuHint: '請在飛書群機器人設置中獲取Webhook地址',
slackHint: '請在Slack應用的Incoming Webhooks中獲取地址',
discordHint: '請在Discord伺服器的整合設置中建立Webhook',
barkHint: '請在Bark App中查看您的設備密鑰',
customHint: '請輸入完整的Webhook接收地址',
// 模態框按鈕
required: '必填項',
cancel: '取消',
testing: '測試中...',
saveChanges: '保存修改',
addPlatformBtn: '新增平台',
// 成功/錯誤消息
loadSettingsFailed: '載入設置失敗',
settingsSaved: '設置已保存',
saveSettingsFailed: '保存設置失敗',
oemSettingsSaved: 'OEM設置保存成功',
oemSettingsSaveFailed: '保存OEM設置失敗',
resetToDefault: '已重置為預設設置',
resetFailed: '重置失敗',
confirmReset: '確定要重置為預設設置嗎?\n\n這將清除所有自定義的網站名稱和圖標設置。',
webhookConfigSaved: '配置已保存',
webhookConfigSaveFailed: '保存配置失敗',
getWebhookConfigFailed: '獲取webhook配置失敗',
platformAdded: '平台已新增',
platformUpdated: '平台已更新',
platformDeleted: '平台已刪除',
platformDeleteFailed: '刪除失敗',
confirmDeletePlatform: '確定要刪除這個平台嗎?',
operationFailed: '操作失敗',
testSuccess: '測試成功webhook連線正常',
testFailed: '測試失敗',
testNotificationSent: '測試通知已發送',
testNotificationFailed: '發送失敗',
// 表單驗證消息
enterBarkDeviceKey: '請輸入Bark設備密鑰',
enterWebhookUrl: '請輸入Webhook URL',
enterValidWebhookUrl: '請輸入有效的Webhook URL',
enterWebhookUrlFirst: '請先輸入Webhook URL',
enterBarkDeviceKeyFirst: '請先輸入Bark設備密鑰',
// 檔案上傳
fileReadFailed: '檔案讀取失敗',
iconLoadFailed: 'Icon failed to load',
// 平台名稱
platforms: {
wechatWork: '企業微信',
dingtalk: '釘釘',
feishu: '飛書',
slack: 'Slack',
discord: 'Discord',
bark: 'Bark',
custom: '自定義'
}
}
}

View File

@@ -4,9 +4,9 @@
<!-- 页面标题 -->
<div class="mb-4 sm:mb-6">
<h3 class="mb-1 text-lg font-bold text-gray-900 dark:text-gray-100 sm:mb-2 sm:text-xl">
系统设置
{{ t('settings.title') }}
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 sm:text-base">网站定制和通知配置</p>
<p class="text-sm text-gray-600 dark:text-gray-400 sm:text-base">{{ t('settings.description') }}</p>
</div>
<!-- 设置分类导航 -->
@@ -22,7 +22,7 @@
@click="activeSection = 'branding'"
>
<i class="fas fa-palette mr-2"></i>
品牌设置
{{ t('settings.branding') }}
</button>
<button
:class="[
@@ -34,7 +34,7 @@
@click="activeSection = 'webhook'"
>
<i class="fas fa-bell mr-2"></i>
通知设置
{{ t('settings.webhook') }}
</button>
</nav>
</div>
@@ -42,7 +42,7 @@
<!-- 加载状态 -->
<div v-if="loading" class="py-12 text-center">
<div class="loading-spinner mx-auto mb-4"></div>
<p class="text-gray-500 dark:text-gray-400">正在加载设置...</p>
<p class="text-gray-500 dark:text-gray-400">{{ t('settings.loading') }}</p>
</div>
<!-- 内容区域 -->
@@ -64,9 +64,9 @@
</div>
<div>
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
网站名称
{{ t('settings.siteName') }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">品牌标识</div>
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('settings.siteNameDescription') }}</div>
</div>
</div>
</td>
@@ -75,11 +75,11 @@
v-model="oemSettings.siteName"
class="form-input w-full max-w-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
maxlength="100"
placeholder="Claude Relay Service"
:placeholder="t('settings.siteNamePlaceholder')"
type="text"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
将显示在浏览器标题和页面头部
{{ t('settings.siteNameHint') }}
</p>
</td>
</tr>
@@ -95,9 +95,9 @@
</div>
<div>
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
网站图标
{{ t('settings.siteIcon') }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">Favicon</div>
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('settings.siteIconDescription') }}</div>
</div>
</div>
</td>
@@ -109,17 +109,17 @@
class="inline-flex items-center gap-3 rounded-lg bg-gray-50 p-3 dark:bg-gray-700"
>
<img
alt="图标预览"
:alt="t('settings.iconPreview')"
class="h-8 w-8"
:src="oemSettings.siteIconData || oemSettings.siteIcon"
@error="handleIconError"
/>
<span class="text-sm text-gray-600 dark:text-gray-400">当前图标</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ t('settings.currentIcon') }}</span>
<button
class="rounded-lg px-3 py-1 font-medium text-red-600 transition-colors hover:bg-red-50 hover:text-red-900"
@click="removeIcon"
>
<i class="fas fa-trash mr-1" />删除
<i class="fas fa-trash mr-1" />{{ t('settings.removeIcon') }}
</button>
</div>
@@ -137,10 +137,10 @@
@click="$refs.iconFileInput.click()"
>
<i class="fas fa-upload mr-2" />
上传图标
{{ t('settings.uploadIcon') }}
</button>
<span class="ml-3 text-xs text-gray-500 dark:text-gray-400"
>支持 .ico, .png, .jpg, .svg 格式最大 350KB</span
>{{ t('settings.iconFormats') }}</span
>
</div>
</div>
@@ -158,9 +158,9 @@
</div>
<div>
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
管理入口
{{ t('settings.adminEntry') }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">登录按钮显示</div>
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('settings.adminEntryDescription') }}</div>
</div>
</div>
</td>
@@ -172,12 +172,12 @@
class="peer relative h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:border-gray-600 dark:bg-gray-700 dark:peer-focus:ring-blue-800"
></div>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">{{
hideAdminButton ? '隐藏登录按钮' : '显示登录按钮'
hideAdminButton ? t('settings.hideLoginButton') : t('settings.showLoginButton')
}}</span>
</label>
</div>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
隐藏后用户需要直接访问 /admin/login 页面登录
{{ t('settings.adminEntryHint') }}
</p>
</td>
</tr>
@@ -195,7 +195,7 @@
>
<div v-if="saving" class="loading-spinner mr-2"></div>
<i v-else class="fas fa-save mr-2" />
{{ saving ? '保存中...' : '保存设置' }}
{{ saving ? t('settings.saving') : t('settings.save') }}
</button>
<button
@@ -204,7 +204,7 @@
@click="resetOemSettings"
>
<i class="fas fa-undo mr-2" />
重置为默认
{{ t('settings.reset') }}
</button>
</div>
@@ -213,7 +213,7 @@
class="text-sm text-gray-500 dark:text-gray-400"
>
<i class="fas fa-clock mr-1" />
最后更新{{ formatDateTime(oemSettings.updatedAt) }}
{{ t('settings.lastUpdated', { time: formatDateTime(oemSettings.updatedAt) }) }}
</div>
</div>
</td>
@@ -233,15 +233,15 @@
<i class="fas fa-tag"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">站点名称</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">自定义您的站点品牌名称</p>
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ t('settings.siteNameCard') }}</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t('settings.siteNameCardDesc') }}</p>
</div>
</div>
<input
v-model="oemSettings.siteName"
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
maxlength="100"
placeholder="Claude Relay Service"
:placeholder="t('settings.siteNamePlaceholder')"
type="text"
/>
</div>
@@ -255,9 +255,9 @@
<i class="fas fa-image"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">站点图标</h3>
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ t('settings.siteIconCard') }}</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">
上传自定义图标或输入图标URL
{{ t('settings.siteIconCardDesc') }}
</p>
</div>
</div>
@@ -268,17 +268,17 @@
class="inline-flex items-center gap-3 rounded-lg bg-gray-50 p-3 dark:bg-gray-700"
>
<img
alt="图标预览"
:alt="t('settings.iconPreview')"
class="h-8 w-8"
:src="oemSettings.siteIconData || oemSettings.siteIcon"
@error="handleIconError"
/>
<span class="text-sm text-gray-600 dark:text-gray-400">当前图标</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ t('settings.currentIcon') }}</span>
<button
class="rounded-lg px-3 py-1 font-medium text-red-600 transition-colors hover:bg-red-50 hover:text-red-900"
@click="removeIcon"
>
删除
{{ t('settings.removeIcon') }}
</button>
</div>
@@ -296,10 +296,10 @@
@click="$refs.iconFileInputMobile.click()"
>
<i class="fas fa-upload mr-2" />
上传图标
{{ t('settings.uploadIcon') }}
</button>
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
支持 .ico, .png, .jpg, .svg 格式最大 350KB
{{ t('settings.iconFormats') }}
</p>
</div>
</div>
@@ -314,8 +314,8 @@
<i class="fas fa-eye-slash"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">管理入口</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">控制登录按钮在首页的显示</p>
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ t('settings.adminEntryCard') }}</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t('settings.adminEntryCardDesc') }}</p>
</div>
</div>
<div class="space-y-2">
@@ -325,11 +325,11 @@
class="peer relative h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:border-gray-600 dark:bg-gray-700 dark:peer-focus:ring-blue-800"
></div>
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">{{
hideAdminButton ? '隐藏登录按钮' : '显示登录按钮'
hideAdminButton ? t('settings.hideLoginButton') : t('settings.showLoginButton')
}}</span>
</label>
<p class="text-xs text-gray-500 dark:text-gray-400">
隐藏后用户需要直接访问 /admin/login 页面登录
{{ t('settings.adminEntryHint') }}
</p>
</div>
</div>
@@ -345,7 +345,7 @@
>
<div v-if="saving" class="loading-spinner mr-2"></div>
<i v-else class="fas fa-save mr-2" />
{{ saving ? '保存中...' : '保存设置' }}
{{ saving ? t('settings.saving') : t('settings.save') }}
</button>
<button
@@ -354,7 +354,7 @@
@click="resetOemSettings"
>
<i class="fas fa-undo mr-2" />
重置为默认
{{ t('settings.reset') }}
</button>
<div
@@ -362,7 +362,7 @@
class="text-center text-sm text-gray-500 dark:text-gray-400"
>
<i class="fas fa-clock mr-1" />
上次更新: {{ formatDateTime(oemSettings.updatedAt) }}
{{ t('settings.lastUpdatedMobile', { time: formatDateTime(oemSettings.updatedAt) }) }}
</div>
</div>
</div>
@@ -377,9 +377,11 @@
>
<div class="flex items-center justify-between">
<div>
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-200">启用通知</h2>
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-200">
{{ t('settings.enableWebhook') }}
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
开启后系统将按配置发送通知到指定平台
{{ t('settings.webhookDescription') }}
</p>
</div>
<label class="relative inline-flex cursor-pointer items-center">
@@ -400,7 +402,7 @@
<div
class="mb-6 rounded-lg bg-white/80 p-6 shadow-lg backdrop-blur-sm dark:bg-gray-800/80"
>
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">通知类型</h2>
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">{{ t('settings.notificationTypes') }}</h2>
<div class="space-y-3">
<div
v-for="(enabled, type) in webhookConfig.notificationTypes"
@@ -469,22 +471,10 @@
</div>
</div>
<div class="mt-3 space-y-1 text-sm">
<div
v-if="platform.type !== 'smtp'"
class="flex items-center text-gray-600 dark:text-gray-400"
>
<div class="flex items-center text-gray-600 dark:text-gray-400">
<i class="fas fa-link mr-2"></i>
<span class="truncate">{{ platform.url }}</span>
</div>
<div
v-if="platform.type === 'smtp' && platform.to"
class="flex items-center text-gray-600 dark:text-gray-400"
>
<i class="fas fa-envelope mr-2"></i>
<span class="truncate">{{
Array.isArray(platform.to) ? platform.to.join(', ') : platform.to
}}</span>
</div>
<div
v-if="platform.enableSign"
class="flex items-center text-gray-600 dark:text-gray-400"
@@ -665,7 +655,6 @@
<option value="slack">🟣 Slack</option>
<option value="discord">🟪 Discord</option>
<option value="bark">🔔 Bark</option>
<option value="smtp">📧 邮件通知</option>
<option value="custom"> 自定义</option>
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
@@ -695,8 +684,8 @@
/>
</div>
<!-- Webhook URL (非Bark和SMTP平台) -->
<div v-if="platformForm.type !== 'bark' && platformForm.type !== 'smtp'">
<!-- Webhook URL (非Bark平台) -->
<div v-if="platformForm.type !== 'bark'">
<label
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
>
@@ -847,141 +836,6 @@
</div>
</div>
<!-- SMTP 平台特有字段 -->
<div v-if="platformForm.type === 'smtp'" class="space-y-5">
<!-- SMTP 主机 -->
<div>
<label
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
>
<i class="fas fa-server mr-2 text-gray-400"></i>
SMTP 服务器
<span class="ml-1 text-xs text-red-500">*</span>
</label>
<input
v-model="platformForm.host"
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
placeholder="例如: smtp.gmail.com"
required
type="text"
/>
</div>
<!-- SMTP 端口和安全设置 -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
>
<i class="fas fa-plug mr-2 text-gray-400"></i>
端口
</label>
<input
v-model.number="platformForm.port"
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
max="65535"
min="1"
placeholder="587"
type="number"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
默认: 587 (TLS) 465 (SSL)
</p>
</div>
<div>
<label
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
>
<i class="fas fa-shield-alt mr-2 text-gray-400"></i>
加密方式
</label>
<select
v-model="platformForm.secure"
class="w-full appearance-none rounded-xl border border-gray-300 bg-white px-4 py-3 pr-10 text-gray-900 shadow-sm transition-all focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
>
<option :value="false">STARTTLS (端口587)</option>
<option :value="true">SSL/TLS (端口465)</option>
</select>
</div>
</div>
<!-- 用户名 -->
<div>
<label
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
>
<i class="fas fa-user mr-2 text-gray-400"></i>
用户名
<span class="ml-1 text-xs text-red-500">*</span>
</label>
<input
v-model="platformForm.user"
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
placeholder="user@example.com"
required
type="email"
/>
</div>
<!-- 密码 -->
<div>
<label
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
>
<i class="fas fa-lock mr-2 text-gray-400"></i>
密码 / 应用密码
<span class="ml-1 text-xs text-red-500">*</span>
</label>
<input
v-model="platformForm.pass"
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
placeholder="邮箱密码或应用专用密码"
required
type="password"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
建议使用应用专用密码而非邮箱登录密码
</p>
</div>
<!-- 发件人邮箱 -->
<div>
<label
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
>
<i class="fas fa-paper-plane mr-2 text-gray-400"></i>
发件人邮箱
<span class="ml-2 text-xs text-gray-500">(可选)</span>
</label>
<input
v-model="platformForm.from"
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
placeholder="默认使用用户名邮箱"
type="email"
/>
</div>
<!-- 收件人邮箱 -->
<div>
<label
class="mb-2 flex items-center text-sm font-medium text-gray-700 dark:text-gray-300"
>
<i class="fas fa-envelope mr-2 text-gray-400"></i>
收件人邮箱
<span class="ml-1 text-xs text-red-500">*</span>
</label>
<input
v-model="platformForm.to"
class="w-full rounded-xl border border-gray-300 bg-white px-4 py-3 text-gray-900 shadow-sm transition-all placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder:text-gray-500"
placeholder="admin@example.com"
required
type="email"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">接收通知的邮箱地址</p>
</div>
</div>
<!-- 签名设置钉钉/飞书 -->
<div
v-if="platformForm.type === 'dingtalk' || platformForm.type === 'feishu'"
@@ -1087,6 +941,7 @@
<script setup>
import { ref, onMounted, onBeforeUnmount, watch, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'
import { showToast } from '@/utils/toast'
import { useSettingsStore } from '@/stores/settings'
@@ -1097,6 +952,9 @@ defineOptions({
name: 'SettingsView'
})
// 使用vue-i18n
const { t } = useI18n()
// 使用settings store
const settingsStore = useSettingsStore()
const { loading, saving, oemSettings } = storeToRefs(settingsStore)
@@ -1154,23 +1012,7 @@ const platformForm = ref({
name: '',
url: '',
enableSign: false,
secret: '',
// Bark特有字段
deviceKey: '',
serverUrl: '',
level: '',
sound: '',
group: '',
// SMTP特有字段
host: '',
port: null,
secure: false,
user: '',
pass: '',
from: '',
to: '',
timeout: null,
ignoreTLS: false
secret: ''
})
// 监听activeSection变化加载对应配置
@@ -1192,48 +1034,17 @@ const platformTypeWatcher = watch(
// 如果不是编辑模式,清空相关字段
if (!editingPlatform.value) {
if (newType === 'bark') {
// 切换到Bark时清空URL和SMTP相关字段
// 切换到Bark时清空URL相关字段
platformForm.value.url = ''
platformForm.value.enableSign = false
platformForm.value.secret = ''
// 清空SMTP字段
platformForm.value.host = ''
platformForm.value.port = null
platformForm.value.secure = false
platformForm.value.user = ''
platformForm.value.pass = ''
platformForm.value.from = ''
platformForm.value.to = ''
platformForm.value.timeout = null
platformForm.value.ignoreTLS = false
} else if (newType === 'smtp') {
// 切换到SMTP时清空URL和Bark相关字段
platformForm.value.url = ''
platformForm.value.enableSign = false
platformForm.value.secret = ''
// 清空Bark字段
platformForm.value.deviceKey = ''
platformForm.value.serverUrl = ''
platformForm.value.level = ''
platformForm.value.sound = ''
platformForm.value.group = ''
} else {
// 切换到其他平台时清空Bark和SMTP相关字段
// 切换到其他平台时清空Bark相关字段
platformForm.value.deviceKey = ''
platformForm.value.serverUrl = ''
platformForm.value.level = ''
platformForm.value.sound = ''
platformForm.value.group = ''
// SMTP 字段
platformForm.value.host = ''
platformForm.value.port = null
platformForm.value.secure = false
platformForm.value.user = ''
platformForm.value.pass = ''
platformForm.value.from = ''
platformForm.value.to = ''
platformForm.value.timeout = null
platformForm.value.ignoreTLS = false
}
}
}
@@ -1244,14 +1055,6 @@ const isPlatformFormValid = computed(() => {
if (platformForm.value.type === 'bark') {
// Bark平台需要deviceKey
return !!platformForm.value.deviceKey
} else if (platformForm.value.type === 'smtp') {
// SMTP平台需要必要的配置
return !!(
platformForm.value.host &&
platformForm.value.user &&
platformForm.value.pass &&
platformForm.value.to
)
} else {
// 其他平台需要URL且URL格式正确
return !!platformForm.value.url && !urlError.value
@@ -1266,7 +1069,7 @@ onMounted(async () => {
await loadWebhookConfig()
}
} catch (error) {
showToast('加载设置失败', 'error')
showToast(t('settings.loadSettingsFailed'), 'error')
}
})
@@ -1310,7 +1113,7 @@ const loadWebhookConfig = async () => {
} catch (error) {
if (error.name === 'AbortError') return
if (!isMounted.value) return
showToast('获取webhook配置失败', 'error')
showToast(t('settings.getWebhookConfigFailed'), 'error')
console.error(error)
}
}
@@ -1323,20 +1126,20 @@ const saveWebhookConfig = async () => {
signal: abortController.value.signal
})
if (response.success && isMounted.value) {
showToast('配置已保存', 'success')
showToast(t('settings.webhookConfigSaved'), 'success')
}
} catch (error) {
if (error.name === 'AbortError') return
if (!isMounted.value) return
showToast('保存配置失败', 'error')
showToast(t('settings.webhookConfigSaveFailed'), 'error')
console.error(error)
}
}
// 验证 URL
const validateUrl = () => {
// Bark和SMTP平台不需要验证URL
if (platformForm.value.type === 'bark' || platformForm.value.type === 'smtp') {
// Bark平台不需要验证URL
if (platformForm.value.type === 'bark') {
urlError.value = false
urlValid.value = false
return
@@ -1364,46 +1167,27 @@ const validateUrl = () => {
}
}
// 验证平台配置
const validatePlatformForm = () => {
if (platformForm.value.type === 'bark') {
if (!platformForm.value.deviceKey) {
showToast('请输入Bark设备密钥', 'error')
return false
}
} else if (platformForm.value.type === 'smtp') {
const requiredFields = [
{ field: 'host', message: 'SMTP服务器' },
{ field: 'user', message: '用户名' },
{ field: 'pass', message: '密码' },
{ field: 'to', message: '收件人邮箱' }
]
for (const { field, message } of requiredFields) {
if (!platformForm.value[field]) {
showToast(`请输入${message}`, 'error')
return false
}
}
} else {
if (!platformForm.value.url) {
showToast('请输入Webhook URL', 'error')
return false
}
if (urlError.value) {
showToast('请输入有效的Webhook URL', 'error')
return false
}
}
return true
}
// 添加/更新平台
const savePlatform = async () => {
if (!isMounted.value) return
// 验证表单
if (!validatePlatformForm()) return
// Bark平台只需要deviceKey其他平台需要URL
if (platformForm.value.type === 'bark') {
if (!platformForm.value.deviceKey) {
showToast(t('settings.enterBarkDeviceKey'), 'error')
return
}
} else {
if (!platformForm.value.url) {
showToast(t('settings.enterWebhookUrl'), 'error')
return
}
if (urlError.value) {
showToast('请输入有效的Webhook URL', 'error')
return
}
}
savingPlatform.value = true
try {
@@ -1423,14 +1207,14 @@ const savePlatform = async () => {
}
if (response.success && isMounted.value) {
showToast(editingPlatform.value ? '平台已更新' : '平台已添加', 'success')
showToast(editingPlatform.value ? t('settings.platformUpdated') : t('settings.platformAdded'), 'success')
await loadWebhookConfig()
closePlatformModal()
}
} catch (error) {
if (error.name === 'AbortError') return
if (!isMounted.value) return
showToast(error.message || '操作失败', 'error')
showToast(error.message || t('settings.operationFailed'), 'error')
console.error(error)
} finally {
if (isMounted.value) {
@@ -1450,7 +1234,7 @@ const editPlatform = (platform) => {
const deletePlatform = async (id) => {
if (!isMounted.value) return
if (!confirm('确定要删除这个平台吗?')) {
if (!confirm(t('settings.confirmDeletePlatform'))) {
return
}
@@ -1459,13 +1243,13 @@ const deletePlatform = async (id) => {
signal: abortController.value.signal
})
if (response.success && isMounted.value) {
showToast('平台已删除', 'success')
showToast(t('settings.platformDeleted'), 'success')
await loadWebhookConfig()
}
} catch (error) {
if (error.name === 'AbortError') return
if (!isMounted.value) return
showToast('删除失败', 'error')
showToast(t('settings.platformDeleteFailed'), 'error')
console.error(error)
}
}
@@ -1512,15 +1296,6 @@ const testPlatform = async (platform) => {
testData.level = platform.level
testData.sound = platform.sound
testData.group = platform.group
} else if (platform.type === 'smtp') {
testData.host = platform.host
testData.port = platform.port
testData.secure = platform.secure
testData.user = platform.user
testData.pass = platform.pass
testData.from = platform.from
testData.to = platform.to
testData.ignoreTLS = platform.ignoreTLS
} else {
testData.url = platform.url
}
@@ -1529,7 +1304,7 @@ const testPlatform = async (platform) => {
signal: abortController.value.signal
})
if (response.success && isMounted.value) {
showToast('测试成功', 'success')
showToast(t('settings.testSuccess'), 'success')
}
} catch (error) {
if (error.name === 'AbortError') return
@@ -1543,8 +1318,24 @@ const testPlatform = async (platform) => {
const testPlatformForm = async () => {
if (!isMounted.value) return
// 验证表单
if (!validatePlatformForm()) return
// Bark平台验证
if (platformForm.value.type === 'bark') {
if (!platformForm.value.deviceKey) {
showToast(t('settings.enterBarkDeviceKeyFirst'), 'error')
return
}
} else {
// 其他平台验证URL
if (!platformForm.value.url) {
showToast(t('settings.enterWebhookUrlFirst'), 'error')
return
}
if (urlError.value) {
showToast('请输入有效的Webhook URL', 'error')
return
}
}
testingConnection.value = true
try {
@@ -1552,7 +1343,7 @@ const testPlatformForm = async () => {
signal: abortController.value.signal
})
if (response.success && isMounted.value) {
showToast('测试成功', 'success')
showToast(t('settings.testSuccess'), 'success')
}
} catch (error) {
if (error.name === 'AbortError') return
@@ -1579,12 +1370,12 @@ const sendTestNotification = async () => {
}
)
if (response.success && isMounted.value) {
showToast('测试通知已发送', 'success')
showToast(t('settings.testNotificationSent'), 'success')
}
} catch (error) {
if (error.name === 'AbortError') return
if (!isMounted.value) return
showToast('发送失败', 'error')
showToast(t('settings.testNotificationFailed'), 'error')
console.error(error)
}
}
@@ -1610,17 +1401,7 @@ const closePlatformModal = () => {
serverUrl: '',
level: '',
sound: '',
group: '',
// SMTP特有字段
host: '',
port: null,
secure: false,
user: '',
pass: '',
from: '',
to: '',
timeout: null,
ignoreTLS: false
group: ''
}
urlError.value = false
urlValid.value = false
@@ -1629,17 +1410,16 @@ const closePlatformModal = () => {
}, 0)
}
// 辅助函数
// 辅助函数 - 转换为计算属性以支持响应式翻译
const getPlatformName = (type) => {
const names = {
wechat_work: '企业微信',
dingtalk: '钉钉',
feishu: '飞书',
slack: 'Slack',
discord: 'Discord',
bark: 'Bark',
smtp: '邮件通知',
custom: '自定义'
wechat_work: t('settings.platforms.wechatWork'),
dingtalk: t('settings.platforms.dingtalk'),
feishu: t('settings.platforms.feishu'),
slack: t('settings.platforms.slack'),
discord: t('settings.platforms.discord'),
bark: t('settings.platforms.bark'),
custom: t('settings.platforms.custom')
}
return names[type] || type
}
@@ -1652,7 +1432,6 @@ const getPlatformIcon = (type) => {
slack: 'fab fa-slack text-purple-600',
discord: 'fab fa-discord text-indigo-600',
bark: 'fas fa-bell text-orange-500',
smtp: 'fas fa-envelope text-blue-600',
custom: 'fas fa-webhook text-gray-600'
}
return icons[type] || 'fas fa-bell'
@@ -1660,36 +1439,33 @@ const getPlatformIcon = (type) => {
const getWebhookHint = (type) => {
const hints = {
wechat_work: '请在企业微信群机器人设置中获取Webhook地址',
dingtalk: '请在钉钉群机器人设置中获取Webhook地址',
feishu: '请在飞书群机器人设置中获取Webhook地址',
slack: '请在Slack应用的Incoming Webhooks中获取地址',
discord: '请在Discord服务器的集成设置中创建Webhook',
bark: '请在Bark App中查看您的设备密钥',
smtp: '请配置SMTP服务器信息支持Gmail、QQ邮箱等',
custom: '请输入完整的Webhook接收地址'
wechat_work: t('settings.wechatWorkHint'),
dingtalk: t('settings.dingtalkHint'),
feishu: t('settings.feishuHint'),
slack: t('settings.slackHint'),
discord: t('settings.discordHint'),
bark: t('settings.barkHint'),
custom: t('settings.customHint')
}
return hints[type] || ''
}
const getNotificationTypeName = (type) => {
const names = {
accountAnomaly: '账号异常',
quotaWarning: '配额警告',
systemError: '系统错误',
securityAlert: '安全警报',
test: '测试通知'
accountAnomaly: t('settings.accountAnomaly'),
quotaWarning: t('settings.quotaWarning'),
systemError: t('settings.systemError'),
securityAlert: t('settings.securityAlert')
}
return names[type] || type
}
const getNotificationTypeDescription = (type) => {
const descriptions = {
accountAnomaly: '账号状态异常、认证失败等',
quotaWarning: 'API调用配额不足警告',
systemError: '系统运行错误和故障',
securityAlert: '安全相关的警报通知',
test: '用于测试Webhook连接是否正常'
accountAnomaly: t('settings.accountAnomalyDesc'),
quotaWarning: t('settings.quotaWarningDesc'),
systemError: t('settings.systemErrorDesc'),
securityAlert: t('settings.securityAlertDesc')
}
return descriptions[type] || ''
}
@@ -1705,28 +1481,28 @@ const saveOemSettings = async () => {
}
const result = await settingsStore.saveOemSettings(settings)
if (result && result.success) {
showToast('OEM设置保存成功', 'success')
showToast(t('settings.oemSettingsSaved'), 'success')
} else {
showToast(result?.message || '保存失败', 'error')
showToast(result?.message || t('settings.saveSettingsFailed'), 'error')
}
} catch (error) {
showToast('保存OEM设置失败', 'error')
showToast(t('settings.oemSettingsSaveFailed'), 'error')
}
}
// 重置OEM设置
const resetOemSettings = async () => {
if (!confirm('确定要重置为默认设置吗?\n\n这将清除所有自定义的网站名称和图标设置。')) return
if (!confirm(t('settings.confirmReset'))) return
try {
const result = await settingsStore.resetOemSettings()
if (result && result.success) {
showToast('已重置为默认设置', 'success')
showToast(t('settings.resetToDefault'), 'success')
} else {
showToast('重置失败', 'error')
showToast(t('settings.resetFailed'), 'error')
}
} catch (error) {
showToast('重置失败', 'error')
showToast(t('settings.resetFailed'), 'error')
}
}
@@ -1747,7 +1523,7 @@ const handleIconUpload = async (event) => {
const base64Data = await settingsStore.fileToBase64(file)
oemSettings.value.siteIconData = base64Data
} catch (error) {
showToast('文件读取失败', 'error')
showToast(t('settings.fileReadFailed'), 'error')
}
// 清除input的值允许重复选择同一文件
@@ -1762,7 +1538,7 @@ const removeIcon = () => {
// 处理图标加载错误
const handleIconError = () => {
console.warn('Icon failed to load')
console.warn(t('settings.iconLoadFailed'))
}
// 格式化日期时间