personnel.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. <template>
  2. <view class="sub-page">
  3. <TopBar title="人员管理" show-back />
  4. <view class="px-4 pt-3">
  5. <!-- 筛选 -->
  6. <view class="filter-bar mb-3">
  7. <view
  8. v-for="status in filterStatuses"
  9. :key="status.value"
  10. class="filter-chip"
  11. :class="{ 'filter-chip-active': currentFilter === status.value }"
  12. @click="currentFilter = status.value"
  13. >
  14. {{ status.label }}
  15. </view>
  16. </view>
  17. <view v-if="loading" class="py-10 text-center">
  18. <text class="empty-text">加载中...</text>
  19. </view>
  20. <view v-else-if="filteredStaff.length === 0">
  21. <EmptyState message="暂无人员" />
  22. </view>
  23. <view v-else class="glass-card">
  24. <view
  25. v-for="staff in filteredStaff"
  26. :key="staff.staffId"
  27. class="list-row"
  28. hover-class="row-hover"
  29. :hover-start-time="0"
  30. :hover-stay-time="120"
  31. @click="showStaffDetail(staff)"
  32. >
  33. <view class="icon-chip mr-3">
  34. <text class="uni-icons uniui-person-filled chip-glyph"></text>
  35. </view>
  36. <view class="flex-1 min-w-0 mr-2">
  37. <text class="row-title truncate">{{ staff.name }}</text>
  38. <text class="row-sub truncate">{{ staff.phone }} | {{ staff.teamName || '未分配班组' }}</text>
  39. </view>
  40. <text class="status-tag flex-shrink-0 mr-1" :class="getStatusTagClass(staff.status)">
  41. {{ getStatusText(staff.status) }}
  42. </text>
  43. <text class="uni-icons uniui-arrowright row-arrow"></text>
  44. </view>
  45. </view>
  46. </view>
  47. </view>
  48. </template>
  49. <script setup lang="ts">
  50. import { ref, computed, onMounted } from 'vue'
  51. import { onPullDownRefresh } from '@dcloudio/uni-app'
  52. import TopBar from '../../components/common/TopBar.vue'
  53. import EmptyState from '../../components/common/EmptyState.vue'
  54. import { getStaffList } from '../../api/staff'
  55. const loading = ref(false)
  56. const staffList = ref<any[]>([])
  57. const currentFilter = ref('all')
  58. const filterStatuses = [
  59. { value: 'all', label: '全部' },
  60. { value: 'on_duty', label: '在岗' },
  61. { value: 'working', label: '施工中' },
  62. { value: 'rest', label: '休息' },
  63. ]
  64. const filteredStaff = computed(() => {
  65. if (currentFilter.value === 'all') return staffList.value
  66. return staffList.value.filter(s => s.status === currentFilter.value)
  67. })
  68. function getStatusText(status: string) {
  69. const map: Record<string, string> = {
  70. on_duty: '在岗',
  71. working: '施工中',
  72. rest: '休息',
  73. }
  74. return map[status] || status
  75. }
  76. function getStatusTagClass(status: string) {
  77. const map: Record<string, string> = {
  78. on_duty: 'status-tag-green',
  79. working: 'status-tag-blue',
  80. rest: 'status-tag-gold',
  81. }
  82. return map[status] || 'status-tag-gray'
  83. }
  84. function showStaffDetail(staff: any) {
  85. uni.showModal({
  86. title: staff.name,
  87. content: `电话: ${staff.phone}\n班组: ${staff.teamName || '未分配'}\n状态: ${getStatusText(staff.status)}`,
  88. showCancel: false
  89. })
  90. }
  91. onMounted(async () => {
  92. await fetchStaff()
  93. })
  94. async function fetchStaff() {
  95. loading.value = true
  96. try {
  97. const res = await getStaffList()
  98. staffList.value = res.rows || []
  99. } catch (error) {
  100. console.error('获取人员列表失败:', error)
  101. } finally {
  102. loading.value = false
  103. }
  104. }
  105. onPullDownRefresh(async () => {
  106. await fetchStaff()
  107. uni.stopPullDownRefresh()
  108. })
  109. </script>
  110. <style scoped>
  111. .sub-page {
  112. max-width: 430px;
  113. margin: 0 auto;
  114. min-height: 100vh;
  115. background-color: #f6fbf9;
  116. padding-bottom: 24px;
  117. }
  118. .glass-card {
  119. background-color: rgba(255, 255, 255, 0.85);
  120. backdrop-filter: blur(14px);
  121. -webkit-backdrop-filter: blur(14px);
  122. border: 1px solid rgba(164, 216, 152, 0.35);
  123. border-radius: 24px;
  124. box-shadow: 0 18px 50px -12px rgba(54, 143, 111, 0.28);
  125. overflow: hidden;
  126. margin-bottom: 12px;
  127. }
  128. /* 筛选条:默认白底灰边,激活态绿色 */
  129. .filter-bar {
  130. display: flex;
  131. gap: 8px;
  132. overflow-x: auto;
  133. }
  134. .filter-chip {
  135. padding: 6px 16px;
  136. border-radius: 999px;
  137. font-size: 13px;
  138. color: #6b7280;
  139. background-color: #ffffff;
  140. border: 1px solid #e5e7eb;
  141. flex-shrink: 0;
  142. }
  143. .filter-chip-active {
  144. color: #ffffff;
  145. font-weight: 600;
  146. background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
  147. border-color: transparent;
  148. }
  149. .list-row {
  150. display: flex;
  151. align-items: center;
  152. padding: 13px 16px;
  153. border-top: 1px solid rgba(164, 216, 152, 0.18);
  154. }
  155. .list-row:first-child {
  156. border-top: none;
  157. }
  158. .row-hover {
  159. background-color: rgba(164, 216, 152, 0.1);
  160. }
  161. .row-title {
  162. display: block;
  163. font-size: 14px;
  164. font-weight: 500;
  165. color: #1f2937;
  166. }
  167. .row-sub {
  168. display: block;
  169. margin-top: 3px;
  170. font-size: 12px;
  171. color: #9ca3af;
  172. }
  173. .row-arrow {
  174. font-size: 16px;
  175. color: #c4d0cb;
  176. flex-shrink: 0;
  177. }
  178. .status-tag {
  179. padding: 2px 10px;
  180. border-radius: 999px;
  181. font-size: 11px;
  182. font-weight: 600;
  183. }
  184. .status-tag-green {
  185. color: #368f6f;
  186. background-color: rgba(164, 216, 152, 0.24);
  187. }
  188. .status-tag-blue {
  189. color: #2563eb;
  190. background-color: rgba(37, 99, 235, 0.1);
  191. }
  192. .status-tag-gold {
  193. color: #b7791f;
  194. background-color: rgba(251, 211, 141, 0.25);
  195. }
  196. .status-tag-gray {
  197. color: #6b7280;
  198. background-color: #f3f4f6;
  199. }
  200. .icon-chip {
  201. width: 42px;
  202. height: 42px;
  203. border-radius: 50%;
  204. flex-shrink: 0;
  205. display: flex;
  206. align-items: center;
  207. justify-content: center;
  208. background-color: rgba(164, 216, 152, 0.2);
  209. }
  210. .chip-glyph {
  211. font-size: 20px;
  212. color: #368f6f;
  213. }
  214. .empty-text {
  215. font-size: 13px;
  216. color: #9ca3af;
  217. }
  218. </style>