feat: add password change, trial subscription, batch import, UX improvements

Server:
- POST /api/v1/admin/password: admin password change with bcrypt migration
- POST /api/v1/subscription/trial: user trial activation, one per user
- POST /api/v1/admin/devices/batch: bulk device import (up to 500)
- Add trial plan (7 days, free) to PLANS constant

Admin console:
- Settings page: add password change form with validation
- Device page: add batch import modal with textarea input

Miniprogram:
- Treating page: add back button with stop-treatment confirmation
- Index page: add mock device bind button (dev mode only)
这个提交包含在:
Guoguo
2026-04-28 19:42:23 -07:00
父节点 4ec42e7816
当前提交 1f55e430c8
修改 9 个文件,包含 258 行新增3 行删除
+13
查看文件
@@ -4,6 +4,7 @@ const { requireUser, requireAdmin } = require('../lib/auth')
const { writeLog } = require('../lib/log')
const PLANS = {
trial: { amount: 0, days: 7 },
monthly: { amount: 99, days: 30 },
yearly: { amount: 899, days: 365 }
}
@@ -26,6 +27,18 @@ function register(router) {
return ok({ order_id: orderId, payment_params: {}, plan, amount: PLANS[plan].amount })
})
router.post('/api/v1/subscription/trial', async ctx => {
const user = await requireUser(ctx)
if (!user) return fail(1001, 'invalid_token')
const usedTrial = await one('SELECT subscription_id FROM subscriptions WHERE user_id = :user_id AND plan = \'trial\' LIMIT 1', { user_id: user.user_id })
if (usedTrial) return fail(2001, '已使用过试用')
const activeSub = await one('SELECT subscription_id FROM subscriptions WHERE user_id = :user_id AND status = 1 LIMIT 1', { user_id: user.user_id })
if (activeSub) return fail(2001, '已有有效订阅')
const orderId = 'TRIAL' + Date.now()
await query('INSERT INTO subscriptions (user_id, plan, status, amount, order_id, start_time, expire_time) VALUES (:user_id, \'trial\', 1, 0, :order_id, NOW(), DATE_ADD(NOW(), INTERVAL 7 DAY))', { user_id: user.user_id, order_id: orderId })
return ok({ status: 'active', plan: 'trial', remaining_days: 7 })
})
// Temporary: admin-only until payment integration
router.post('/api/v1/subscription/verify', async ctx => {
const admin = await requireAdmin(ctx)