FunTaskItem.vue 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  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: var(--surface, #ffffff);
  72. border-radius: var(--radius-md, 20rpx);
  73. padding: 24rpx;
  74. margin-bottom: 20rpx;
  75. border: 2rpx solid var(--border, #FED7AA);
  76. box-shadow: var(--shadow-sm);
  77. transition: transform 0.15s ease;
  78. }
  79. .fun-task-item:active { transform: scale(0.98); }
  80. .fun-task-item.completed { opacity: 0.65; background: #FEF2F2; border-color: #FECACA; }
  81. .fun-task-item.urgent { border: 2rpx solid var(--color-error, #EF4444); animation: pulse-urgent 1.5s infinite; }
  82. @keyframes pulse-urgent { 0%, 100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.3); } 50% { box-shadow: 0 0 0 12rpx rgba(239,68,68,0); } }
  83. .task-icon { font-size: 44rpx; width: 72rpx; height: 72rpx; background: rgba(249,115,22,0.1); border-radius: 16rpx; display: flex; align-items: center; justify-content: center; margin-right: 20rpx; }
  84. .task-content { flex: 1; }
  85. .task-title { font-size: 28rpx; font-weight: 600; color: var(--text, #3D2E1E); margin-bottom: 8rpx; }
  86. .task-meta { display: flex; align-items: center; gap: 16rpx; }
  87. .points-badge { background: linear-gradient(135deg, #FEF3C7, #FDE68A); color: #92400E; font-size: 22rpx; padding: 4rpx 14rpx; border-radius: 20rpx; font-weight: 700; }
  88. .deadline { font-size: 22rpx; color: var(--muted, #94A3B8); }
  89. .task-action { margin-left: 16rpx; }
  90. .complete-btn { display: flex; align-items: center; background: linear-gradient(135deg, var(--color-primary, #F97316), #FB923C); color: #fff; border: none; border-radius: 30rpx; padding: 14rpx 28rpx; font-size: 24rpx; font-weight: 600; box-shadow: 0 4rpx 8rpx rgba(249,115,22,0.3); }
  91. .complete-btn:active { transform: scale(0.95); opacity: 0.9; }
  92. .btn-icon { margin-right: 6rpx; }
  93. .completed-badge { display: flex; align-items: center; background: rgba(34,197,94,0.1); color: var(--color-success, #22C55E); padding: 12rpx 20rpx; border-radius: 30rpx; font-size: 24rpx; font-weight: 600; }
  94. .check-icon { margin-right: 6rpx; }
  95. </style>