Files
jurong_circle_frontdesk/src/views/TaskCenter.vue
2025-07-26 15:35:53 +08:00

645 lines
13 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>
<div class="task-center">
<!-- 导航栏 -->
<nav class="navbar">
<div class="nav-left">
<el-button
type="text"
@click="$router.go(-1)"
class="back-btn"
>
<el-icon><ArrowLeft /></el-icon>
返回
</el-button>
</div>
<div class="nav-center">
<h1 class="nav-title">任务中心</h1>
</div>
<div class="nav-right">
<el-button
type="text"
@click="$router.push('/points-history')"
class="points-btn"
>
<el-icon><Coin /></el-icon>
{{ userPoints }}
</el-button>
</div>
</nav>
<!-- 任务统计 -->
<div class="task-stats">
<div class="stats-card">
<div class="stat-item">
<div class="stat-icon">
<el-icon><Trophy /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ completedTasks }}</div>
<div class="stat-label">已完成</div>
</div>
</div>
<div class="stat-item">
<div class="stat-icon">
<el-icon><Clock /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ pendingTasks }}</div>
<div class="stat-label">进行中</div>
</div>
</div>
<div class="stat-item">
<div class="stat-icon">
<el-icon><Star /></el-icon>
</div>
<div class="stat-info">
<div class="stat-value">{{ totalRewards }}</div>
<div class="stat-label">总积分</div>
</div>
</div>
</div>
</div>
<!-- 任务分类 -->
<div class="task-categories">
<div class="category-tabs">
<div
v-for="category in categories"
:key="category.key"
:class="['category-tab', { active: activeCategory === category.key }]"
@click="activeCategory = category.key"
>
<el-icon>{{ category.icon }}</el-icon>
<span>{{ category.name }}</span>
</div>
</div>
</div>
<!-- 任务列表 -->
<div class="task-list">
<div
v-for="task in filteredTasks"
:key="task.id"
:class="['task-item', {
completed: task.status === 'completed',
claimed: task.status === 'claimed'
}]"
>
<div class="task-icon">
<el-icon :size="24">{{ getTaskIcon(task.type) }}</el-icon>
</div>
<div class="task-content">
<div class="task-title">{{ task.title }}</div>
<div class="task-desc">{{ task.description }}</div>
<div class="task-progress" v-if="task.progress !== undefined">
<el-progress
:percentage="(task.progress / task.target) * 100"
:show-text="false"
:stroke-width="4"
/>
<span class="progress-text">{{ task.progress }}/{{ task.target }}</span>
</div>
</div>
<div class="task-reward">
<div class="reward-points">+{{ task.points }}</div>
<el-button
v-if="task.status === 'pending'"
type="primary"
size="small"
@click="doTask(task)"
:loading="task.loading"
>
{{ getTaskButtonText(task) }}
</el-button>
<el-button
v-else-if="task.status === 'completed'"
type="success"
size="small"
@click="claimReward(task)"
:loading="task.loading"
>
领取奖励
</el-button>
<el-tag
v-else-if="task.status === 'claimed'"
type="success"
size="small"
>
已领取
</el-tag>
</div>
</div>
</div>
<!-- 空状态 -->
<div v-if="filteredTasks.length === 0" class="empty-state">
<el-icon :size="60" color="#c0c4cc"><DocumentRemove /></el-icon>
<p>暂无任务</p>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import {
ArrowLeft,
Coin,
Trophy,
Clock,
Star,
Calendar,
ShoppingCart,
Share,
User,
CreditCard,
Present,
DocumentRemove
} from '@element-plus/icons-vue'
import api from '@/utils/api'
const router = useRouter()
const userStore = useUserStore()
// 响应式数据
const userPoints = ref(0)
const activeCategory = ref('daily')
const tasks = ref([])
const loading = ref(false)
// 任务分类
const categories = ref([
{ key: 'daily', name: '每日任务', icon: Calendar },
{ key: 'shopping', name: '购物任务', icon: ShoppingCart },
{ key: 'social', name: '社交任务', icon: Share },
{ key: 'profile', name: '完善资料', icon: User },
{ key: 'special', name: '特殊任务', icon: Present }
])
// 计算属性
const filteredTasks = computed(() => {
return tasks.value.filter(task => task.category === activeCategory.value)
})
const completedTasks = computed(() => {
return tasks.value.filter(task => task.status === 'claimed').length
})
const pendingTasks = computed(() => {
return tasks.value.filter(task => task.status === 'pending' || task.status === 'completed').length
})
const totalRewards = computed(() => {
return tasks.value
.filter(task => task.status === 'claimed')
.reduce((total, task) => total + task.points, 0)
})
// 方法
/**
* 获取任务图标
*/
const getTaskIcon = (type) => {
const iconMap = {
purchase: ShoppingCart,
share: Share,
profile: User,
transfer: CreditCard,
invite: User,
review: Star
}
return iconMap[type] || Present
}
/**
* 获取任务按钮文本
*/
const getTaskButtonText = (task) => {
const textMap = {
purchase: '去购买',
share: '去分享',
profile: '去完善',
transfer: '去转账',
invite: '去邀请',
review: '去评价'
}
return textMap[task.type] || '去完成'
}
/**
* 执行任务
*/
const doTask = async (task) => {
task.loading = true
try {
switch (task.type) {
case 'purchase':
router.push('/shop')
break
case 'share':
await shareApp()
break
case 'profile':
router.push('/profile')
break
case 'transfer':
router.push('/transfers')
break
case 'invite':
await showInviteDialog()
break
case 'review':
router.push('/orders')
break
default:
// 直接完成任务
await completeTask(task.id)
break
}
} catch (error) {
ElMessage.error('操作失败,请重试')
} finally {
task.loading = false
}
}
/**
* 分享应用
*/
const shareApp = async () => {
if (navigator.share) {
try {
await navigator.share({
title: '融互通 - 资金互助平台',
text: '发现一个很棒的资金互助平台,快来看看吧!',
url: window.location.origin
})
// 完成分享任务
await completeTask('share_app')
ElMessage.success('分享成功!')
} catch (error) {
if (error.name !== 'AbortError') {
ElMessage.error('分享失败')
}
}
} else {
// 复制链接到剪贴板
try {
await navigator.clipboard.writeText(window.location.origin)
await completeTask('share_app')
ElMessage.success('链接已复制到剪贴板!')
} catch (error) {
ElMessage.error('复制失败')
}
}
}
/**
* 显示邀请对话框
*/
const showInviteDialog = async () => {
// 这里可以实现邀请功能
ElMessage.info('邀请功能开发中...')
}
/**
* 完成任务
*/
const completeTask = async (taskId) => {
try {
const response = await api.post(`/tasks/${taskId}/complete`)
// 更新任务状态
const task = tasks.value.find(t => t.id === taskId)
if (task) {
task.status = 'completed'
}
ElMessage.success('任务完成!')
} catch (error) {
ElMessage.error('任务完成失败')
}
}
/**
* 领取奖励
*/
const claimReward = async (task) => {
task.loading = true
try {
const response = await api.post(`/tasks/${task.id}/claim`)
// 更新任务状态和用户积分
task.status = 'claimed'
userPoints.value += task.points
ElMessage.success(`获得 ${task.points} 积分!`)
} catch (error) {
ElMessage.error('领取失败,请重试')
} finally {
task.loading = false
}
}
/**
* 获取任务列表
*/
const getTasks = async () => {
loading.value = true
try {
const response = await api.get('/tasks')
tasks.value = response.data.map(task => ({
...task,
loading: false
}))
} catch (error) {
// 如果API不存在使用模拟数据
tasks.value = [
{
id: 'first_purchase',
category: 'shopping',
type: 'purchase',
title: '首次购买',
description: '在积分商城完成首次购买',
points: 50,
status: 'pending',
loading: false
},
{
id: 'share_app',
category: 'social',
type: 'share',
title: '分享应用',
description: '分享应用给朋友',
points: 20,
status: 'pending',
loading: false
},
{
id: 'complete_profile',
category: 'profile',
type: 'profile',
title: '完善个人资料',
description: '完善头像、姓名等个人信息',
points: 30,
status: 'pending',
progress: 2,
target: 5,
loading: false
},
{
id: 'first_transfer',
category: 'special',
type: 'transfer',
title: '首次转账',
description: '完成首次转账操作',
points: 100,
status: 'completed',
loading: false
}
]
} finally {
loading.value = false
}
}
/**
* 获取用户积分
*/
const getUserPoints = async () => {
try {
const response = await api.get('/user/points')
userPoints.value = response.data.points
} catch (error) {
console.error('获取用户积分失败:', error)
}
}
// 生命周期
onMounted(() => {
getTasks()
getUserPoints()
})
</script>
<style scoped>
.task-center {
min-height: 100vh;
background: #f5f7fa;
}
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
height: 56px;
background: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.nav-left,
.nav-right {
flex: 1;
}
.nav-right {
display: flex;
justify-content: flex-end;
}
.back-btn,
.points-btn {
color: #333;
font-size: 14px;
}
.points-btn {
color: #ff6b35;
font-weight: bold;
}
.nav-title {
margin: 0;
font-size: 18px;
font-weight: 500;
text-align: center;
}
.task-stats {
padding: 16px;
}
.stats-card {
background: white;
border-radius: 12px;
padding: 20px;
display: flex;
justify-content: space-around;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-bottom: 8px;
}
.stat-value {
font-size: 20px;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.stat-label {
font-size: 12px;
color: #666;
}
.task-categories {
padding: 0 16px 16px;
}
.category-tabs {
display: flex;
background: white;
border-radius: 12px;
padding: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.category-tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
font-size: 12px;
color: #666;
}
.category-tab.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.category-tab .el-icon {
margin-bottom: 4px;
}
.task-list {
padding: 0 16px;
}
.task-item {
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
display: flex;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s;
}
.task-item.completed {
border-left: 4px solid #67c23a;
}
.task-item.claimed {
opacity: 0.6;
border-left: 4px solid #909399;
}
.task-icon {
width: 48px;
height: 48px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
margin-right: 16px;
}
.task-content {
flex: 1;
}
.task-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.task-desc {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.task-progress {
display: flex;
align-items: center;
gap: 8px;
}
.task-progress .el-progress {
flex: 1;
}
.progress-text {
font-size: 12px;
color: #666;
white-space: nowrap;
}
.task-reward {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.reward-points {
font-size: 14px;
font-weight: bold;
color: #ff6b35;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #666;
}
.empty-state p {
margin-top: 16px;
font-size: 14px;
}
</style>