|
@@ -160,8 +160,23 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
- <view class="field" v-if="!editMode">
|
|
|
|
|
- <text class="field-label">参与成员</text>
|
|
|
|
|
|
|
+ <view class="field-row" v-if="!editMode">
|
|
|
|
|
+ <view class="field half">
|
|
|
|
|
+ <text class="field-label">回复时效</text>
|
|
|
|
|
+ <view class="radio-group">
|
|
|
|
|
+ <view class="radio-btn" :class="{ active: form.respondDeadlineHours === '24' }" @click="form.respondDeadlineHours = '24'">24h</view>
|
|
|
|
|
+ <view class="radio-btn" :class="{ active: form.respondDeadlineHours === '48' }" @click="form.respondDeadlineHours = '48'">48h</view>
|
|
|
|
|
+ <view class="radio-btn" :class="{ active: form.respondDeadlineHours === '72' }" @click="form.respondDeadlineHours = '72'">72h</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="field half">
|
|
|
|
|
+ <text class="field-label">超时处理</text>
|
|
|
|
|
+ <view class="field-tip">超时未回复的成员将自动视为拒绝</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="field" v-if="!editMode || editingStatus === 'pending'">
|
|
|
|
|
+ <text class="field-label">参与成员 <text class="required" v-if="editMode && editingStatus === 'pending'">(待同意挑战可修改)</text></text>
|
|
|
<view class="member-grid">
|
|
<view class="member-grid">
|
|
|
<view
|
|
<view
|
|
|
class="member-item"
|
|
class="member-item"
|
|
@@ -183,6 +198,96 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
|
|
|
|
|
|
|
+ <!-- 挑战详情弹窗 -->
|
|
|
|
|
+ <view class="overlay" v-if="showDetailModal" @click="closeDetailModal">
|
|
|
|
|
+ <view class="modal detail-modal" @click.stop>
|
|
|
|
|
+ <view class="modal-header">
|
|
|
|
|
+ <text class="modal-title">挑战详情</text>
|
|
|
|
|
+ <text class="modal-close" @click="closeDetailModal">×</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view v-if="detailChallenge" class="detail-body">
|
|
|
|
|
+ <view class="detail-header">
|
|
|
|
|
+ <text class="detail-title">{{ detailChallenge.title }}</text>
|
|
|
|
|
+ <text class="detail-badge" :class="detailStatusClass">{{ detailStatusText }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <text class="detail-desc">{{ detailChallenge.description || '' }}</text>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 待同意:显示确认进度(pending 状态挑战) -->
|
|
|
|
|
+ <view class="pending-section" v-if="detailChallenge.status === 'pending'">
|
|
|
|
|
+ <text class="pending-hint">等待成员确认(已同意 {{ detailAgreedCount }}/{{ detailTotalCount }})</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 目标展示(两种模式) -->
|
|
|
|
|
+ <view class="detail-target">
|
|
|
|
|
+ <view class="detail-row">
|
|
|
|
|
+ <text class="detail-label">目标模式:</text>
|
|
|
|
|
+ <text>{{ detailChallenge.targetMode === 'aggregate' ? '全家合计' : '每人目标' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="detail-row">
|
|
|
|
|
+ <text class="detail-label">目标值:</text>
|
|
|
|
|
+ <text>{{ detailChallenge.targetValue }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="detail-row">
|
|
|
|
|
+ <text class="detail-label">持续天数:</text>
|
|
|
|
|
+ <text>{{ detailChallenge.durationDays }}天</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="detail-row" v-if="detailChallenge.startDate">
|
|
|
|
|
+ <text class="detail-label">开始日期:</text>
|
|
|
|
|
+ <text>{{ detailChallenge.startDate.slice(0,10) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="detail-row" v-if="detailChallenge.endDate">
|
|
|
|
|
+ <text class="detail-label">截止日期:</text>
|
|
|
|
|
+ <text>{{ detailChallenge.endDate.slice(0,10) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="detail-row" v-if="detailChallenge.respondDeadline">
|
|
|
|
|
+ <text class="detail-label">回复截止:</text>
|
|
|
|
|
+ <text :class="detailDeadlinePassed ? 'detail-label detail-label-danger' : 'detail-label'">{{ detailChallenge.respondDeadline.slice(0,16).replace('T',' ') }}{{ detailDeadlinePassed ? '(已超时)' : '' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="detail-row" v-if="detailChallenge.rewardPoints">
|
|
|
|
|
+ <text class="detail-label">奖励积分:</text>
|
|
|
|
|
+ <text class="detail-label detail-label-reward">+{{ detailChallenge.rewardPoints }}分</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 活动/已完成进度 -->
|
|
|
|
|
+ <view class="detail-progress" v-if="detailChallenge.status !== 'pending'">
|
|
|
|
|
+ <text class="detail-progress-title">进度</text>
|
|
|
|
|
+ <view class="detail-progress-bar-wrap">
|
|
|
|
|
+ <view class="detail-progress-bar-bg">
|
|
|
|
|
+ <view class="detail-progress-bar-fill" :style="{width: detailProgressPercent + '%'}"></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="detail-progress-text">{{ detailChallenge.totalProgress || 0 }}/{{ detailChallenge.targetValue }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="detail-member-list" v-if="detailChallenge.memberProgress && detailChallenge.memberProgress.length > 0">
|
|
|
|
|
+ <view class="detail-member-row" v-for="mp in detailChallenge.memberProgress" :key="mp.memberId">
|
|
|
|
|
+ <text class="detail-member-name">{{ mp.nickname }}</text>
|
|
|
|
|
+ <text class="detail-member-val">{{ mp.progressValue || 0 }}/{{ detailChallenge.targetValue }}</text>
|
|
|
|
|
+ <text class="detail-member-status" :class="'member-' + mp.status">{{ detailMemberStatusLabel(mp.status) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 同意/拒绝按钮 -->
|
|
|
|
|
+ <view class="detail-action" v-if="detailChallenge.status === 'pending' && detailChallenge.myStatus === 'pending'">
|
|
|
|
|
+ <view class="respond-btn respond-agree" @click="onDetailRespond('agree')">✅ 同意</view>
|
|
|
|
|
+ <view class="respond-btn respond-reject" @click="onDetailRespond('reject')">✖ 拒绝</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 编辑/删除按钮(仅创建者) -->
|
|
|
|
|
+ <view class="detail-action" v-if="creatorCanOperate">
|
|
|
|
|
+ <button class="btn-edit-detail" @click="closeDetailModal; openEditModal(detailChallenge)">编辑</button>
|
|
|
|
|
+ <button class="btn-delete-detail" @click="confirmDeleteDetail">删除</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="modal-footer">
|
|
|
|
|
+ <button class="btn-cancel" @click="closeDetailModal">关闭</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
<!-- 家庭成员不足提示弹窗 -->
|
|
<!-- 家庭成员不足提示弹窗 -->
|
|
|
<view class="overlay" v-if="showMemberPrompt" @click="showMemberPrompt = false">
|
|
<view class="overlay" v-if="showMemberPrompt" @click="showMemberPrompt = false">
|
|
|
<view class="modal prompt-modal" @click.stop>
|
|
<view class="modal prompt-modal" @click.stop>
|
|
@@ -224,7 +329,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import { getChallengeList, getChallengeHistory, createChallenge, updateChallenge, deleteChallenge, getChallengeTemplates, getFamilyMemberList, getMyMembership } from '../../utils/api.js'
|
|
|
|
|
|
|
+import { getChallengeList, getChallengeHistory, createChallenge, updateChallenge, deleteChallenge, getChallengeTemplates, getFamilyMemberList, getMyMembership, respondChallenge } from '../../utils/api.js'
|
|
|
import store from '../../store/index.js'
|
|
import store from '../../store/index.js'
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
@@ -237,10 +342,13 @@ export default {
|
|
|
familyMembers: [],
|
|
familyMembers: [],
|
|
|
selectedMemberIds: [],
|
|
selectedMemberIds: [],
|
|
|
showModal: false,
|
|
showModal: false,
|
|
|
|
|
+ showDetailModal: false,
|
|
|
|
|
+ detailChallenge: null,
|
|
|
showMemberPrompt: false,
|
|
showMemberPrompt: false,
|
|
|
showAddChildPrompt: false,
|
|
showAddChildPrompt: false,
|
|
|
editMode: false,
|
|
editMode: false,
|
|
|
editingId: null,
|
|
editingId: null,
|
|
|
|
|
+ editingStatus: null,
|
|
|
form: {
|
|
form: {
|
|
|
title: '',
|
|
title: '',
|
|
|
description: '',
|
|
description: '',
|
|
@@ -248,12 +356,60 @@ export default {
|
|
|
targetMode: 'all_members',
|
|
targetMode: 'all_members',
|
|
|
targetValue: '7',
|
|
targetValue: '7',
|
|
|
durationDays: '7',
|
|
durationDays: '7',
|
|
|
- rewardPoints: '50'
|
|
|
|
|
|
|
+ rewardPoints: '50',
|
|
|
|
|
+ respondDeadlineHours: '48'
|
|
|
},
|
|
},
|
|
|
isFreeMember: false
|
|
isFreeMember: false
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- onLoad() {
|
|
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ detailStatusClass: function() {
|
|
|
|
|
+ if (!this.detailChallenge) return ''
|
|
|
|
|
+ if (this.detailChallenge.status === 'active') return 'status-active'
|
|
|
|
|
+ if (this.detailChallenge.status === 'pending') return 'status-pending'
|
|
|
|
|
+ if (this.detailChallenge.status === 'completed') return 'status-completed'
|
|
|
|
|
+ if (this.detailChallenge.status === 'cancelled') return 'status-cancelled'
|
|
|
|
|
+ return 'status-active'
|
|
|
|
|
+ },
|
|
|
|
|
+ detailStatusText: function() {
|
|
|
|
|
+ if (!this.detailChallenge) return ''
|
|
|
|
|
+ if (this.detailChallenge.status === 'active') return '进行中'
|
|
|
|
|
+ if (this.detailChallenge.status === 'pending') return '待同意'
|
|
|
|
|
+ if (this.detailChallenge.status === 'completed') return '已完成'
|
|
|
|
|
+ if (this.detailChallenge.status === 'cancelled') return '已取消'
|
|
|
|
|
+ return '进行中'
|
|
|
|
|
+ },
|
|
|
|
|
+ detailAgreedCount: function() {
|
|
|
|
|
+ if (!this.detailChallenge || !this.detailChallenge.memberProgress) return 0
|
|
|
|
|
+ var count = 0
|
|
|
|
|
+ for (var i = 0; i < this.detailChallenge.memberProgress.length; i++) {
|
|
|
|
|
+ if (this.detailChallenge.memberProgress[i].status === 'agreed') count++
|
|
|
|
|
+ }
|
|
|
|
|
+ return count
|
|
|
|
|
+ },
|
|
|
|
|
+ detailTotalCount: function() {
|
|
|
|
|
+ if (!this.detailChallenge || !this.detailChallenge.memberProgress) return 0
|
|
|
|
|
+ return this.detailChallenge.memberProgress.length
|
|
|
|
|
+ },
|
|
|
|
|
+ detailProgressPercent: function() {
|
|
|
|
|
+ if (!this.detailChallenge || this.detailChallenge.targetValue <= 0) return 0
|
|
|
|
|
+ return Math.round(this.detailChallenge.totalProgress / this.detailChallenge.targetValue * 100)
|
|
|
|
|
+ },
|
|
|
|
|
+ detailDeadlinePassed: function() {
|
|
|
|
|
+ if (!this.detailChallenge || !this.detailChallenge.respondDeadline) return false
|
|
|
|
|
+ return new Date().getTime() > new Date(this.detailChallenge.respondDeadline).getTime()
|
|
|
|
|
+ },
|
|
|
|
|
+ creatorCanOperate: function() {
|
|
|
|
|
+ if (!this.detailChallenge) return false
|
|
|
|
|
+ return String(this.detailChallenge.creatorId) === String(this.$store.state.userId)
|
|
|
|
|
+ },
|
|
|
|
|
+ detailMemberStatusLabel: function(status) {
|
|
|
|
|
+ var map = { pending: '待确认', agreed: '已同意', rejected: '已拒绝' }
|
|
|
|
|
+ return map[status] || status
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onLoad(options) {
|
|
|
|
|
+ this._pendingChallengeId = options && options.id
|
|
|
this.loadTemplates()
|
|
this.loadTemplates()
|
|
|
this.loadMembers()
|
|
this.loadMembers()
|
|
|
this.checkMembership()
|
|
this.checkMembership()
|
|
@@ -283,6 +439,7 @@ export default {
|
|
|
])
|
|
])
|
|
|
this.activeChallenges = Array.isArray(res1.data) ? res1.data : []
|
|
this.activeChallenges = Array.isArray(res1.data) ? res1.data : []
|
|
|
this.historyChallenges = Array.isArray(res2.data) ? res2.data : []
|
|
this.historyChallenges = Array.isArray(res2.data) ? res2.data : []
|
|
|
|
|
+ this.openPendingChallengeIfNeeded()
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
this.activeChallenges = []
|
|
this.activeChallenges = []
|
|
|
this.historyChallenges = []
|
|
this.historyChallenges = []
|
|
@@ -290,6 +447,22 @@ export default {
|
|
|
this.loading = false
|
|
this.loading = false
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ // 从首页卡片点击进入时,自动展开对应挑战详情弹窗
|
|
|
|
|
+ openPendingChallengeIfNeeded() {
|
|
|
|
|
+ var id = this._pendingChallengeId
|
|
|
|
|
+ if (!id) return
|
|
|
|
|
+ this._pendingChallengeId = null
|
|
|
|
|
+ var found = null
|
|
|
|
|
+ for (var i = 0; i < this.activeChallenges.length; i++) {
|
|
|
|
|
+ if (String(this.activeChallenges[i].id) === String(id)) { found = this.activeChallenges[i]; break }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!found) {
|
|
|
|
|
+ for (var j = 0; j < this.historyChallenges.length; j++) {
|
|
|
|
|
+ if (String(this.historyChallenges[j].id) === String(id)) { found = this.historyChallenges[j]; break }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (found) this.viewChallenge(found)
|
|
|
|
|
+ },
|
|
|
async loadTemplates() {
|
|
async loadTemplates() {
|
|
|
try {
|
|
try {
|
|
|
const res = await getChallengeTemplates()
|
|
const res = await getChallengeTemplates()
|
|
@@ -376,7 +549,8 @@ export default {
|
|
|
this.editingId = null
|
|
this.editingId = null
|
|
|
this.form = {
|
|
this.form = {
|
|
|
title: '', description: '', challengeType: 'custom',
|
|
title: '', description: '', challengeType: 'custom',
|
|
|
- targetMode: 'all_members', targetValue: '7', durationDays: '7', rewardPoints: '50'
|
|
|
|
|
|
|
+ targetMode: 'all_members', targetValue: '7', durationDays: '7', rewardPoints: '50',
|
|
|
|
|
+ respondDeadlineHours: '48'
|
|
|
}
|
|
}
|
|
|
this.selectedMemberIds = this.familyMembers.map(m => m.id)
|
|
this.selectedMemberIds = this.familyMembers.map(m => m.id)
|
|
|
this.showModal = true
|
|
this.showModal = true
|
|
@@ -384,6 +558,7 @@ export default {
|
|
|
openEditModal(ch) {
|
|
openEditModal(ch) {
|
|
|
this.editMode = true
|
|
this.editMode = true
|
|
|
this.editingId = ch.id
|
|
this.editingId = ch.id
|
|
|
|
|
+ this.editingStatus = ch.status
|
|
|
this.form = {
|
|
this.form = {
|
|
|
title: ch.title || '',
|
|
title: ch.title || '',
|
|
|
description: ch.description || '',
|
|
description: ch.description || '',
|
|
@@ -393,6 +568,14 @@ export default {
|
|
|
durationDays: String(ch.durationDays || 7),
|
|
durationDays: String(ch.durationDays || 7),
|
|
|
rewardPoints: String(ch.rewardPoints || 50)
|
|
rewardPoints: String(ch.rewardPoints || 50)
|
|
|
}
|
|
}
|
|
|
|
|
+ // 编辑时预填充参与成员:从 memberProgress 读取所有状态的成员
|
|
|
|
|
+ var selected = []
|
|
|
|
|
+ if (ch.memberProgress && ch.memberProgress.length) {
|
|
|
|
|
+ for (var i = 0; i < ch.memberProgress.length; i++) {
|
|
|
|
|
+ selected.push(ch.memberProgress[i].memberId)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.selectedMemberIds = selected
|
|
|
this.showModal = true
|
|
this.showModal = true
|
|
|
},
|
|
},
|
|
|
closeModal() {
|
|
closeModal() {
|
|
@@ -421,7 +604,8 @@ export default {
|
|
|
durationDays: parseInt(this.form.durationDays || 7, 10),
|
|
durationDays: parseInt(this.form.durationDays || 7, 10),
|
|
|
rewardPoints: parseInt(this.form.rewardPoints || 0, 10)
|
|
rewardPoints: parseInt(this.form.rewardPoints || 0, 10)
|
|
|
}
|
|
}
|
|
|
- if (!this.editMode) data.participantMemberIds = this.selectedMemberIds
|
|
|
|
|
|
|
+ if (!this.editMode || this.editingStatus === 'pending') data.participantMemberIds = this.selectedMemberIds
|
|
|
|
|
+ if (!this.editMode && this.form.respondDeadlineHours) data.respondDeadlineHours = parseInt(this.form.respondDeadlineHours, 10)
|
|
|
|
|
|
|
|
this.loading = true
|
|
this.loading = true
|
|
|
try {
|
|
try {
|
|
@@ -464,8 +648,58 @@ export default {
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
viewChallenge(ch) {
|
|
viewChallenge(ch) {
|
|
|
- // 跳转到挑战详情
|
|
|
|
|
- uni.navigateTo({ url: '/pages/activity/index' })
|
|
|
|
|
|
|
+ // 打开挑战详情弹窗
|
|
|
|
|
+ this.detailChallenge = ch
|
|
|
|
|
+ this.showDetailModal = true
|
|
|
|
|
+ },
|
|
|
|
|
+ closeDetailModal() {
|
|
|
|
|
+ this.showDetailModal = false
|
|
|
|
|
+ this.detailChallenge = null
|
|
|
|
|
+ },
|
|
|
|
|
+ async onDetailRespond(action) {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '确认操作',
|
|
|
|
|
+ content: action === 'agree' ? '确认同意参与该挑战吗?' : '确认拒绝该挑战吗?',
|
|
|
|
|
+ success: function(res) {
|
|
|
|
|
+ if (res.confirm) {
|
|
|
|
|
+ respondChallenge({ challengeId: self.detailChallenge.id, action: action }).then(function(resp) {
|
|
|
|
|
+ if (resp && resp.code === 200) {
|
|
|
|
|
+ uni.showToast({ title: action === 'agree' ? '已同意' : '已拒绝', icon: 'success' })
|
|
|
|
|
+ self.closeDetailModal()
|
|
|
|
|
+ self.loadData()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.showToast({ title: (resp && resp.message) || '操作失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {
|
|
|
|
|
+ uni.showToast({ title: '操作失败,请重试', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ confirmDeleteDetail() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ if (!this.detailChallenge) return
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '确认删除',
|
|
|
|
|
+ content: '确定要取消这个挑战吗?',
|
|
|
|
|
+ success: function(r) {
|
|
|
|
|
+ if (r.confirm) {
|
|
|
|
|
+ deleteChallenge(self.detailChallenge.id).then(function(res) {
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ uni.showToast({ title: '已删除', icon: 'success' })
|
|
|
|
|
+ self.closeDetailModal()
|
|
|
|
|
+ self.loadData()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.showToast({ title: res.message || '删除失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {
|
|
|
|
|
+ uni.showToast({ title: '删除失败', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
},
|
|
},
|
|
|
goToAddMember() {
|
|
goToAddMember() {
|
|
|
this.showMemberPrompt = false
|
|
this.showMemberPrompt = false
|
|
@@ -854,6 +1088,185 @@ export default {
|
|
|
border-color: #10B981;
|
|
border-color: #10B981;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* 详情弹窗 */
|
|
|
|
|
+.detail-modal {
|
|
|
|
|
+ max-width: 90vw;
|
|
|
|
|
+ padding: 32rpx 28rpx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 24rpx;
|
|
|
|
|
+ box-shadow: 0 8rpx 48rpx rgba(0,0,0,0.12);
|
|
|
|
|
+}
|
|
|
|
|
+.detail-modal .modal-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+ padding-bottom: 16rpx;
|
|
|
|
|
+ border-bottom: 2rpx solid #E2E8F0;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-modal .modal-title {
|
|
|
|
|
+ font-size: 34rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #1E293B;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-modal .modal-close {
|
|
|
|
|
+ font-size: 30rpx;
|
|
|
|
|
+ color: #64748B;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-modal .modal-close:hover {
|
|
|
|
|
+ color: #DC2626;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-body {
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ color: #475569;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-header .detail-title {
|
|
|
|
|
+ font-size: 30rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #1E293B;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-header .detail-badge {
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ padding: 2rpx 8rpx;
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-header .status-active { background: #DCFCE7; color: #16A34A; }
|
|
|
|
|
+.detail-header .status-pending { background: #FEF3C7; color: #B45309; }
|
|
|
|
|
+.detail-header .status-completed { background: #F1F5F9; color: #64748B; }
|
|
|
|
|
+.detail-header .status-cancelled { background: #FEE2E2; color: #DC2626; }
|
|
|
|
|
+.detail-header .status-not-launched { background: #E5E7EB; color: #6B7280; }
|
|
|
|
|
+.detail-desc {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #64748B;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-target {
|
|
|
|
|
+ margin-top: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-row .detail-label {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #647489;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-row .detail-value {
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ color: #10B981;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-row .detail-value-danger {
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ color: #DC2626;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-progress {
|
|
|
|
|
+ margin-top: 16rpx;
|
|
|
|
|
+ padding-top: 16rpx;
|
|
|
|
|
+ border-top: 2rpx solid #E2E8F0;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-progress-title {
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #64748B;
|
|
|
|
|
+ margin-bottom: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-progress-bar-wrap {
|
|
|
|
|
+ height: 10rpx;
|
|
|
|
|
+ background: #E2E8F0;
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-progress-bar-fill {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ background: linear-gradient(90deg, #10B981, #34D399);
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+ transition: width 0.3s ease;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-progress-text {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ color: #64748B;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-member-list {
|
|
|
|
|
+ margin-top: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-member-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 8rpx 0;
|
|
|
|
|
+ border-bottom: 1rpx solid #F1F5F9;
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-member-name {
|
|
|
|
|
+ color: #1E293B;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-member-val {
|
|
|
|
|
+ color: #64748B;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-member-status {
|
|
|
|
|
+ padding: 2rpx 6rpx;
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+ font-size: 20rpx;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-member-pending { background: #FEF3C7; color: #B45309; }
|
|
|
|
|
+.detail-member-agreed { background: #DCFCE7; color: #16A34A; }
|
|
|
|
|
+.detail-member-rejected { background: #FEE2E2; color: #DC2626; }
|
|
|
|
|
+.detail-action {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12rpx;
|
|
|
|
|
+ margin-top: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-action .respond-btn {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 72rpx;
|
|
|
|
|
+ line-height: 72rpx;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-action .btn-edit-detail {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 72rpx;
|
|
|
|
|
+ line-height: 72rpx;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ background: #F3F4F6;
|
|
|
|
|
+ color: #374151;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-action .btn-delete-detail {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 72rpx;
|
|
|
|
|
+ line-height: 72rpx;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ background: #FEE2E2;
|
|
|
|
|
+ color: #DC2626;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-label-reward {
|
|
|
|
|
+ color: #F59E0B;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-label-danger {
|
|
|
|
|
+ color: #DC2626;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/* 底部按钮 */
|
|
/* 底部按钮 */
|
|
|
.modal-footer {
|
|
.modal-footer {
|
|
|
display: flex;
|
|
display: flex;
|