完善保险项目和养殖端小程序
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
"pages/device/device",
|
||||
"pages/device/eartag/eartag",
|
||||
"pages/device/collar/collar",
|
||||
"pages/device/host/host",
|
||||
"pages/device/fence/fence",
|
||||
"pages/device/eartag-detail/eartag-detail",
|
||||
"pages/device/eartag-add/eartag-add",
|
||||
"pages/alert/alert"
|
||||
|
||||
@@ -1,63 +1,257 @@
|
||||
|
||||
// 状态映射
|
||||
const statusMap = {
|
||||
'在线': '在线',
|
||||
'离线': '离线',
|
||||
'告警': '告警',
|
||||
'online': '在线',
|
||||
'offline': '离线',
|
||||
'alarm': '告警'
|
||||
}
|
||||
|
||||
// 佩戴状态映射
|
||||
const wearStatusMap = {
|
||||
1: '已佩戴',
|
||||
0: '未佩戴'
|
||||
}
|
||||
|
||||
// 连接状态映射
|
||||
const connectStatusMap = {
|
||||
1: '已连接',
|
||||
0: '未连接'
|
||||
}
|
||||
|
||||
// 设备状态映射
|
||||
const deviceStatusMap = {
|
||||
'使用中': '使用中',
|
||||
'待机': '待机',
|
||||
'维护': '维护',
|
||||
'故障': '故障'
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [], // 项圈数据列表
|
||||
searchValue: '', // 搜索值
|
||||
currentPage: 1, // 当前页码
|
||||
total: 0, // 总数据量
|
||||
pageSize: 10 // 每页数量
|
||||
pageSize: 10, // 每页数量
|
||||
totalPages: 0, // 总页数
|
||||
pageNumbers: [], // 页码数组
|
||||
isSearching: false, // 是否在搜索状态
|
||||
searchResult: null // 搜索结果
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 检查登录状态
|
||||
if (!this.checkLoginStatus()) {
|
||||
return
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
const token = wx.getStorageSync('token')
|
||||
const userInfo = wx.getStorageSync('userInfo')
|
||||
|
||||
if (!token || !userInfo) {
|
||||
console.log('用户未登录,跳转到登录页')
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录后再使用',
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('用户已登录:', userInfo.username)
|
||||
return true
|
||||
},
|
||||
|
||||
// 加载数据
|
||||
loadData() {
|
||||
const { currentPage, pageSize, searchValue } = this.data
|
||||
const url = `https://ad.ningmuyun.com/api/smart-devices/collars?page=${currentPage}&limit=${pageSize}&deviceId=${searchValue}&_t=${Date.now()}`
|
||||
|
||||
// 检查登录状态
|
||||
const token = wx.getStorageSync('token')
|
||||
if (!token) {
|
||||
wx.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + getApp().globalData.token // 添加认证头
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.data) {
|
||||
const data = res.data.data
|
||||
this.setData({
|
||||
list: (data.items || []).map(item => ({
|
||||
...item,
|
||||
statusText: statusMap[item.status] || item.status
|
||||
})),
|
||||
total: data.total || 0
|
||||
console.log('API响应:', res)
|
||||
if (res.statusCode === 200 && res.data) {
|
||||
// 处理API响应格式
|
||||
const response = res.data
|
||||
console.log('API响应数据:', response)
|
||||
|
||||
if (response.success && response.data) {
|
||||
// 处理 {success: true, data: [...]} 格式
|
||||
const data = response.data
|
||||
const total = response.total || response.pagination?.total || 0
|
||||
const totalPages = Math.ceil(total / this.data.pageSize)
|
||||
const pageNumbers = this.generatePageNumbers(this.data.currentPage, totalPages)
|
||||
|
||||
this.setData({
|
||||
list: Array.isArray(data) ? data.map(item => this.formatItemData(item)) : [],
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
pageNumbers: pageNumbers
|
||||
})
|
||||
} else if (response.data && response.data.items) {
|
||||
// 处理 {data: {items: [...]}} 格式
|
||||
const data = response.data
|
||||
this.setData({
|
||||
list: (data.items || []).map(item => this.formatItemData(item)),
|
||||
total: data.total || 0
|
||||
})
|
||||
} else {
|
||||
// 直接数组格式
|
||||
this.setData({
|
||||
list: Array.isArray(response) ? response.map(item => this.formatItemData(item)) : [],
|
||||
total: response.length || 0
|
||||
})
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
// 处理401未授权
|
||||
wx.showToast({
|
||||
title: '登录已过期,请重新登录',
|
||||
icon: 'none'
|
||||
})
|
||||
// 清除本地存储
|
||||
wx.removeStorageSync('token')
|
||||
wx.removeStorageSync('userInfo')
|
||||
// 跳转到登录页
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.data.message || '数据加载失败',
|
||||
title: res.data?.message || '数据加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('请求失败:', err)
|
||||
wx.showToast({
|
||||
title: err.errMsg.includes('401') ? '请登录后重试' : '请求失败',
|
||||
title: err.errMsg?.includes('401') ? '请登录后重试' : '网络请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
console.error(err)
|
||||
},
|
||||
complete: () => wx.hideLoading()
|
||||
complete: () => {
|
||||
wx.hideLoading()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 格式化单个设备数据
|
||||
formatItemData(item) {
|
||||
return {
|
||||
...item,
|
||||
// 状态映射
|
||||
statusText: statusMap[item.status] || item.status || '在线',
|
||||
// 佩戴状态映射
|
||||
wearStatusText: wearStatusMap[item.is_wear] || wearStatusMap[item.bandge_status] || '未知',
|
||||
// 连接状态映射
|
||||
connectStatusText: connectStatusMap[item.is_connect] || '未知',
|
||||
// 设备状态映射
|
||||
deviceStatusText: deviceStatusMap[item.deviceStatus] || item.deviceStatus || '未知',
|
||||
// 格式化电池电量
|
||||
batteryText: `${item.battery || item.batteryPercent || 0}%`,
|
||||
// 格式化温度
|
||||
temperatureText: `${item.temperature || item.raw?.temperature_raw || '0'}°C`,
|
||||
// 格式化步数
|
||||
stepsText: `${item.steps || 0}步`,
|
||||
// 格式化信号强度
|
||||
signalText: item.rsrp ? `${item.rsrp}dBm` : '未知',
|
||||
// 格式化GPS信号
|
||||
gpsText: item.gpsSignal ? `${item.gpsSignal}颗卫星` : '无信号',
|
||||
// 格式化位置信息
|
||||
locationText: item.location || (item.longitude && item.latitude ? '有定位' : '无定位'),
|
||||
// 格式化最后更新时间
|
||||
lastUpdateText: item.lastUpdate || '未知',
|
||||
// 格式化设备序列号
|
||||
snText: item.sn ? `SN:${item.sn}` : '未知',
|
||||
// 格式化更新间隔
|
||||
updateIntervalText: item.updateInterval ? `${Math.floor(item.updateInterval / 1000)}秒` : '未知'
|
||||
}
|
||||
},
|
||||
|
||||
// 生成页码数组
|
||||
generatePageNumbers(currentPage, totalPages) {
|
||||
const pageNumbers = []
|
||||
const maxVisible = 5 // 最多显示5个页码
|
||||
|
||||
if (totalPages <= maxVisible) {
|
||||
// 总页数少于等于5页,显示所有页码
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pageNumbers.push(i)
|
||||
}
|
||||
} else {
|
||||
// 总页数大于5页,智能显示页码
|
||||
let start = Math.max(1, currentPage - 2)
|
||||
let end = Math.min(totalPages, start + maxVisible - 1)
|
||||
|
||||
if (end - start + 1 < maxVisible) {
|
||||
start = Math.max(1, end - maxVisible + 1)
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
pageNumbers.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
return pageNumbers
|
||||
},
|
||||
|
||||
// 上一页
|
||||
onPrevPage() {
|
||||
if (this.data.currentPage > 1) {
|
||||
this.setData({
|
||||
currentPage: this.data.currentPage - 1
|
||||
}, () => {
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 下一页
|
||||
onNextPage() {
|
||||
if (this.data.currentPage < this.data.totalPages) {
|
||||
this.setData({
|
||||
currentPage: this.data.currentPage + 1
|
||||
}, () => {
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索输入
|
||||
onSearchInput(e) {
|
||||
this.setData({ searchValue: e.detail.value.trim() })
|
||||
@@ -65,7 +259,138 @@ Page({
|
||||
|
||||
// 执行搜索
|
||||
onSearch() {
|
||||
this.setData({ currentPage: 1 }, () => this.loadData())
|
||||
const searchValue = this.data.searchValue.trim()
|
||||
if (!searchValue) {
|
||||
wx.showToast({
|
||||
title: '请输入项圈编号',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证项圈编号格式(数字)
|
||||
if (!/^\d+$/.test(searchValue)) {
|
||||
wx.showToast({
|
||||
title: '项圈编号只能包含数字',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置搜索状态
|
||||
this.setData({
|
||||
isSearching: true,
|
||||
searchResult: null,
|
||||
currentPage: 1
|
||||
})
|
||||
|
||||
// 执行精确搜索
|
||||
this.performExactSearch(searchValue)
|
||||
},
|
||||
|
||||
// 执行精确搜索
|
||||
performExactSearch(searchValue) {
|
||||
const url = `https://ad.ningmuyun.com/api/smart-devices/collars?page=1&limit=1&deviceId=${searchValue}&_t=${Date.now()}`
|
||||
|
||||
// 检查登录状态
|
||||
const token = wx.getStorageSync('token')
|
||||
if (!token) {
|
||||
wx.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wx.showLoading({ title: '搜索中...' })
|
||||
wx.request({
|
||||
url,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('搜索API响应:', res)
|
||||
if (res.statusCode === 200 && res.data) {
|
||||
const response = res.data
|
||||
|
||||
if (response.success && response.data) {
|
||||
const data = response.data
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// 找到匹配的设备
|
||||
const device = this.formatItemData(data[0])
|
||||
this.setData({
|
||||
searchResult: device,
|
||||
list: [], // 清空列表显示
|
||||
total: 1,
|
||||
totalPages: 1,
|
||||
pageNumbers: [1]
|
||||
})
|
||||
wx.showToast({
|
||||
title: '搜索成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
// 没有找到匹配的设备
|
||||
this.setData({
|
||||
searchResult: null,
|
||||
list: [],
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
pageNumbers: []
|
||||
})
|
||||
wx.showToast({
|
||||
title: '未找到该设备',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '搜索失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
wx.showToast({
|
||||
title: '登录已过期,请重新登录',
|
||||
icon: 'none'
|
||||
})
|
||||
wx.removeStorageSync('token')
|
||||
wx.removeStorageSync('userInfo')
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '搜索失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('搜索请求失败:', err)
|
||||
wx.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
complete: () => {
|
||||
wx.hideLoading()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 清除搜索
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
isSearching: false,
|
||||
searchResult: null,
|
||||
currentPage: 1
|
||||
})
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 分页切换
|
||||
|
||||
@@ -7,31 +7,211 @@
|
||||
bindinput="onSearchInput"
|
||||
value="{{searchValue}}"
|
||||
/>
|
||||
<button bindtap="onSearch">查询</button>
|
||||
<button bindtap="onSearch" class="search-btn">查询</button>
|
||||
<button wx:if="{{isSearching}}" bindtap="clearSearch" class="clear-btn">清除</button>
|
||||
</view>
|
||||
|
||||
<!-- 搜索状态提示 -->
|
||||
<view wx:if="{{isSearching}}" class="search-status">
|
||||
<text>搜索项圈编号: {{searchValue}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<view wx:if="{{isSearching && searchResult}}" class="search-result">
|
||||
<view class="result-header">
|
||||
<text class="result-title">搜索结果</text>
|
||||
<text class="result-subtitle">找到匹配的设备</text>
|
||||
</view>
|
||||
<view class="item search-item" bindtap="viewCollarDetail" data-id="{{searchResult.id}}">
|
||||
<!-- 设备基本信息 -->
|
||||
<view class="item-header">
|
||||
<text class="device-sn">{{searchResult.snText}}</text>
|
||||
<text class="device-status {{searchResult.status === '在线' ? 'online' : 'offline'}}">{{searchResult.statusText}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 设备详细信息 -->
|
||||
<view class="item-content">
|
||||
<view class="info-row">
|
||||
<text class="label">项圈编号:</text>
|
||||
<text class="value">{{searchResult.sn}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">佩戴状态:</text>
|
||||
<text class="value {{searchResult.is_wear === 1 ? 'wear-on' : 'wear-off'}}">{{searchResult.wearStatusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">连接状态:</text>
|
||||
<text class="value {{searchResult.is_connect === 1 ? 'connect-on' : 'connect-off'}}">{{searchResult.connectStatusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">电池电量:</text>
|
||||
<text class="value battery-{{searchResult.batteryPercent > 50 ? 'high' : searchResult.batteryPercent > 20 ? 'medium' : 'low'}}">{{searchResult.batteryText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">体温:</text>
|
||||
<text class="value">{{searchResult.temperatureText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">步数:</text>
|
||||
<text class="value">{{searchResult.stepsText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">信号强度:</text>
|
||||
<text class="value">{{searchResult.signalText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">GPS信号:</text>
|
||||
<text class="value">{{searchResult.gpsText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">位置状态:</text>
|
||||
<text class="value">{{searchResult.locationText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">最后更新:</text>
|
||||
<text class="value">{{searchResult.lastUpdateText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">更新间隔:</text>
|
||||
<text class="value">{{searchResult.updateIntervalText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="item-actions">
|
||||
<button class="btn-detail" size="mini" bindtap="viewCollarDetail" data-id="{{searchResult.id}}" catchtap="true">查看详情</button>
|
||||
<button class="btn-edit" size="mini" bindtap="editCollar" data-id="{{searchResult.id}}" catchtap="true">编辑</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无搜索结果 -->
|
||||
<view wx:if="{{isSearching && !searchResult}}" class="no-result">
|
||||
<view class="no-result-icon">🔍</view>
|
||||
<text class="no-result-text">未找到项圈编号为 "{{searchValue}}" 的设备</text>
|
||||
<button class="retry-btn" bindtap="clearSearch">重新搜索</button>
|
||||
</view>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<view class="list">
|
||||
<view wx:if="{{!isSearching}}" class="list">
|
||||
<block wx:for="{{list}}" wx:key="id">
|
||||
<view class="item">
|
||||
<text>项圈编号: {{item.deviceId}}</text>
|
||||
<text>状态: {{item.status | statusText}}</text>
|
||||
<text>电量: {{item.battery}}%</text>
|
||||
<text>最后在线: {{item.lastOnlineTime}}</text>
|
||||
<view class="item" bindtap="viewCollarDetail" data-id="{{item.id}}">
|
||||
<!-- 设备基本信息 -->
|
||||
<view class="item-header">
|
||||
<text class="device-sn">{{item.snText}}</text>
|
||||
<text class="device-status {{item.status === '在线' ? 'online' : 'offline'}}">{{item.statusText}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 设备详细信息 -->
|
||||
<view class="item-content">
|
||||
<view class="info-row">
|
||||
<text class="label">项圈编号:</text>
|
||||
<text class="value">{{item.sn}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">佩戴状态:</text>
|
||||
<text class="value {{item.is_wear === 1 ? 'wear-on' : 'wear-off'}}">{{item.wearStatusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">连接状态:</text>
|
||||
<text class="value {{item.is_connect === 1 ? 'connect-on' : 'connect-off'}}">{{item.connectStatusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">电池电量:</text>
|
||||
<text class="value battery-{{item.batteryPercent > 50 ? 'high' : item.batteryPercent > 20 ? 'medium' : 'low'}}">{{item.batteryText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">体温:</text>
|
||||
<text class="value">{{item.temperatureText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">步数:</text>
|
||||
<text class="value">{{item.stepsText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">信号强度:</text>
|
||||
<text class="value">{{item.signalText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">GPS信号:</text>
|
||||
<text class="value">{{item.gpsText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">位置状态:</text>
|
||||
<text class="value">{{item.locationText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">最后更新:</text>
|
||||
<text class="value">{{item.lastUpdateText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-row">
|
||||
<text class="label">更新间隔:</text>
|
||||
<text class="value">{{item.updateIntervalText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="item-actions">
|
||||
<button class="btn-detail" size="mini" bindtap="viewCollarDetail" data-id="{{item.id}}" catchtap="true">查看详情</button>
|
||||
<button class="btn-edit" size="mini" bindtap="editCollar" data-id="{{item.id}}" catchtap="true">编辑</button>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<view class="pagination">
|
||||
<block wx:for="{{pages}}" wx:key="index">
|
||||
<text
|
||||
class="{{currentPage === item ? 'active' : ''}}"
|
||||
bindtap="onPageChange"
|
||||
data-page="{{item}}"
|
||||
<view class="pagination" wx:if="{{!isSearching && totalPages > 1}}">
|
||||
<view class="pagination-info">
|
||||
<text>共 {{total}} 条数据,第 {{currentPage}} / {{totalPages}} 页</text>
|
||||
</view>
|
||||
<view class="pagination-buttons">
|
||||
<button
|
||||
class="page-btn prev-btn {{currentPage <= 1 ? 'disabled' : ''}}"
|
||||
bindtap="onPrevPage"
|
||||
disabled="{{currentPage <= 1}}"
|
||||
>
|
||||
{{item}}
|
||||
</text>
|
||||
</block>
|
||||
上一页
|
||||
</button>
|
||||
|
||||
<view class="page-numbers">
|
||||
<block wx:for="{{pageNumbers}}" wx:key="index">
|
||||
<text
|
||||
class="page-number {{currentPage === item ? 'active' : ''}}"
|
||||
bindtap="onPageChange"
|
||||
data-page="{{item}}"
|
||||
>
|
||||
{{item}}
|
||||
</text>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="page-btn next-btn {{currentPage >= totalPages ? 'disabled' : ''}}"
|
||||
bindtap="onNextPage"
|
||||
disabled="{{currentPage >= totalPages}}"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -6,13 +6,137 @@
|
||||
.search-box {
|
||||
display: flex;
|
||||
margin-bottom: 20rpx;
|
||||
gap: 15rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
flex: 1;
|
||||
border: 1rpx solid #ddd;
|
||||
padding: 10rpx 20rpx;
|
||||
margin-right: 20rpx;
|
||||
border: 2rpx solid #e8e8e8;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
background: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-box input:focus {
|
||||
border-color: #1890ff;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 4rpx rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: linear-gradient(135deg, #1890ff, #40a9ff);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
background: linear-gradient(135deg, #ff4d4f, #ff7875);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 77, 79, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.clear-btn:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.search-status {
|
||||
background: linear-gradient(135deg, #e6f7ff, #bae7ff);
|
||||
border: 2rpx solid #91d5ff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search-status text {
|
||||
color: #1890ff;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-result {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
background: linear-gradient(135deg, #f6ffed, #d9f7be);
|
||||
border: 2rpx solid #b7eb8f;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #52c41a;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.result-subtitle {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #73d13d;
|
||||
}
|
||||
|
||||
.search-item {
|
||||
border: 2rpx solid #52c41a;
|
||||
box-shadow: 0 4rpx 16rpx rgba(82, 196, 26, 0.2);
|
||||
}
|
||||
|
||||
.no-result {
|
||||
text-align: center;
|
||||
padding: 80rpx 40rpx;
|
||||
background: #fafafa;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.no-result-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.no-result-text {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 40rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: linear-gradient(135deg, #1890ff, #40a9ff);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.list {
|
||||
@@ -21,26 +145,212 @@
|
||||
|
||||
.item {
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
margin-bottom: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
border: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.item text {
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.device-sn {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.device-status.online {
|
||||
background: #e8f5e8;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.device-status.offline {
|
||||
background: #fff2e8;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 状态颜色 */
|
||||
.wear-on {
|
||||
color: #52c41a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wear-off {
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.connect-on {
|
||||
color: #52c41a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.connect-off {
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.battery-high {
|
||||
color: #52c41a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.battery-medium {
|
||||
color: #fa8c16;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.battery-low {
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20rpx;
|
||||
padding-top: 15rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.btn-detail {
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background: #52c41a;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 40rpx;
|
||||
padding: 30rpx;
|
||||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||
border: 2rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
text-align: center;
|
||||
margin-bottom: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
padding: 15rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pagination text {
|
||||
margin: 0 10rpx;
|
||||
padding: 5rpx 15rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
.page-btn {
|
||||
padding: 16rpx 28rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
border: 2rpx solid #d9d9d9;
|
||||
background: linear-gradient(135deg, #fff, #f8f9fa);
|
||||
color: #333;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.pagination .active {
|
||||
background-color: #07C160;
|
||||
.page-btn:not(.disabled):active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.page-btn.disabled {
|
||||
background: linear-gradient(135deg, #f5f5f5, #e8e8e8);
|
||||
color: #ccc;
|
||||
border-color: #d9d9d9;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.page-numbers {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
margin: 0 20rpx;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
padding: 16rpx 20rpx;
|
||||
border: 2rpx solid #d9d9d9;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #fff, #f8f9fa);
|
||||
min-width: 60rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.page-number:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.page-number.active {
|
||||
background: linear-gradient(135deg, #1890ff, #40a9ff);
|
||||
color: white;
|
||||
border-color: #1890ff;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
842
mini_program/farm-monitor-dashboard/pages/device/fence/fence.js
Normal file
842
mini_program/farm-monitor-dashboard/pages/device/fence/fence.js
Normal file
@@ -0,0 +1,842 @@
|
||||
Page({
|
||||
data: {
|
||||
// 围栏数据
|
||||
fenceList: [],
|
||||
loading: false,
|
||||
|
||||
// 设备统计
|
||||
stats: {
|
||||
smartCollector: 0,
|
||||
smartDevice: 0
|
||||
},
|
||||
|
||||
// 地图相关
|
||||
mapCenter: {
|
||||
lng: 106.2751866,
|
||||
lat: 38.4689544
|
||||
},
|
||||
mapZoom: 15,
|
||||
mapLocked: true, // 地图位置锁定
|
||||
lastMapCenter: null, // 上次地图中心位置
|
||||
|
||||
// 当前选中的围栏
|
||||
selectedFence: null,
|
||||
selectedFenceIndex: 0, // 当前选中的围栏索引
|
||||
|
||||
// 显示控制
|
||||
showPasture: true,
|
||||
mapType: 'normal', // normal, satellite
|
||||
|
||||
// 地图标记和多边形
|
||||
fenceMarkers: [],
|
||||
fencePolygons: [],
|
||||
|
||||
// 缓存数据
|
||||
cachedFenceData: null,
|
||||
isOfflineMode: false,
|
||||
|
||||
// 地图锁定相关
|
||||
includePoints: [], // 用于强制锁定地图位置的点
|
||||
mapLockTimer: null, // 地图锁定监控定时器
|
||||
|
||||
// 围栏类型配置
|
||||
fenceTypes: {
|
||||
'grazing': { name: '放牧围栏', color: '#52c41a', icon: '🌿' },
|
||||
'safety': { name: '安全围栏', color: '#1890ff', icon: '🛡️' },
|
||||
'restricted': { name: '限制围栏', color: '#ff4d4f', icon: '⚠️' },
|
||||
'collector': { name: '收集围栏', color: '#fa8c16', icon: '📦' }
|
||||
},
|
||||
|
||||
// 搜索和过滤
|
||||
searchValue: '',
|
||||
selectedFenceType: '',
|
||||
filteredFenceList: []
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
console.log('电子围栏页面加载')
|
||||
this.checkLoginStatus()
|
||||
|
||||
// 启动地图锁定监控定时器
|
||||
this.startMapLockTimer()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 清除定时器
|
||||
if (this.data.mapLockTimer) {
|
||||
clearInterval(this.data.mapLockTimer)
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 先尝试加载缓存数据,如果没有缓存再请求API
|
||||
if (!this.loadCachedData()) {
|
||||
this.loadFenceData()
|
||||
}
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
const token = wx.getStorageSync('token')
|
||||
const userInfo = wx.getStorageSync('userInfo')
|
||||
|
||||
if (!token || !userInfo) {
|
||||
wx.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
// 加载围栏数据
|
||||
loadFenceData() {
|
||||
if (!this.checkLoginStatus()) return
|
||||
|
||||
const token = wx.getStorageSync('token')
|
||||
const url = `https://ad.ningmuyun.com/api/electronic-fence?page=1&limit=100&_t=${Date.now()}`
|
||||
|
||||
this.setData({ loading: true })
|
||||
wx.request({
|
||||
url,
|
||||
method: 'GET',
|
||||
timeout: 30000,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('围栏API响应:', res)
|
||||
if (res.statusCode === 200 && res.data) {
|
||||
const response = res.data
|
||||
|
||||
if (response.success && response.data) {
|
||||
const fenceList = response.data.map(fence => this.formatFenceData(fence))
|
||||
|
||||
// 生成地图标记和多边形数据
|
||||
const fenceMarkers = this.generateFenceMarkers(fenceList)
|
||||
const fencePolygons = this.generateFencePolygons(fenceList)
|
||||
|
||||
// 缓存数据
|
||||
const cacheData = {
|
||||
fenceList: fenceList,
|
||||
fenceMarkers: fenceMarkers,
|
||||
fencePolygons: fencePolygons,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
wx.setStorageSync('fenceCache', cacheData)
|
||||
|
||||
this.setData({
|
||||
fenceList: fenceList,
|
||||
fenceMarkers: fenceMarkers,
|
||||
fencePolygons: fencePolygons,
|
||||
cachedFenceData: cacheData,
|
||||
isOfflineMode: false,
|
||||
stats: {
|
||||
smartCollector: 2, // 从API获取或硬编码
|
||||
smartDevice: 4 // 从API获取或硬编码
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有围栏数据,设置默认选中第一个围栏
|
||||
if (fenceList.length > 0) {
|
||||
const firstFence = fenceList[0]
|
||||
const centerLng = parseFloat(firstFence.center.lng)
|
||||
const centerLat = parseFloat(firstFence.center.lat)
|
||||
|
||||
this.setData({
|
||||
selectedFence: firstFence,
|
||||
selectedFenceIndex: 0,
|
||||
mapCenter: {
|
||||
lng: centerLng,
|
||||
lat: centerLat
|
||||
},
|
||||
mapLocked: true, // 初始化后锁定地图
|
||||
lastMapCenter: {
|
||||
latitude: centerLat,
|
||||
longitude: centerLng
|
||||
}
|
||||
})
|
||||
|
||||
// 立即更新include-points来强制锁定
|
||||
this.updateIncludePoints()
|
||||
|
||||
// 多次强制锁定,确保地图不会移动
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: centerLng,
|
||||
lat: centerLat
|
||||
}
|
||||
})
|
||||
this.updateIncludePoints()
|
||||
console.log('第一次延迟强制锁定:', centerLng, centerLat)
|
||||
}, 500)
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: centerLng,
|
||||
lat: centerLat
|
||||
}
|
||||
})
|
||||
this.updateIncludePoints()
|
||||
console.log('第二次延迟强制锁定:', centerLng, centerLat)
|
||||
}, 1000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: centerLng,
|
||||
lat: centerLat
|
||||
}
|
||||
})
|
||||
this.updateIncludePoints()
|
||||
console.log('第三次延迟强制锁定:', centerLng, centerLat)
|
||||
}, 2000)
|
||||
}
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: response.message || '数据加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
wx.showToast({
|
||||
title: '登录已过期,请重新登录',
|
||||
icon: 'none'
|
||||
})
|
||||
wx.removeStorageSync('token')
|
||||
wx.removeStorageSync('userInfo')
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
} else if (res.statusCode === 502) {
|
||||
wx.showModal({
|
||||
title: '服务器错误',
|
||||
content: '服务器暂时不可用(502错误),请稍后重试',
|
||||
confirmText: '重试',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
setTimeout(() => {
|
||||
this.loadFenceData()
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (res.statusCode >= 500) {
|
||||
wx.showModal({
|
||||
title: '服务器错误',
|
||||
content: `服务器错误(${res.statusCode}),请稍后重试`,
|
||||
confirmText: '重试',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
setTimeout(() => {
|
||||
this.loadFenceData()
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.data?.message || `请求失败(${res.statusCode})`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('请求失败:', err)
|
||||
|
||||
// 根据错误类型显示不同的提示和处理方式
|
||||
let errorMessage = '网络请求失败'
|
||||
let errorTitle = '请求失败'
|
||||
let showRetry = true
|
||||
|
||||
if (err.errMsg && err.errMsg.includes('timeout')) {
|
||||
errorMessage = '请求超时,服务器响应较慢'
|
||||
errorTitle = '请求超时'
|
||||
} else if (err.errMsg && err.errMsg.includes('fail')) {
|
||||
if (err.errMsg.includes('502')) {
|
||||
errorMessage = '服务器网关错误(502),服务暂时不可用'
|
||||
errorTitle = '服务器错误'
|
||||
} else if (err.errMsg.includes('503')) {
|
||||
errorMessage = '服务器维护中(503),请稍后重试'
|
||||
errorTitle = '服务维护'
|
||||
} else if (err.errMsg.includes('504')) {
|
||||
errorMessage = '服务器响应超时(504),请重试'
|
||||
errorTitle = '服务器超时'
|
||||
} else {
|
||||
errorMessage = '网络连接失败,请检查网络设置'
|
||||
errorTitle = '网络错误'
|
||||
}
|
||||
} else if (err.errMsg && err.errMsg.includes('ssl')) {
|
||||
errorMessage = 'SSL证书错误,请检查网络环境'
|
||||
errorTitle = '安全连接错误'
|
||||
} else if (err.errMsg && err.errMsg.includes('dns')) {
|
||||
errorMessage = 'DNS解析失败,请检查网络连接'
|
||||
errorTitle = '网络解析错误'
|
||||
}
|
||||
|
||||
// 尝试加载缓存数据
|
||||
this.loadCachedData()
|
||||
|
||||
if (showRetry) {
|
||||
wx.showModal({
|
||||
title: errorTitle,
|
||||
content: errorMessage + ',是否重试?',
|
||||
confirmText: '重试',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 显示重试提示
|
||||
wx.showLoading({
|
||||
title: '重试中...',
|
||||
mask: true
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.hideLoading()
|
||||
this.loadFenceData()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: errorMessage,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 格式化围栏数据
|
||||
formatFenceData(fence) {
|
||||
// 处理围栏类型映射
|
||||
let fenceType = fence.type || 'grazing'
|
||||
if (fenceType === '放牧围栏') fenceType = 'grazing'
|
||||
else if (fenceType === '安全围栏') fenceType = 'safety'
|
||||
else if (fenceType === '限制围栏') fenceType = 'restricted'
|
||||
else if (fenceType === '收集围栏') fenceType = 'collector'
|
||||
|
||||
return {
|
||||
id: fence.id,
|
||||
name: fence.name,
|
||||
type: fenceType,
|
||||
typeName: this.data.fenceTypes[fenceType]?.name || fenceType,
|
||||
typeColor: this.data.fenceTypes[fenceType]?.color || '#666',
|
||||
typeIcon: this.data.fenceTypes[fenceType]?.icon || '📍',
|
||||
description: fence.description,
|
||||
coordinates: fence.coordinates || [],
|
||||
center: fence.center,
|
||||
area: fence.area,
|
||||
grazingStatus: fence.grazingStatus,
|
||||
insideCount: fence.insideCount,
|
||||
outsideCount: fence.outsideCount,
|
||||
isActive: fence.isActive,
|
||||
createdAt: fence.createdAt,
|
||||
updatedAt: fence.updatedAt
|
||||
}
|
||||
},
|
||||
|
||||
// 加载缓存数据
|
||||
loadCachedData() {
|
||||
try {
|
||||
const cachedData = wx.getStorageSync('fenceCache')
|
||||
if (cachedData && cachedData.timestamp) {
|
||||
const cacheAge = Date.now() - cachedData.timestamp
|
||||
const maxCacheAge = 24 * 60 * 60 * 1000 // 24小时
|
||||
|
||||
if (cacheAge < maxCacheAge) {
|
||||
console.log('加载缓存数据,缓存时间:', new Date(cachedData.timestamp))
|
||||
|
||||
this.setData({
|
||||
fenceList: cachedData.fenceList || [],
|
||||
fenceMarkers: cachedData.fenceMarkers || [],
|
||||
fencePolygons: cachedData.fencePolygons || [],
|
||||
isOfflineMode: true,
|
||||
stats: {
|
||||
smartCollector: 2,
|
||||
smartDevice: 4
|
||||
}
|
||||
})
|
||||
|
||||
// 如果有围栏数据,设置默认选中第一个围栏
|
||||
if (cachedData.fenceList && cachedData.fenceList.length > 0) {
|
||||
const firstFence = cachedData.fenceList[0]
|
||||
const centerLng = parseFloat(firstFence.center.lng)
|
||||
const centerLat = parseFloat(firstFence.center.lat)
|
||||
|
||||
this.setData({
|
||||
selectedFence: firstFence,
|
||||
selectedFenceIndex: 0,
|
||||
mapCenter: {
|
||||
lng: centerLng,
|
||||
lat: centerLat
|
||||
},
|
||||
mapLocked: true,
|
||||
lastMapCenter: {
|
||||
latitude: centerLat,
|
||||
longitude: centerLng
|
||||
}
|
||||
})
|
||||
|
||||
// 立即更新include-points来强制锁定
|
||||
this.updateIncludePoints()
|
||||
|
||||
// 多次强制锁定,确保地图不会移动
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: centerLng,
|
||||
lat: centerLat
|
||||
}
|
||||
})
|
||||
this.updateIncludePoints()
|
||||
console.log('缓存数据第一次延迟强制锁定:', centerLng, centerLat)
|
||||
}, 500)
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: centerLng,
|
||||
lat: centerLat
|
||||
}
|
||||
})
|
||||
this.updateIncludePoints()
|
||||
console.log('缓存数据第二次延迟强制锁定:', centerLng, centerLat)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 显示离线模式提示
|
||||
wx.showToast({
|
||||
title: '已加载缓存数据(离线模式)',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
|
||||
return true
|
||||
} else {
|
||||
console.log('缓存数据已过期,清除缓存')
|
||||
wx.removeStorageSync('fenceCache')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载缓存数据失败:', error)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
onBack() {
|
||||
wx.navigateBack()
|
||||
},
|
||||
|
||||
// 显示设置菜单
|
||||
onShowMenu() {
|
||||
const menuItems = ['围栏设置', '设备管理', '历史记录']
|
||||
|
||||
// 添加地图锁定/解锁选项
|
||||
const lockText = this.data.mapLocked ? '解锁地图' : '锁定地图'
|
||||
menuItems.unshift(lockText)
|
||||
|
||||
// 如果有多个围栏,添加围栏切换选项
|
||||
if (this.data.fenceList.length > 1) {
|
||||
menuItems.unshift('切换围栏')
|
||||
}
|
||||
|
||||
wx.showActionSheet({
|
||||
itemList: menuItems,
|
||||
success: (res) => {
|
||||
const tapIndex = res.tapIndex
|
||||
console.log('选择了:', tapIndex)
|
||||
|
||||
let actualIndex = tapIndex
|
||||
|
||||
// 处理围栏切换
|
||||
if (this.data.fenceList.length > 1 && tapIndex === 0) {
|
||||
this.showFenceSelector()
|
||||
return
|
||||
} else if (this.data.fenceList.length > 1) {
|
||||
actualIndex = tapIndex - 1
|
||||
}
|
||||
|
||||
// 处理地图锁定/解锁
|
||||
if (actualIndex === 0) {
|
||||
this.toggleMapLock()
|
||||
} else {
|
||||
// 其他菜单项
|
||||
const menuIndex = this.data.fenceList.length > 1 ? actualIndex - 1 : actualIndex
|
||||
switch (menuIndex) {
|
||||
case 0: // 围栏设置
|
||||
wx.showToast({ title: '围栏设置功能开发中', icon: 'none' })
|
||||
break
|
||||
case 1: // 设备管理
|
||||
wx.showToast({ title: '设备管理功能开发中', icon: 'none' })
|
||||
break
|
||||
case 2: // 历史记录
|
||||
wx.showToast({ title: '历史记录功能开发中', icon: 'none' })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 显示围栏选择器
|
||||
showFenceSelector() {
|
||||
const fenceNames = this.data.fenceList.map(fence => fence.name)
|
||||
|
||||
wx.showActionSheet({
|
||||
itemList: fenceNames,
|
||||
success: (res) => {
|
||||
const selectedIndex = res.tapIndex
|
||||
const selectedFence = this.data.fenceList[selectedIndex]
|
||||
|
||||
this.setData({
|
||||
selectedFence: selectedFence,
|
||||
selectedFenceIndex: selectedIndex
|
||||
})
|
||||
|
||||
// 自动定位到选中的围栏
|
||||
this.locateToSelectedFence()
|
||||
|
||||
wx.showToast({
|
||||
title: `已切换到${selectedFence.name}`,
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 切换牧场显示
|
||||
onTogglePasture() {
|
||||
const newShowPasture = !this.data.showPasture
|
||||
this.setData({
|
||||
showPasture: newShowPasture
|
||||
})
|
||||
|
||||
if (newShowPasture && this.data.selectedFence) {
|
||||
// 如果显示牧场,定位到选中的围栏
|
||||
this.locateToSelectedFence()
|
||||
}
|
||||
},
|
||||
|
||||
// 定位到选中的围栏
|
||||
locateToSelectedFence() {
|
||||
if (!this.data.selectedFence) {
|
||||
wx.showToast({
|
||||
title: '没有选中的围栏',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const fence = this.data.selectedFence
|
||||
|
||||
// 计算围栏的边界,用于设置合适的地图视野
|
||||
const coordinates = fence.coordinates
|
||||
if (coordinates && coordinates.length > 0) {
|
||||
let minLat = coordinates[0].lat
|
||||
let maxLat = coordinates[0].lat
|
||||
let minLng = coordinates[0].lng
|
||||
let maxLng = coordinates[0].lng
|
||||
|
||||
coordinates.forEach(coord => {
|
||||
minLat = Math.min(minLat, coord.lat)
|
||||
maxLat = Math.max(maxLat, coord.lat)
|
||||
minLng = Math.min(minLng, coord.lng)
|
||||
maxLng = Math.max(maxLng, coord.lng)
|
||||
})
|
||||
|
||||
// 计算中心点和合适的缩放级别
|
||||
const centerLat = (minLat + maxLat) / 2
|
||||
const centerLng = (minLng + maxLng) / 2
|
||||
|
||||
// 根据围栏大小调整缩放级别
|
||||
const latDiff = maxLat - minLat
|
||||
const lngDiff = maxLng - minLng
|
||||
const maxDiff = Math.max(latDiff, lngDiff)
|
||||
|
||||
let zoom = 15
|
||||
if (maxDiff > 0.01) zoom = 12
|
||||
else if (maxDiff > 0.005) zoom = 14
|
||||
else if (maxDiff > 0.002) zoom = 16
|
||||
else zoom = 18
|
||||
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: centerLng,
|
||||
lat: centerLat
|
||||
},
|
||||
mapZoom: zoom,
|
||||
mapLocked: true, // 定位后锁定地图
|
||||
lastMapCenter: {
|
||||
latitude: centerLat,
|
||||
longitude: centerLng
|
||||
}
|
||||
})
|
||||
|
||||
// 立即更新include-points来强制锁定
|
||||
setTimeout(() => {
|
||||
this.updateIncludePoints()
|
||||
}, 100)
|
||||
|
||||
wx.showToast({
|
||||
title: `已定位到${fence.name}`,
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
} else {
|
||||
// 如果没有坐标点,使用中心点定位
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: parseFloat(fence.center.lng),
|
||||
lat: parseFloat(fence.center.lat)
|
||||
},
|
||||
mapZoom: 15,
|
||||
mapLocked: true, // 定位后锁定地图
|
||||
lastMapCenter: {
|
||||
latitude: parseFloat(fence.center.lat),
|
||||
longitude: parseFloat(fence.center.lng)
|
||||
}
|
||||
})
|
||||
|
||||
// 立即更新include-points来强制锁定
|
||||
setTimeout(() => {
|
||||
this.updateIncludePoints()
|
||||
}, 100)
|
||||
|
||||
wx.showToast({
|
||||
title: `已定位到${fence.name}`,
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 切换地图类型
|
||||
onSwitchMap() {
|
||||
const mapType = this.data.mapType === 'normal' ? 'satellite' : 'normal'
|
||||
this.setData({
|
||||
mapType: mapType
|
||||
})
|
||||
|
||||
wx.showToast({
|
||||
title: mapType === 'normal' ? '切换到普通地图' : '切换到卫星地图',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
|
||||
// 解锁/锁定地图
|
||||
toggleMapLock() {
|
||||
const newLocked = !this.data.mapLocked
|
||||
this.setData({
|
||||
mapLocked: newLocked
|
||||
})
|
||||
|
||||
// 更新include-points
|
||||
this.updateIncludePoints()
|
||||
|
||||
wx.showToast({
|
||||
title: newLocked ? '地图已锁定' : '地图已解锁',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
},
|
||||
|
||||
// 地图标记点击事件
|
||||
onMarkerTap(e) {
|
||||
const markerId = e.detail.markerId
|
||||
const fence = this.data.fenceList.find(f => f.id === markerId)
|
||||
|
||||
if (fence) {
|
||||
// 选中该围栏
|
||||
this.setData({
|
||||
selectedFence: fence,
|
||||
selectedFenceIndex: this.data.fenceList.findIndex(f => f.id === markerId)
|
||||
})
|
||||
|
||||
wx.showModal({
|
||||
title: `${fence.typeIcon} ${fence.name}`,
|
||||
content: `类型: ${fence.typeName}\n状态: ${fence.grazingStatus}\n面积: ${fence.area}平方米\n坐标点: ${fence.coordinates.length}个\n描述: ${fence.description || '无描述'}`,
|
||||
confirmText: '定位到此围栏',
|
||||
cancelText: '关闭',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 定位到选中的围栏
|
||||
this.locateToSelectedFence()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 地图区域变化
|
||||
onRegionChange(e) {
|
||||
console.log('地图区域变化:', e.detail)
|
||||
|
||||
// 强制锁定地图 - 无论什么情况都恢复位置
|
||||
if (this.data.lastMapCenter) {
|
||||
console.log('强制锁定地图位置')
|
||||
|
||||
// 立即恢复地图位置
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: this.data.lastMapCenter.longitude,
|
||||
lat: this.data.lastMapCenter.latitude
|
||||
}
|
||||
})
|
||||
|
||||
// 更新include-points来强制锁定
|
||||
this.updateIncludePoints()
|
||||
}
|
||||
},
|
||||
|
||||
// 更新include-points来强制锁定地图
|
||||
updateIncludePoints() {
|
||||
if (this.data.lastMapCenter) {
|
||||
const center = this.data.lastMapCenter
|
||||
|
||||
// 创建更紧密的四个点来强制锁定地图视野
|
||||
const offset = 0.0005 // 减小偏移量,使锁定更紧密
|
||||
const points = [
|
||||
{ latitude: center.latitude - offset, longitude: center.longitude - offset },
|
||||
{ latitude: center.latitude + offset, longitude: center.longitude - offset },
|
||||
{ latitude: center.latitude + offset, longitude: center.longitude + offset },
|
||||
{ latitude: center.latitude - offset, longitude: center.longitude + offset }
|
||||
]
|
||||
|
||||
this.setData({
|
||||
includePoints: points
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
includePoints: []
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 启动地图锁定监控定时器
|
||||
startMapLockTimer() {
|
||||
if (this.data.mapLockTimer) {
|
||||
clearInterval(this.data.mapLockTimer)
|
||||
}
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (this.data.lastMapCenter) {
|
||||
// 强制更新地图位置 - 无论锁定状态如何
|
||||
this.setData({
|
||||
mapCenter: {
|
||||
lng: this.data.lastMapCenter.longitude,
|
||||
lat: this.data.lastMapCenter.latitude
|
||||
}
|
||||
})
|
||||
|
||||
// 更新include-points
|
||||
this.updateIncludePoints()
|
||||
|
||||
console.log('定时器强制锁定地图位置:', this.data.lastMapCenter)
|
||||
}
|
||||
}, 500) // 每500毫秒检查一次,更频繁
|
||||
|
||||
this.setData({
|
||||
mapLockTimer: timer
|
||||
})
|
||||
},
|
||||
|
||||
// 地图点击事件
|
||||
onMapTap(e) {
|
||||
console.log('地图点击:', e.detail)
|
||||
},
|
||||
|
||||
// 关闭围栏信息面板
|
||||
onCloseFenceInfo() {
|
||||
this.setData({
|
||||
selectedFence: null
|
||||
})
|
||||
},
|
||||
|
||||
// 定位围栏
|
||||
onLocateFence() {
|
||||
if (this.data.selectedFence) {
|
||||
this.locateToSelectedFence()
|
||||
}
|
||||
},
|
||||
|
||||
// 查看围栏详情
|
||||
onViewFenceDetails() {
|
||||
if (this.data.selectedFence) {
|
||||
wx.showModal({
|
||||
title: `${this.data.selectedFence.typeIcon} ${this.data.selectedFence.name}`,
|
||||
content: `围栏ID: ${this.data.selectedFence.id}\n类型: ${this.data.selectedFence.typeName}\n状态: ${this.data.selectedFence.grazingStatus}\n面积: ${this.data.selectedFence.area}平方米\n坐标点: ${this.data.selectedFence.coordinates.length}个\n内部设备: ${this.data.selectedFence.insideCount}个\n外部设备: ${this.data.selectedFence.outsideCount}个\n创建时间: ${this.data.selectedFence.createdAt}\n更新时间: ${this.data.selectedFence.updatedAt}\n描述: ${this.data.selectedFence.description || '无描述'}`,
|
||||
showCancel: false,
|
||||
confirmText: '确定'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 生成围栏标记
|
||||
generateFenceMarkers(fenceList) {
|
||||
return fenceList.map((fence, index) => {
|
||||
return {
|
||||
id: fence.id,
|
||||
latitude: parseFloat(fence.center.lat),
|
||||
longitude: parseFloat(fence.center.lng),
|
||||
iconPath: '', // 使用默认图标
|
||||
width: 30,
|
||||
height: 30,
|
||||
title: fence.name,
|
||||
callout: {
|
||||
content: `${fence.typeIcon} ${fence.name}\n${fence.typeName}\n${fence.grazingStatus}\n${fence.coordinates.length}个坐标点`,
|
||||
color: '#333',
|
||||
fontSize: 12,
|
||||
borderRadius: 8,
|
||||
bgColor: '#fff',
|
||||
padding: 12,
|
||||
display: 'BYCLICK',
|
||||
borderWidth: 1,
|
||||
borderColor: fence.typeColor || '#3cc51f'
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 生成围栏多边形
|
||||
generateFencePolygons(fenceList) {
|
||||
return fenceList.map((fence, index) => {
|
||||
const points = fence.coordinates.map(coord => ({
|
||||
latitude: coord.lat,
|
||||
longitude: coord.lng
|
||||
}))
|
||||
|
||||
// 根据围栏类型设置颜色
|
||||
const strokeColor = fence.typeColor || (fence.isActive ? '#3cc51f' : '#ff6b6b')
|
||||
const fillColor = fence.typeColor ?
|
||||
`${fence.typeColor}33` : // 添加透明度
|
||||
(fence.isActive ? 'rgba(60, 197, 31, 0.2)' : 'rgba(255, 107, 107, 0.2)')
|
||||
|
||||
return {
|
||||
points: points,
|
||||
strokeWidth: 3,
|
||||
strokeColor: strokeColor,
|
||||
fillColor: fillColor
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "电子围栏",
|
||||
"navigationBarBackgroundColor": "#3cc51f",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#f5f5f5"
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<!-- 电子围栏页面 -->
|
||||
<view class="fence-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header">
|
||||
<view class="header-left" bindtap="onBack">
|
||||
<text class="back-icon">‹</text>
|
||||
</view>
|
||||
<view class="header-title">电子围栏</view>
|
||||
<view class="header-right">
|
||||
<text class="menu-icon" bindtap="onShowMenu">⋯</text>
|
||||
<text class="minimize-icon" bindtap="onShowMenu">−</text>
|
||||
<text class="target-icon" bindtap="onShowMenu">◎</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 离线模式提示 -->
|
||||
<view wx:if="{{isOfflineMode}}" class="offline-notice">
|
||||
<text class="offline-icon">📡</text>
|
||||
<text class="offline-text">离线模式 - 显示缓存数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 地图锁定提示 -->
|
||||
<view wx:if="{{mapLocked}}" class="map-lock-notice">
|
||||
<text class="lock-icon">🔒</text>
|
||||
<text class="lock-text">地图已锁定 - 防止自动移动</text>
|
||||
</view>
|
||||
|
||||
<!-- 控制面板 -->
|
||||
<view class="control-panel">
|
||||
<!-- 左侧控制区 -->
|
||||
<view class="left-controls">
|
||||
<!-- 设置按钮 -->
|
||||
<view class="settings-btn" bindtap="onShowMenu">
|
||||
<text class="settings-icon">⚙</text>
|
||||
</view>
|
||||
|
||||
<!-- 显示牧场按钮 -->
|
||||
<view class="pasture-btn {{showPasture ? 'active' : ''}}" bindtap="onTogglePasture">
|
||||
<text>显示牧场</text>
|
||||
</view>
|
||||
|
||||
<!-- 设备统计信息 -->
|
||||
<view class="device-stats">
|
||||
<view class="stats-item">
|
||||
<text class="stats-label">智能采集器:</text>
|
||||
<text class="stats-value">{{stats.smartCollector}}</text>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-label">智能设备:</text>
|
||||
<text class="stats-value">{{stats.smartDevice}}</text>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<text class="stats-label">围栏总数:</text>
|
||||
<text class="stats-value">{{fenceList.length}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 牧场名称 -->
|
||||
<view class="pasture-name">各德</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧控制区 -->
|
||||
<view class="right-controls">
|
||||
<view class="switch-map-btn" bindtap="onSwitchMap">
|
||||
<text>切换地图</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 围栏信息面板 -->
|
||||
<view wx:if="{{selectedFence}}" class="fence-info-panel">
|
||||
<view class="panel-header">
|
||||
<view class="fence-title">
|
||||
<text class="fence-icon">{{selectedFence.typeIcon}}</text>
|
||||
<text class="fence-name">{{selectedFence.name}}</text>
|
||||
</view>
|
||||
<view class="close-btn" bindtap="onCloseFenceInfo">
|
||||
<text>✕</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-content">
|
||||
<view class="info-row">
|
||||
<text class="info-label">围栏类型:</text>
|
||||
<text class="info-value" style="color: {{selectedFence.typeColor}}">{{selectedFence.typeName}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">放牧状态:</text>
|
||||
<text class="info-value">{{selectedFence.grazingStatus}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">围栏面积:</text>
|
||||
<text class="info-value">{{selectedFence.area}}平方米</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">坐标点数:</text>
|
||||
<text class="info-value">{{selectedFence.coordinates.length}}个</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">围栏描述:</text>
|
||||
<text class="info-value">{{selectedFence.description || '无描述'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="panel-actions">
|
||||
<view class="action-btn primary" bindtap="onLocateFence">
|
||||
<text>定位围栏</text>
|
||||
</view>
|
||||
<view class="action-btn secondary" bindtap="onViewFenceDetails">
|
||||
<text>查看详情</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地图区域 -->
|
||||
<view class="map-container">
|
||||
<map
|
||||
id="fenceMap"
|
||||
class="fence-map"
|
||||
longitude="{{mapCenter.lng}}"
|
||||
latitude="{{mapCenter.lat}}"
|
||||
scale="{{mapZoom}}"
|
||||
markers="{{fenceMarkers}}"
|
||||
polygons="{{fencePolygons}}"
|
||||
show-location="{{false}}"
|
||||
enable-scroll="{{false}}"
|
||||
enable-zoom="{{false}}"
|
||||
enable-rotate="{{false}}"
|
||||
enable-overlooking="{{false}}"
|
||||
enable-satellite="{{false}}"
|
||||
enable-traffic="{{false}}"
|
||||
enable-3D="{{false}}"
|
||||
enable-compass="{{false}}"
|
||||
enable-scale="{{false}}"
|
||||
enable-poi="{{false}}"
|
||||
enable-building="{{false}}"
|
||||
include-points="{{includePoints}}"
|
||||
bindmarkertap="onMarkerTap"
|
||||
bindregionchange="onRegionChange"
|
||||
bindtap="onMapTap"
|
||||
>
|
||||
<!-- 地图加载中 -->
|
||||
<view wx:if="{{loading}}" class="map-loading">
|
||||
<text>地图加载中...</text>
|
||||
</view>
|
||||
</map>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,358 @@
|
||||
/* 电子围栏页面样式 */
|
||||
|
||||
.fence-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 离线模式提示 */
|
||||
.offline-notice {
|
||||
width: 100%;
|
||||
background: #ff9500;
|
||||
color: #ffffff;
|
||||
padding: 16rpx 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
font-size: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.offline-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.offline-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 地图锁定提示 */
|
||||
.map-lock-notice {
|
||||
width: 100%;
|
||||
background: #007aff;
|
||||
color: #ffffff;
|
||||
padding: 16rpx 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
font-size: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.lock-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.lock-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #3cc51f;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 32rpx;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 48rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 36rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.menu-icon,
|
||||
.minimize-icon,
|
||||
.target-icon {
|
||||
font-size: 32rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 控制面板 */
|
||||
.control-panel {
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
padding: 24rpx 32rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 左侧控制区 */
|
||||
.left-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.pasture-btn {
|
||||
padding: 16rpx 32rpx;
|
||||
background: #3cc51f;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
min-width: 160rpx;
|
||||
}
|
||||
|
||||
.pasture-btn.active {
|
||||
background: #2a9d16;
|
||||
}
|
||||
|
||||
.device-stats {
|
||||
background: #333333;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
min-width: 240rpx;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stats-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pasture-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
/* 右侧控制区 */
|
||||
.right-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.switch-map-btn {
|
||||
padding: 16rpx 32rpx;
|
||||
background: #3cc51f;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
min-width: 160rpx;
|
||||
}
|
||||
|
||||
/* 地图容器 */
|
||||
.map-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.fence-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #ffffff;
|
||||
padding: 24rpx 48rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 围栏信息面板 */
|
||||
.fence-info-panel {
|
||||
position: absolute;
|
||||
top: 200rpx;
|
||||
right: 32rpx;
|
||||
width: 320rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background: linear-gradient(135deg, #3cc51f, #2a9d16);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.fence-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.fence-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.fence-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 12rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
padding: 24rpx;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-btn.secondary {
|
||||
background: #ffffff;
|
||||
color: #3cc51f;
|
||||
border: 2rpx solid #3cc51f;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 750rpx) {
|
||||
.control-panel {
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.right-controls {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.device-stats {
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.fence-info-panel {
|
||||
position: relative;
|
||||
top: auto;
|
||||
right: auto;
|
||||
width: 100%;
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
}
|
||||
477
mini_program/farm-monitor-dashboard/pages/device/host/host.js
Normal file
477
mini_program/farm-monitor-dashboard/pages/device/host/host.js
Normal file
@@ -0,0 +1,477 @@
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
searchValue: '',
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
totalPages: 0,
|
||||
pageNumbers: [],
|
||||
paginationList: [], // 分页页码列表
|
||||
stats: {
|
||||
total: 0,
|
||||
online: 0,
|
||||
offline: 0
|
||||
},
|
||||
loading: false,
|
||||
isSearching: false,
|
||||
searchResult: null
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
console.log('智能主机页面加载')
|
||||
this.checkLoginStatus()
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadData().then(() => {
|
||||
wx.stopPullDownRefresh()
|
||||
})
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
const token = wx.getStorageSync('token')
|
||||
const userInfo = wx.getStorageSync('userInfo')
|
||||
|
||||
if (!token || !userInfo) {
|
||||
console.log('用户未登录,跳转到登录页')
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录后再使用',
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('用户已登录:', userInfo.username)
|
||||
return true
|
||||
},
|
||||
|
||||
// 加载数据
|
||||
loadData() {
|
||||
const { currentPage, pageSize } = this.data
|
||||
const url = `https://ad.ningmuyun.com/api/smart-devices/hosts?page=${currentPage}&limit=${pageSize}&_t=${Date.now()}&refresh=true`
|
||||
|
||||
const token = wx.getStorageSync('token')
|
||||
if (!token) {
|
||||
wx.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ loading: true })
|
||||
wx.request({
|
||||
url,
|
||||
method: 'GET',
|
||||
timeout: 30000, // 设置30秒超时
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('API响应:', res)
|
||||
if (res.statusCode === 200 && res.data) {
|
||||
const response = res.data
|
||||
|
||||
if (response.success && response.data) {
|
||||
const data = response.data
|
||||
const total = response.total || 0
|
||||
const totalPages = Math.ceil(total / this.data.pageSize)
|
||||
const paginationList = this.generatePaginationList(this.data.currentPage, totalPages)
|
||||
|
||||
this.setData({
|
||||
list: Array.isArray(data) ? data.map(item => this.formatItemData(item)) : [],
|
||||
total: total,
|
||||
totalPages: totalPages,
|
||||
paginationList: paginationList,
|
||||
stats: response.stats || { total: 0, online: 0, offline: 0 }
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: response.message || '数据加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
wx.showToast({
|
||||
title: '登录已过期,请重新登录',
|
||||
icon: 'none'
|
||||
})
|
||||
wx.removeStorageSync('token')
|
||||
wx.removeStorageSync('userInfo')
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: res.data?.message || '数据加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('请求失败:', err)
|
||||
|
||||
// 根据错误类型显示不同的提示
|
||||
let errorMessage = '网络请求失败'
|
||||
if (err.errMsg && err.errMsg.includes('timeout')) {
|
||||
errorMessage = '请求超时,请检查网络连接'
|
||||
} else if (err.errMsg && err.errMsg.includes('fail')) {
|
||||
errorMessage = '网络连接失败,请重试'
|
||||
} else if (err.errMsg && err.errMsg.includes('401')) {
|
||||
errorMessage = '请登录后重试'
|
||||
}
|
||||
|
||||
wx.showModal({
|
||||
title: '请求失败',
|
||||
content: errorMessage + ',是否重试?',
|
||||
confirmText: '重试',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 用户选择重试
|
||||
setTimeout(() => {
|
||||
this.loadData()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
complete: () => {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 格式化单个设备数据
|
||||
formatItemData(item) {
|
||||
return {
|
||||
...item,
|
||||
statusText: item.networkStatus || '未知',
|
||||
signalText: item.signalValue || '未知',
|
||||
batteryText: `${item.battery || 0}%`,
|
||||
temperatureText: `${item.temperature || 0}°C`,
|
||||
deviceNumberText: item.deviceNumber || '未知',
|
||||
updateTimeText: item.updateTime || '未知'
|
||||
}
|
||||
},
|
||||
|
||||
// 生成分页页码列表
|
||||
generatePaginationList(currentPage, totalPages) {
|
||||
const paginationList = []
|
||||
const maxVisiblePages = 5 // 最多显示5个页码
|
||||
|
||||
if (totalPages <= maxVisiblePages) {
|
||||
// 总页数少于等于5页,显示所有页码
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
paginationList.push({
|
||||
page: i,
|
||||
active: i === currentPage,
|
||||
text: i.toString()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 总页数大于5页,显示省略号
|
||||
if (currentPage <= 3) {
|
||||
// 当前页在前3页
|
||||
for (let i = 1; i <= 4; i++) {
|
||||
paginationList.push({
|
||||
page: i,
|
||||
active: i === currentPage,
|
||||
text: i.toString()
|
||||
})
|
||||
}
|
||||
paginationList.push({
|
||||
page: -1,
|
||||
active: false,
|
||||
text: '...'
|
||||
})
|
||||
paginationList.push({
|
||||
page: totalPages,
|
||||
active: false,
|
||||
text: totalPages.toString()
|
||||
})
|
||||
} else if (currentPage >= totalPages - 2) {
|
||||
// 当前页在后3页
|
||||
paginationList.push({
|
||||
page: 1,
|
||||
active: false,
|
||||
text: '1'
|
||||
})
|
||||
paginationList.push({
|
||||
page: -1,
|
||||
active: false,
|
||||
text: '...'
|
||||
})
|
||||
for (let i = totalPages - 3; i <= totalPages; i++) {
|
||||
paginationList.push({
|
||||
page: i,
|
||||
active: i === currentPage,
|
||||
text: i.toString()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 当前页在中间
|
||||
paginationList.push({
|
||||
page: 1,
|
||||
active: false,
|
||||
text: '1'
|
||||
})
|
||||
paginationList.push({
|
||||
page: -1,
|
||||
active: false,
|
||||
text: '...'
|
||||
})
|
||||
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
|
||||
paginationList.push({
|
||||
page: i,
|
||||
active: i === currentPage,
|
||||
text: i.toString()
|
||||
})
|
||||
}
|
||||
paginationList.push({
|
||||
page: -1,
|
||||
active: false,
|
||||
text: '...'
|
||||
})
|
||||
paginationList.push({
|
||||
page: totalPages,
|
||||
active: false,
|
||||
text: totalPages.toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return paginationList
|
||||
},
|
||||
|
||||
// 搜索输入
|
||||
onSearchInput(e) {
|
||||
this.setData({ searchValue: e.detail.value.trim() })
|
||||
},
|
||||
|
||||
// 执行搜索
|
||||
onSearch() {
|
||||
const searchValue = this.data.searchValue.trim()
|
||||
if (!searchValue) {
|
||||
wx.showToast({
|
||||
title: '请输入主机编号',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证主机编号格式(数字和字母)
|
||||
if (!/^[A-Za-z0-9]+$/.test(searchValue)) {
|
||||
wx.showToast({
|
||||
title: '主机编号只能包含数字和字母',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置搜索状态
|
||||
this.setData({
|
||||
isSearching: true,
|
||||
searchResult: null,
|
||||
currentPage: 1
|
||||
})
|
||||
|
||||
// 执行精确搜索
|
||||
this.performExactSearch(searchValue)
|
||||
},
|
||||
|
||||
// 执行精确搜索
|
||||
performExactSearch(searchValue) {
|
||||
const url = `https://ad.ningmuyun.com/api/smart-devices/hosts?page=1&limit=1&deviceId=${searchValue}&_t=${Date.now()}`
|
||||
|
||||
const token = wx.getStorageSync('token')
|
||||
if (!token) {
|
||||
wx.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
wx.showLoading({ title: '搜索中...' })
|
||||
wx.request({
|
||||
url,
|
||||
method: 'GET',
|
||||
timeout: 30000, // 设置30秒超时
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('搜索API响应:', res)
|
||||
if (res.statusCode === 200 && res.data) {
|
||||
const response = res.data
|
||||
|
||||
if (response.success && response.data) {
|
||||
const data = response.data
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// 找到匹配的设备
|
||||
const device = this.formatItemData(data[0])
|
||||
this.setData({
|
||||
searchResult: device,
|
||||
list: [], // 清空列表显示
|
||||
total: 1,
|
||||
totalPages: 1,
|
||||
paginationList: [{ page: 1, active: true, text: '1' }]
|
||||
})
|
||||
wx.showToast({
|
||||
title: '搜索成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
// 没有找到匹配的设备
|
||||
this.setData({
|
||||
searchResult: null,
|
||||
list: [],
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
paginationList: []
|
||||
})
|
||||
wx.showToast({
|
||||
title: '未找到该设备',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '搜索失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
wx.showToast({
|
||||
title: '登录已过期,请重新登录',
|
||||
icon: 'none'
|
||||
})
|
||||
wx.removeStorageSync('token')
|
||||
wx.removeStorageSync('userInfo')
|
||||
setTimeout(() => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '搜索失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('搜索请求失败:', err)
|
||||
|
||||
// 根据错误类型显示不同的提示
|
||||
let errorMessage = '网络请求失败'
|
||||
if (err.errMsg && err.errMsg.includes('timeout')) {
|
||||
errorMessage = '搜索超时,请检查网络连接'
|
||||
} else if (err.errMsg && err.errMsg.includes('fail')) {
|
||||
errorMessage = '网络连接失败,请重试'
|
||||
}
|
||||
|
||||
wx.showModal({
|
||||
title: '搜索失败',
|
||||
content: errorMessage + ',是否重试?',
|
||||
confirmText: '重试',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 用户选择重试
|
||||
setTimeout(() => {
|
||||
this.performExactSearch(this.data.searchValue)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
complete: () => {
|
||||
wx.hideLoading()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 清除搜索
|
||||
clearSearch() {
|
||||
this.setData({
|
||||
searchValue: '',
|
||||
isSearching: false,
|
||||
searchResult: null,
|
||||
currentPage: 1
|
||||
})
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 上一页
|
||||
onPrevPage() {
|
||||
if (this.data.currentPage > 1) {
|
||||
this.setData({
|
||||
currentPage: this.data.currentPage - 1
|
||||
}, () => {
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 下一页
|
||||
onNextPage() {
|
||||
if (this.data.currentPage < this.data.totalPages) {
|
||||
this.setData({
|
||||
currentPage: this.data.currentPage + 1
|
||||
}, () => {
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 分页切换
|
||||
onPageChange(e) {
|
||||
const page = parseInt(e.currentTarget.dataset.page)
|
||||
if (page > 0 && page !== this.data.currentPage) {
|
||||
this.setData({
|
||||
currentPage: page
|
||||
}, () => {
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 查看主机详情
|
||||
viewHostDetail(e) {
|
||||
const hostId = e.currentTarget.dataset.id
|
||||
wx.navigateTo({
|
||||
url: `/pages/device/host-detail/host-detail?id=${hostId}`
|
||||
})
|
||||
},
|
||||
|
||||
// 主机定位总览
|
||||
onLocationOverview() {
|
||||
wx.navigateTo({
|
||||
url: '/pages/device/host-location/host-location'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"navigationBarTitleText": "智能主机",
|
||||
"enablePullDownRefresh": true,
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
177
mini_program/farm-monitor-dashboard/pages/device/host/host.wxml
Normal file
177
mini_program/farm-monitor-dashboard/pages/device/host/host.wxml
Normal file
@@ -0,0 +1,177 @@
|
||||
<view class="container">
|
||||
<!-- 搜索区域 -->
|
||||
<view class="search-section">
|
||||
<view class="search-box">
|
||||
<view class="search-icon">🔍</view>
|
||||
<input
|
||||
placeholder="搜索"
|
||||
bindinput="onSearchInput"
|
||||
value="{{searchValue}}"
|
||||
class="search-input"
|
||||
/>
|
||||
<button bindtap="onSearch" class="search-btn">查询</button>
|
||||
<button wx:if="{{isSearching}}" bindtap="clearSearch" class="clear-btn">清除</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索状态提示 -->
|
||||
<view wx:if="{{isSearching}}" class="search-status">
|
||||
<text>搜索主机编号: {{searchValue}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 统计卡片区域 -->
|
||||
<view class="stats-section">
|
||||
<view class="stats-card">
|
||||
<text class="stats-label">主机总数</text>
|
||||
<text class="stats-value">{{stats.total}}</text>
|
||||
</view>
|
||||
<view class="stats-card">
|
||||
<text class="stats-label">联网数量</text>
|
||||
<text class="stats-value">{{stats.online}}</text>
|
||||
</view>
|
||||
<view class="stats-card">
|
||||
<text class="stats-label">断网数量</text>
|
||||
<text class="stats-value">{{stats.offline}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<view wx:if="{{isSearching && searchResult}}" class="search-result">
|
||||
<view class="result-header">
|
||||
<text class="result-title">搜索结果</text>
|
||||
<text class="result-subtitle">找到匹配的设备</text>
|
||||
</view>
|
||||
<view class="host-item search-item" bindtap="viewHostDetail" data-id="{{searchResult.id}}">
|
||||
<!-- 主机编号和状态 -->
|
||||
<view class="host-header">
|
||||
<text class="host-number">主机编号: {{searchResult.deviceNumberText}}</text>
|
||||
<view class="status-btn {{searchResult.networkStatus === '已联网' ? 'online' : 'offline'}}">
|
||||
{{searchResult.statusText}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备详细信息 -->
|
||||
<view class="host-details">
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备电量:</text>
|
||||
<text class="detail-value">{{searchResult.batteryText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备信号值:</text>
|
||||
<text class="detail-value">{{searchResult.signalText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备温度:</text>
|
||||
<text class="detail-value">{{searchResult.temperatureText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">绑带状态:</text>
|
||||
<text class="detail-value">{{searchResult.statusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">数据更新时间:</text>
|
||||
<text class="detail-value">{{searchResult.updateTimeText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无搜索结果 -->
|
||||
<view wx:if="{{isSearching && !searchResult}}" class="no-result">
|
||||
<view class="no-result-icon">🔍</view>
|
||||
<text class="no-result-text">未找到主机编号为 "{{searchValue}}" 的设备</text>
|
||||
<button class="retry-btn" bindtap="clearSearch">重新搜索</button>
|
||||
</view>
|
||||
|
||||
<!-- 主机设备列表 -->
|
||||
<view wx:if="{{!isSearching}}" class="host-list">
|
||||
<block wx:for="{{list}}" wx:key="id">
|
||||
<view class="host-item" bindtap="viewHostDetail" data-id="{{item.id}}">
|
||||
<!-- 主机编号和状态 -->
|
||||
<view class="host-header">
|
||||
<text class="host-number">主机编号: {{item.deviceNumberText}}</text>
|
||||
<view class="status-btn {{item.networkStatus === '已联网' ? 'online' : 'offline'}}">
|
||||
{{item.statusText}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备详细信息 -->
|
||||
<view class="host-details">
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备电量:</text>
|
||||
<text class="detail-value">{{item.batteryText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备信号值:</text>
|
||||
<text class="detail-value">{{item.signalText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">设备温度:</text>
|
||||
<text class="detail-value">{{item.temperatureText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">绑带状态:</text>
|
||||
<text class="detail-value">{{item.statusText}}</text>
|
||||
</view>
|
||||
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">数据更新时间:</text>
|
||||
<text class="detail-value">{{item.updateTimeText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<view wx:if="{{!isSearching && totalPages > 1}}" class="pagination-container">
|
||||
<view class="pagination-info">
|
||||
<text>共 {{total}} 条记录,第 {{currentPage}}/{{totalPages}} 页</text>
|
||||
</view>
|
||||
|
||||
<view class="pagination-controls">
|
||||
<!-- 上一页按钮 -->
|
||||
<view
|
||||
class="pagination-btn {{currentPage === 1 ? 'disabled' : ''}}"
|
||||
bindtap="onPrevPage"
|
||||
>
|
||||
上一页
|
||||
</view>
|
||||
|
||||
<!-- 页码列表 -->
|
||||
<view class="pagination-pages">
|
||||
<view
|
||||
wx:for="{{paginationList}}"
|
||||
wx:key="index"
|
||||
class="pagination-page {{item.active ? 'active' : ''}} {{item.page === -1 ? 'ellipsis' : ''}}"
|
||||
bindtap="onPageChange"
|
||||
data-page="{{item.page}}"
|
||||
>
|
||||
{{item.text}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 下一页按钮 -->
|
||||
<view
|
||||
class="pagination-btn {{currentPage === totalPages ? 'disabled' : ''}}"
|
||||
bindtap="onNextPage"
|
||||
>
|
||||
下一页
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="footer-action">
|
||||
<button class="location-btn" bindtap="onLocationOverview">
|
||||
主机定位总览
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
370
mini_program/farm-monitor-dashboard/pages/device/host/host.wxss
Normal file
370
mini_program/farm-monitor-dashboard/pages/device/host/host.wxss
Normal file
@@ -0,0 +1,370 @@
|
||||
/* 智能主机页面样式 */
|
||||
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding-bottom: 120rpx; /* 为底部按钮留出空间 */
|
||||
}
|
||||
|
||||
/* 搜索区域 */
|
||||
.search-section {
|
||||
background: #52c41a;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: white;
|
||||
border-radius: 25rpx;
|
||||
padding: 15rpx 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
background: linear-gradient(135deg, #52c41a, #73d13d);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
background: linear-gradient(135deg, #ff4d4f, #ff7875);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.search-status {
|
||||
background: linear-gradient(135deg, #e6f7ff, #bae7ff);
|
||||
border: 2rpx solid #91d5ff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin: 0 20rpx 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search-status text {
|
||||
color: #1890ff;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-result {
|
||||
margin: 0 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
background: linear-gradient(135deg, #f6ffed, #d9f7be);
|
||||
border: 2rpx solid #b7eb8f;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #52c41a;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.result-subtitle {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #73d13d;
|
||||
}
|
||||
|
||||
.search-item {
|
||||
border: 2rpx solid #52c41a;
|
||||
box-shadow: 0 4rpx 16rpx rgba(82, 196, 26, 0.2);
|
||||
}
|
||||
|
||||
.no-result {
|
||||
text-align: center;
|
||||
padding: 80rpx 40rpx;
|
||||
background: #fafafa;
|
||||
border-radius: 12rpx;
|
||||
margin: 0 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.no-result-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.no-result-text {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 40rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: linear-gradient(135deg, #52c41a, #73d13d);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 25rpx;
|
||||
padding: 20rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
/* 统计卡片区域 */
|
||||
.stats-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
flex: 1;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
text-align: center;
|
||||
border: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 主机设备列表 */
|
||||
.host-list {
|
||||
padding: 0 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.host-item {
|
||||
background: white;
|
||||
border-radius: 12rpx;
|
||||
padding: 25rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
border: 1rpx solid #e8e8e8;
|
||||
}
|
||||
|
||||
.host-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 15rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.host-number {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
border: 2rpx solid;
|
||||
}
|
||||
|
||||
.status-btn.online {
|
||||
color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
background: rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
|
||||
.status-btn.offline {
|
||||
color: #ff4d4f;
|
||||
border-color: #ff4d4f;
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
}
|
||||
|
||||
/* 设备详细信息 */
|
||||
.host-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
min-width: 140rpx;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 分页组件样式 */
|
||||
.pagination-container {
|
||||
padding: 32rpx;
|
||||
background: #ffffff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
text-align: center;
|
||||
margin-bottom: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
padding: 16rpx 24rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.pagination-btn.disabled {
|
||||
background: #f0f0f0;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.pagination-pages {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.pagination-page {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pagination-page.active {
|
||||
background: #3cc51f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pagination-page.ellipsis {
|
||||
background: transparent;
|
||||
color: #999;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 底部操作按钮 */
|
||||
.footer-action {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx;
|
||||
background: white;
|
||||
border-top: 1rpx solid #e8e8e8;
|
||||
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.location-btn {
|
||||
width: 100%;
|
||||
background: #52c41a;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
padding: 25rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(82, 196, 26, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.location-btn:active {
|
||||
background: #389e0d;
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 80rpx 40rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state .empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.empty-state .empty-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ Page({
|
||||
currentAlertData: [
|
||||
{ title: '今日未被采集', value: '6', isAlert: false, bgIcon: '📄', url: '/pages/alert/collar' },
|
||||
{ title: '项圈绑带剪断', value: '0', isAlert: false, bgIcon: '🏢', url: '/pages/alert/collar' },
|
||||
{ title: '电子围栏', value: '3', isAlert: false, bgIcon: '🚧', url: '/pages/fence' },
|
||||
{ title: '电子围栏', value: '3', isAlert: false, bgIcon: '🚧', url: '/pages/device/fence/fence' },
|
||||
{ title: '今日运动量偏高', value: '0', isAlert: false, bgIcon: '📈', url: '/pages/alert/collar' },
|
||||
{ title: '今日运动量偏低', value: '3', isAlert: true, bgIcon: '📉', url: '/pages/alert/collar' },
|
||||
{ title: '传输频次过快', value: '0', isAlert: false, bgIcon: '⚡', url: '/pages/alert/collar' },
|
||||
@@ -33,7 +33,7 @@ Page({
|
||||
],
|
||||
// 智能工具
|
||||
smartTools: [
|
||||
{ name: '电子围栏', icon: '🎯', color: 'orange', url: '/pages/fence' },
|
||||
{ name: '电子围栏', icon: '🎯', color: 'orange', url: '/pages/device/fence/fence' },
|
||||
{ name: '扫码溯源', icon: '🛡️', color: 'blue', url: '/pages/trace' },
|
||||
{ name: '档案拍照', icon: '📷', color: 'red', url: '/pages/photo' },
|
||||
{ name: '检测工具', icon: '📊', color: 'purple', url: '/pages/detection' }
|
||||
@@ -47,6 +47,8 @@ Page({
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 检查登录状态
|
||||
this.checkLoginStatus()
|
||||
this.fetchHomeData()
|
||||
},
|
||||
|
||||
@@ -60,6 +62,30 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
checkLoginStatus() {
|
||||
const token = wx.getStorageSync('token')
|
||||
const userInfo = wx.getStorageSync('userInfo')
|
||||
|
||||
if (!token || !userInfo) {
|
||||
console.log('用户未登录,跳转到登录页')
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '请先登录后再使用',
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
wx.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('用户已登录:', userInfo.username)
|
||||
return true
|
||||
},
|
||||
|
||||
// 获取首页数据
|
||||
async fetchHomeData() {
|
||||
this.setData({ loading: true })
|
||||
@@ -105,7 +131,7 @@ Page({
|
||||
alertData = [
|
||||
{ title: '今日未被采集', value: '6', isAlert: false, bgIcon: '📄', url: '/pages/alert/collar' },
|
||||
{ title: '项圈绑带剪断', value: '0', isAlert: false, bgIcon: '🏢', url: '/pages/alert/collar' },
|
||||
{ title: '电子围栏', value: '3', isAlert: false, bgIcon: '🚧', url: '/pages/fence' },
|
||||
{ title: '电子围栏', value: '3', isAlert: false, bgIcon: '🚧', url: '/pages/device/fence/fence' },
|
||||
{ title: '今日运动量偏高', value: '0', isAlert: false, bgIcon: '📈', url: '/pages/alert/collar' },
|
||||
{ title: '今日运动量偏低', value: '3', isAlert: true, bgIcon: '📉', url: '/pages/alert/collar' },
|
||||
{ title: '传输频次过快', value: '0', isAlert: false, bgIcon: '⚡', url: '/pages/alert/collar' },
|
||||
|
||||
Reference in New Issue
Block a user