Files
citywalk-stamp/CLAUDE.md
YANG JIANKUAN 394b643304 refactor: 兑换机制改为一图章一奖品并引入库存
- 废弃 RedemptionRule(集 N 换 1),新增 Prize 表与 Stamp 1:1 关联
- Redemption 记录直接绑定到 stampId + prizeId + prizeName 快照
- 兑换事务用 updateMany + stock>0 条件作乐观锁
- 兑换后保留 Collection 记录,图章持续彩色点亮并标记"已兑换"
- 用户端入口改为点击已收集图章弹出兑换,库存为 0 时按钮禁用
- 管理后台删除 /admin/rules,奖品编辑嵌入 StampForm

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 15:30:28 +08:00

4.2 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

CityWalk 图章收集系统 — 移动端 H5 + 管理后台。游客在城市点位扫描二维码收集图章,每枚图章绑定一个特定奖品(带库存),已收集的图章可兑换对应奖品。兑换后图章保持彩色点亮并标记为"已兑换",同一图章不可二次收集或二次兑换。

Commands

# Development (需要同时运行两个服务)
pnpm dev:server    # Express API on :3000
pnpm dev:web       # Vite dev server on :5173

# Database
pnpm db:generate   # Generate Prisma client after schema changes
pnpm db:migrate    # Create and apply migrations (prisma migrate dev)
pnpm db:push       # Push schema directly (dev only, no migration file)
pnpm db:seed       # Seed sample data (16 stamps, each with a Prize of stock 100)

# Build
pnpm build         # Build all packages

Architecture

pnpm monorepo with 3 packages sharing tsconfig.base.json (target ES2022, moduleResolution bundler):

  • packages/shared — Prisma client singleton + shared TypeScript types (ApiResponse<T>, StampWithStatus, etc.)
  • packages/server (port 3000) — Express 5 + Zod validation + JWT auth. Routes: auth, stamps, collection, redemption, admin. File uploads via multer to packages/server/uploads/.
  • packages/web (port 5173) — React 19 + Vite 8 + Tailwind CSS 4. Dual interface: mobile H5 (/, /album) + PC admin (/admin/*). Vite proxies /api and /uploads to server.

API Response Format

All endpoints return: { success: boolean, data?: T, error?: { code: string, message: string } }

Authentication

  • Users: JWT (7-day expiry, single token in localStorage stamp_token). Middleware: requireAuth, optionalAuth.
  • Admin: API key via X-Admin-Key header (matched against ADMIN_API_KEY env var). Middleware: requireAdmin.

Frontend Routing

/                    → LandingPage (also handles /?stamp={id} for collection popup)
/album               → AlbumPage (stamp grid + redemption)
/collect/:stampId    → Redirects to /?stamp={stampId} (QR code entry point)
/admin               → AdminLogin
/admin/stamps        → Stamp CRUD + QR code generation
/admin/redemptions   → Redemption history + stats

Collection Flow

QR codes encode /collect/{stampId} → redirects to /?stamp={stampId} → LandingPage shows collection overlay. All interactions (register, collect, close) happen as modals on top of the landing page. The stamp ID is stored in sessionStorage during registration to resume collection after auth.

Redemption Transaction

Each Stamp has an optional Prize (1:1, Prize.stampId @unique). Redemption is atomic: inside prisma.$transaction we check the user has a Collection for the stamp, no existing Redemption for (user, stamp), the prize is enabled, then prisma.prize.updateMany({ where: { id, stock: { gt: 0 } }, data: { stock: { decrement: 1 } } }) acts as a stock lock (throws OUT_OF_STOCK if zero rows updated) before creating the Redemption record with a prizeName snapshot. Collection rows are not deleted — the @@unique([userId, stampId]) constraints on both Collection and Redemption naturally block re-collection and re-redemption of the same stamp.

Critical: Tailwind CSS v4 Layer Architecture

Custom CSS in packages/web/src/index.css must use @layer to avoid overriding Tailwind utilities:

@import "tailwindcss";

@layer base {        /* Reset, CSS variables, body/heading fonts */}
@keyframes ... { }   /* Keyframes stay OUTSIDE layers */
@layer components {  /* .animate-*, .stagger-children, .paper-texture, etc. */}

Why: In Tailwind CSS v4, unlayered styles always beat @layer utilities. If base resets like * { padding: 0 } are unlayered, they override Tailwind's px-4, py-3, etc., breaking all utility spacing across the app.

Environment

DATABASE_URL="file:./dev.db"         # SQLite (relative to project root)
JWT_SECRET="..."                     # JWT signing key
ADMIN_API_KEY="..."                  # Admin panel access key
SERVER_PORT=3000
SITE_URL="http://localhost:5173"     # Used in QR code URL generation