| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118 |
- <template>
- <view class="container">
- <!-- 搜索区域 -->
- <view class="search-section">
- <view class="search-box" :class="{ 'search-focus': isSearchFocused }">
- <view class="search-icon">
- <!-- <text class="iconfont icon-search"></text> -->
- <image src="@/static/icons/search.png" style="width: 40rpx; height: 40rpx; padding-right: 10rpx;"
- mode="widthFix" />
- </view>
- <input
- class="search-input"
- type="text"
- v-model="searchKey"
- placeholder="搜索设备名称 / 编号"
- confirm-type="search"
- @confirm="onSearch"
- @focus="isSearchFocused = true"
- @blur="isSearchFocused = false"
- />
- <view class="clear-icon" v-if="searchKey" @click="clearSearch">
- <text class="iconfont icon-close"></text>
- </view>
- </view>
- </view>
-
- <!-- 状态筛选区域 -->
- <view class="filter-section">
- <view
- class="filter-item"
- :class="{ active: currentStatus === -1 }"
- @click="selectStatus(-1)"
- >
- 全部
- </view>
- <view
- class="filter-item"
- :class="{ active: currentStatus === 1 }"
- @click="selectStatus(1)"
- >
- <view class="filter-dot online-dot"></view>
- 在线 ({{ onlineDevices }})
- </view>
- <view
- class="filter-item"
- :class="{ active: currentStatus === 0 }"
- @click="selectStatus(0)"
- >
- <view class="filter-dot offline-dot"></view>
- 离线 ({{ offlineDevices }})
- </view>
- <!-- <view
- class="filter-item"
- :class="{ active: currentStatus === 2 }"
- @click="selectStatus(2)"
- >
- <view class="filter-dot fault-dot"></view>
- 故障 ({{ getStatusCount(2) }})
- </view> -->
- </view>
-
- <!-- 设备列表区域 -->
- <scroll-view
- scroll-y
- class="device-list"
- @scrolltolower="onReachBottom"
- :scroll-with-animation="true"
- :enable-back-to-top="true"
- :refresher-enabled="true"
- :refresher-threshold="80"
- :refresher-triggered="isRefreshing"
- @refresherrefresh="handleRefresh"
- @refresherrestore="isRefreshing = false"
- >
- <!-- 无数据提示 -->
- <view v-if="deviceList.length === 0 && !loading" class="empty-tips">
- <view class="empty-icon">
- <text class="iconfont icon-empty"></text>
- </view>
- <text class="empty-text">{{ searchKey ? '未找到匹配的设备' : '暂无设备' }}</text>
- <view v-if="searchKey" class="empty-action" @click="clearSearch">
- <text>清除搜索条件</text>
- </view>
- </view>
-
- <!-- 首次加载中状态 -->
- <view v-if="loading && deviceList.length === 0" class="loading-container">
- <view class="loading-spinner"></view>
- <text class="loading-text">加载中...</text>
- </view>
-
- <!-- 设备列表 -->
- <view
- v-for="(item, index) in deviceList"
- :key="index"
- class="device-card"
- :class="{
- 'has-alert': item.hasAlert,
- 'is-offline': item.status === 0
- }"
- hover-class="device-card-hover"
- hover-stay-time="70"
- @click="navigateToDeviceDetail(item)"
- >
- <!-- 设备基本信息 -->
- <view class="device-info">
- <view class="device-icon-wrapper">
- <!-- 告警标记 -->
- <view v-if="item.hasAlert" class="alarm-badge">
- 1
- </view>
-
- <view class="device-icon-container" :class="{'offline-icon': item.status === 0}">
- <image :src="getDeviceTypeIcon(item.deviceTypeId)" mode="aspectFit" class="device-icon"></image>
- </view>
- </view>
-
- <view class="device-meta">
- <view class="device-name-row">
- <text class="device-name" :class="{'offline-text': item.status === 0}">{{ item.deviceName }}</text>
- <view
- class="status-tag"
- :class="{
- 'status-online': item.status === 1,
- 'status-offline': item.status === 0,
- 'status-fault': item.status === 2,
- 'status-maintain': item.status === 3
- }"
- >
- <text class="status-dot" :class="{
- 'offline-dot': item.status === 0,
- 'fault-dot': item.status === 2,
- 'maintain-dot': item.status === 3
- }"></text>
- {{ getStatusText(item.status) }}
- </view>
- </view>
-
- <view class="device-id">
- <text class="id-label">设备编号:</text>
- <text class="id-value">{{ item.deviceId }}</text>
- </view>
-
- <view class="device-location">
- <text class="location-label">安装位置:</text>
- <text class="location-value">{{ item.fieldName || '未指定位置' }}</text>
- </view>
- </view>
- </view>
-
- <!-- 底部信息栏 -->
- <view class="device-footer">
- <text class="update-time">最后活跃: {{ formatDate(item.lastActiveTime) }}</text>
- </view>
- </view>
-
- <!-- 加载更多提示 -->
- <view v-if="deviceList.length > 0" class="load-more">
- <view class="load-more-content" v-if="loadMoreStatus === 'loading'">
- <view class="loading-icon"></view>
- <text>正在加载...</text>
- </view>
- <view class="load-more-content" v-if="loadMoreStatus === 'noMore'">
- <text>没有更多了</text>
- </view>
- <view class="load-more-content" v-if="loadMoreStatus === 'more'" @click="onReachBottom">
- <text>点击加载更多</text>
- </view>
- </view>
- </scroll-view>
- </view>
- </template>
- <script setup>
- import { ref, reactive, computed } from 'vue'
- import { onLoad} from '@dcloudio/uni-app'
- import { fetchDevicesByType } from "@/api/services/device.js";
- import { machinesDeviceList } from "@/api/services/agriculturalMachines.js";
- import storage from "@/utils/storage.js";
- // 响应式数据
- const deviceType = ref('') // monitor, sensor, control, irrigation, tractor
- const deviceTypeName = ref('')
- const deviceList = ref([])
- const searchKey = ref('')
- const isSearchFocused = ref(false)
- const currentStatus = ref(-1) // -1代表全部
- const pageNum = ref(1)
- const pageSize = ref(10)
- const total = ref(0)
- const loading = ref(false)
- const isRefreshing = ref(false)
- const loadMoreStatus = ref('more') // 加载更多状态: more-加载更多 loading-加载中 noMore-没有更多了
- const deviceTypeMap = reactive({
- 'monitor': { name: '监控设备', icon: '/static/icons/camera.png', class: 'type-monitor' },
- 'sensor': { name: '采集设备', icon: '/static/icons/sensor.png', class: 'type-sensor' },
- 'control': { name: '控制设备', icon: '/static/icons/control.png', class: 'type-control' },
- 'irrigation': { name: '灌溉设备', icon: '/static/icons/water.png', class: 'type-irrigation' },
- 'tractor': { name: '农机设备', icon: '/static/icons/tractor.png', class: 'type-tractor' }
- })
- const currentFieldId = ref(null)
- const onlineDevices = ref(0)
- const offlineDevices = ref(0)
- // 计算属性
- // 设备类型对应的样式类
- const deviceTypeClass = computed(() => {
- return deviceTypeMap[deviceType.value]?.class || ''
- })
- // uni-app 生命周期 - onLoad
- onLoad((options) => {
- // 获取传递的设备类型
- console.log("options类型:", options);
- const { type, typeOnline, typeOffline } = options;
- // 处理传递过来的统计数量
- offlineDevices.value = typeOffline
- onlineDevices.value = typeOnline
- if (type && deviceTypeMap[type]) {
- deviceType.value = type;
- deviceTypeName.value = deviceTypeMap[type].name;
- }
-
- // 获取当前地块ID
- initFieldInfo();
-
- // 加载设备列表
- deviceType.value === 'tractor' ? loadMachinesList() : loadDeviceList();
- })
- // 方法定义
- const loadMachinesList = () => {
- machinesDeviceList({
- pageNum: pageNum.value,
- pageSize: pageSize.value,
- }).then(res => {
- console.log("res收到发斯蒂芬斯蒂",res);
-
- res.data.code === 200 && res.data.rows && (deviceList.value = res.data.rows);
- res.data.code !== 200 && handleApiError(res);
- }).catch(error => {
- console.error('获取农机设备列表失败', error);
- uni.showToast({
- title: '获取农机设备列表失败',
- icon: 'none'
- });
- }).finally(() => {
- loading.value = false;
- });
- }
- // 初始化地块信息
- const initFieldInfo = () => {
- const currentPlots = JSON.parse(storage.getPlots() || '{}');
- if (currentPlots) {
- currentFieldId.value = currentPlots.id;
- }
- }
- // 获取特定状态的设备数量
- const getStatusCount = (status) => {
- return deviceList.value.filter(device => device.status === status).length;
- }
- // 加载设备列表
- const loadDeviceList = (reset = true) => {
- if (loading.value) return;
-
- if (reset) {
- pageNum.value = 1;
- deviceList.value = [];
- }
-
- loading.value = true;
- loadMoreStatus.value = 'loading';
-
- // 构建查询参数
- const params = {
- pageNum: pageNum.value,
- pageSize: pageSize.value,
- deviceQueryParams: searchKey.value || undefined,
- deviceType: deviceType.value || undefined,
- fieldId: currentFieldId.value || undefined
- };
-
- // 如果状态不是全部,添加状态筛选
- if (currentStatus.value !== -1) {
- params.status = currentStatus.value;
- }
-
- // 调用API获取设备列表
- fetchDevicesByType(params)
- .then(res => {
- console.log("res", res);
- if (res.data.code === 200 && res.data.rows) {
- const { rows, total: totalCount } = res.data;
-
- // 更新设备列表
- if (reset) {
- deviceList.value = rows;
- } else {
- deviceList.value = [...deviceList.value, ...rows];
- }
-
- total.value = totalCount;
- // 为农技增加模拟数据 后续可删除
- if(deviceType.value === 'tractor' ){
- const newDevices = generateMockDevices();
- console.log("newDevices",newDevices);
- deviceList.value = [...deviceList.value, ...newDevices];
- total.value = deviceList.value.length
- }
-
- // 标记有告警的设备
- deviceList.value.forEach(device => {
- // 这里可以根据实际情况设置hasAlert属性
- device.hasAlert = false; // 示例,实际应该根据后台数据判断
- });
-
- // 更新加载更多状态
- if (deviceList.value.length >= totalCount) {
- loadMoreStatus.value = 'noMore';
- } else {
- loadMoreStatus.value = 'more';
- }
- } else {
- handleApiError(res);
- }
- })
- .catch(error => {
- console.error('获取设备列表失败', error);
- uni.showToast({
- title: '获取设备列表失败',
- icon: 'none'
- });
- loadMoreStatus.value = 'more';
- })
- .finally(() => {
- loading.value = false;
- isRefreshing.value = false;
- uni.hideLoading();
- });
- }
- // 处理API错误
- const handleApiError = (res) => {
- console.error('API错误', res);
- uni.showToast({
- title: res.msg || '获取数据失败',
- icon: 'none'
- });
- }
- // 搜索
- const onSearch = () => {
- loadDeviceList();
- }
- // 清除搜索
- const clearSearch = () => {
- searchKey.value = '';
- loadDeviceList();
- }
- // 选择状态
- const selectStatus = (value) => {
- currentStatus.value = value;
- loadDeviceList();
- }
- // 处理下拉刷新
- const handleRefresh = () => {
- isRefreshing.value = true;
- loadDeviceList();
- }
- // 跳转到设备详情页
- const navigateToDeviceDetail = (device) => {
- console.log("device",device);
- // 根据设备类型跳转到不同的详情页
- let url = '';
- // 先发送事件
- uni.$emit('passDeviceData', {
- deviceId: device.deviceId,
- deviceTypeId: device.deviceTypeId,
- fieldName: device.fieldName,
- deviceName: device.deviceName
- });
- if (device.deviceTypeId === '2') {
- url = `/pages/device/device-list/detail-camera?id=${device.id}`;
- } else if (device.deviceTypeId === '1' || device.deviceTypeId === '4') {
- // 采集设备跳转到采集设备详情页,同时传递设备编码,便于判断设备子类型
- // url = `/pages/device/device-list/detail-collector?id=${device.deviceId}&deviceTypeId=${device.deviceTypeId}&fieldName=${device.fieldName}&deviceName=${device.deviceName}`;
- url = `/pages/device/device-list/detail-collector`;
-
- } else if (device.type === 'tractor') {
- // 农机设备跳转到农机设备详情页
- url = `/pages/device/device-list/detail-machine?id=${device.id}&deviceId=${device.code}`;
- } else {
- // 其他类型设备暂时使用通用详情页
- url = `/pages/device/device-detail/index?id=${device.id}&type=${device.type}`;
- }
-
- // 先跳转
- uni.navigateTo({
- url: url,
- success: () => {
- // 跳转成功后再发送事件,延迟一点确保页面onLoad注册完成
- setTimeout(() => {
- uni.$emit('passDeviceData', {
- deviceId: device.deviceId,
- deviceTypeId: device.deviceTypeId,
- fieldName: device.fieldName,
- deviceName: device.deviceName,
- status:device.status
- });
- }, 100); // 100ms 通常足够,必要时可加到 200
- },
- fail: (e)=>{
- console.log(e);
- }
- });
- }
- // 获取设备类型图标
- const getDeviceTypeIcon = (typeId) => {
- // 根据后端设备类型ID获取对应前端类型的图标
- const typeMapping = {
- '1': 'sensor', // 传感器
- '2': 'monitor', // 摄像头
- '3': 'control', // 控制器
- '4': 'irrigation', // 气象设备/灌溉设备
- '5': 'tractor' // 农机设备
- };
-
- const frontendType = typeMapping[typeId] || deviceType.value;
- return deviceTypeMap[frontendType]?.icon || '/static/icons/device.png';
- }
- // 获取设备类型名称
- const getDeviceTypeName = (typeId) => {
- const typeNames = {
- '1': '采集设备',
- '2': '监控设备',
- '3': '控制设备',
- '4': '灌溉设备',
- '5': '农机设备'
- };
-
- return typeNames[typeId] || '未知类型';
- }
- // 获取状态文本
- const getStatusText = (status) => {
- const statusMap = {
- 0: '离线',
- 1: '在线',
- 2: '故障',
- 3: '维护中'
- };
-
- return statusMap[status] || '未知状态';
- }
- // 格式化日期
- const formatDate = (dateStr) => {
- if (!dateStr) return '未知';
-
- // 解析为 Date
- const parsedStr = dateStr.replace(' ', 'T');
- const date = new Date(parsedStr);
- if (isNaN(date)) return '无效时间';
-
- const now = new Date();
- // 计算差时长(分钟)
- const diff = Math.floor((now - date) / 1000 / 60);
-
- if (diff < 1) return '刚刚更新';
- if (diff < 5) return '1分钟前更新';
- if (diff < 10) return '5分钟前更新';
- if (diff < 60) return `${diff}分钟前更新`;
-
- if (diff < 120) return '1小时前更新';
- if (diff < 24 * 60) return `${Math.floor(diff / 60)}小时前更新`;
- if (diff < 7 * 24 * 60) return `${Math.floor(diff / (60 * 24))}天前更新`;
-
- return parsedStr.split('T')[0] + ' 更新';
- }
- // 生成模拟设备数据
- const generateMockDevices = () => {
- const devices = [];
- const locations = ['东区A1地块', '西区B2地块', '南区C3地块', '北区D4地块'];
- const updateTimes = ['刚刚更新', '1分钟前更新', '5分钟前更新', '10分钟前更新', '1小时前更新'];
-
- // 根据当前页码和限制数量生成对应数量的模拟数据
- const startIndex = (pageNum.value - 1) * pageSize.value;
- for (let i = 0; i < pageSize.value; i++) {
- const index = startIndex + i;
-
- // 如果已经生成了30条数据,则停止
- if (index >= 30) break;
-
- // 对于采集设备类型,生成随机的气象或土壤设备
- let devType = deviceType.value;
- let deviceCode = `DEV${String(index + 1001).padStart(4, '0')}`;
-
- // 如果是采集设备,随机生成气象站或土壤墒情设备
- if (deviceType.value === 'sensor') {
- // 随机分配采集设备子类型:气象站或土壤墒情
- const sensorSubType = Math.random() > 0.5 ? 'weather' : 'soil';
- deviceCode = sensorSubType === 'weather' ? `W${deviceCode}` : `S${deviceCode}`;
- }
-
- devices.push({
- id: `device-${index + 1}`,
- deviceName: `${getDeviceTypeName(deviceType.value)}-${index + 1}`,
- deviceId: deviceCode,
- type: devType,
- status: Math.random() > 0.3 ? 'online' : 'offline', // 70% 概率在线
- fieldName: locations[Math.floor(Math.random() * locations.length)],
- updateTime: updateTimes[Math.floor(Math.random() * updateTimes.length)],
- alarmCount: Math.random() > 0.7 ? Math.floor(Math.random() * 3) + 1 : 0 // 30% 概率有告警
- });
- }
-
- return devices;
- }
- /* formatDate(dateStr) {
- if (!dateStr) return '未知';
-
- const date = new Date(dateStr);
- const now = new Date();
-
- // 今天的日期
- if (date.toDateString() === now.toDateString()) {
- return `今天 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
- }
-
- // 一周内
- const days = ['日', '一', '二', '三', '四', '五', '六'];
- const dayDiff = Math.floor((now - date) / (24 * 60 * 60 * 1000));
- if (dayDiff < 7) {
- return `周${days[date.getDay()]} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
- }
-
- // 超过一周
- return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
- }, */
- // 页面上拉触底事件
- const onReachBottom = () => {
- if (loadMoreStatus.value === 'more') {
- pageNum.value++;
- loadDeviceList(false);
- }
- }
- // uni-app 生命周期 - 下拉刷新
- const onPullDownRefresh = () => {
- loadDeviceList();
- setTimeout(() => {
- uni.stopPullDownRefresh();
- }, 1000);
- }
- // uni-app 生命周期 - 页面显示
- const onShow = () => {
- uni.setNavigationBarTitle({
- title: deviceTypeName.value || '设备列表'
- });
- }
- </script>
- <style scoped>
- /* 图标字体 */
- @font-face {
- font-family: "iconfont";
- src: url('data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAOMAAsAAAAACFAAAAM+AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDMgqDGIJmATYCJAMUCwwABCAFhGcHRhvqBsgOJaDgwABgBNFYPPy3H/3OSB4kWdI1Pb2QBw8gSpZIsxBFsTCNTIm//9dv/7FzF1E1S9ysIZWDPNdPJdSYbfLy/+fpTTMwKn5ZZ3OImpRUTZ8FHAcYFrLCBqyAHZD3CP9I09oXUgNcDSEAS8oGR507tO6KhRQTNQGQyeNGD8PCkVATUkFYsMgGcuqQ5YiFWruCPAVYnn9f/KGFBQxrghRq0KjzAXS5TP3SHp3ZmXUyEIfTAYDvDEiQARSQiaJjIjKaZiDJ+hpQYnY1i8tK1ZfWl83/fxYLm8TS//EADBBU6C9ghIBwJXxprxqwhC8d6x9AvAB4BcARgGWgRlWY71Cn5I5XvJ5Ot5sXy91Ud9Oj1nXnfbK9vK09bVsrGq2rrB1sxMcbxZmXntbdI8u6cRGhGy9r92Hj7hCjx1G6Dd9G9N6sbSvwVZiw+/hC0D7WFi5tbcltVyPOdqm41HkQr9T0bKFa2zrLLVeKJufZy7vn7aXtY+3XMvl65fJ7wnHT7ZJWozcvrHC5XNnTGy5/0NvX1xdSu8W9Ml0ePXEqZ3Jm0r6SLSdPbJnc27Mm05YsrLjfI8Ypvb5dL5pAj9m5c3Tzw+UvtK/SzO/Vv+Dt/9Lfq3+Xr30Hf9pz0JsnGgsPDBWHv+v12l5zGsE7Pz9A2d0W9WJ2dUZ/a/PkxQW58o/asvHDfyvJEw39G/5zf2p9OmZFYYGKoioVKPzPpwQA0+d2xv7C/aL/Dx/m7QfkjDLZL8AH4F3p+8PBrGIuqFLmM3uqfI6+YxAFCRZWAQ9LM8Q3JuRm2/bCgWEDCgzzIFgrEYk0AxgciQCDtQxgSRr76yYgfkIiHoB0FxGEEKfAEOQMJCHukYj0CQyjPsMQ0oJgyQD6hI6DL3FILJJLiqDqQs9kbYnLnTRuoA+5j7HsWmUJG2QdvdPrY51xR0WpwQZZIJvsGJ8OWYgVpomO28rIDCGTHHGJpqFe7xSPlUXs04FBiQV1FxSCqhboGVlrEldXpOGP94G4Hwydnaj6DjJBj1dvVtZizE8WlMYD9rHWaVsLsmF6QhZEmMDsIk1wOZWLgSHSSTkEJzGNQvvLc3HLFVXz9euBBWkm+YbLZKGH3uISrbIXRuNaAA==');
- }
- .iconfont {
- font-family: "iconfont" !important;
- font-size: 24rpx;
- font-style: normal;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- }
- .icon-search:before {
- content: "\e6e1";
- font-size: 32rpx;
- }
- .icon-close:before {
- content: "\e6a7";
- font-size: 28rpx;
- }
- .icon-right:before {
- content: "\e6a3";
- font-size: 28rpx;
- }
- .icon-empty:before {
- content: "\e6a9";
- font-size: 80rpx;
- }
- /* 容器样式 */
- .container {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
- background-color: #F9FCFA;
- padding-bottom: 20rpx;
- }
- /* 搜索区域 */
- .search-section {
- padding: 20rpx 30rpx;
- background-color: #FFFFFF;
- margin-bottom: 2rpx;
- width: 100%;
- box-sizing: border-box;
- position: relative;
- z-index: 5;
- box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
- }
- .search-box {
- display: flex;
- align-items: center;
- background-color: #F7F7F7;
- height: 80rpx;
- border-radius: 40rpx;
- padding: 0 24rpx;
- width: 100%;
- box-sizing: border-box;
- border: 2rpx solid transparent;
- transition: all 0.3s ease;
- }
- .search-box.search-focus {
- border-color: #4CAF50;
- background-color: #FFFFFF;
- box-shadow: 0 0 10rpx rgba(76, 175, 80, 0.1);
- }
- .search-icon {
- color: #4CAF50;
- width: 60rpx;
- display: flex;
- justify-content: center;
- }
- .search-input {
- flex: 1;
- height: 80rpx;
- font-size: 28rpx;
- color: #333333;
- }
- .clear-icon {
- width: 60rpx;
- display: flex;
- justify-content: center;
- color: #999;
- }
- /* 状态筛选区域 */
- .filter-section {
- display: flex;
- padding: 24rpx 24rpx;
- background-color: #FFFFFF;
- margin-bottom: 20rpx;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
- position: relative;
- z-index: 4;
- flex-wrap: wrap;
- }
- .filter-item {
- display: flex;
- align-items: center;
- font-size: 28rpx;
- color: #666666;
- margin-right: 30rpx;
- padding: 12rpx 20rpx;
- border-radius: 30rpx;
- transition: all 0.2s ease;
- position: relative;
- margin-bottom: 10rpx;
- }
- .filter-item.active {
- background-color: #F0F9F0;
- color: #4CAF50;
- font-weight: 500;
- }
- .filter-dot {
- width: 14rpx;
- height: 14rpx;
- border-radius: 50%;
- margin-right: 10rpx;
- }
- .online-dot {
- background-color: #4CAF50;
- box-shadow: 0 0 6rpx rgba(76, 175, 80, 0.5);
- }
- .offline-dot {
- background-color: #F56C6C;
- box-shadow: 0 0 6rpx rgba(245, 108, 108, 0.5);
- }
- .fault-dot {
- background-color: #FF9800;
- box-shadow: 0 0 6rpx rgba(255, 152, 0, 0.5);
- }
- .maintain-dot {
- background-color: #2196F3;
- box-shadow: 0 0 6rpx rgba(33, 150, 243, 0.5);
- }
- /* 设备列表区域 */
- .device-list {
- flex: 1;
- padding: 0 30rpx;
- box-sizing: border-box;
- width: 100%;
- position: relative;
- z-index: 3;
- height: calc(100vh - 220rpx);
- }
- /* 空数据提示 */
- .empty-tips {
- padding: 120rpx 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .empty-icon {
- color: #DDDDDD;
- margin-bottom: 20rpx;
- }
- .empty-text {
- font-size: 28rpx;
- color: #999999;
- margin-bottom: 20rpx;
- }
- .empty-action {
- font-size: 26rpx;
- color: #4CAF50;
- padding: 12rpx 30rpx;
- border-radius: 30rpx;
- background-color: rgba(76, 175, 80, 0.1);
- }
- /* 首次加载中状态 */
- .loading-container {
- padding: 80rpx 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .loading-spinner {
- width: 60rpx;
- height: 60rpx;
- border: 4rpx solid #E0E0E0;
- border-top: 4rpx solid #4CAF50;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- margin-bottom: 20rpx;
- }
- .loading-text {
- font-size: 28rpx;
- color: #999999;
- }
- /* 设备卡片 */
- .device-card {
- position: relative;
- background-color: #FFFFFF;
- border-radius: 24rpx;
- padding: 28rpx;
- margin-bottom: 24rpx;
- box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.05);
- transition: all 0.25s ease;
- width: 100%;
- box-sizing: border-box;
- border: 1rpx solid rgba(0, 0, 0, 0.05);
- overflow: hidden;
- }
- .device-card::before {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- width: 6rpx;
- height: 100%;
- background: linear-gradient(to bottom, #66CC6A, #3BB44A);
- opacity: 0;
- transition: opacity 0.3s ease;
- }
- .device-card:active::before {
- opacity: 1;
- }
- .device-card-hover {
- background-color: #f2fef6;
- box-shadow: 0 10rpx 25rpx rgba(59, 180, 74, 0.1);
- transform: translateY(-3rpx);
- }
- .device-card.has-alert {
- box-shadow: 0 6rpx 20rpx rgba(245, 108, 108, 0.08);
- border: 1rpx solid rgba(245, 108, 108, 0.08);
- }
- .device-card.has-alert::before {
- background: linear-gradient(to bottom, #FF8F8F, #F56C6C);
- }
- .device-card.is-offline {
- opacity: 0.9;
- }
- .device-card.is-offline.device-card-hover {
- background-color: #f5f5f5;
- box-shadow: 0 10rpx 25rpx rgba(0, 0, 0, 0.05);
- }
- /* 告警角标 */
- .alarm-badge {
- position: absolute;
- top: -8rpx;
- right: -8rpx;
- min-width: 36rpx;
- height: 36rpx;
- border-radius: 18rpx;
- background-color: #F56C6C;
- color: #FFFFFF;
- font-size: 22rpx;
- font-weight: 600;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 0 8rpx;
- z-index: 3;
- box-shadow: 0 3rpx 8rpx rgba(245, 108, 108, 0.3);
- animation: pulse 1.5s infinite;
- }
- @keyframes pulse {
- 0% {
- transform: scale(1);
- }
- 50% {
- transform: scale(1.1);
- }
- 100% {
- transform: scale(1);
- }
- }
- /* 设备基本信息 */
- .device-info {
- display: flex;
- margin-bottom: 24rpx;
- width: 100%;
- }
- .device-icon-wrapper {
- position: relative;
- margin-right: 24rpx;
- flex-shrink: 0;
- }
- .device-icon-container {
- width: 96rpx;
- height: 96rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #66CC6A 0%, #3BB44A 100%);
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- box-shadow: 0 6rpx 16rpx rgba(59, 180, 74, 0.2);
- transition: all 0.3s ease;
- }
- .device-icon-container.offline-icon {
- background: linear-gradient(135deg, #AAB2BD 0%, #656D78 100%);
- box-shadow: 0 6rpx 16rpx rgba(101, 109, 120, 0.2);
- }
- .device-icon {
- width: 52rpx;
- height: 52rpx;
- filter: brightness(0) invert(1);
- }
- .device-meta {
- flex: 1;
- width: calc(100% - 120rpx);
- overflow: hidden;
- }
- .device-name-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 14rpx;
- width: 100%;
- }
- .device-name {
- font-size: 34rpx;
- font-weight: 600;
- color: #333333;
- max-width: 65%;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- transition: color 0.3s ease;
- }
- .device-name.offline-text {
- color: #656D78;
- }
- .status-tag {
- padding: 6rpx 16rpx 6rpx 32rpx;
- border-radius: 8rpx;
- font-size: 24rpx;
- font-weight: 500;
- flex-shrink: 0;
- border: 1rpx solid;
- position: relative;
- overflow: hidden;
- }
- .status-online {
- background-color: rgba(76, 175, 80, 0.1);
- color: #4CAF50;
- border-color: rgba(76, 175, 80, 0.3);
- }
- .status-offline {
- background-color: rgba(245, 108, 108, 0.1);
- color: #F56C6C;
- border-color: rgba(245, 108, 108, 0.3);
- }
- .status-fault {
- background-color: rgba(255, 152, 0, 0.1);
- color: #FF9800;
- border-color: rgba(255, 152, 0, 0.3);
- }
- .status-maintain {
- background-color: rgba(33, 150, 243, 0.1);
- color: #2196F3;
- border-color: rgba(33, 150, 243, 0.3);
- }
- .status-dot {
- position: absolute;
- width: 8rpx;
- height: 8rpx;
- background-color: #4CAF50;
- border-radius: 50%;
- top: 50%;
- left: 16rpx;
- transform: translateY(-50%);
- box-shadow: 0 0 4rpx rgba(76, 175, 80, 0.8);
- animation: blink 1.5s infinite;
- display: inline-block;
- }
- .status-dot.offline-dot {
- background-color: #F56C6C;
- box-shadow: 0 0 4rpx rgba(245, 108, 108, 0.8);
- }
- .status-dot.fault-dot {
- background-color: #FF9800;
- box-shadow: 0 0 4rpx rgba(255, 152, 0, 0.8);
- }
- .status-dot.maintain-dot {
- background-color: #2196F3;
- box-shadow: 0 0 4rpx rgba(33, 150, 243, 0.8);
- }
- @keyframes blink {
- 0% {
- opacity: 0.4;
- }
- 50% {
- opacity: 1;
- }
- 100% {
- opacity: 0.4;
- }
- }
- .device-id, .device-location {
- display: flex;
- font-size: 26rpx;
- margin-top: 10rpx;
- color: #666666;
- width: 100%;
- overflow: hidden;
- }
- .id-label, .location-label {
- color: #999999;
- margin-right: 8rpx;
- flex-shrink: 0;
- }
- .id-value, .location-value {
- color: #666666;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- /* 底部信息栏 */
- .device-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-top: 18rpx;
- border-top: 1rpx solid #F2F2F2;
- }
- .update-time {
- font-size: 24rpx;
- color: #999999;
- }
- .device-actions {
- display: flex;
- align-items: center;
- color: #CCCCCC;
- }
- /* 加载更多区域 */
- .load-more {
- padding: 20rpx 0 40rpx;
- }
- .load-more-content {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 60rpx;
- font-size: 24rpx;
- color: #999999;
- }
- .loading-icon {
- width: 30rpx;
- height: 30rpx;
- margin-right: 10rpx;
- border: 2rpx solid #E0E0E0;
- border-top: 2rpx solid #4CAF50;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- /* 悬浮新增按钮 */
- .floating-add-btn {
- position: fixed;
- right: 40rpx;
- bottom: 160rpx;
- width: 120rpx;
- height: 120rpx;
- border-radius: 60rpx;
- background: linear-gradient(135deg, #3BB44A, #66CC6A);
- box-shadow: 0 12rpx 30rpx rgba(59, 180, 74, 0.35);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 99;
- }
- .floating-add-btn .plus {
- font-size: 60rpx;
- color: #ffffff;
- line-height: 1;
- }
- </style>
|