related-items.vue 7.3 KB

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