Files

271 lines
5.7 KiB
Plaintext

<template>
<view class="page">
<view class="-status-bar"></view>
<view class="header">
<view class="coins-card">
<uni-icons type="wallet-filled" size="36" color="#FFB300"></uni-icons>
<view class="coins-info">
<text class="coins-num">{{ coins }}</text>
<text class="coins-label">我的金币</text>
</view>
</view>
</view>
<scroll-view direction="vertical" :show-scrollbar="false" style="flex:1;">
<view class="task-item" v-for="(item, idx) in tasks" :key="item.id">
<view class="task-icon">
<uni-icons :type="item.icon" size="30" color="#fff"></uni-icons>
</view>
<view class="task-main">
<text class="task-title">{{ item.title }}</text>
<text class="task-desc">{{ item.desc }}</text>
<view class="task-progress" v-if="item.status != 2">
<view class="bar">
<view class="bar-inner" :style="{ width: progressWidth(item) }"></view>
</view>
<text class="bar-text">{{ item.current }}/{{ item.target }}</text>
</view>
</view>
<view class="task-reward">+{{ item.reward }}</view>
<button class="task-btn" :class="btnClass(item.status)" :disabled="item.status == 2"
@click="onReceive(item)">
{{ btnText(item.status) }}
</button>
</view>
<view v-if="tasks.length === 0 && !loading" class="empty">
<text class="empty-text">暂无任务</text>
</view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import Api from '@/common/api-service.uts'
type ITask = {
id : number
title : string
desc : string
icon : string
reward : number
target : number
current : number
status : number
}
const coins = ref<number>(0)
const loading = ref<boolean>(false)
const tasks = reactive<ITask[]>([])
const btnText = (status : number) : string => {
if (status == 2) return '已领取'
if (status == 1) return '领取'
return '去完成'
}
const btnClass = (status : number) : string => {
if (status == 2) return 'done'
if (status == 1) return 'ready'
return 'todo'
}
const progressWidth = (item : ITask) : string => {
const pct = item.target > 0 ? (item.current / item.target * 100) : 0
return Math.min(100, pct) + '%'
}
function loadData() {
if (loading.value) return
loading.value = true
Api.task.list()
.then((res : UTSJSONObject) => {
coins.value = (res.get('coins') as number) ?? 0
const list = (res.get('list') as Array<ITask>) ?? []
tasks.splice(0, tasks.length)
list.forEach((t : ITask) => tasks.push(t))
})
.catch(() => {
uni.showToast({ title: '加载失败', icon: 'none' })
})
.finally(() => { loading.value = false })
}
function onReceive(item : ITask) {
if (item.status == 2) return
if (item.status == 0) {
uni.showToast({ title: '任务未完成', icon: 'none' })
return
}
uni.showLoading({ title: '领取中...', mask: true })
Api.task.receive(item.id)
.then((res : UTSJSONObject) => {
item.status = 2
coins.value = (res.get('coins_left') as number) ?? coins.value
uni.hideLoading()
uni.showToast({ title: '领取成功 +' + item.reward + '金币', icon: 'none' })
})
.catch((e : any) => {
uni.hideLoading()
const msg = (e as UTSJSONObject)?.get('message') as string
uni.showToast({ title: msg ?? '领取失败', icon: 'none' })
})
}
onMounted(() => { loadData() })
onShow(() => { loadData() })
</script>
<style lang="scss">
.page {
flex: 1;
background-color: #f7f7f7;
}
.-status-bar {
height: var(--status-bar-height);
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
}
.header {
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
padding-bottom: 30rpx;
}
.coins-card {
display: flex;
flex-flow: row nowrap;
align-items: center;
margin: 24rpx 30rpx 0;
padding: 30rpx;
background-color: rgba(255, 255, 255, 0.18);
border-radius: 20rpx;
}
.coins-info {
display: flex;
flex-direction: column;
margin-left: 20rpx;
}
.coins-num {
font-size: 44rpx;
font-weight: bold;
color: #fff;
}
.coins-label {
font-size: 24rpx;
color: #ffe;
margin-top: 4rpx;
}
.task-item {
display: flex;
flex-flow: row nowrap;
align-items: center;
margin: 20rpx 30rpx 0;
padding: 28rpx 24rpx;
background-color: #fff;
border-radius: 18rpx;
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.04);
}
.task-icon {
width: 84rpx;
height: 84rpx;
border-radius: 20rpx;
background: linear-gradient(135deg, #FF8E53 0%, #FF6B6B 100%);
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
}
.task-main {
flex: 1;
display: flex;
flex-direction: column;
}
.task-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.task-desc {
font-size: 22rpx;
color: #999;
margin-top: 4rpx;
}
.task-progress {
display: flex;
flex-flow: row nowrap;
align-items: center;
margin-top: 10rpx;
}
.bar {
flex: 1;
height: 12rpx;
border-radius: 6rpx;
background-color: #f0f0f0;
overflow: hidden;
margin-right: 12rpx;
}
.bar-inner {
height: 100%;
background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%);
border-radius: 6rpx;
}
.bar-text {
font-size: 20rpx;
color: #bbb;
}
.task-reward {
font-size: 26rpx;
font-weight: bold;
color: #FF9500;
margin-right: 16rpx;
}
.task-btn {
height: 60rpx;
padding: 0 28rpx;
line-height: 60rpx;
border-radius: 30rpx;
font-size: 26rpx;
border: none;
}
.task-btn.ready {
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%);
color: #fff;
}
.task-btn.todo {
background-color: #f0f0f0;
color: #999;
}
.task-btn.done {
background-color: #f0f0f0;
color: #ccc;
}
.empty {
display: flex;
align-items: center;
justify-content: center;
padding: 120rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #bbb;
}
</style>