related-items.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. <template>
  2. <view class="related-items-page">
  3. <view class="tab-bar">
  4. <view v-for="(tab, idx) in tabs" :key="idx" :class="'tab-item ' + (activeTab === idx ? 'active' : '')" @click="switchTab(idx)">
  5. <text class="tab-text">{{ tab.label }}</text>
  6. <view v-if="activeTab === idx" class="tab-underline"></view>
  7. </view>
  8. </view>
  9. <view v-if="loading" class="loading-state"><text>加载中...</text></view>
  10. <view v-else-if="error" class="error-state"><text class="error-text">{{ error }}</text></view>
  11. <view v-else class="content">
  12. <view v-if="activeTab === 0" class="tab-content">
  13. <view v-if="!items.pendingTasks || items.pendingTasks.length === 0" class="empty-state"><text class="empty-text">暂无待办任务</text></view>
  14. <view v-else class="item-list">
  15. <view v-for="(item, idx) in items.pendingTasks" :key="idx" class="item-card" @click="goToTask(item)">
  16. <view class="item-header">
  17. <text class="item-title">{{ item.title }}</text>
  18. <text :class="'status-tag ' + statusClass(item.status)">{{ statusText(item.status) }}</text>
  19. </view>
  20. <view class="item-meta">
  21. <text class="meta-text" v-if="item.assignee">负责人: {{ item.assignee }}</text>
  22. <text class="meta-text" v-if="item.dueDate">截止: {{ item.dueDate }}</text>
  23. </view>
  24. </view>
  25. </view>
  26. </view>
  27. <view v-if="activeTab === 1" class="tab-content">
  28. <view v-if="!items.pendingAssessments || items.pendingAssessments.length === 0" class="empty-state"><text class="empty-text">暂无待测评</text></view>
  29. <view v-else class="item-list">
  30. <view v-for="(item, idx) in items.pendingAssessments" :key="idx" class="item-card">
  31. <view class="item-header">
  32. <text class="item-title">{{ item.childName }}的测评</text>
  33. <text :class="'status-tag ' + statusClass(item.status)">{{ statusText(item.status) }}</text>
  34. </view>
  35. <view class="item-meta">
  36. <text class="meta-text" v-if="item.appointmentDate">{{ item.appointmentDate }}</text>
  37. </view>
  38. </view>
  39. </view>
  40. </view>
  41. <view v-if="activeTab === 2" class="tab-content">
  42. <view v-if="!items.healthAlerts || items.healthAlerts.length === 0" class="empty-state"><text class="empty-text">暂无健康提醒</text></view>
  43. <view v-else class="item-list">
  44. <view v-for="(item, idx) in items.healthAlerts" :key="idx" class="item-card">
  45. <view class="item-header">
  46. <text class="item-title">{{ item.message }}</text>
  47. <text :class="'alert-level ' + alertLevelClass(item.severity)">{{ item.severity }}</text>
  48. </view>
  49. <view class="item-meta">
  50. <text class="meta-text" v-if="item.members && item.members.length > 0">成员: {{ item.members.join(', ') }}</text>
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. <view v-if="activeTab === 3" class="tab-content">
  56. <view v-if="!items.upcomingMilestones || items.upcomingMilestones.length === 0" class="empty-state"><text class="empty-text">暂无里程碑</text></view>
  57. <view v-else class="item-list">
  58. <view v-for="(item, idx) in items.upcomingMilestones" :key="idx" class="item-card">
  59. <view class="item-header">
  60. <text class="item-title">{{ item.description }}</text>
  61. </view>
  62. <view class="item-meta">
  63. <text class="meta-text" v-if="item.targetDate">目标: {{ item.targetDate }}</text>
  64. <text class="meta-text" v-if="item.daysAway !== undefined">{{ item.daysAway }}天后</text>
  65. </view>
  66. </view>
  67. </view>
  68. </view>
  69. </view>
  70. </view>
  71. </template>
  72. <script>
  73. export default {
  74. data: function() {
  75. return {
  76. activeTab: 0,
  77. tabs: [{ label: '任务' }, { label: '测评' }, { label: '健康' }, { label: '里程碑' }],
  78. items: { pendingTasks: [], pendingAssessments: [], healthAlerts: [], upcomingMilestones: [] },
  79. loading: false, error: null
  80. }
  81. },
  82. onLoad: function() { this.loadRelatedItems() },
  83. methods: {
  84. switchTab: function(idx) { this.activeTab = idx },
  85. loadRelatedItems: function() {
  86. var self = this
  87. self.loading = true; self.error = null
  88. self.$store.dispatch('tianpan/loadRelatedItems', { daysAhead: 7 })
  89. .then(function(data) { self.items = data || self.items })
  90. .catch(function(e) { self.error = e.message || '加载失败' })
  91. .finally(function() { self.loading = false })
  92. },
  93. goBack: function() { uni.navigateBack() },
  94. statusText: function(s) { var map = { pending: '待处理', in_progress: '进行中', completed: '已完成', cancelled: '已取消' }; return map[s] || s || '' },
  95. statusClass: function(s) { var map = { pending: 'pending', in_progress: 'in-progress', completed: 'completed', cancelled: 'cancelled' }; return map[s] || 'pending' },
  96. alertLevelClass: function(l) { var map = { low: 'alert-low', medium: 'alert-medium', high: 'alert-high' }; return map[l] || 'alert-low' },
  97. goToTask: function(item) { if (item.id) { uni.navigateTo({ url: '/pages/tasks/detail?id=' + item.id }) } }
  98. }
  99. }
  100. </script>
  101. <style scoped>
  102. .related-items-page { min-height: 100vh; background: #F3F4F6; }
  103. .nav-right { width: 40px; }
  104. .tab-bar { display: flex; background: #FFF; border-bottom: 1px solid #E5E7EB; position: sticky; top: 0; z-index: 10; }
  105. .tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; padding: 12px 0; position: relative; }
  106. .tab-text { font-size: 14px; color: #6B7280; }
  107. .tab-item.active .tab-text { color: #F97316; font-weight: 500; }
  108. .tab-underline { position: absolute; bottom: 0; width: 40px; height: 2px; background: #F97316; border-radius: 1px; }
  109. .loading-state, .error-state, .empty-state { padding: 60px 20px; display: flex; justify-content: center; align-items: center; }
  110. .error-text, .empty-text { font-size: 14px; color: #9CA3AF; }
  111. .content { padding: 12px; }
  112. .tab-content { min-height: 300px; }
  113. .item-list { display: flex; flex-direction: column; }
  114. .item-card { background: #FFF; border-radius: 12px; padding: 14px; margin-bottom: 10px; }
  115. .item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
  116. .item-title { font-size: 14px; font-weight: 500; color: #1F2937; flex: 1; }
  117. .status-tag { font-size: 11px; padding: 2px 8px; border-radius: 10px; }
  118. .status-tag.pending { background: #FEF3C7; color: #92400E; }
  119. .status-tag.in-progress { background: #DBEAFE; color: #1E40AF; }
  120. .status-tag.completed { background: #DCFCE7; color: #166534; }
  121. .status-tag.cancelled { background: #F3F4F6; color: #6B7280; }
  122. .alert-level { font-size: 11px; padding: 2px 8px; border-radius: 10px; }
  123. .alert-low { background: #DCFCE7; color: #166534; }
  124. .alert-medium { background: #FEF3C7; color: #92400E; }
  125. .alert-high { background: #FEE2E2; color: #991B1B; }
  126. .item-meta { display: flex; gap: 12px; margin-bottom: 4px; }
  127. .meta-text { font-size: 12px; color: #9CA3AF; }
  128. </style>