Ship signup sheet, subscribe reminders, cover crop, and theme updates.
Wire real WeChat app config, prefer activity cover over posters, and add admin cover cropping for the home banner. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+67
-1
@@ -3,6 +3,7 @@ const express = require('express')
|
||||
const QRCode = require('qrcode')
|
||||
const db = require('../lib/db')
|
||||
const passwords = require('../lib/passwords')
|
||||
const wechat = require('../lib/wechat')
|
||||
const { uploader } = require('../lib/upload')
|
||||
const { adminAuth } = require('../lib/auth')
|
||||
const { uid, token, ok, fail, wrap, str } = require('../lib/http')
|
||||
@@ -162,6 +163,69 @@ router.delete('/activities/:id', auth, (req, res) => {
|
||||
ok(res, { id: req.params.id })
|
||||
})
|
||||
|
||||
router.get('/settings', auth, (req, res) => {
|
||||
const settings = db.read().settings || {}
|
||||
ok(res, {
|
||||
subscribeTmplId: settings.subscribeTmplId || '',
|
||||
wxConfigured: wechat.wxConfigured()
|
||||
})
|
||||
})
|
||||
|
||||
router.put('/settings', auth, (req, res) => {
|
||||
const subscribeTmplId = str(req.body && req.body.subscribeTmplId, 80)
|
||||
const settings = db.update((data) => {
|
||||
if (!data.settings) data.settings = {}
|
||||
data.settings.subscribeTmplId = subscribeTmplId
|
||||
return data.settings
|
||||
})
|
||||
ok(res, settings)
|
||||
})
|
||||
|
||||
router.post(
|
||||
'/activities/:id/remind',
|
||||
auth,
|
||||
wrap(async (req, res) => {
|
||||
const data = db.read()
|
||||
const activity = data.activities.find((item) => item.id === req.params.id)
|
||||
if (!activity) return fail(res, 404, '活动不存在')
|
||||
const tmplId = str(data.settings && data.settings.subscribeTmplId)
|
||||
if (!tmplId) return fail(res, 400, '请先在后台配置订阅消息模板 ID')
|
||||
|
||||
const targets = data.signups.filter((item) => item.activityId === activity.id && item.remindAccepted)
|
||||
if (!targets.length) return fail(res, 400, '没有可提醒的报名用户(需用户报名时同意订阅)')
|
||||
|
||||
if (!wechat.wxConfigured()) {
|
||||
return ok(res, {
|
||||
demo: true,
|
||||
total: targets.length,
|
||||
sent: targets.length,
|
||||
failed: 0,
|
||||
message: '演示模式:未配置微信密钥,已模拟发送成功'
|
||||
})
|
||||
}
|
||||
|
||||
const page = `pages/detail/detail?id=${activity.id}`
|
||||
const payload = wechat.buildRemindData(activity)
|
||||
let sent = 0
|
||||
const failures = []
|
||||
for (const signup of targets) {
|
||||
try {
|
||||
const result = await wechat.sendSubscribeMessage({
|
||||
openid: signup.openid,
|
||||
templateId: tmplId,
|
||||
page,
|
||||
data: payload
|
||||
})
|
||||
if (result.ok) sent += 1
|
||||
else failures.push({ openid: signup.openid, errmsg: result.errmsg || String(result.errcode) })
|
||||
} catch (error) {
|
||||
failures.push({ openid: signup.openid, errmsg: error.message || '发送失败' })
|
||||
}
|
||||
}
|
||||
ok(res, { demo: false, total: targets.length, sent, failed: failures.length, failures: failures.slice(0, 20) })
|
||||
})
|
||||
)
|
||||
|
||||
router.get('/posters', auth, (req, res) => {
|
||||
ok(res, db.read().posters)
|
||||
})
|
||||
@@ -227,7 +291,7 @@ function csvCell(value) {
|
||||
}
|
||||
|
||||
router.get('/signups/export', adminAuth({ allowQueryToken: true }), (req, res) => {
|
||||
const header = ['姓名', '手机号', '公司', '活动', '分享人', '报名时间', '是否签到', '签到时间']
|
||||
const header = ['姓名', '手机号', '公司', '职位', '活动', '分享人', '订阅提醒', '报名时间', '是否签到', '签到时间']
|
||||
const lines = [header.join(',')]
|
||||
signupRows(str(req.query.activityId)).forEach((item) => {
|
||||
lines.push(
|
||||
@@ -235,8 +299,10 @@ router.get('/signups/export', adminAuth({ allowQueryToken: true }), (req, res) =
|
||||
item.name,
|
||||
item.phone,
|
||||
item.company,
|
||||
item.position || '',
|
||||
item.activityTitle,
|
||||
item.sourceSharer,
|
||||
item.remindAccepted ? '是' : '否',
|
||||
item.createdAt,
|
||||
item.checkedIn ? '是' : '否',
|
||||
item.checkedInAt
|
||||
|
||||
Reference in New Issue
Block a user