chore: establish V1.1 recovery baseline

This commit is contained in:
2026-09-09 22:05:00 +08:00
commit b46b48eb33
947 changed files with 155471 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
/**
* 公益活动列表页 U31
*
* 重构要点(遵循 AGENTS.md 不变量):
* - 数据:ActivityStore.fetchActivities / loadMoreActivities(游标分页)
* - 列表项:ui-activity-card 组件,"公益免费"为主视觉标识(AGENTS 第 1 条:用户永久免费)
* - 跳转:点击卡片 → activity-detail,不在列表页处理报名
* - 单向意向保密:本页不接入互选意向提交(仅在 activity-matches 页由用户主动操作,AGENTS 第 6 条)
* - 下拉刷新 + onReachBottom 加载更多
*/
const { activityStore, appStore, authStore } = require('../../stores');
const { to } = require('../../constants');
Page({
data: {
activities: [],
loading: false,
loadingMore: false,
hasMore: true,
firstLoaded: false,
/** @type {{title:string, description:string, illustration:string, actions:Array}} */
emptyConfig: {
title: '暂无进行中的活动',
description: '近期暂无报名中的公益活动,请稍后再来',
illustration: 'activity',
actions: [
{ text: '我的活动', event: 'my', variant: 'secondary' },
],
},
},
onLoad() {
if (!authStore.state.isLoggedIn) return;
this._unsub = activityStore.bindToPage(this, {
activities: 'activities',
activitiesHasMore: 'activitiesHasMore',
});
this.fetchActivities(true);
},
onShow() {
if (!authStore.state.isLoggedIn) return;
// 从活动详情返回后,若列表为空则刷新(避免详情页报名后列表不同步)
if (this.data.firstLoaded && this.data.activities.length === 0) {
this.fetchActivities(true);
}
},
onUnload() {
if (this._unsub) this._unsub();
},
/** 首次或下拉刷新拉取活动列表 */
async fetchActivities(first = false) {
if (this.data.loading) return;
this.setData({ loading: true });
try {
await activityStore.fetchActivities();
this.setData({
loading: false,
firstLoaded: true,
hasMore: activityStore.state.activitiesHasMore,
});
} catch (err) {
this.setData({ loading: false, firstLoaded: true });
appStore.toastError(err, '加载活动失败');
}
},
/** 加载更多(onReachBottom 触发) */
async loadMore() {
if (this.data.loadingMore || !this.data.hasMore) return;
this.setData({ loadingMore: true });
try {
await activityStore.loadMoreActivities();
this.setData({
loadingMore: false,
hasMore: activityStore.state.activitiesHasMore,
});
} catch (err) {
this.setData({ loadingMore: false });
appStore.toastError(err, '加载更多失败');
}
},
onPullDownRefresh() {
this.fetchActivities(true).then(() => {
wx.stopPullDownRefresh();
});
},
onReachBottom() {
this.loadMore();
},
/** 点击活动卡片 → 跳转详情 */
onActivityTap(e) {
const activityId = e.detail.activityId;
if (!activityId) return;
wx.navigateTo({ url: to.activityDetail(activityId) });
},
/** 空状态:去我的活动 */
onEmptyAction(e) {
const { event } = e.detail;
if (event === 'my') {
wx.navigateTo({ url: '/pages/my-activities/my-activities' });
}
},
/** 顶部"我的活动"入口 */
onMyActivities() {
wx.navigateTo({ url: '/pages/my-activities/my-activities' });
},
});
+10
View File
@@ -0,0 +1,10 @@
{
"navigationBarTitleText": "公益活动",
"enablePullDownRefresh": true,
"usingComponents": {
"ui-section-header": "/components/section-header/index",
"ui-activity-card": "/components/activity-card/index",
"ui-loading": "/components/loading/index",
"ui-empty": "/components/empty/index"
}
}
+43
View File
@@ -0,0 +1,43 @@
<!-- 公益活动列表页 U31 -->
<view class="act-page">
<!-- 顶部区:标题 + 我的活动入口 -->
<view class="act-page__topbar">
<ui-section-header
title="公益活动"
subtitle="丹阳本地公益组织主办"
action="我的活动"
bind:action="onMyActivities"
/>
</view>
<!-- 首次加载 -->
<view wx:if="{{loading && !firstLoaded}}" class="act-loading">
<ui-loading mode="skeleton" rows="3" />
</view>
<!-- 列表 -->
<view wx:elif="{{activities.length}}" class="act-list">
<ui-activity-card
wx:for="{{activities}}"
wx:key="activityId"
activity="{{item}}"
bind:tap="onActivityTap"
/>
<!-- 加载更多 -->
<view wx:if="{{loadingMore}}" class="act-list__more">
<ui-loading mode="spinner" inline text="加载更多活动…" />
</view>
<view wx:elif="{{!hasMore}}" class="act-list__end">已到底部</view>
</view>
<!-- 空状态 -->
<view wx:else class="act-empty">
<ui-empty
title="{{emptyConfig.title}}"
description="{{emptyConfig.description}}"
illustration="{{emptyConfig.illustration}}"
actions="{{emptyConfig.actions}}"
bind:action="onEmptyAction"
/>
</view>
</view>
+44
View File
@@ -0,0 +1,44 @@
/**
* 公益活动列表页 U31
*/
.act-page {
min-height: 100vh;
background: var(--color-bg-page);
padding-bottom: var(--spacing-section);
}
.act-page__topbar {
padding: var(--spacing-3) var(--spacing-page-x) var(--spacing-1);
}
/* ============ 加载骨架 ============ */
.act-loading {
padding: var(--spacing-3) var(--spacing-page-x);
}
/* ============ 列表 ============ */
.act-list {
padding: var(--spacing-2) var(--spacing-page-x) 0;
display: flex;
flex-direction: column;
gap: var(--spacing-3);
}
.act-list__more {
padding: var(--spacing-3) 0;
display: flex;
justify-content: center;
}
.act-list__end {
padding: var(--spacing-3) 0;
text-align: center;
font-size: var(--font-size-xs);
color: var(--color-text-placeholder);
}
/* ============ 空状态 ============ */
.act-empty {
padding-top: 160rpx;
}