- 落地页:顶部改为活动海报,底部替换为「活动规则」5 条编号列表 - 集章收集弹窗 (StampPopup):新增奖品规则卡片展示 Prize 信息 - 集章册 (AlbumPage / StampGrid):所有状态图章均可点击查看详情 - 兑换弹窗 (RedeemModal):新增 uncollected 分支,统一承载未收集/ 已集齐/已兑换三种状态;新增可选品牌说明区 - 后端 /api/stamps/:id 补充返回 prize 字段 - 管理后台字段标签改名:备注 → 品牌说明;奖品描述 → 奖品说明 - 新增一次性脚本 update-brand-rules,批量写入 16 条品牌权益文案 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
113 lines
3.2 KiB
TypeScript
113 lines
3.2 KiB
TypeScript
import { Router } from "express";
|
|
import { prisma } from "@stamp/shared";
|
|
import { optionalAuth, requireAuth } from "../middleware/auth.js";
|
|
|
|
const router = Router();
|
|
|
|
router.get("/", optionalAuth, async (req, res) => {
|
|
const stamps = await prisma.stamp.findMany({
|
|
where: { enabled: true },
|
|
orderBy: { sortOrder: "asc" },
|
|
include: { prize: true },
|
|
});
|
|
|
|
const collectionMap = new Map<string, Date>();
|
|
const redeemedSet = new Set<string>();
|
|
|
|
if (req.userId) {
|
|
const [userCollections, userRedemptions] = await Promise.all([
|
|
prisma.collection.findMany({
|
|
where: { userId: req.userId },
|
|
select: { stampId: true, collectedAt: true },
|
|
}),
|
|
prisma.redemption.findMany({
|
|
where: { userId: req.userId },
|
|
select: { stampId: true },
|
|
}),
|
|
]);
|
|
userCollections.forEach((c) => collectionMap.set(c.stampId, c.collectedAt));
|
|
userRedemptions.forEach((r) => redeemedSet.add(r.stampId));
|
|
}
|
|
|
|
const data = stamps.map((s) => ({
|
|
id: s.id,
|
|
name: s.name,
|
|
note: s.note,
|
|
imageColor: s.imageColor,
|
|
imageGrey: s.imageGrey,
|
|
sortOrder: s.sortOrder,
|
|
collected: collectionMap.has(s.id),
|
|
collectedAt: collectionMap.get(s.id)?.toISOString() ?? null,
|
|
redeemed: redeemedSet.has(s.id),
|
|
prize: s.prize
|
|
? {
|
|
id: s.prize.id,
|
|
name: s.prize.name,
|
|
description: s.prize.description,
|
|
stock: s.prize.stock,
|
|
enabled: s.prize.enabled,
|
|
}
|
|
: null,
|
|
}));
|
|
|
|
res.json({ success: true, data });
|
|
});
|
|
|
|
router.get("/:id", async (req, res) => {
|
|
const stamp = await prisma.stamp.findUnique({
|
|
where: { id: req.params.id },
|
|
include: { prize: true },
|
|
});
|
|
if (!stamp) {
|
|
res.status(404).json({ success: false, error: { code: "NOT_FOUND", message: "图章不存在" } });
|
|
return;
|
|
}
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
id: stamp.id,
|
|
name: stamp.name,
|
|
note: stamp.note,
|
|
imageColor: stamp.imageColor,
|
|
imageGrey: stamp.imageGrey,
|
|
sortOrder: stamp.sortOrder,
|
|
prize: stamp.prize
|
|
? {
|
|
id: stamp.prize.id,
|
|
name: stamp.prize.name,
|
|
description: stamp.prize.description,
|
|
stock: stamp.prize.stock,
|
|
enabled: stamp.prize.enabled,
|
|
}
|
|
: null,
|
|
},
|
|
});
|
|
});
|
|
|
|
router.post("/:id/collect", requireAuth, async (req, res) => {
|
|
const stamp = await prisma.stamp.findUnique({ where: { id: req.params.id, enabled: true } });
|
|
if (!stamp) {
|
|
res.status(404).json({ success: false, error: { code: "NOT_FOUND", message: "图章不存在" } });
|
|
return;
|
|
}
|
|
|
|
const existing = await prisma.collection.findUnique({
|
|
where: { userId_stampId: { userId: req.userId!, stampId: stamp.id } },
|
|
});
|
|
if (existing) {
|
|
res.status(409).json({ success: false, error: { code: "ALREADY_COLLECTED", message: "你已经收集了这枚图章" } });
|
|
return;
|
|
}
|
|
|
|
const collection = await prisma.collection.create({
|
|
data: { userId: req.userId!, stampId: stamp.id },
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { id: collection.id, stampId: collection.stampId, collectedAt: collection.collectedAt.toISOString() },
|
|
});
|
|
});
|
|
|
|
export default router;
|