|
|
@@ -1,378 +1,135 @@
|
|
|
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' });
|
|
|
+ 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 time = new Date().getTime()
|
|
|
- await sendEmail(email, 'RunForge - 邮箱验证码',
|
|
|
- `<html lang="zh-CN">
|
|
|
-
|
|
|
-<head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>RunForge - 邮箱验证码</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap:10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .code {
|
|
|
- margin: 20px 0;
|
|
|
- font-size: 1.5em;
|
|
|
- text-align: center;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
-</head>
|
|
|
-
|
|
|
-<body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>RunForge - 邮箱验证码</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>尊敬的用户:</p>
|
|
|
- <p>您正在本站进行邮箱验证操作,如非您本人操作,请忽略此邮件。</p>
|
|
|
- <p>您的验证码为:</p>
|
|
|
- <div class="code">
|
|
|
- ${code}
|
|
|
- </div>
|
|
|
- <p class="important">验证码5分钟内有效,超时请重新获取</p>
|
|
|
- <p class="footer">Copyright © 2025 RunForge</p>
|
|
|
- </div>
|
|
|
-</body>
|
|
|
+ const bodyHtml = [
|
|
|
+ pEsc('尊敬的用户:'),
|
|
|
+ pEsc('您正在进行邮箱验证。如非本人操作,请忽略本邮件。'),
|
|
|
+ pEsc('您的验证码为:'),
|
|
|
+ codeBox(code),
|
|
|
+ notice('验证码 5 分钟内有效,超时请重新获取。', 'danger')
|
|
|
+ ].join('')
|
|
|
+ await sendEmail(
|
|
|
+ email,
|
|
|
+ 'RunForge|邮箱验证码',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '邮箱验证码',
|
|
|
+ headline: '邮箱验证码',
|
|
|
+ variant: 'default',
|
|
|
+ bodyHtml,
|
|
|
+ footerExtra: '请勿向他人泄露验证码。'
|
|
|
+ })
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
-</html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|邮箱换绑成功',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '邮箱换绑成功',
|
|
|
+ headline: '邮箱换绑成功',
|
|
|
+ variant: 'success',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
async registerSuccess(email, username) {
|
|
|
const time = new Date().getTime()
|
|
|
- await sendEmail(email, '您已成功注册RunForge账号',
|
|
|
- `<html lang="zh-CN">
|
|
|
-
|
|
|
-<head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>RunForge - 注册成功提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
-</head>
|
|
|
-
|
|
|
-<body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>RunForge - 注册成功提醒</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>尊敬的 ${username}:</p>
|
|
|
- <p>您已成功注册RunForge账号:</p>
|
|
|
- <div class="info">
|
|
|
- <p><strong>用户名:</strong> ${username}</p>
|
|
|
- <p><strong>注册时间:</strong> ${this.stramptoTime(time)}</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p class="footer">Copyright © 2025 RunForge</p>
|
|
|
- </div>
|
|
|
-</body>
|
|
|
-
|
|
|
-</html>`
|
|
|
+ const bodyHtml = [
|
|
|
+ pEsc(`尊敬的 ${username}:`),
|
|
|
+ pEsc('您已成功注册 RunForge 账号,账号信息如下:'),
|
|
|
+ panel([
|
|
|
+ kv('用户名', username),
|
|
|
+ kv('注册时间', this.stramptoTime(time))
|
|
|
+ ].join('')),
|
|
|
+ pEsc('感谢您的使用。', { last: true })
|
|
|
+ ].join('')
|
|
|
+ await sendEmail(
|
|
|
+ email,
|
|
|
+ 'RunForge|注册成功',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '注册成功',
|
|
|
+ headline: '注册成功',
|
|
|
+ variant: 'success',
|
|
|
+ bodyHtml,
|
|
|
+ footerExtra: '如非本人注册,请尽快联系客服处理。'
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
async updateSuccess(email, data) {
|
|
|
- await sendEmail(email, '乐跑账号更新成功提醒',
|
|
|
- `<html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>乐跑账号更新成功提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
-
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>乐跑账号更新好啦,宝宝快看看~ 🎉💖✨</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>亲爱的 ${data.name} 宝宝 🌸:</p>
|
|
|
- <p>嘻嘻~ 你已经成功更新了乐跑账号的登录信息啦 🥰💌:</p>
|
|
|
-
|
|
|
- <div class="info">
|
|
|
- <p><strong>学号:</strong> ${data.account} 🎓</p>
|
|
|
- <p><strong>年级:</strong> ${data.grade_id} 📚</p>
|
|
|
- <p><strong>学院:</strong> ${data.academy_name} 🏫</p>
|
|
|
- <p><strong>更新时间:</strong> ${this.stramptoTime(new Date().getTime())} ⏰</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p class="important">
|
|
|
- ${data.auto_run === 0
|
|
|
- ? '现在还没有帮宝宝开启自动乐跑呢 🐾💦 如果想要开始跑跑的话,记得登录后去RunForge手动点一下哦~ 🌈💕'
|
|
|
- : '已经为宝宝开启了自动乐跑啦 🏃♀️✨ 登录后系统会乖乖替你完成乐跑 💖 记得留意邮箱提醒哟~ 📬'}
|
|
|
- </p>
|
|
|
- <p class="important">宝宝可以随时在登录乐跑账号的设备上打开“智慧体育”小程序进行考试、查看跑步记录等操作,但记得不要在其他设备上登录“智慧体育”小程序哦 🚫📱,不然会失效,到时候又要重新登录啦 😢💦~</p>
|
|
|
- <p class="important">有问题随时喊RunForge客服小可爱呀 💕💌 我们都会耐心陪宝宝解决的哟 ✨🥰</p>
|
|
|
-
|
|
|
- <p class="footer">Copyright © 2025 RunForge 🌟</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
-
|
|
|
- </html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|乐跑账号更新成功',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '乐跑账号更新成功',
|
|
|
+ headline: '乐跑账号信息已更新',
|
|
|
+ variant: 'success',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
async lepaoOver(email, data) {
|
|
|
- await sendEmail(email, '🎉乐跑完成祝贺',
|
|
|
- `<!DOCTYPE html>
|
|
|
- <html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <title>🎉 RunForge - 乐跑完成祝贺</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- background-color: #fffaf4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
|
- }
|
|
|
- .container {
|
|
|
- max-width: 620px;
|
|
|
- margin: 40px auto;
|
|
|
- padding: 30px;
|
|
|
- background-color: #fff;
|
|
|
- border-radius: 14px;
|
|
|
- box-shadow: 0 8px 25px rgba(255, 183, 77, 0.2);
|
|
|
- border: 2px dashed #ffe0b2;
|
|
|
- }
|
|
|
- .head {
|
|
|
- text-align: center;
|
|
|
- margin-bottom: 30px;
|
|
|
- }
|
|
|
- .head h2 {
|
|
|
- color: #ff7043;
|
|
|
- font-size: 26px;
|
|
|
- margin: 0;
|
|
|
- }
|
|
|
- p {
|
|
|
- color: #444;
|
|
|
- font-size: 16px;
|
|
|
- line-height: 1.8;
|
|
|
- }
|
|
|
- .info {
|
|
|
- background-color: #fff3e0;
|
|
|
- border-left: 5px solid #ffb74d;
|
|
|
- padding: 15px 20px;
|
|
|
- margin: 25px 0;
|
|
|
- border-radius: 10px;
|
|
|
- }
|
|
|
- .info p {
|
|
|
- margin: 8px 0;
|
|
|
- }
|
|
|
- .footer {
|
|
|
- text-align: center;
|
|
|
- color: #bbb;
|
|
|
- font-size: 14px;
|
|
|
- margin-top: 40px;
|
|
|
- }
|
|
|
- .highlight {
|
|
|
- color: #ff5722;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>乐跑目标完成🎉</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>亲爱的 <span class="highlight">${data.name}</span> 宝宝 ꒰。•ᴗ•。꒱:</p>
|
|
|
-
|
|
|
- <p>
|
|
|
- ✨恭喜恭喜~你已经火力全开地完成了设定乐跑目标啦!🎊<br>
|
|
|
- 不得不说,你就是我们心中的 <strong>小跑神</strong> 🌟💨,坚持到最后真的超级棒!💪( •̀ᄇ• ́)و
|
|
|
- </p>
|
|
|
-
|
|
|
- <p>
|
|
|
- 跑过的风,晒过的阳,每一步都是你努力的见证!🌤️🍃<br>
|
|
|
- 我们在远程为你打Call中 📣📣📣 ~真的好佩服佩服你!
|
|
|
- </p>
|
|
|
-
|
|
|
- <p>
|
|
|
- 这一学期和你一起“并肩作战”,我们感到非常幸运!🧡<br>
|
|
|
- 希望你收获了健康,也收获了快乐~o(≧▽≦)o
|
|
|
- </p>
|
|
|
-
|
|
|
- <p>
|
|
|
- 📬 如果有任何问题,随时给我们戳小窗~我们在线等你哟 (´▽`)ノ♪
|
|
|
- </p>
|
|
|
-
|
|
|
- <p>
|
|
|
- 🥳 祝你假期躺赢、学习加buff、每天都开心到冒泡泡!✧*。٩(ˊᗜˋ*)و✧*。<br>
|
|
|
- 📚 新学期我们一起继续加速奔跑吧~Let's gooo!🔥🔥
|
|
|
- </p>
|
|
|
-
|
|
|
- <p>
|
|
|
- 🧡我们已经为您关闭了自动乐跑功能,如您需要继续乐跑,还可前往RunForge自行开启哦!
|
|
|
- </p>
|
|
|
-
|
|
|
- <p class="footer">Copyright © 2025 RunForge</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
- </html>
|
|
|
- `
|
|
|
+ const bodyHtml = [
|
|
|
+ pEsc(`尊敬的 ${data.name}:`),
|
|
|
+ pEsc('您设定的乐跑目标已全部完成,系统已为您关闭自动乐跑功能。'),
|
|
|
+ pEsc('感谢您的配合。若仍需跑步,可在 RunForge 中按需重新开启相关功能。'),
|
|
|
+ notice('如有疑问,请通过 RunForge 工单反馈。', 'neutral'),
|
|
|
+ pEsc('祝您学习生活愉快。', { last: true })
|
|
|
+ ].join('')
|
|
|
+
|
|
|
+ await sendEmail(
|
|
|
+ email,
|
|
|
+ 'RunForge|乐跑目标已完成',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '乐跑目标已完成',
|
|
|
+ headline: '乐跑目标已完成',
|
|
|
+ variant: 'success',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
@@ -388,697 +145,262 @@ class emailTemplate {
|
|
|
: '—'
|
|
|
const timeStr = timeSec > 0 ? this.formatSecondsToMinSec(timeSec) : '—'
|
|
|
|
|
|
- let goalHtml = ''
|
|
|
+ let goalLines = ''
|
|
|
if (target_count === 0) {
|
|
|
- goalHtml = `
|
|
|
- <p><strong>累计次数:</strong> ${total_num} 次 ✨</p>`
|
|
|
+ goalLines = kv('累计次数', `${total_num} 次`)
|
|
|
} else {
|
|
|
- const remain = Math.max(0, target_count - total_num)
|
|
|
- const hitGoal = total_num >= target_count
|
|
|
- goalHtml = `
|
|
|
- <p><strong>目标次数:</strong> ${target_count} 次 🎯</p>
|
|
|
- <p><strong>累计次数:</strong> ${total_num} 次 ✨</p>`
|
|
|
+ goalLines = [
|
|
|
+ kv('目标次数', `${target_count} 次`),
|
|
|
+ kv('累计次数', `${total_num} 次`)
|
|
|
+ ].join('')
|
|
|
}
|
|
|
|
|
|
- await sendEmail(email, '乐跑成功提醒',
|
|
|
- `<html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>乐跑成功提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- text-indent: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
-
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>乐跑成功啦 🎉💖</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>亲爱的 ${data.name} 宝宝:</p>
|
|
|
- <p>耶耶耶~ 我们已经乖乖帮你完成了一次乐跑啦 ✨🏃♀️💨</p>
|
|
|
-
|
|
|
- <div class="info">
|
|
|
- <p><strong>学号:</strong> ${data.account}</p>
|
|
|
- <p><strong>跑区:</strong> ${passTit} 🌈</p>
|
|
|
- <p><strong>跑步时间:</strong> ${timeStr} ⏱️</p>
|
|
|
- <p><strong>平均配速:</strong> ${paceStr} 🐇</p>
|
|
|
- <p><strong>跑步距离:</strong> ${distanceKm || '—'} Km 💕</p>
|
|
|
- ${goalHtml}
|
|
|
- </div>
|
|
|
-
|
|
|
- <p class="important">如果宝宝开启了自动乐跑,要记得不要在其他设备上登录“智慧体育”小程序哦 🚫📱,不然登录就会失效,要重新来一次啦~</p>
|
|
|
- <p class="important">有问题随时找RunForge客服小可爱呀 💌 我们会陪你耐心解决的~</p>
|
|
|
- <p class="footer">Copyright © 2025 RunForge ✨</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
-
|
|
|
- </html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|乐跑成功通知',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '乐跑成功',
|
|
|
+ headline: '乐跑已成功记录',
|
|
|
+ variant: 'success',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
async lepaoFail(email, data) {
|
|
|
const time = new Date().getTime()
|
|
|
- await sendEmail(email, '乐跑失败提醒',
|
|
|
- `<html lang="zh-CN">
|
|
|
-
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>乐跑失败提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
-
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>乐跑没有成功呢 😢💦</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>亲爱的 ${data.name ?? data.account} 宝宝 🌸:</p>
|
|
|
- <p>我们刚刚尝试帮你完成乐跑的时候,遇到了一点小意外呢 💔💦:</p>
|
|
|
-
|
|
|
- <div class="info">
|
|
|
- <p><strong>学号:</strong> ${data.account} 🎓</p>
|
|
|
- <p><strong>时间:</strong> ${this.stramptoTime(time)} ⏰</p>
|
|
|
- <p><strong>失败原因:</strong> ${data.reason} 😭</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p class="important">宝宝如果是登录失效的话 🥺✨,要记得重新启动RunForge乐跑登录器,再登录“智慧体育”小程序就可以啦 💕</p>
|
|
|
- <p class="important">如果还是不懂,随时可以来找RunForge客服小可爱哟 💌 我们会耐心陪宝宝解决问题的 🌈🥰</p>
|
|
|
-
|
|
|
- <p class="footer">Copyright © 2025 RunForge 💖</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
-
|
|
|
- </html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|乐跑失败通知',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '乐跑失败',
|
|
|
+ headline: '乐跑未能完成',
|
|
|
+ variant: 'danger',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
async orderNewReply(email, data) {
|
|
|
- await sendEmail(email, 'RunForge - 工单状态更新提醒',
|
|
|
- `<html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>工单状态更新提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
-
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>工单状态更新提醒</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>尊敬的用户:</p>
|
|
|
- <p>您编号为${data.id}的工单有新的回复:</p>
|
|
|
- <div class="info">
|
|
|
- <p><strong>回复内容:</strong> ${data.content}</p>
|
|
|
- <p><strong>回复时间:</strong> ${this.stramptoTime(new Date().getTime())}</p>
|
|
|
- </div>
|
|
|
- <p class="important">${data.files.length > 0 ? '当前回复内含有附件,前往RunForge官网内查看' : ''}</p>
|
|
|
- <p class="important">请前往RunForge官网回复工单,请勿直接回复邮件。</p>
|
|
|
- <p class="footer">Copyright © 2025 RunForge</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
-
|
|
|
- </html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|工单新回复',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '工单状态更新',
|
|
|
+ headline: '工单有新回复',
|
|
|
+ variant: 'default',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
async sendCountRequestNotifyAdmins(email, data) {
|
|
|
- await sendEmail(email, 'RunForge - 赠送次数待审核提醒',
|
|
|
- `<html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>赠送次数待审核提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- text-indent: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
-
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>赠送次数待审核提醒</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>尊敬的管理员:</p>
|
|
|
- <p>系统收到一条新的赠送次数申请,请及时审核处理:</p>
|
|
|
- <div class="info">
|
|
|
- <p><strong>申请ID:</strong> ${data.requestId}</p>
|
|
|
- <p><strong>赠送人:</strong> ${data.senderUsername}</p>
|
|
|
- <p><strong>接收人:</strong> ${data.receiverUsername}</p>
|
|
|
- <p><strong>赠送次数:</strong> ${data.count}</p>
|
|
|
- <p><strong>申请时间:</strong> ${this.stramptoTime(data.createTime)}</p>
|
|
|
- </div>
|
|
|
- <p class="important">请前往 RunForge 管理后台进行审核,请勿直接回复邮件。</p>
|
|
|
- <p class="footer">Copyright © 2025 RunForge</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
-
|
|
|
- </html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|赠送次数待审核',
|
|
|
+ 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 = `RunForge - 乐跑异常告警`
|
|
|
- await sendEmail(email, subject,
|
|
|
- `<html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>RunForge - 乐跑异常告警</title>
|
|
|
- <style>
|
|
|
- body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin:0; padding:0; }
|
|
|
- .container { width: 86%; max-width: 760px; margin: 20px auto; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
|
|
|
- .head { text-align:center; color:#2c3e50; }
|
|
|
- .info { background-color:#ecf0f1; padding: 14px 16px; border-radius: 6px; margin: 18px 0; }
|
|
|
- .info p { margin: 8px 0; text-indent: 0; word-break: break-all; }
|
|
|
- .tag { display:inline-block; background:#ffe8e6; color:#c0392b; padding: 2px 8px; border-radius: 999px; font-size: 12px; }
|
|
|
- .footer { font-size: 12px; text-align:center; color:#7f8c8d; margin-top: 30px; }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>乐跑异常告警 <span class="tag">非常见错误</span></h2>
|
|
|
- </div>
|
|
|
- <p>尊敬的管理员:</p>
|
|
|
- <p>系统在自动乐跑流程中捕获到一条非常见错误。请关注是否为接口变更、代理异常或程序缺陷。</p>
|
|
|
- <div class="info">
|
|
|
- <p><strong>账号:</strong> ${safe(data.account)}</p>
|
|
|
- <p><strong>姓名:</strong> ${safe(data.name)}</p>
|
|
|
- <p><strong>任务:</strong> ${safe(data.taskType)}</p>
|
|
|
- <p><strong>TraceId:</strong> ${safe(data.traceId)}</p>
|
|
|
- <p><strong>时间:</strong> ${this.stramptoTime(time)}</p>
|
|
|
- <p><strong>错误信息:</strong> ${safe(data.reason)}</p>
|
|
|
- <p><strong>错误码:</strong> ${safe(data.code)}</p>
|
|
|
- </div>
|
|
|
- <p class="footer">Copyright © 2025 RunForge</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
- </html>`
|
|
|
+ const subject = 'RunForge|乐跑异常告警'
|
|
|
+
|
|
|
+ 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) {
|
|
|
- await sendEmail(email, 'RunForge - 赠送次数审核通过提醒',
|
|
|
- `<html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>赠送次数审核通过提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- text-indent: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
-
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>赠送次数审核通过提醒</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>尊敬的用户:</p>
|
|
|
- <p>您收到的一笔赠送次数申请已审核通过,次数已到账:</p>
|
|
|
- <div class="info">
|
|
|
- <p><strong>赠送人:</strong> ${data.senderUsername}</p>
|
|
|
- <p><strong>到账次数:</strong> ${data.count}</p>
|
|
|
- <p><strong>审核时间:</strong> ${this.stramptoTime(data.reviewTime)}</p>
|
|
|
- </div>
|
|
|
- <p class="footer">Copyright © 2025 RunForge</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
-
|
|
|
- </html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|赠送次数审核通过',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '赠送次数审核通过',
|
|
|
+ headline: '赠送次数已到账',
|
|
|
+ variant: 'success',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
async sendCountRequestRejected(email, data) {
|
|
|
- await sendEmail(email, 'RunForge - 赠送次数审核失败提醒',
|
|
|
- `<html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>赠送次数审核失败提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- text-indent: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
-
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>赠送次数审核失败提醒</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>尊敬的用户:</p>
|
|
|
- <p>您发起的一笔赠送次数申请未通过审核,已将次数退回到您的账户:</p>
|
|
|
- <div class="info">
|
|
|
- <p><strong>接收人:</strong> ${data.receiverUsername}</p>
|
|
|
- <p><strong>退回次数:</strong> ${data.count}</p>
|
|
|
- <p><strong>审核时间:</strong> ${this.stramptoTime(data.reviewTime)}</p>
|
|
|
- <p><strong>拒绝原因:</strong> ${data.rejectReason}</p>
|
|
|
- </div>
|
|
|
- <p class="important">如有疑问,请前往 RunForge 联系客服处理。</p>
|
|
|
- <p class="footer">Copyright © 2025 RunForge</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
-
|
|
|
- </html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|赠送次数审核未通过',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '赠送次数审核未通过',
|
|
|
+ headline: '赠送次数审核未通过',
|
|
|
+ variant: 'warning',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
async powerCheck(email, data) {
|
|
|
- await sendEmail(email, '宿舍电费提醒',
|
|
|
- `<html lang="zh-CN">
|
|
|
- <head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>宿舍电费提醒</title>
|
|
|
- <style>
|
|
|
- body {
|
|
|
- font-family: Arial, sans-serif;
|
|
|
- background-color: #f4f4f4;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .container {
|
|
|
- width: 80%;
|
|
|
- margin: 20px auto;
|
|
|
- background-color: #fff;
|
|
|
- padding: 20px;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .head {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- color: #2c3e50;
|
|
|
- }
|
|
|
-
|
|
|
- p {
|
|
|
- font-size: 16px;
|
|
|
- color: #34495e;
|
|
|
- line-height: 1.6;
|
|
|
- text-indent: 2em;
|
|
|
- }
|
|
|
-
|
|
|
- .info {
|
|
|
- background-color: #ecf0f1;
|
|
|
- padding: 15px;
|
|
|
- border-radius: 5px;
|
|
|
- margin: 20px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .info p {
|
|
|
- margin: 5px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .important {
|
|
|
- color: #e74c3c;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .footer {
|
|
|
- font-size: 14px;
|
|
|
- text-align: center;
|
|
|
- color: #7f8c8d;
|
|
|
- margin-top: 50px;
|
|
|
- }
|
|
|
- </style>
|
|
|
- </head>
|
|
|
-
|
|
|
- <body>
|
|
|
- <div class="container">
|
|
|
- <div class="head">
|
|
|
- <h2>宿舍电费提醒</h2>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p>尊敬的用户:</p>
|
|
|
- <p>${data.building}${data.room}宿舍电费已低于预设提醒阈值,请留意:</p>
|
|
|
-
|
|
|
- <div class="info">
|
|
|
- <p><strong>校区:</strong> ${data.area}</p>
|
|
|
- <p><strong>楼栋:</strong> ${data.building}</p>
|
|
|
- <p><strong>寝室号:</strong> ${data.room}</p>
|
|
|
- <p><strong>当前余额:</strong> ¥${data.now_balance}</p>
|
|
|
- <p><strong>提醒阈值:</strong> ¥${data.lowest}</p>
|
|
|
- <p><strong>扣费时间:</strong> ${data.now_change_time}</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p class="important">当前电费已低于预设提醒阈值${Number(data.lowest - data.now_balance).toFixed(2)}元,请及时充值</p>
|
|
|
- <p class="footer">Copyright © 2025 RunForge ✨</p>
|
|
|
- </div>
|
|
|
- </body>
|
|
|
-
|
|
|
- </html>`
|
|
|
+ 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,
|
|
|
+ 'RunForge|宿舍电费提醒',
|
|
|
+ renderEmail({
|
|
|
+ pageTitle: '宿舍电费提醒',
|
|
|
+ headline: '宿舍电费余额提醒',
|
|
|
+ variant: 'warning',
|
|
|
+ bodyHtml
|
|
|
+ })
|
|
|
)
|
|
|
}
|
|
|
|
|
|
- // 时长计算
|
|
|
formatSecondsToMinSec(totalSeconds) {
|
|
|
- const minutes = Math.floor(totalSeconds / 60);
|
|
|
- const seconds = totalSeconds % 60;
|
|
|
+ const minutes = Math.floor(totalSeconds / 60)
|
|
|
+ const seconds = totalSeconds % 60
|
|
|
|
|
|
- return `${minutes}分${seconds.toString().padStart(2, '0')}秒`;
|
|
|
+ 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);
|
|
|
+ const paceInSeconds = seconds / kilometers
|
|
|
+ const minutes = Math.floor(paceInSeconds / 60)
|
|
|
+ const remainingSeconds = Math.round(paceInSeconds % 60)
|
|
|
|
|
|
- return `${minutes}'${remainingSeconds.toString().padStart(2, '0')}''`;
|
|
|
+ return `${minutes}'${remainingSeconds.toString().padStart(2, '0')}''`
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const EmailTemplate = new emailTemplate()
|
|
|
-module.exports = EmailTemplate
|
|
|
+module.exports = EmailTemplate
|