Files
yimo-resource-blog/pages/task/detail.vue
2026-09-16 16:49:25 +08:00

159 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page" v-if="task.id">
<view class="summary-card">
<image v-if="task.coverUrl" class="cover" :src="resolveUrl(task.coverUrl)" mode="aspectFill"></image>
<view class="summary-main">
<view class="title">{{ task.taskName }}</view>
<view class="tags"><text class="tag">{{ task.cycleType === 'DAILY' ? '每日任务' : '单次任务' }}</text><text class="points">+{{ task.rewardPoints }}积分</text></view>
</view>
</view>
<view class="section">
<view class="section-title">任务说明</view>
<view class="paragraph">{{ task.description || '完成任务后上传相关截图,平台审核通过后积分自动到账。' }}</view>
</view>
<view v-if="task.operationGuide" class="section">
<view class="section-title">操作步骤</view>
<view class="paragraph guide">{{ task.operationGuide }}</view>
</view>
<view v-if="exampleImages.length" class="section">
<view class="section-title">正确截图示例</view>
<view class="image-grid"><image v-for="(url,index) in exampleImages" :key="url" class="example-image" :src="resolveUrl(url)" mode="aspectFill" @click="previewExamples(index)"></image></view>
</view>
<view v-if="submission" class="section status-section">
<view class="section-title">当前状态</view>
<view class="status-line"><text :class="['status-badge', submissionStatusClass]">{{ submissionStatusText }}</text><text class="submit-time">{{ submission.lastSubmitTime }}</text></view>
<view v-if="submission.rejectReason" class="reject-reason">{{ submission.status === 3 ? '撤销原因:' : '未通过原因:' }}{{ submission.rejectReason }}</view>
<view v-if="currentAttachments.length" class="old-images">
<image v-for="(item,index) in currentAttachments" :key="item.id" class="old-image" :src="item.previewUrl" mode="aspectFill" @click="previewOld(index)"></image>
</view>
</view>
<view v-if="task.canSubmit" class="section">
<view class="section-title">上传完成截图</view>
<view class="upload-tip">最多3张,单张不超过5MB;截图将保存到七牛云用于人工审核</view>
<view class="image-grid upload-grid">
<view v-for="(item,index) in uploads" :key="item.objectKey" class="upload-item">
<image class="upload-image" :src="item.localPath" mode="aspectFill" @click="previewUploads(index)"></image>
<text class="remove cuIcon-close" @click.stop="removeUpload(index)"></text>
</view>
<view v-if="uploads.length < 3" class="choose-image" @click="chooseImages"><text class="cuIcon-cameraadd"></text><view>上传截图</view></view>
</view>
<textarea v-model="remark" class="remark" maxlength="300" placeholder="可补充说明微信昵称、群名称等信息(选填)"></textarea>
</view>
<view class="bottom-space"></view>
<view class="bottom-bar">
<button v-if="task.canSubmit" class="submit-btn" :disabled="submitting || uploads.length === 0" @click="submit">{{ submitting ? '正在提交...' : (task.currentStatus === 2 ? '重新提交审核' : '提交审核') }}</button>
<button v-else class="submit-btn disabled" disabled>{{ buttonText }}</button>
</view>
</view>
</template>
<script>
import config from '@/config'
import { getPointTask, getMyPointTaskSubmission, submitPointTask, resubmitPointTask } from '@/api/app/index.js'
import { uploadPointTaskImage } from '@/utils/qiniu.js'
export default {
data() { return { id: null, selectedSubmissionId: null, task: {}, submission: null, uploads: [], remark: '', submitting: false } },
computed: {
exampleImages() { return String(this.task.exampleImages || '').split(',').map(v => v.trim()).filter(Boolean) },
currentAttachments() { return this.submission && this.submission.attachments ? this.submission.attachments.filter(v => v.isCurrent === 1) : [] },
submissionStatusText() { return this.submission ? ({ 0: '审核中', 1: '审核通过', 2: '审核未通过', 3: '奖励已撤销' }[this.submission.status] || '未知状态') : '' },
submissionStatusClass() { return this.submission ? ({ 0: 'pending', 1: 'approved', 2: 'rejected', 3: 'revoked' }[this.submission.status] || '') : '' },
buttonText() {
if (this.task.currentStatus === 0) return '审核中'
if (this.task.currentStatus === 1) return this.task.cycleType === 'DAILY' ? '今日已完成' : '任务已完成'
if (this.task.currentStatus === 3) return '奖励已撤销'
return '暂不可提交'
}
},
onLoad(options) { this.id = Number(options.id); this.selectedSubmissionId = options.submissionId ? Number(options.submissionId) : null; this.load() },
onPullDownRefresh() { this.load() },
methods: {
load() {
getPointTask(this.id).then(res => {
this.task = res.data || {}
this.submission = null
const submissionId = this.selectedSubmissionId || this.task.currentSubmissionId
if (submissionId) return getMyPointTaskSubmission(submissionId)
}).then(res => { if (res && res.data) { this.submission = res.data; this.remark = this.submission.remark || '' } }).finally(() => uni.stopPullDownRefresh())
},
resolveUrl(url) {
if (!url || /^(https?:)?\/\//i.test(url)) return url
return config.baseUrl + (url.charAt(0) === '/' ? url : '/' + url)
},
chooseImages() {
uni.chooseImage({ count: 3 - this.uploads.length, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: async res => {
const files = res.tempFiles || (res.tempFilePaths || []).map(path => ({ path: path, size: 0 }))
for (let i = 0; i < files.length; i++) {
if (Number(files[i].size || 0) > 5 * 1024 * 1024) { uni.showToast({ title: '单张截图不能超过5MB', icon: 'none' }); continue }
try {
uni.showLoading({ title: '上传第' + (i + 1) + '张' })
const uploaded = await uploadPointTaskImage(files[i])
this.uploads.push(uploaded)
} catch (e) {
uni.showToast({ title: (e && e.message) || '截图上传失败', icon: 'none' })
} finally { uni.hideLoading() }
}
} })
},
removeUpload(index) { this.uploads.splice(index, 1) },
previewExamples(index) { uni.previewImage({ current: this.resolveUrl(this.exampleImages[index]), urls: this.exampleImages.map(this.resolveUrl) }) },
previewUploads(index) { uni.previewImage({ current: this.uploads[index].localPath, urls: this.uploads.map(v => v.localPath) }) },
previewOld(index) { uni.previewImage({ current: this.currentAttachments[index].previewUrl, urls: this.currentAttachments.map(v => v.previewUrl) }) },
submit() {
if (!this.uploads.length || this.submitting) return
this.submitting = true
const data = { attachmentKeys: this.uploads.map(v => v.objectKey), remark: String(this.remark || '').trim() }
const action = this.task.currentStatus === 2 && this.task.currentSubmissionId ? resubmitPointTask(this.task.currentSubmissionId, data) : submitPointTask(this.task.id, data)
action.then(() => {
uni.showModal({ title: '提交成功', content: '截图已提交,请耐心等待后台审核。', showCancel: false })
this.uploads = []
this.load()
}).finally(() => { this.submitting = false })
}
}
}
</script>
<style scoped>
.page { min-height: 100vh; padding: 26rpx; box-sizing: border-box; background: #f5f7fb; }
.summary-card, .section { margin-bottom: 22rpx; padding: 28rpx; border-radius: 22rpx; background: #fff; box-shadow: 0 8rpx 24rpx rgba(34, 48, 79, .05); }
.summary-card { display: flex; align-items: center; }
.cover { width: 132rpx; height: 132rpx; flex-shrink: 0; border-radius: 18rpx; background: #eef2fa; }
.summary-main { flex: 1; min-width: 0; margin-left: 24rpx; }
.title { font-size: 35rpx; font-weight: 700; color: #242933; }
.tags { display: flex; align-items: center; margin-top: 20rpx; }
.tag { padding: 6rpx 15rpx; border-radius: 16rpx; color: #5576e9; background: #edf1ff; font-size: 22rpx; }
.points { margin-left: 16rpx; color: #ff8721; font-size: 29rpx; font-weight: 700; }
.section-title { margin-bottom: 20rpx; color: #282d36; font-size: 30rpx; font-weight: 650; }
.paragraph { color: #626a76; font-size: 26rpx; line-height: 42rpx; }
.guide { white-space: pre-wrap; }
.image-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
.example-image, .upload-item, .choose-image { width: 190rpx; height: 190rpx; border-radius: 14rpx; }
.example-image, .upload-image { background: #edf0f5; }
.upload-item { position: relative; overflow: hidden; }
.upload-image { width: 100%; height: 100%; }
.remove { position: absolute; top: 8rpx; right: 8rpx; width: 38rpx; height: 38rpx; border-radius: 50%; color: #fff; background: rgba(0,0,0,.58); text-align: center; line-height: 38rpx; font-size: 24rpx; }
.choose-image { display: flex; flex-direction: column; align-items: center; justify-content: center; border: 2rpx dashed #cbd2df; color: #8790a0; font-size: 23rpx; }
.choose-image .cuIcon-cameraadd { margin-bottom: 10rpx; font-size: 52rpx; color: #667ff1; }
.upload-tip { margin: -5rpx 0 22rpx; color: #9ba2ae; font-size: 22rpx; line-height: 34rpx; }
.remark { width: 100%; height: 150rpx; margin-top: 24rpx; padding: 20rpx; box-sizing: border-box; border-radius: 14rpx; background: #f7f8fb; font-size: 25rpx; }
.status-line { display: flex; align-items: center; justify-content: space-between; }
.status-badge { padding: 8rpx 18rpx; border-radius: 18rpx; font-size: 24rpx; }
.status-badge.pending { color: #df8b16; background: #fff3df; }
.status-badge.approved { color: #24925d; background: #e8f7ef; }
.status-badge.rejected { color: #dc5151; background: #ffeded; }
.status-badge.revoked { color: #777f8c; background: #eff0f3; }
.submit-time { color: #9da4af; font-size: 22rpx; }
.reject-reason { margin-top: 20rpx; padding: 18rpx; border-radius: 12rpx; color: #d65050; background: #fff5f5; font-size: 24rpx; line-height: 36rpx; }
.old-images { display: flex; flex-wrap: wrap; gap: 15rpx; margin-top: 22rpx; }
.old-image { width: 150rpx; height: 150rpx; border-radius: 12rpx; }
.bottom-space { height: 130rpx; }
.bottom-bar { position: fixed; z-index: 10; left: 0; right: 0; bottom: 0; padding: 18rpx 28rpx calc(18rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -7rpx 25rpx rgba(20, 35, 66, .08); }
.submit-btn { height: 84rpx; border: 0; border-radius: 42rpx; color: #fff; background: linear-gradient(90deg, #567cf4, #735ff1); font-size: 29rpx; line-height: 84rpx; }
.submit-btn.disabled { color: #969da8; background: #e7e9ee; }
</style>