管理开放API的访问密钥,控制第三方应用的访问权限
+加载中...
++ 您还没有创建任何访问密钥。创建密钥后,您可以安全地将 VoiceHub 集成到第三方应用程序中。 +
+ +{{ apiKey.description || '暂无描述' }}
+{{ apiKey.creatorName || '未知' }}
++ {{ formatDate(apiKey.createdAt).split(' ')[0] }} +
+请妥善保管以下API密钥,它只会显示这一次。
++ 请立即复制并保存此API密钥,关闭此窗口后将无法再次查看完整密钥 +
+导出和导入数据库备份
+创建数据库备份文件
+ +从备份文件恢复数据
+ ++ 快速调整学生年级、班级或通过 Excel 批量修改 +
+正在获取最新的统计数据...
+{{ error }}
+ +洞察校园声音背后的互动趋势与影响力
++ {{ stat.label }} +
+近 7 日投稿量波动情况
++ 正在加载趋势数据... +
++ {{ panelStates.trends.error }} +
+ +暂无趋势数据
++ {{ panelStates.topSongs.error }} +
+ +暂无数据
++ {{ panelStates.activeUsers.error }} +
+ +暂无活跃用户
++ {{ panelStates.semesterComparison.error }} +
+ +暂无对比数据
+暂无在线用户
++ 执行系统底层维护任务,包括备份、恢复及全局数据重置 +
++ {{ card.desc }} +
+数据库维护建议
++ 建议每周进行一次全量备份。在执行“恢复备份”或“重置数据库”前,请务必先创建一份当前的数据备份,以免误操作造成不可挽回的损失。 +
++ 选择包含的内容 +
++ 备份文件将以 .json 格式生成并自动下载 +
++ 仅支持 VoiceHub 导出的 .json 格式 +
+ ++ 注意:覆盖模式将永久清空当前数据库中对应的表内容。此操作将导致现有会话中断。 +
++ 如果您的数据表 ID + 出现了断档或在手动操作数据库后无法自增,重置序列可以将数据库底层的计数器更新为当前 ID + 最大值 +1,从而解决 ID 冲突导致的写入失败问题。此操作不会修改任何现有数据。 +
++ 重置操作将永久删除系统中的所有 + 歌曲、投稿记录、排期文件、通知、日志及除您以外的用户账号。 +
+自定义系统邮件的主题与内容,支持变量替换
+请在左侧选择一个邮件模板
+即时推送系统公告、活动信息或重要提醒至指定群体
++ 将向系统中所有已注册的活跃用户发送此通知。 +
++ 未选择任何班级 +
++ 未选择任何用户 +
+{{ error }}
+{{ success }}
++ {{ form.content || '这里将显示通知的详细内容预览...' }} +
++ 预览图仅供参考布局,实际发送效果可能会根据用户设备显示语言和屏幕尺寸有所不同。 +
++ 定义校园广播的常规播放窗口,用户仅可在这些时间段内进行点歌或查看排期 +
+关于时段限制
++ 启用全局时段选择后,系统将仅在定义的时段内开放点歌功能。如果未设置开始或结束时间,系统将视为该边界不设限。 +
+加载中...
+获取数据失败
+{{ error }}
+点击“添加播出时段”按钮创建第一个播出时段
++ {{ playTime.description || '暂无详细描述...' }} +
+留空表示不限制开始时间
+留空表示不限制结束时间
++ 此操作不可恢复,相关的歌曲点播和排期的时段设置将受影响或被清除。 +
+管理全校师生点歌投稿的开放时间、限制数量及全局状态控制
++ {{ slot.description || '暂无详细描述...' }} +
+ + ++ {{ stat.label }} +
+填 0 表示不限制投稿总数
++ 删除时段后,原本属于此周期的投稿关联记录可能失效。建议先尝试 + 禁用 该时段。 +
+未找到匹配的歌曲
+暂无歌曲记录
+请从待排库中添加歌曲
+暂无详细申请记录
++ 定义系统的教学周期,点歌、排期及统计数据将基于当前活跃学期进行归档 +
+未设置当前活跃学期
+ +学期切换说明
++ 切换学期后,前端点歌界面将清空旧排期。所有历史数据会自动归档至对应学期下,您仍可在数据中心进行查看。 +
+加载中...
++ 创建于 {{ formatDate(sem.createdAt) }} +
+暂无学期数据
++ 提示:请按照统一的命名规范设置学期名称,这有助于数据归档后的检索与分析。 +
++ 管理站点全局属性、视觉识别、点歌逻辑及系统安全策略 +
+加载配置中...
+启用联合投稿
+允许用户添加联合投稿人并发起协作投稿
+启用投稿备注留言
+允许用户在投稿时附加公开或仅管理员可见的备注
+启用重播申请
+允许用户对本学期已播放过的歌曲再次申请
+启用投稿限额
+限制单个用户的点歌频率
++ 站点基础配置在保存后将立即对所有终端生效。请在修改关键业务逻辑(如投稿限额)前确保已知晓对现有用户的影响。 +
++ 配置 SMTP 服务以发送系统验证码、投稿通知及其他重要提醒 +
++ 通常与发件人账号一致,部分服务商强制要求一致。 +
+{{ testResult.message }}
++ {{ testResult.detail }} +
+{{ searchQuery ? '没有找到匹配的歌曲' : '暂无歌曲数据' }}
+{{ song.artist }}
+ {{ formatDate(song.createdAt) }} +投稿人: {{ rejectSongInfo.requester }}
+{{ songTitle }}
+ + {{ isPublic ? '公开备注' : '仅管理员可见' }} + ++ {{ content }} +
+系统共有 {{ totalUsers }} 位成员 · 权限与账户管理
+@{{ user.username }}
+账户状态
++ 角色/等级 +
++ 所在班级 +
++ {{ user.grade || '-' }} {{ user.class || '-' }} +
++ 最近活动 +
+{{ formatDate(user.lastLogin) }}
++ 最后登录IP +
+{{ user.lastLoginIp || '-' }}
+请填写以下账户详细信息以继续
++ 正在为 + {{ resetPasswordUser.name }} 修改登录凭据 +
+ ++ 支持 .xlsx 格式文件,请按模板要求上传 +
+查看完整的账户资料与操作记录
+{{ error }}
+ ++ {{ userSongs.user.grade || '' }} {{ userSongs.user.class || '' }} +
+即将绑定 {{ providerName }} 账号: {{ providerUsername }}
+登录您的VoiceHub账户
+加载绑定状态...
+
+
diff --git a/VoiceHub-main/app/components/Auth/Providers/GitHub/Icon.vue b/VoiceHub-main/app/components/Auth/Providers/GitHub/Icon.vue
new file mode 100644
index 0000000..80bc99e
--- /dev/null
+++ b/VoiceHub-main/app/components/Auth/Providers/GitHub/Icon.vue
@@ -0,0 +1,13 @@
+
+
+
diff --git a/VoiceHub-main/app/components/Auth/Providers/Google/Icon.vue b/VoiceHub-main/app/components/Auth/Providers/Google/Icon.vue
new file mode 100644
index 0000000..3bfdecf
--- /dev/null
+++ b/VoiceHub-main/app/components/Auth/Providers/Google/Icon.vue
@@ -0,0 +1,22 @@
+
+
+
diff --git a/VoiceHub-main/app/components/Auth/TwoFactorSetup.vue b/VoiceHub-main/app/components/Auth/TwoFactorSetup.vue
new file mode 100644
index 0000000..f7ff862
--- /dev/null
+++ b/VoiceHub-main/app/components/Auth/TwoFactorSetup.vue
@@ -0,0 +1,234 @@
+
+ + {{ isEnabled ? '您的账户已开启双重认证保护' : '未开启,建议开启以提升账户安全性' }} +
++ {{ method === 'totp' ? '请输入您的认证器应用生成的6位验证码' : '请输入发送到您邮箱的验证码' }} +
+加载中...
+当您投稿的歌曲被选中安排播放时通知您
+当您投稿的歌曲被播放时通知您
+当您投稿的歌曲获得新投票时通知您
+接收系统公告和其他重要通知
+设置通知自动刷新的时间间隔
++ UP主:{{ video?.artist }} +
+这首歌曲已经在列表中了,不能重复投稿
++ 您可以为它点赞支持,或者尝试搜索其他歌曲! +
+ + ++ {{ song.artist }} +
+ ++ 投稿者:{{ song.requester }} +
+歌曲导入操作已完成
+选择以往学期的歌曲并导入到当前学期
+扫描二维码以安全登录您的账号
+正在获取二维码...
++ 二维码已过期,请点击刷新 +
++ 请使用网易云音乐APP扫码登录 +
+
+
+ 登录成功,正在跳转... +
++ 说明:登录状态将保存到您的浏览器中,用于搜索播客等功能。 +
+查看并快速投稿您最近在网易云播放的歌曲
+1. 投稿时无需加入书名号
+2. 除DJ外,其他类型歌曲均接收(包括小语种)
+3. 禁止投递含有违规内容的歌曲
+4. 点播的歌曲将由管理员进行审核
+5. 审核通过后将安排在播放时段播出
+6. 提交即表明我已阅读投稿须知并已知该歌曲有概率无法播出
++ 7. + 本系统仅提供音乐搜索和播放管理功能,不存储任何音乐文件。所有音乐内容均来自第三方音乐平台,版权归原平台及版权方所有。用户点歌时请确保遵守相关音乐平台的服务条款,尊重音乐作品版权。我们鼓励用户支持正版音乐,在官方平台购买和收听喜爱的音乐作品。 +
+8. 最终解释权归广播站所有
++ 《{{ group.song.title }} - {{ group.song.artist }}》 + 已播放 + 已排期 +
+ ++ {{ + isSuperAdmin + ? '该歌曲已播放,您可以继续投稿' + : enableReplayRequests + ? '该歌曲已播放' + : '该歌曲已播放,无法进行投票操作' + }} +
++ 该歌曲已排期,无法进行投票操作 +
++ 该歌曲已在列表中,是否要投票支持? +
+
+
+ 《{{ group.title }} - {{ group.artist }}》 + 全部已播放 + 全部已排期 + {{ group.episodes.length }} 个剧集 +
+该合集有 {{ group.episodes.length }} 个剧集在列表中
+暂无排期信息
+点歌后等待管理员安排播出时间
+当前日期暂无排期
+请选择其他日期查看
+{{ submissionNoteDialog.note }}
+歌词加载中...
+{{ error }}
+暂无歌词
+{{ song.artist }}
+无法加载视频
++ {{ errorMessage }} +
+ +{{ errorDetails }}
+ {{ message }}
+ + ++ {{ subtitle }} +
++ {{ message }} +
+ + ++ Account Management +
+@{{ auth.user.value?.username }}
+绑定社交账号以便更快捷地登录系统
+为了您的账号安全,建议定期更换高强度密码
+{{ errorMessage }}
+错误代码
+
+ {{ errorCode }}
+
+ + 如果您认为这是一个错误,请联系系统管理员 +
+{{ isFirstLogin ? '请设置一个安全的密码' : '更新您的登录密码' }}
+您需要登录才能查看通知
+ +暂无通知
+投稿前请仔细阅读以下规则
+投稿时无需加入书名号
+除DJ外,其他类型歌曲均接收(包括小语种)
+禁止投递含有违规内容的歌曲
+每天夜自修静班前
++ PLAYBACK TIME +
++ Notification Settings +
+加载设置中...
+配置您希望在系统内接收的消息类型
+当您投稿的歌曲被选中播放时通知您
+当您投稿的歌曲播放完成时通知您
+当您投稿的歌曲获得投票时通知您
+接收系统重要通知和公告
+当投票数达到此阈值时才发送通知
+设置系统自动检查新通知的时间间隔
+绑定您的社交账号以接收实时推送通知
++ 当前绑定邮箱 +
+{{ userEmail }}
+绑定邮箱后,您可以接收到实时的邮件通知提醒。
++ 验证码已发送至您的邮箱,请在 5 分钟内完成验证。若未收到邮件,请检查垃圾箱。 +
++ 当前绑定 ID +
++ {{ localSettings.meowUserId }} +
++ 通过 MeoW 客户端接收更及时的系统通知和歌曲状态变更提醒。 +
+ + ++ 验证码已发送至 MeoW ID: + {{ meowUserId }},请在客户端查收。 +
+正在生成您的年度回忆...
+可能是网络出了点小差错
+快去点一首喜欢的歌吧
+{{headerSubtitle}}
+
+ 此邮件由系统自动发送,请勿回复。
+ 如有疑问,请联系管理员。{{#if ipAddress}}
This email was requested from: {{ipAddress}}{{/if}}
+
您好,{{name}}!
+您正在验证邮箱:{{email}}
+请在{{expiresInMinutes}}分钟内输入以下验证码完成验证:
+若非本人操作,请忽略本邮件。
+ `, + generic: ` +您投稿的歌曲《{{songTitle}}》已被安排播放。
+播放日期:{{playDate}}
+ {{#if playTimeName}} +播出时段:{{playTimeName}}{{#if playTimeRange}}({{playTimeRange}}){{/if}}
+ {{/if}} + {{#if message}} +{{message}}
+ {{/if}} + `, + songPlayed: ` +您投稿的歌曲《{{songTitle}}》已播放。
+ {{#if message}} +{{message}}
+ {{/if}} + `, + songVoted: ` +您投稿的歌曲《{{songTitle}}》获得了新的投票。
+当前共有 {{votesCount}} 个投票。
+ {{#if message}} +{{message}}
+ {{/if}} + ` + } + + /** 模板配置 */ + private builtinTemplates: Record< + string, + { + name: string + subject: string + contentType: string + headerSubtitle: string + actionText?: string + } + > = { + 'verification.code': { + name: '邮箱验证码', + subject: '邮箱验证码 | {{siteTitle}}', + contentType: 'verification', + headerSubtitle: '邮箱验证' + }, + 'notification.generic': { + name: '通用通知', + subject: '{{title}} | {{siteTitle}}通知推送', + contentType: 'generic', + headerSubtitle: '通知推送', + actionText: '查看详情' + }, + 'notification.songSelected': { + name: '歌曲被选中', + subject: '收到新选中 | {{siteTitle}}通知推送', + contentType: 'songSelected', + headerSubtitle: '通知推送' + }, + 'notification.songPlayed': { + name: '歌曲已播放', + subject: '歌曲已播放 | {{siteTitle}}通知推送', + contentType: 'songPlayed', + headerSubtitle: '通知推送' + }, + 'notification.songVoted': { + name: '收到新投票', + subject: '收到新投票 | {{siteTitle}}通知推送', + contentType: 'songVoted', + headerSubtitle: '通知推送' + } + } + + private constructor() {} + + static getInstance(): SmtpService { + if (!SmtpService.instance) { + SmtpService.instance = new SmtpService() + // 首次创建实例时,尝试初始化SMTP配置(异步执行,不阻塞) + SmtpService.instance.initializeSmtpConfig().catch((error) => { + console.log('SmtpService实例创建时初始化配置失败:', error.message) + }) + } + return SmtpService.instance + } + + // 暴露内置模板(只读) + getBuiltinTemplates(): Record]*style="[^"]*text-align: center[^"]*"[^>]*>.*?此邮件由系统自动发送,请勿回复。.*?<\/p>)/s,
+ `$1`.replace(
+ '此邮件由系统自动发送,请勿回复。',
+ `此邮件由系统自动发送,请勿回复。 通知推送
+ 此邮件由系统自动发送,请勿回复。
This email was requested from: ${formattedIP}`
+ )
+ )
+ }
+
+ const mailOptions = {
+ from: {
+ name: this.smtpConfig.fromName,
+ address: this.smtpConfig.fromEmail
+ },
+ to,
+ subject,
+ html: finalHtml,
+ text: textContent || finalHtml.replace(/<[^>]*>/g, '') // 简单的HTML转文本
+ }
+
+ try {
+ const result = await this.transporter!.sendMail(mailOptions)
+ console.log(`邮件发送成功: ${result.messageId}`)
+ return true
+ } catch (error: any) {
+ // 自动重试机制:如果是因为发件人地址不匹配导致的553/501错误,尝试使用认证用户作为发件人
+ if (
+ (error.responseCode === 553 || error.responseCode === 501) &&
+ this.smtpConfig.auth?.user
+ ) {
+ console.warn(
+ `SMTP发件人地址不匹配 (${error.responseCode}),正在尝试使用认证用户重试发送...`
+ )
+ try {
+ // 这里可以安全地使用 mailOptions,因为它在 try 块外部定义
+ const retryMailOptions = {
+ ...mailOptions,
+ from: {
+ ...mailOptions.from,
+ address: this.smtpConfig.auth.user
+ }
+ }
+ const result = await this.transporter!.sendMail(retryMailOptions)
+ console.log(`重试发送成功: ${result.messageId}`)
+ return true
+ } catch (retryError) {
+ console.error('重试发送失败:', retryError)
+ throw error // 如果重试也失败,抛出原始错误
+ }
+ }
+
+ console.error('发送邮件失败:', error)
+ throw error
+ }
+ }
+
+ /**
+ * 渲染模板:优先使用自定义模板,否则回退到内置模板
+ */
+ async renderTemplate(
+ key: string,
+ data: Record
如果您收到这封邮件,说明SMTP配置已经正确设置。',
+ undefined,
+ formattedIP
+ )
+
+ const success = await this.sendMail(to, subject, htmlContent, undefined, ipAddress)
+ return {
+ success,
+ message: success ? '测试邮件发送成功' : '测试邮件发送失败'
+ }
+ } catch (error) {
+ return {
+ success: false,
+ message: '测试邮件发送失败',
+ detail: error instanceof Error ? error.message : '未知错误'
+ }
+ }
+ }
+
+ /**
+ * 生成邮件HTML模板
+ */
+ generateEmailTemplate(
+ title: string,
+ content: string,
+ actionUrl?: string,
+ formattedIP?: string
+ ): string {
+ return `
+ ${this.smtpConfig?.fromName || '校园广播站'}
+ ${title}
+
+
+
+
+ 如有疑问,请联系管理员。${formattedIP ? `
This email was requested from: ${formattedIP}` : ''}
+