FunTaskItem.vue 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. <template>
  2. <view class="fun-task-item" :class="{ completed: task.status === 'completed', urgent: isUrgent }">
  3. <view class="task-icon">{{ getCategoryIcon() }}</view>
  4. <view class="task-content">
  5. <view class="task-title">{{ task.title }}</view>
  6. <view class="task-meta">
  7. <text class="points-badge">+{{ task.points }}分</text>
  8. <text class="deadline">{{ getTimeLeft() }}</text>
  9. </view>
  10. </view>
  11. <view class="task-action">
  12. <button v-if="task.status === 'pending'" class="complete-btn" @click="$emit('complete')">
  13. <text class="btn-icon">✨</text>
  14. <text>完成</text>
  15. </button>
  16. <view v-else class="completed-badge">
  17. <text class="check-icon">✅</text>
  18. <text>已完成</text>
  19. </view>
  20. </view>
  21. </view>
  22. </template>
  23. <script>
  24. export default {
  25. name: 'FunTaskItem',
  26. props: {
  27. task: { type: Object, required: true }
  28. },
  29. computed: {
  30. isUrgent() {
  31. if (this.task.status === 'completed') return false
  32. if (!this.task.deadline) return false
  33. const deadline = new Date(this.task.deadline)
  34. const now = new Date()
  35. const hoursLeft = (deadline - now) / (1000 * 60 * 60)
  36. return hoursLeft < 2
  37. }
  38. },
  39. methods: {
  40. getCategoryIcon() {
  41. const icons = {
  42. '学习类': '📚',
  43. '生活类': '🏠',
  44. '运动类': '⚽',
  45. '其他': '⭐'
  46. }
  47. return icons[this.task.category] || '📝'
  48. },
  49. getTimeLeft() {
  50. if (!this.task.deadline) return ''
  51. const deadline = new Date(this.task.deadline)
  52. const now = new Date()
  53. const diff = deadline - now
  54. if (diff < 0) return '已超时'
  55. const hours = Math.floor(diff / (1000 * 60 * 60))
  56. const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
  57. if (hours > 24) {
  58. const days = Math.floor(hours / 24)
  59. return `${days}天后`
  60. }
  61. if (hours > 0) return `${hours}小时后`
  62. return `${minutes}分钟后`
  63. }
  64. }
  65. }
  66. </script>
  67. <style scoped>
  68. .fun-task-item {
  69. display: flex;
  70. align-items: center;
  71. background: #fff;
  72. border-radius: 20rpx;
  73. padding: 24rpx;
  74. margin-bottom: 20rpx;
  75. box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
  76. transition: transform 0.2s;
  77. }
  78. .fun-task-item:active { transform: scale(0.98); }
  79. .fun-task-item.completed { opacity: 0.7; background: #F0FDF4; }
  80. .fun-task-item.urgent { border: 2rpx solid #FF6B6B; animation: pulse-urgent 1s infinite; }
  81. @keyframes pulse-urgent { 0%, 100% { box-shadow: 0 0 0 0 rgba(255,107,107,0.4); } 50% { box-shadow: 0 0 0 10rpx rgba(255,107,107,0); } }
  82. .task-icon { font-size: 48rpx; width: 80rpx; height: 80rpx; background: #F0F9FF; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; margin-right: 20rpx; }
  83. .task-content { flex: 1; }
  84. .task-title { font-size: 30rpx; font-weight: 600; color: #1F2937; margin-bottom: 8rpx; }
  85. .task-meta { display: flex; align-items: center; gap: 16rpx; }
  86. .points-badge { background: linear-gradient(135deg, #FEF3C7, #FDE68A); color: #92400E; font-size: 22rpx; padding: 4rpx 12rpx; border-radius: 20rpx; font-weight: 600; }
  87. .deadline { font-size: 22rpx; color: #6B7280; }
  88. .task-action { margin-left: 20rpx; }
  89. .complete-btn { display: flex; align-items: center; background: linear-gradient(135deg, #0EA5E9, #06B6D4); color: #fff; border: none; border-radius: 30rpx; padding: 12rpx 24rpx; font-size: 24rpx; font-weight: 600; }
  90. .btn-icon { margin-right: 8rpx; }
  91. .completed-badge { display: flex; align-items: center; background: #ECFDF5; color: #059669; padding: 12rpx 20rpx; border-radius: 30rpx; font-size: 24rpx; }
  92. .check-icon { margin-right: 8rpx; }
  93. </style>