Phase 14 (multi-type public-welfare activities) verification closure. Backend (D1-D11): - D1 CRITICAL: ActivityMapper.countRemainingCapacity had no @Select and no XML -> activity detail / register / approve all threw Invalid bound statement. Removed the shell; unified on ActivityRegistrationMapper. - D2 CRITICAL: register & approve were read-then-write -> oversold under concurrency. Replaced with single atomic SQL (insertIfCapacityAvailable / approveIfCapacityAvailable, affectedRows==0 => full). - D3 HIGH: activityType / orgRole never validated against production chk_activity_type / chk_org_role -> 500 only on PostgreSQL. - D4 HIGH: ActivityListItemDTO had no activityType -> miniapp category tabs always empty (Gate failure). - D5: male/female counts were fabricated as approvedCount/2; now real aggregation over user_profiles.gender. - D6: minAge/maxAge was a TODO no-op; added assertAgeInRange. - D7: registration_policy write-only; per-type eligibility now enforced (identity / local eligibility / gender quota). - D8: POST /operator/activities dropped type, multi-org and capacity groups and set organizer_org_id to the operator user id; now delegates to createActivityFromAdmin (single implementation). - D9: three operator write endpoints were mounted on the user-side ActivityController (dual implementation, zero callers) -> moved. - D10: NPE on soft-deleted activity in getMyActivities; NPE/500 on missing admin form fields (Instant.parse(null)); tolerant time parsing added. - D11: DTO lacked registeredCount / status / coverFileId / description -> card always showed 0 registered, "view detail", no cover, no summary. Frontend contract (F1-F3): - F1: miniapp category code "classroom" is not in the production whitelist (should be relationship_education); three types had no tab at all. - F2: ui-activity-card read 7 fields that do not exist on the backend; now reads only real DTO fields, plus per-type badge and eligibility hint. - F3: admin-web had no activity management page at all; added ActivityManagementView.vue (type-driven dynamic form, multi-org, capacity groups, publish/cancel) + /activities route and nav entry. Test fidelity (T1-T2): - T1: deleted two assertTrue(true) placeholder tests (testNoOverbooking, testNoDuplicateRegistration) that covered the exact broken invariants. - T2: test schema lacked V023 columns/constraints and three tables; the activity tables were only in the OPTIONAL set (zero coverage) -> schema completed and moved to REQUIRED_IN_TEST_SCHEMA. Tests: ActivityPhase14Test (23 cases), ActivityConstraintGuardTest (9 cases, parses real db/migration/V*.sql plus miniapp source). Two negative-verification rounds proved both guard families actually turn red. Regression: 450 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS (baseline 420). admin-web vue-tsc 0 error, vite build OK.
36 lines
1.9 KiB
Plaintext
36 lines
1.9 KiB
Plaintext
<!--
|
|
公益活动卡片(规范 §43-46)
|
|
|
|
不变量(AGENTS.md 第 1 条):"公益免费"作为主要视觉标识突出展示,
|
|
前端不实现任何支付入口。
|
|
|
|
Phase 14:所有 activity.* 字段都必须在 ActivityListItemDTO 中真实存在。
|
|
守卫 ActivityConstraintGuardTest#cardFieldsExistInListItemDto 会解析本文件
|
|
校验字段名 —— 此前这里的 registeredCount / currentMaleCount / currentFemaleCount /
|
|
coverImageUrl / activityStartAt / suitableFor / intro 后端一个都没有,
|
|
导致封面、摘要、报名人数恒不显示。
|
|
-->
|
|
<view class="ui-act {{compact ? 'ui-act--compact' : ''}}" bindtap="onTap">
|
|
<view class="ui-act__cover">
|
|
<image class="ui-act__cover-img" src="{{cover || '/assets/covers/activity-default.jpg'}}" mode="aspectFill" />
|
|
<view class="ui-act__fee-badge">公益活动</view>
|
|
</view>
|
|
<view class="ui-act__body">
|
|
<view class="ui-act__title">{{activity.title}}</view>
|
|
<!-- 类型化卡片:不同类型显示不同标签与资格提示(Gate ①) -->
|
|
<view wx:if="{{typeLabel}}" class="ui-act__type-row">
|
|
<text class="ui-act__type-badge">{{typeLabel}}</text>
|
|
<text wx:if="{{eligibilityText}}" class="ui-act__eligibility">{{eligibilityText}}</text>
|
|
</view>
|
|
<view class="ui-act__meta"><text>{{startAtText}}</text></view>
|
|
<view class="ui-act__meta"><text>{{activity.locationName}}</text></view>
|
|
<view wx:if="{{!compact && activity.description}}" class="ui-act__intro">{{activity.description}}</view>
|
|
<view class="ui-act__footer">
|
|
<view class="ui-act__capacity">
|
|
<text wx:if="{{capacity}}">报名人数 {{registeredCount}}/{{capacity}}</text>
|
|
</view>
|
|
<view class="ui-act__status-action {{activity.status !== 'registration_open' ? 'disabled' : ''}}">{{activity.status === 'registration_open' ? '立即报名' : (activity.status === 'ongoing' ? '进行中' : '查看详情')}}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|