| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- const sendEmail = require('./Email')
- const {
- renderEmail,
- escapeNl2br,
- pEsc,
- kv,
- panel,
- notice,
- codeBox
- } = require('./emailLayout')
- class emailTemplate {
- stramptoTime(time) {
- if (time < 10)
- return ''
- return new Date(+time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
- }
- async checkEmail(email, code) {
- const bodyHtml = [
- pEsc('尊敬的用户:'),
- pEsc('您正在进行邮箱验证。如非本人操作,请忽略本邮件。'),
- pEsc('您的验证码为:'),
- codeBox(code),
- notice('验证码 5 分钟内有效,超时请重新获取。', 'danger')
- ].join('')
- await sendEmail(
- email,
- '🔑 邮箱验证码',
- renderEmail({
- pageTitle: '邮箱验证码',
- headline: '🔑 邮箱验证码',
- variant: 'default',
- bodyHtml,
- footerExtra: '请勿向他人泄露验证码。'
- })
- )
- }
- async bindEmailSuccess(email) {
- const time = this.stramptoTime(new Date().getTime())
- const bodyHtml = [
- pEsc('尊敬的用户:'),
- pEsc('您的 RunForge 账号绑定邮箱已成功更新。'),
- panel(kv('操作时间', time)),
- pEsc('如非本人操作,请尽快联系客服处理。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '✅ 邮箱换绑成功',
- renderEmail({
- pageTitle: '邮箱换绑成功',
- headline: '✅ 邮箱换绑成功',
- variant: 'success',
- bodyHtml
- })
- )
- }
- async registerSuccess(email, username) {
- const time = new Date().getTime()
- const bodyHtml = [
- pEsc(`尊敬的 ${username}:`),
- pEsc('您已成功注册 RunForge 账号,账号信息如下:'),
- panel([
- kv('用户名', username),
- kv('注册时间', this.stramptoTime(time))
- ].join('')),
- pEsc('感谢您的使用。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '✅ 注册成功',
- renderEmail({
- pageTitle: '注册成功',
- headline: '✅ 注册成功',
- variant: 'success',
- bodyHtml,
- footerExtra: '如非本人注册,请尽快联系客服处理。'
- })
- )
- }
- async updateSuccess(email, data) {
- const autoNote = data.auto_run === 0
- ? '当前未开启自动乐跑。如需跑步,请登录 RunForge 后手动发起。'
- : '已为您开启自动乐跑,系统将按计划代为完成跑步任务,请留意后续邮件提醒。'
- const bodyHtml = [
- pEsc(`尊敬的 ${data.name}:`),
- pEsc('您的乐跑账号登录信息已更新成功,详情如下:'),
- panel([
- kv('学号', data.account),
- kv('年级', data.grade_id),
- kv('学院', data.academy_name),
- kv('更新时间', this.stramptoTime(new Date().getTime()))
- ].join('')),
- notice(autoNote, 'neutral'),
- notice('请在当前登录乐跑账号的设备上使用「智慧体育」小程序;请勿在其他设备登录该小程序,以免登录状态失效并需重新绑定。', 'neutral'),
- pEsc('如需帮助,请联系 RunForge 客服。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '✅ 乐跑账号更新成功',
- renderEmail({
- pageTitle: '乐跑账号更新成功',
- headline: '✅ 乐跑账号信息已更新',
- variant: 'success',
- bodyHtml
- })
- )
- }
- async lepaoOver(email, data) {
- const bodyHtml = [
- pEsc(`尊敬的 ${data.name}:`),
- pEsc('您设定的乐跑目标已全部完成,系统已为您关闭自动乐跑功能。'),
- pEsc('感谢您的配合。若仍需跑步,可在 RunForge 中按需重新开启相关功能。'),
- notice('如有疑问,请通过 RunForge 工单反馈。', 'neutral'),
- pEsc('祝您学习生活愉快。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '🏁 乐跑目标已完成',
- renderEmail({
- pageTitle: '乐跑目标已完成',
- headline: '🏁 乐跑目标已全部完成',
- variant: 'success',
- bodyHtml
- })
- )
- }
- async lepaoSuccess(email, data) {
- const target_count = Number(data.target_count) || 0
- const total_num = Number(data.total_num) || 0
- const timeSec = Number(data.time) || 0
- const distanceKm = Number(data.distance) || 0
- const passTit = data.pass_tit != null && data.pass_tit !== '' ? data.pass_tit : '—'
- const paceStr =
- distanceKm > 0 && timeSec > 0
- ? this.calculatePace(timeSec, distanceKm)
- : '—'
- const timeStr = timeSec > 0 ? this.formatSecondsToMinSec(timeSec) : '—'
- let goalLines = ''
- if (target_count === 0) {
- goalLines = kv('累计次数', `${total_num} 次`)
- } else {
- goalLines = [
- kv('目标次数', `${target_count} 次`),
- kv('累计次数', `${total_num} 次`)
- ].join('')
- }
- const bodyHtml = [
- pEsc(`尊敬的 ${data.name}:`),
- pEsc('系统已成功代您完成一次乐跑,摘要如下:'),
- panel([
- kv('学号', data.account),
- kv('跑区', passTit),
- kv('用时', timeStr),
- kv('平均配速', paceStr),
- kv('距离', `${distanceKm || '—'} km`),
- goalLines
- ].join('')),
- notice('若已开启自动乐跑,请勿在除更新乐跑账号信息以外的其他设备登录「智慧体育」小程序,以免登录状态失效。', 'neutral')
- ].join('')
- await sendEmail(
- email,
- '✅ 乐跑成功通知',
- renderEmail({
- pageTitle: '乐跑成功',
- headline: '✅ 乐跑已成功记录',
- variant: 'success',
- bodyHtml
- })
- )
- }
- async lepaoFail(email, data) {
- const time = new Date().getTime()
- const displayName = data.name ?? data.account
- const bodyHtml = [
- pEsc(`尊敬的 ${displayName}:`),
- pEsc('系统在尝试执行乐跑任务时未成功,详情如下:'),
- panel([
- kv('学号', data.account),
- kv('时间', this.stramptoTime(time)),
- kv('原因', data.reason)
- ].join('')),
- notice('若为登录失效,请在 RunForge 乐跑登录器中重新完成登录后再试。', 'warning'),
- pEsc('若问题持续存在,请联系 RunForge 客服并说明学号与失败时间,便于排查。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '❌ 乐跑失败通知',
- renderEmail({
- pageTitle: '乐跑失败',
- headline: '❌ 乐跑未能完成',
- variant: 'danger',
- bodyHtml
- })
- )
- }
- async orderNewReply(email, data) {
- const files = Array.isArray(data.files) ? data.files : []
- const filesNote = files.length > 0
- ? notice('本回复包含附件,请登录 RunForge 官网,在工单详情中查看。', 'neutral')
- : ''
- const bodyHtml = [
- pEsc('尊敬的用户:'),
- pEsc(`您提交的工单(编号 ${data.id})有新的回复:`),
- panel([
- '<p><strong>回复内容:</strong></p>',
- `<p style="margin-top:8px;line-height:1.55;">${escapeNl2br(data.content)}</p>`,
- kv('回复时间', this.stramptoTime(new Date().getTime()))
- ].join('')),
- filesNote,
- notice('请登录 RunForge 官网,在工单详情中回复。', 'warning'),
- pEsc('感谢您的理解与配合。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '💬 工单新回复',
- renderEmail({
- pageTitle: '工单状态更新',
- headline: '💬 工单有新回复',
- variant: 'default',
- bodyHtml
- })
- )
- }
- async sendCountRequestNotifyAdmins(email, data) {
- const bodyHtml = [
- pEsc('尊敬的管理员:'),
- pEsc('系统收到一条新的「赠送次数」申请,请及时登录管理后台审核处理。'),
- panel([
- kv('申请 ID', String(data.requestId)),
- kv('赠送人', data.senderUsername),
- kv('接收人', data.receiverUsername),
- kv('赠送次数', String(data.count)),
- kv('申请时间', this.stramptoTime(data.createTime))
- ].join('')),
- notice('请前往 RunForge 管理后台完成审核。', 'warning'),
- pEsc('谢谢。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '⏳ 赠送次数待审核',
- renderEmail({
- pageTitle: '赠送次数待审核',
- headline: '⏳ 赠送次数待审核',
- variant: 'warning',
- bodyHtml
- })
- )
- }
- async lepaoAdminWarning(email, data) {
- const time = new Date().getTime()
- const safe = (v) => (v === undefined || v === null) ? '' : String(v)
- const subject = '🚨 乐跑异常告警'
- const bodyHtml = [
- pEsc('尊敬的管理员:'),
- pEsc('自动乐跑流程中出现非常见错误,请关注是否为接口变更、网络异常或程序缺陷,并及时跟进。'),
- panel([
- kv('账号', safe(data.account)),
- kv('姓名', safe(data.name)),
- kv('任务', safe(data.taskType)),
- kv('TraceId', safe(data.traceId)),
- kv('时间', this.stramptoTime(time)),
- kv('错误信息', safe(data.reason)),
- kv('错误码', safe(data.code))
- ].join('')),
- pEsc('以上为系统自动告警,如需更多信息请结合服务端日志进一步排查。', { last: true })
- ].join('')
- await sendEmail(
- email,
- subject,
- renderEmail({
- pageTitle: '乐跑异常告警',
- headline: '🚨 乐跑异常告警',
- headlineSuffixHtml: '<span class="tag">非常见错误</span>',
- variant: 'danger',
- bodyHtml
- })
- )
- }
- async sendCountRequestApproved(email, data) {
- const bodyHtml = [
- pEsc('尊敬的用户:'),
- pEsc('您收到的一笔赠送次数申请已通过审核,次数已到账:'),
- panel([
- kv('赠送人', data.senderUsername),
- kv('到账次数', String(data.count)),
- kv('审核时间', this.stramptoTime(data.reviewTime))
- ].join('')),
- pEsc('您可在 RunForge 中查看账户余额与明细。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '✅ 赠送次数审核通过',
- renderEmail({
- pageTitle: '赠送次数审核通过',
- headline: '✅ 赠送次数已到账',
- variant: 'success',
- bodyHtml
- })
- )
- }
- async sendCountRequestRejected(email, data) {
- const bodyHtml = [
- pEsc('尊敬的用户:'),
- pEsc('您发起的一笔赠送次数申请未通过审核,相关次数已退回至您的账户:'),
- panel([
- kv('接收人', data.receiverUsername),
- kv('退回次数', String(data.count)),
- kv('审核时间', this.stramptoTime(data.reviewTime)),
- kv('拒绝原因', data.rejectReason)
- ].join('')),
- notice('如有疑问,请通过 RunForge 联系客服处理。', 'neutral'),
- pEsc('感谢您的理解。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '⚠️ 赠送次数审核未通过',
- renderEmail({
- pageTitle: '赠送次数审核未通过',
- headline: '⚠️ 赠送次数审核未通过',
- variant: 'warning',
- bodyHtml
- })
- )
- }
- async powerCheck(email, data) {
- const gap = Number(data.lowest) - Number(data.now_balance)
- const gapStr = Number.isFinite(gap) ? gap.toFixed(2) : ''
- const bodyHtml = [
- pEsc('尊敬的用户:'),
- pEsc(`${data.building}${data.room} 宿舍电费已低于您设定的提醒阈值,请关注余额并及时充值。`),
- panel([
- kv('校区', data.area),
- kv('楼栋', data.building),
- kv('寝室号', data.room),
- kv('当前余额', `¥${data.now_balance}`),
- kv('提醒阈值', `¥${data.lowest}`),
- kv('最近扣费时间', data.now_change_time)
- ].join('')),
- notice(
- gapStr !== ''
- ? `当前余额已低于提醒阈值 ${gapStr} 元,建议尽快充值以免影响用电。`
- : '当前余额已低于提醒阈值,建议尽快充值以免影响用电。',
- 'warning'
- ),
- pEsc('您可在 RunForge 中调整提醒阈值或管理电费订阅。', { last: true })
- ].join('')
- await sendEmail(
- email,
- '⚡ 宿舍电费提醒',
- renderEmail({
- pageTitle: '宿舍电费提醒',
- headline: '⚡ 宿舍电费余额提醒',
- variant: 'warning',
- bodyHtml
- })
- )
- }
- formatSecondsToMinSec(totalSeconds) {
- const minutes = Math.floor(totalSeconds / 60)
- const seconds = totalSeconds % 60
- return `${minutes}分${seconds.toString().padStart(2, '0')}秒`
- }
- calculatePace(seconds, kilometers) {
- const paceInSeconds = seconds / kilometers
- const minutes = Math.floor(paceInSeconds / 60)
- const remainingSeconds = Math.round(paceInSeconds % 60)
- return `${minutes}'${remainingSeconds.toString().padStart(2, '0')}''`
- }
- }
- const EmailTemplate = new emailTemplate()
- module.exports = EmailTemplate
|