feat: 新增定时账单导入提醒与通知直达导入页

- 新增通知配置、渠道、发送日志三张表(MySQL/SQLite 双建表脚本)及实体、Mapper、Service、Controller
- 支持邮箱(SMTP)与 Server 酱两种推送渠道,抽象 NotificationSender 工厂统一发送
- 新增 NotificationScheduler 定时任务,按每天/每周/每月周期检查并推送账单导入提醒
- 新增全局通知链接地址配置(/api/app/notification-url),随提醒一并下发
- 前端系统设置页新增「定时通知」面板,支持周期配置、渠道增删改测、立即发送与发送日志
- 新增 /bill-import 直达页,供提醒链接一键打开账单上传弹窗
- 登录支持 redirect 回跳,仅允许站内路径以防开放重定向
This commit is contained in:
2026-09-18 16:47:09 +08:00
parent c1f5d02564
commit e49b6b7b81
45 changed files with 3469 additions and 513 deletions
+68
View File
@@ -128,6 +128,7 @@ export default {
aiSection: { zh: 'AI 模型配置', en: 'AI Model', ja: 'AI モデル' },
viewUsage: { zh: '使用情况', en: 'Usage', ja: '利用状況' },
registrationSection: { zh: '注册设置', en: 'Registration', ja: '登録設定' },
notificationSection: { zh: '定时通知', en: 'Scheduled Notifications', ja: '定期通知' },
},
// 注册设置
@@ -142,6 +143,73 @@ export default {
loadFailed: { zh: '加载注册状态失败', en: 'Failed to load registration status', ja: '登録状態の読み込みに失敗しました' },
},
// 定时通知
notification: {
statusOn: { zh: '已开启', en: 'Enabled', ja: '有効' },
statusOff: { zh: '已关闭', en: 'Disabled', ja: '無効' },
intro: { zh: '按自定义周期,通过邮箱或 Server 酱提醒你及时导入新的账单。', en: 'Get reminded via email or ServerChan to import new bills on your own schedule.', ja: 'カスタム周期でメール・ServerChan を通じて、新しい明細のインポートをお知らせします。' },
linkUrl: { zh: '通知链接地址', en: 'Notification link URL', ja: '通知リンク URL' },
linkUrlPlaceholder: { zh: '如 https://finance.rosercode.cn/bill-import', en: 'e.g. https://finance.rosercode.cn/bill-import', ja: '例:https://finance.rosercode.cn/bill-import' },
linkUrlSaveSuccess: { zh: '通知链接地址已保存', en: 'Notification link URL saved', ja: '通知リンク URL を保存しました' },
linkUrlSaveFailed: { zh: '保存通知链接地址失败', en: 'Failed to save notification link URL', ja: '通知リンク URL の保存に失敗しました' },
enabled: { zh: '启用定时提醒', en: 'Enable reminders', ja: '定期リマインダーを有効化' },
enabledHint: { zh: '开启后按下方周期自动发送提醒', en: 'Sends reminders automatically based on the schedule below', ja: '有効にすると以下の周期で自動送信します' },
frequency: { zh: '提醒周期', en: 'Frequency', ja: '通知周期' },
freqDaily: { zh: '每天', en: 'Daily', ja: '毎日' },
freqWeekly: { zh: '每周', en: 'Weekly', ja: '毎週' },
freqMonthly: { zh: '每月', en: 'Monthly', ja: '毎月' },
weekday1: { zh: '周一', en: 'Monday', ja: '月曜日' },
weekday2: { zh: '周二', en: 'Tuesday', ja: '火曜日' },
weekday3: { zh: '周三', en: 'Wednesday', ja: '水曜日' },
weekday4: { zh: '周四', en: 'Thursday', ja: '木曜日' },
weekday5: { zh: '周五', en: 'Friday', ja: '金曜日' },
weekday6: { zh: '周六', en: 'Saturday', ja: '土曜日' },
weekday7: { zh: '周日', en: 'Sunday', ja: '日曜日' },
dayOfMonthSuffix: { zh: '日', en: 'day', ja: '日' },
sendTime: { zh: '提醒时间', en: 'Time', ja: '通知時刻' },
sendTimePlaceholder: { zh: '例如 09:00', en: 'e.g. 09:00', ja: '例:09:00' },
sendTimeInvalid: { zh: '提醒时间格式应为 HH:mm', en: 'Time must be in HH:mm format', ja: '時刻は HH:mm 形式で入力してください' },
save: { zh: '保存', en: 'Save', ja: '保存' },
saveSuccess: { zh: '通知设置已保存', en: 'Notification settings saved', ja: '通知設定を保存しました' },
sendNow: { zh: '立即发送', en: 'Send now', ja: '今すぐ送信' },
sendNowSuccess: { zh: '已发送 {count} 个渠道', en: 'Sent to {count} channel(s)', ja: '{count} 件のチャネルに送信しました' },
sendNowFailed: { zh: '发送提醒失败', en: 'Failed to send reminder', ja: 'リマインダーの送信に失敗しました' },
channels: { zh: '推送渠道', en: 'Channels', ja: '通知チャネル' },
addChannel: { zh: '添加渠道', en: 'Add channel', ja: 'チャネルを追加' },
editChannel: { zh: '编辑渠道', en: 'Edit channel', ja: 'チャネルを編集' },
channelType: { zh: '渠道类型', en: 'Type', ja: 'チャネル種別' },
channelName: { zh: '渠道名称', en: 'Name', ja: 'チャネル名' },
channelNamePlaceholder: { zh: '便于识别的名称', en: 'A name to identify it', ja: '識別しやすい名前' },
enabledLabel: { zh: '已启用', en: 'Enabled', ja: '有効' },
disabledLabel: { zh: '已停用', en: 'Disabled', ja: '無効' },
typeEmail: { zh: '邮箱', en: 'Email', ja: 'メール' },
typeServerChan: { zh: 'Server 酱', en: 'ServerChan', ja: 'ServerChan' },
smtpHost: { zh: 'SMTP 服务器', en: 'SMTP host', ja: 'SMTP サーバー' },
smtpPort: { zh: 'SMTP 端口', en: 'SMTP port', ja: 'SMTP ポート' },
username: { zh: '邮箱账号', en: 'Email account', ja: 'メールアカウント' },
usernameRequired: { zh: '请填写邮箱账号', en: 'Please enter the email account', ja: 'メールアカウントを入力してください' },
sendKey: { zh: 'SendKey', en: 'SendKey', ja: 'SendKey' },
sendKeyPlaceholder: { zh: '在 sct.ftqq.com 获取的 SendKey', en: 'SendKey obtained from sct.ftqq.com', ja: 'sct.ftqq.com で取得した SendKey' },
sendKeyRequired: { zh: '请填写 Server 酱 SendKey', en: 'Please enter the ServerChan SendKey', ja: 'ServerChan の SendKey を入力してください' },
password: { zh: '授权码', en: 'Auth code', ja: '認証コード' },
passwordPlaceholder: { zh: '邮箱 SMTP 授权码', en: 'SMTP authorization code', ja: 'SMTP 認証コード' },
passwordKeep: { zh: '已配置,留空表示不修改', en: 'Configured; leave blank to keep', ja: '設定済み。空欄なら変更しません' },
to: { zh: '收件人', en: 'Recipient', ja: '宛先' },
toPlaceholder: { zh: '默认与账号相同', en: 'Defaults to the account', ja: '既定ではアカウントと同じ' },
ssl: { zh: '使用 SSL', en: 'Use SSL', ja: 'SSL を使用' },
test: { zh: '测试', en: 'Test', ja: 'テスト' },
testSuccess: { zh: '测试消息已发送', en: 'Test message sent', ja: 'テストメッセージを送信しました' },
testFailed: { zh: '测试发送失败', en: 'Test failed', ja: 'テスト送信に失敗しました' },
confirmDeleteChannel: { zh: '确认删除渠道「{name}」?', en: 'Delete channel "{name}"?', ja: 'チャネル「{name}」を削除しますか?' },
saveChannelSuccess: { zh: '渠道已保存', en: 'Channel saved', ja: 'チャネルを保存しました' },
noChannels: { zh: '暂无渠道,请先添加', en: 'No channels yet, please add one', ja: 'チャネルがありません。追加してください' },
logs: { zh: '最近发送记录', en: 'Recent deliveries', ja: '最近の送信履歴' },
success: { zh: '成功', en: 'Success', ja: '成功' },
failed: { zh: '失败', en: 'Failed', ja: '失敗' },
noLogs: { zh: '暂无发送记录', en: 'No delivery records yet', ja: '送信履歴はまだありません' },
loadFailed: { zh: '加载通知设置失败', en: 'Failed to load notification settings', ja: '通知設定の読み込みに失敗しました' },
},
// 模型使用情况
aiUsage: {
title: { zh: '模型使用情况', en: 'Model Usage', ja: 'モデル利用状況' },