fix: resolve quality audit issues

- utils.js: force UTC+8 output to match MySQL timezone on SCF
- admin subscriptions: return stats (plan counts + revenue) in response
- admin devices: implement keyword search filter
- device-detail: fix binding history field names (bind_time/unbind_time/bind_status)
- user-detail: fix treatment time field (start_time not started_at)
- dashboard: show actual mode per treatment, use sub_stats from dashboard API
这个提交包含在:
Guoguo
2026-04-29 05:37:36 -07:00
父节点 8d1cde8636
当前提交 9f0e629c82
修改 5 个文件,包含 28 行新增15 行删除
+16 -3
查看文件
@@ -78,8 +78,15 @@ function register(router) {
const admin = await requireAdmin(ctx)
if (!admin) return fail(1002, '未授权,请重新登录')
const p = pageParams(ctx)
const total = await query('SELECT COUNT(*) AS total FROM devices', {})
const records = await query('SELECT d.*, b.user_id AS bound_user, b.bind_time AS activated_at FROM devices d LEFT JOIN bindings b ON b.device_id = d.device_id AND b.bind_status = 1 ORDER BY d.created_at DESC' + limitClause(p.pageSize, p.offset), {})
const keyword = (ctx.query.keyword || '').trim()
let where = ''
const params = {}
if (keyword) {
where = ' WHERE d.device_id LIKE :kw OR d.device_name LIKE :kw'
params.kw = '%' + keyword + '%'
}
const total = await query('SELECT COUNT(*) AS total FROM devices d' + where, params)
const records = await query('SELECT d.*, b.user_id AS bound_user, b.bind_time AS activated_at FROM devices d LEFT JOIN bindings b ON b.device_id = d.device_id AND b.bind_status = 1' + where + ' ORDER BY d.created_at DESC' + limitClause(p.pageSize, p.offset), params)
return ok({ records, total: total[0].total })
})
@@ -239,7 +246,13 @@ function register(router) {
'SELECT s.*, u.nickname FROM subscriptions s LEFT JOIN users u ON u.user_id = s.user_id' + where + ' ORDER BY s.created_at DESC' + limitClause(p.pageSize, p.offset),
params
)
return ok({ records, total: total[0].total })
const statsRow = await query('SELECT ' +
'SUM(CASE WHEN plan = \'monthly\' AND status = 1 AND expire_time > NOW() THEN 1 ELSE 0 END) AS monthly_count, ' +
'SUM(CASE WHEN plan = \'yearly\' AND status = 1 AND expire_time > NOW() THEN 1 ELSE 0 END) AS yearly_count, ' +
'SUM(CASE WHEN plan = \'trial\' AND status = 1 AND expire_time > NOW() THEN 1 ELSE 0 END) AS trial_count, ' +
'COALESCE(SUM(CASE WHEN MONTH(start_time) = MONTH(NOW()) AND YEAR(start_time) = YEAR(NOW()) THEN amount ELSE 0 END), 0) AS monthly_revenue ' +
'FROM subscriptions', {})
return ok({ records, total: total[0].total, stats: statsRow[0] || {} })
})
router.post('/api/v1/admin/subscriptions', async ctx => {