Files
mp-pilates/EXPLORATION_SUMMARY.md
2026-04-05 13:25:54 +08:00

12 KiB
Raw Blame History

卡种管理 (Card Types Management) - Complete Exploration Summary

Date: 2026-04-05
Project: MP-Pilates (WeChat Mini-Program for Pilates Studio Booking)
Focus: Card types (卡种) admin feature


📦 What Was Explored

A comprehensive exploration of the card types management system across all three tiers of the application:

  • Frontend (Vue 3 + Uni-app)
  • Backend (NestJS)
  • Database (Prisma/MySQL)
  • Shared Types

Total Files Analyzed: 13 files, ~1,800 lines of code


📚 Documentation Generated

Three comprehensive documentation files have been created in the project root:

1. CARD_TYPES_ANALYSIS.md (Complete Technical Guide)

  • Sections: 11 major sections
  • Content:
    • Database schema details
    • Shared types and DTOs
    • Server-side implementation (controller, service, DTOs)
    • Frontend admin page structure
    • Admin store (Pinia) implementation
    • Complete workflow flows
    • API communication details
    • Price handling
    • Card type categories
    • Field requirements & validation table
    • Detailed bug analysis: Edit popup closes immediately

2. CARD_TYPES_FLOW_DIAGRAM.txt (Visual Architecture)

  • Content:
    • Database tier diagram (CardType model, enums, soft delete)
    • API tier diagram (endpoints, validators, DTOs)
    • Shared types tier
    • Frontend tier (page structure, store, components)
    • Complete operation flows (Add, Edit, Toggle, Delete)
    • Bug analysis with solutions (3 solution options)

3. CARD_TYPES_QUICK_REFERENCE.md (Quick Lookup)

  • Sections: 13 quick-reference sections
  • Content:
    • File quick links with line numbers
    • Key data model
    • API endpoints
    • DTOs & validation rules
    • UI components
    • Form fields
    • Operations guide
    • React refs & state
    • Admin store methods
    • Bug explanation and solutions
    • Price handling notes
    • Testing checklist
    • Card type categories

🎯 Key Findings

Data Structure

CardType
├── id (UUID)
├── name (卡种名称)
├── type (TIMES | DURATION | TRIAL)
├── totalTimes (次卡的次数)
├── durationDays (有效天数)
├── price (现价,单位:分)
├── originalPrice (原价,可选)
├── description (描述)
├── isActive (上架状态)
├── sortOrder (显示顺序)
└── timestamps

Three Card Type Categories

  1. 次卡 (TIMES): Class count-based (e.g., 10 classes)
  2. 月卡 (DURATION): Time period-based (e.g., 30 days)
  3. 体验卡 (TRIAL): Trial cards

Core Operations

  • Create: Add new card types
  • Read: View all cards (admin) or active cards (public)
  • Update: Edit card details or toggle status
  • Delete: Soft delete (sets isActive=false)

API Endpoints

GET    /membership/card-types              (public)
GET    /admin/card-types                   (admin only)
POST   /admin/card-types                   (admin only)
PUT    /admin/card-types/:id               (admin only)
DELETE /admin/card-types/:id               (admin only)

🐛 Critical Bug Identified

Edit Modal Closes Immediately on Tap

Symptom: When user taps the [编辑] button, the edit form modal appears and then instantly closes.

Root Cause: Event propagation issue

  • User taps [编辑] button
  • openEdit() sets showModal = true
  • Modal renders in the same event tick
  • Tap event propagates to modal-mask element
  • @tap.self="closeModal" fires immediately
  • Modal closes

Current Code (Buggy):

<view class="ct-action-btn edit-btn" @tap="openEdit(ct)">
  <text>编辑</text>
</view>

<view v-if="showModal" class="modal-mask" @tap.self="closeModal">
  <!-- form -->
</view>

Recommended Fix (Option 1 - Simplest):

<view class="ct-action-btn edit-btn" @tap.stop="openEdit(ct)">
  <!-- Add .stop modifier to stop event propagation -->
</view>

Alternative Fixes: See CARD_TYPES_QUICK_REFERENCE.md for 2 additional solutions using nextTick() or state guards.


📂 File Inventory

Frontend Files

File Purpose Lines
packages/app/src/pages/admin/card-types.vue Admin page (ADD, EDIT, DELETE, TOGGLE) 607
packages/app/src/stores/admin.ts Pinia store (state + API calls) 198
packages/app/src/utils/request.ts HTTP request utilities 80
packages/app/src/utils/format.ts Price & date formatting 46

Backend Files

File Purpose Lines
packages/server/src/membership/membership.controller.ts API endpoints 68
packages/server/src/membership/membership.service.ts Business logic 173
packages/server/src/membership/dto/create-card-type.dto.ts Create validation 45
packages/server/src/membership/dto/update-card-type.dto.ts Update validation 49

Database Files

File Purpose Lines
packages/server/prisma/schema.prisma DB schema definition 205

Shared/Types Files

File Purpose Lines
packages/shared/src/types/card-type.ts CardType, CreateCardTypeDto, UpdateCardTypeDto 39
packages/shared/src/enums.ts CardTypeCategory enum 47
packages/shared/src/types/api.ts API response types 20
packages/shared/src/types/membership.ts Membership types 19

Total: 13 files, ~1,800 lines analyzed


🔄 Complete Workflow

Adding a New Card Type

User → [ 新增卡种] → openAdd()
      ↓
Modal appears with empty form
User fills: name, type, price, durationDays
      ↓
[确认] → submitForm()
      ↓
Validate inputs → Build payload → adminStore.createCardType()
      ↓
POST /admin/card-types → Backend creates card
      ↓
Refetch list → Modal closes → Page updates

Editing a Card Type

User → [编辑] on card → openEdit(card)
      ↓
Modal appears with card data
User edifies fields
      ↓
[确认] → submitForm()
      ↓
Validate inputs → Build payload → adminStore.updateCardType(id, payload)
      ↓
PUT /admin/card-types/:id → Backend updates card
      ↓
Refetch list → Modal closes → Page updates

Toggling Status (上架/下架)

User → [上架/下架] button → toggleActive(card)
      ↓
adminStore.updateCardType(id, { isActive: !current })
      ↓
PUT /admin/card-types/:id → Backend toggles isActive
      ↓
Refetch list → Card UI updates (opacity, status tag, button text)

Deleting a Card Type

User → [删除] button → confirmDelete(card)
      ↓
Confirmation dialog appears
User confirms
      ↓
adminStore.deleteCardType(id)
      ↓
DELETE /admin/card-types/:id → Backend soft-deletes (isActive=false)
      ↓
Refetch list → Page updates

💾 Database Details

CardType Model

  • Storage: MySQL table card_types
  • Primary Key: UUID
  • Important Field: isActive (boolean, default: true)
  • Delete Strategy: Soft delete (set isActive=false, not actually removed)
  • Relationships:
    • One-to-many with Membership
    • One-to-many with Order

Indexes

  • isActive (for filtering active cards)
  • sortOrder (for ordering)

🎨 UI/UX Details

Page Layout

┌─ Toolbar ─────────────┐
│ Count + Add button    │
├──────────────────────┤
│ Loading skeleton      │ (while loading)
├──────────────────────┤
│ Card List             │
├──────────────────────┤
│ Modal (Add/Edit)      │ (if showModal=true)
└──────────────────────┘

Card Display

  • Header: Colored band (type-specific gradient)
  • Status tag: "销售中" or "已下架"
  • Content: Name, price, description, meta info
  • Actions: 3 buttons (编辑, 上架/下架, 删除)
  • Inactive styling: opacity: 0.6 when isActive=false

Modal Form

Title: 新增卡种 / 编辑卡种
Fields:
  - 卡种名称 (text input)
  - 类型 (picker)
  - 现价 (digit)
  - 原价 (digit, optional)
  - 次数 (number, optional)
  - 有效天数 (number, default: 90)
  - 排序值 (number, default: 0)
  - 描述 (textarea, optional)
Buttons: [取消] [确认/保存中...]

🔐 Security & Auth

Authentication

  • All admin endpoints require JWT Bearer token
  • Token stored in localStorage and included in all requests

Authorization

  • Admin endpoints require UserRole.ADMIN
  • Enforced via RolesGuard on backend

Public Endpoints

  • GET /membership/card-types (no auth needed)
  • Returns only isActive=true cards

📊 Validation Rules

On Create

Field Required Validation
name Non-empty string
type One of: TIMES, DURATION, TRIAL
durationDays Int, Min: 1
price Number, Min: 0
totalTimes - Int, Min: 1 (optional)
originalPrice - Number, Min: 0 (optional)
description - String, Max: 200 (optional)
sortOrder - Int, Min: 0 (optional, default: 0)

On Update

  • All fields optional (partial update)
  • Can include isActive for toggling status

💡 Price Handling

Critical: Prices are stored as integers (cents), not floats

  • In DB: 98000 (cents)
  • In API: { price: 98000 }
  • Display: ¥980.00 (using formatPrice utility)

Conversion:

// Display
formatPrice(cents: number): string {
  return (cents / 100).toFixed(2)
}

// Store (frontend → backend)
// User inputs: "980"
// Send as: 98000 (no need to convert, prices are already in cents in the UI)

🧪 Testing Recommendations

Unit Tests Needed

  • CardType service methods (create, update, delete)
  • Card type validation (DTO validation)
  • Price formatting utilities

Integration Tests Needed

  • Admin endpoints require ADMIN role
  • Public endpoint returns only active cards
  • Soft delete sets isActive=false

E2E Tests Needed (Frontend)

  • Create card flow
  • Edit card flow (including bug fix)
  • Toggle status flow
  • Delete card flow
  • Modal closes properly on submit
  • Modal closes on outside tap
  • Modal closes on cancel button

🚀 Next Steps (If Implementing Bug Fix)

  1. Locate file: packages/app/src/pages/admin/card-types.vue
  2. Find: Line 67 with <view class="ct-action-btn edit-btn" @tap="openEdit(ct)">
  3. Change: @tap="openEdit(ct)"@tap.stop="openEdit(ct)"
  4. Also check: Lines 6 and 77 (other buttons that might have same issue)
  5. Test: Try editing a card - modal should stay open

📖 How to Use This Documentation

  1. Quick lookup: Start with CARD_TYPES_QUICK_REFERENCE.md
  2. Understanding architecture: Read CARD_TYPES_FLOW_DIAGRAM.txt
  3. Deep dive: Consult CARD_TYPES_ANALYSIS.md for detailed information
  4. Bug fix: Find solution in Quick Reference "THE BUG" section

📝 Summary Statistics

Metric Value
Files Analyzed 13
Total Lines of Code ~1,800
Endpoints 5
Card Type Categories 3
Core Operations 4 (CRUD)
Bugs Identified 1
Bug Severity High (UX-breaking)
Documentation Pages 3
Recommended Solution @tap.stop modifier

Exploration Complete

All files related to the card types management feature have been thoroughly reviewed, analyzed, and documented.

Key Achievement: Identified and documented the root cause of the edit popup bug, along with three solution approaches.

Ready to:

  • Implement bug fix
  • Build additional features
  • Optimize performance
  • Add tests
  • Deploy updates

Generated: 2026-04-05
Location: /Users/richard/Documents/code/pilates/mp-pilates/