devices.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. <script setup lang="ts">
  2. import { computed, ref } from 'vue'
  3. import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
  4. import AppHeader from '@/components/AppHeader.vue'
  5. import SearchBar from '@/components/SearchBar.vue'
  6. import SegmentTabs from '@/components/SegmentTabs.vue'
  7. import { getMobileEquipmentPage, type Equipment } from '@/services/equipment'
  8. import { navigateTo } from '@/utils/navigation'
  9. const keyword = ref('')
  10. const activeStatus = ref(0)
  11. const alarmOnly = ref(false)
  12. const devices = ref<Equipment[]>([])
  13. const total = ref(0)
  14. const pageNum = ref(1)
  15. const loading = ref(false)
  16. const loaded = ref(false)
  17. const hasMore = computed(() => devices.value.length < total.value)
  18. const statusTabs = [{ label: '全部', value: '' }, { label: '在线', value: '1' }, { label: '离线', value: '0' }]
  19. onLoad(() => loadDevices(true))
  20. onPullDownRefresh(async () => {
  21. await loadDevices(true)
  22. uni.stopPullDownRefresh()
  23. })
  24. onReachBottom(() => loadDevices())
  25. async function loadDevices(reset = false) {
  26. if (loading.value || (!reset && !hasMore.value)) return
  27. if (reset) {
  28. pageNum.value = 1
  29. devices.value = []
  30. total.value = 0
  31. }
  32. loading.value = true
  33. try {
  34. const result = await getMobileEquipmentPage({
  35. pageNum: pageNum.value,
  36. pageSize: 10,
  37. keyword: keyword.value.trim() || undefined,
  38. onlineStatus: statusTabs[activeStatus.value].value === '' ? undefined : Number(statusTabs[activeStatus.value].value),
  39. alarmStatus: alarmOnly.value ? 1 : undefined,
  40. })
  41. devices.value = reset ? result.records : devices.value.concat(result.records)
  42. total.value = Number(result.total) || 0
  43. pageNum.value += 1
  44. } catch (error) {
  45. uni.showToast({ title: error instanceof Error ? error.message : '设备列表获取失败', icon: 'none' })
  46. } finally {
  47. loading.value = false
  48. loaded.value = true
  49. }
  50. }
  51. function selectStatus(value: number) {
  52. if (activeStatus.value === value) return
  53. activeStatus.value = value
  54. loadDevices(true)
  55. }
  56. function search() { loadDevices(true) }
  57. function toggleAlarm() { alarmOnly.value = !alarmOnly.value; loadDevices(true) }
  58. function openDetail(item: Equipment) {
  59. navigateTo(`/pages/office/device-detail?id=${encodeURIComponent(item.equipmentId)}`)
  60. }
  61. function onlineLabel(status?: number) { return Number(status) === 1 ? '在线' : '离线' }
  62. function districtLabel(district?: number) { return ['城南', '城北', '太常'][Number(district)] || '—' }
  63. </script>
  64. <template>
  65. <view class="page">
  66. <AppHeader title="设备信息查询" />
  67. <SegmentTabs :model-value="activeStatus" :items="statusTabs.map(item => item.label)" @update:model-value="selectStatus" />
  68. <SearchBar v-model="keyword" attached placeholder="请输入设备编码、名称" @search="search">
  69. <template #prefix>
  70. <view class="alarm-prefix" @tap="toggleAlarm">
  71. <view class="checkbox" :class="{ checked: alarmOnly }">{{ alarmOnly ? '✓' : '' }}</view>
  72. <text>报警</text>
  73. </view>
  74. </template>
  75. </SearchBar>
  76. <view class="result-count">共 {{ total }} 条设备</view>
  77. <view class="page-content device-list">
  78. <view v-for="item in devices" :key="item.equipmentId" class="device-card card" @tap="openDetail(item)">
  79. <view class="device-head">
  80. <text class="device-name">{{ item.equipmentName || '未命名设备' }}</text>
  81. <text class="online" :class="Number(item.onlineStatus) === 1 ? 'online-on' : 'online-off'"><i></i>{{ onlineLabel(item.onlineStatus) }}</text>
  82. <text v-if="Number(item.alarmStatus) === 1" class="alarm-tag">报警</text>
  83. </view>
  84. <text class="device-meta">编码:{{ item.equipmentCode || '—' }}</text>
  85. <text class="device-meta">类型:{{ item.equipmentTypeName || '—' }}</text>
  86. <text class="device-meta">片区:{{ districtLabel(item.district) }}</text>
  87. <view class="device-foot"><text class="location">{{ item.equipmentLocation || '未填写安装位置' }}</text><text class="detail-link">详情 ›</text></view>
  88. </view>
  89. <view v-if="loaded && !devices.length" class="empty">暂无匹配设备</view>
  90. <view v-else-if="loading" class="load-tip">正在加载...</view>
  91. <view v-else-if="devices.length && !hasMore" class="load-tip">已加载全部</view>
  92. </view>
  93. </view>
  94. </template>
  95. <style scoped lang="scss">
  96. .alarm-prefix{display:flex;flex-shrink:0;align-items:center;gap:8rpx;padding-left:24rpx;color:#25364b;font-size:23rpx}.checkbox{display:flex;align-items:center;justify-content:center;width:30rpx;height:30rpx;border:2rpx solid #aab6c3;border-radius:5rpx;color:#fff;font-size:22rpx}.checkbox.checked{border-color:#3b5bdb;background:#3b5bdb}.result-count{padding:14rpx 26rpx 0;color:#8a9aa5;background:#fff;font-size:22rpx}.device-list{padding-top:18rpx}.device-card{padding:23rpx 24rpx;margin-bottom:16rpx}.device-head{display:flex;align-items:center;gap:12rpx;margin-bottom:12rpx}.device-name{flex:1;min-width:0;overflow:hidden;color:#0b1119;font-size:28rpx;font-weight:700;text-overflow:ellipsis;white-space:nowrap}.online{display:flex;flex-shrink:0;align-items:center;gap:7rpx;font-size:21rpx;font-weight:600}.online i{width:12rpx;height:12rpx;border-radius:50%}.online-on{color:#1a9e6b}.online-on i{background:#1a9e6b}.online-off{color:#e02020}.online-off i{background:#e02020}.alarm-tag{flex-shrink:0;padding:4rpx 13rpx;color:#e02020;border-radius:99rpx;background:#fdecec;font-size:20rpx;font-weight:600}.device-meta{display:block;margin-top:7rpx;color:#5a6b73;font-size:23rpx;line-height:1.5}.device-foot{display:flex;align-items:center;justify-content:space-between;gap:12rpx;margin-top:14rpx;padding-top:13rpx;border-top:1rpx dashed #e8eef3}.location{flex:1;min-width:0;overflow:hidden;color:#3b5bdb;font-size:22rpx;text-overflow:ellipsis;white-space:nowrap}.detail-link{flex-shrink:0;color:#3b5bdb;font-size:23rpx;font-weight:600}.empty,.load-tip{padding:56rpx 0;color:#98a4b2;text-align:center;font-size:23rpx}
  97. </style>