|
|
@@ -3,11 +3,21 @@
|
|
|
<view class="card-header">
|
|
|
<text class="card-title">{{ challenge.title }}</text>
|
|
|
<text class="card-status" :class="statusClass">
|
|
|
- {{ challenge.status === 'active' ? '进行中' : (challenge.status === 'cancelled' ? '已取消' : '已完成') }}
|
|
|
+ {{ statusText }}
|
|
|
</text>
|
|
|
</view>
|
|
|
<text class="card-desc">{{ challenge.description || '' }}</text>
|
|
|
|
|
|
+ <!-- 待同意:显示确认进度(pending 状态挑战) -->
|
|
|
+ <view class="pending-section" v-if="challenge.status === 'pending'">
|
|
|
+ <text class="pending-hint">等待成员确认(已同意 {{ agreedCount }}/{{ totalCount }})</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 成员状态徽标:rejected 成员显示"已拒绝" -->
|
|
|
+ <view class="member-status-row" v-if="hasRejected">
|
|
|
+ <text class="member-status-tag member-rejected" v-for="mp in rejectedMembers" :key="getMemberKey(mp)">{{ mp.nickname }} 已拒绝</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
<!-- 目标展示(两种模式) -->
|
|
|
<view class="target-section" v-if="challenge.targetMode">
|
|
|
<view class="target-header">
|
|
|
@@ -44,6 +54,13 @@
|
|
|
<text class="detail-label reward" v-if="challenge.rewardPoints">· 🏆 +{{ challenge.rewardPoints }}分</text>
|
|
|
</view>
|
|
|
|
|
|
+ <!-- 待同意:同意/拒绝按钮(仅我的状态=pending 时显示) -->
|
|
|
+ <view class="card-action" v-if="challenge.status === 'pending' && challenge.myStatus === 'pending'">
|
|
|
+ <view class="respond-btn respond-agree" @click="onRespond('agree')">✅ 同意</view>
|
|
|
+ <view class="respond-btn respond-reject" @click="onRespond('reject')">✖ 拒绝</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 进行中:打卡 -->
|
|
|
<view class="card-action" v-if="showCheckin && challenge.status === 'active'">
|
|
|
<view class="checkin-btn" @click="onCheckin">✅ 打卡</view>
|
|
|
</view>
|
|
|
@@ -60,6 +77,36 @@ export default {
|
|
|
statusClass: function() {
|
|
|
return 'status-' + (this.challenge && this.challenge.status ? this.challenge.status : 'active')
|
|
|
},
|
|
|
+ statusText: function() {
|
|
|
+ if (!this.challenge) return ''
|
|
|
+ if (this.challenge.status === 'active') return '进行中'
|
|
|
+ if (this.challenge.status === 'pending') return '待同意'
|
|
|
+ if (this.challenge.status === 'cancelled') return '已取消'
|
|
|
+ return '已完成'
|
|
|
+ },
|
|
|
+ agreedCount: function() {
|
|
|
+ var list = this.challenge && this.challenge.memberProgress ? this.challenge.memberProgress : []
|
|
|
+ var count = 0
|
|
|
+ for (var i = 0; i < list.length; i++) {
|
|
|
+ if (list[i].status === 'agreed') count++
|
|
|
+ }
|
|
|
+ return count
|
|
|
+ },
|
|
|
+ totalCount: function() {
|
|
|
+ var list = this.challenge && this.challenge.memberProgress ? this.challenge.memberProgress : []
|
|
|
+ return list.length
|
|
|
+ },
|
|
|
+ hasRejected: function() {
|
|
|
+ return this.rejectedMembers.length > 0
|
|
|
+ },
|
|
|
+ rejectedMembers: function() {
|
|
|
+ var list = this.challenge && this.challenge.memberProgress ? this.challenge.memberProgress : []
|
|
|
+ var result = []
|
|
|
+ for (var i = 0; i < list.length; i++) {
|
|
|
+ if (list[i].status === 'rejected') result.push(list[i])
|
|
|
+ }
|
|
|
+ return result
|
|
|
+ },
|
|
|
targetModeLabel: function() {
|
|
|
if (!this.challenge) return ''
|
|
|
if (this.challenge.targetMode === 'aggregate') return '🎯 全家目标'
|
|
|
@@ -88,6 +135,22 @@ export default {
|
|
|
var p = Math.round((value || 0) / target * 100)
|
|
|
return Math.min(p, 100)
|
|
|
},
|
|
|
+ getMemberKey: function(mp) {
|
|
|
+ return mp.memberId
|
|
|
+ },
|
|
|
+ onRespond: function(action) {
|
|
|
+ var self = this
|
|
|
+ var tip = action === 'agree' ? '确认同意参与该挑战吗?' : '确认拒绝该挑战吗?'
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认操作',
|
|
|
+ content: tip,
|
|
|
+ success: function(res) {
|
|
|
+ if (res.confirm) {
|
|
|
+ self.$emit('respond', { challengeId: self.challenge.id, action: action })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
onCheckin: function() {
|
|
|
var self = this
|
|
|
uni.showModal({
|
|
|
@@ -140,6 +203,36 @@ export default {
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
+/* 待同意提示区 */
|
|
|
+.pending-section {
|
|
|
+ background: #FEF3C7;
|
|
|
+ border-radius: 10rpx;
|
|
|
+ padding: 10rpx 12rpx;
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+}
|
|
|
+.pending-hint {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #B45309;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+/* 成员状态徽标行 */
|
|
|
+.member-status-row {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8rpx;
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+}
|
|
|
+.member-status-tag {
|
|
|
+ font-size: 18rpx;
|
|
|
+ padding: 2rpx 10rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+}
|
|
|
+.member-rejected {
|
|
|
+ background: #FEE2E2;
|
|
|
+ color: #DC2626;
|
|
|
+}
|
|
|
+
|
|
|
/* 目标区 */
|
|
|
.target-section {
|
|
|
background: #F8FAFC;
|
|
|
@@ -251,4 +344,20 @@ export default {
|
|
|
font-size: 22rpx;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
+.respond-btn {
|
|
|
+ padding: 10rpx 24rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-left: 12rpx;
|
|
|
+}
|
|
|
+.respond-agree {
|
|
|
+ background: #10B981;
|
|
|
+ color: #FFFFFF;
|
|
|
+}
|
|
|
+.respond-reject {
|
|
|
+ background: #FFFFFF;
|
|
|
+ color: #DC2626;
|
|
|
+ border: 2rpx solid #FCA5A5;
|
|
|
+}
|
|
|
</style>
|