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
@@ -0,0 +1,115 @@
/**
* 活动反馈页 U32
* 活动结束后开放,用户评价 + 表达进一步了解意向
*/
const api = require('../../utils/api');
Page({
data: {
activityId: '',
activityTitle: '',
activityEndAt: '',
overallExperience: '', // very_satisfied / satisfied / normal / dissatisfied
willingNextTime: '', // willing / maybe / not_now
metSomeoneInteresting: null, // true / false / null
otherParticipants: [],
selectedInterests: [],
submitting: false,
feedbackSubmitted: false
},
onLoad(options) {
if (options.id) {
this.setData({ activityId: options.id });
this.loadData();
}
},
async loadData() {
try {
const token = wx.getStorageSync('accessToken');
// 加载活动信息
const activityRes = await api.get(`/activities/${this.data.activityId}`, {}, {
Authorization: `Bearer ${token}`
});
this.setData({
activityTitle: activityRes.title,
activityEndAt: activityRes.endAt
});
// 加载同活动已签到用户列表(仅用于选择意向对象)
const partsRes = await api.get(`/activities/${this.data.activityId}/participants`, {}, {
Authorization: `Bearer ${token}`
});
this.setData({
otherParticipants: (partsRes || []).filter(p => p.userId !== wx.getStorageSync('userId'))
});
} catch (err) {
console.error('加载失败', err);
}
},
onOverallChange(e) {
this.setData({ overallExperience: e.currentTarget.dataset.value });
},
onWillingChange(e) {
this.setData({ willingNextTime: e.currentTarget.dataset.value });
},
onMetSomeoneChange(e) {
this.setData({ metSomeoneInteresting: e.currentTarget.dataset.value === 'yes' ? true : false });
},
toggleInterest(userId) {
const selected = this.data.selectedInterests;
const idx = selected.indexOf(userId);
if (idx >= 0) {
selected.splice(idx, 1);
} else {
selected.push(userId);
}
this.setData({ selectedInterests: selected });
},
async submitFeedback() {
const { overallExperience, willingNextTime, metSomeoneInteresting, selectedInterests, activityId } = this.data;
if (!overallExperience || !willingNextTime) {
wx.showToast({ title: '请填写必填项', icon: 'none' });
return;
}
this.setData({ submitting: true });
try {
const token = wx.getStorageSync('accessToken');
const body = {
overallExperience,
willingNextTime,
metSomeoneInteresting,
interestTargetUserIds: selectedInterests
};
const res = await api.post(`/activities/${activityId}/feedback`, body, {
Authorization: `Bearer ${token}`
});
this.setData({ feedbackSubmitted: true, submitting: false });
if ((res.newMutuals || []).length > 0) {
wx.showModal({
title: '双向匹配成功!',
content: `您与${res.newMutuals.length}位朋友互相表达了进一步了解意愿,快去缘分中心看看吧`,
showCancel: false,
success: () => {
wx.switchTab({ url: '/pages/fate-center/fate-center' });
}
});
}
} catch (err) {
wx.showToast({ title: err.message || '提交失败', icon: 'none' });
this.setData({ submitting: false });
}
},
skipFeedback() {
wx.navigateBack();
}
});
@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "活动反馈",
"usingComponents": {}
}
@@ -0,0 +1,74 @@
<!-- 活动反馈页 U32 -->
<view class="page" wx:if="{{!feedbackSubmitted}}">
<view class="header">
<text class="title">活动反馈</text>
<text class="activity-name">{{activityTitle}}</text>
</view>
<view class="section">
<text class="section-title">整体体验</text>
<view class="radio-group">
<view class="radio-item {{overallExperience==='very_satisfied'?'active':''}}" data-value="very_satisfied" bindtap="onOverallChange">
<text>😊 非常满意</text>
</view>
<view class="radio-item {{overallExperience==='satisfied'?'active':''}}" data-value="satisfied" bindtap="onOverallChange">
<text>🙂 满意</text>
</view>
<view class="radio-item {{overallExperience==='normal'?'active':''}}" data-value="normal" bindtap="onOverallChange">
<text>😐 一般</text>
</view>
<view class="radio-item {{overallExperience==='dissatisfied'?'active':''}}" data-value="dissatisfied" bindtap="onOverallChange">
<text>😕 不满意</text>
</view>
</view>
</view>
<view class="section">
<text class="section-title">是否愿意参加下一次活动</text>
<view class="radio-group">
<view class="radio-item {{willingNextTime==='willing'?'active':''}}" data-value="willing" bindtap="onWillingChange">
<text>💚 愿意</text>
</view>
<view class="radio-item {{willingNextTime==='maybe'?'active':''}}" data-value="maybe" bindtap="onWillingChange">
<text>🤔 看情况</text>
</view>
<view class="radio-item {{willingNextTime==='not_now'?'active':''}}" data-value="not_now" bindtap="onWillingChange">
<text>😶 暂不参加</text>
</view>
</view>
</view>
<view class="section" wx:if="{{activityEndAt}}">
<text class="section-title">是否在活动中认识了想进一步了解的人</text>
<view class="radio-group">
<view class="radio-item {{metSomeoneInteresting===true?'active':''}}" data-value="yes" bindtap="onMetSomeoneChange">
<text>是</text>
</view>
<view class="radio-item {{metSomeoneInteresting===false?'active':''}}" data-value="no" bindtap="onMetSomeoneChange">
<text>否</text>
</view>
</view>
<view class="participant-list" wx:if="{{metSomeoneInteresting && otherParticipants.length > 0}}">
<text class="list-hint">选择您想进一步了解的人(仅活动结束后开放)</text>
<view class="participant-item" wx:for="{{otherParticipants}}" wx:key="userId"
bindtap="toggleInterest" data-user-id="{{item.userId}}">
<image class="avatar" src="{{item.avatarFileKey || '/assets/default-avatar.png'}}" mode="aspectFill"/>
<text class="name">{{item.displayName}}</text>
<text class="checkmark" wx:if="{{selectedInterests.indexOf(item.userId) >= 0}}">✓</text>
</view>
</view>
</view>
<button class="btn-submit" bindtap="submitFeedback" loading="{{submitting}}" disabled="{{!overallExperience || !willingNextTime}}">
提交反馈
</button>
<button class="btn-skip" bindtap="skipFeedback">跳过反馈</button>
</view>
<!-- 已提交状态 -->
<view class="page submitted" wx:if="{{feedbackSubmitted}}">
<view class="success-icon">✓</view>
<text class="success-title">反馈已提交</text>
<text class="success-hint">感谢您的参与!</text>
<button class="btn-home" bindtap="goHome">返回首页</button>
</view>
@@ -0,0 +1,170 @@
.page {
padding: 32rpx;
background: #FFF8F7;
min-height: 100vh;
}
.header {
text-align: center;
margin-bottom: 32rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #1A1A1A;
display: block;
}
.activity-name {
font-size: 24rpx;
color: #999;
display: block;
margin-top: 8rpx;
}
.section {
background: #fff;
border-radius: 20rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #1A1A1A;
display: block;
margin-bottom: 20rpx;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.radio-item {
padding: 24rpx;
background: #F9F9F9;
border-radius: 16rpx;
font-size: 28rpx;
color: #333;
border: 2rpx solid transparent;
transition: all 0.2s;
}
.radio-item.active {
background: #FFF3F2;
border-color: #E96B6B;
color: #E96B6B;
}
.participant-list {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 1rpx solid #f0f0f0;
}
.list-hint {
font-size: 24rpx;
color: #999;
display: block;
margin-bottom: 16rpx;
}
.participant-item {
display: flex;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.participant-item:last-child {
border-bottom: none;
}
.avatar {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
margin-right: 16rpx;
background: #EEE;
}
.name {
flex: 1;
font-size: 28rpx;
color: #333;
}
.checkmark {
color: #E96B6B;
font-size: 32rpx;
font-weight: bold;
}
.btn-submit {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: #E96B6B;
color: #fff;
border-radius: 44rpx;
font-size: 30rpx;
margin-top: 24rpx;
}
.btn-submit[disabled] {
background: #CCC;
}
.btn-skip {
width: 100%;
height: 88rpx;
line-height: 88rpx;
background: transparent;
color: #999;
border-radius: 44rpx;
font-size: 28rpx;
border: 1rpx solid #DDD;
margin-top: 16rpx;
}
/* 已提交状态 */
.submitted {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 32rpx;
}
.success-icon {
font-size: 80rpx;
color: #E96B6B;
margin-bottom: 24rpx;
}
.success-title {
font-size: 36rpx;
font-weight: bold;
color: #1A1A1A;
margin-bottom: 16rpx;
}
.success-hint {
font-size: 28rpx;
color: #999;
margin-bottom: 48rpx;
}
.btn-home {
width: 80%;
height: 88rpx;
line-height: 88rpx;
background: #E96B6B;
color: #fff;
border-radius: 44rpx;
font-size: 30rpx;
}