首页接入真实数据
parent
4457402aa4
commit
a8d60b5829
1
App.vue
1
App.vue
|
|
@ -60,7 +60,6 @@ export default {
|
||||||
console.log('小程序切前台/首次显示')
|
console.log('小程序切前台/首次显示')
|
||||||
const token = getToken() || this.globalData.mqtt.token
|
const token = getToken() || this.globalData.mqtt.token
|
||||||
if (token) {
|
if (token) {
|
||||||
this.$tab.reLaunch('/pages/index')
|
|
||||||
// 兜底检查:如果globalData里没有列表,但缓存里有,补充恢复
|
// 兜底检查:如果globalData里没有列表,但缓存里有,补充恢复
|
||||||
if (this.globalData.mqtt.subscribeList.length === 0) {
|
if (this.globalData.mqtt.subscribeList.length === 0) {
|
||||||
const savedSubscribeList = uni.getStorageSync('mqtt_subscribe_list')
|
const savedSubscribeList = uni.getStorageSync('mqtt_subscribe_list')
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,13 @@ export function getAgri(id) {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
export function getAgriInfo(query) {
|
||||||
|
return request({
|
||||||
|
url: '/assets/agri/findAgriInfoByUser',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 新增大棚管理
|
// 新增大棚管理
|
||||||
export function addAgri(data) {
|
export function addAgri(data) {
|
||||||
|
|
|
||||||
182
pages/index.vue
182
pages/index.vue
|
|
@ -1,23 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<view class="page-container">
|
<view class="page-container">
|
||||||
<z-paging ref="paging" refresher-only bg-color="radial-gradient(circle at top left, #E8F4F8 40%, #F8FCFE 100%)"
|
<z-paging ref="index" refresher-only bg-color="radial-gradient(circle at top left, #E8F4F8 40%, #F8FCFE 100%)"
|
||||||
:show-empty="false" :show-footer="false" @onRefresh="refresh">
|
:show-empty="false" :show-footer="false" @onRefresh="refresh">
|
||||||
<template #refresher="{refresherStatus}">
|
<template #refresher="{refresherStatus}">
|
||||||
<!-- 此处的custom-refresh为demo中自定义的组件,非z-paging的内置组件,请在实际项目中自行创建。这里插入什么view,下拉刷新就显示什么view -->
|
<!-- 此处的custom-refresh为demo中自定义的组件,非z-paging的内置组件,请在实际项目中自行创建。这里插入什么view,下拉刷新就显示什么view -->
|
||||||
<custom-refresher :status="refresherStatus" />
|
<custom-refresher :status="refresherStatus" />
|
||||||
</template>
|
</template>
|
||||||
<!-- confirm:confirm; input:change; cancel:点击取消;
|
|
||||||
clear:点击清除;focus:获取焦点;blur:失去焦点 -->
|
|
||||||
<uni-row class="demo-uni-row" >
|
<uni-row class="demo-uni-row" >
|
||||||
<uni-col :span="7">
|
<uni-col :span="7">
|
||||||
<uni-data-select align="left" :clear="false" v-model="value" :localdata="range" ></uni-data-select>
|
<uni-data-select align="left" :clear="false" v-model="value" @change="change" :localdata="range" ></uni-data-select>
|
||||||
</uni-col>
|
</uni-col>
|
||||||
<uni-col :span="17">
|
<uni-col :span="17">
|
||||||
<uni-search-bar @confirm="search" :focus="true" bgColor="#fbfdfe"
|
<!-- confirm:confirm; input:change; cancel:点击取消;
|
||||||
|
clear:点击清除;focus:获取焦点;blur:失去焦点 -->
|
||||||
|
<uni-search-bar :focus="true" bgColor="#fbfdfe"
|
||||||
v-model="searchValue"
|
v-model="searchValue"
|
||||||
cancelButton="always"
|
cancelButton="always"
|
||||||
placeholder="请输入搜索内容"
|
@input="input"
|
||||||
@cancel="cancel" @clear="cancel">
|
@cancel="cancel"
|
||||||
|
@clear="cancel"
|
||||||
|
placeholder="请输入搜索内容">
|
||||||
</uni-search-bar>
|
</uni-search-bar>
|
||||||
</uni-col>
|
</uni-col>
|
||||||
</uni-row>
|
</uni-row>
|
||||||
|
|
@ -36,13 +38,13 @@
|
||||||
@tap="item.online !== '离线' && onItemTap(item)"
|
@tap="item.online !== '离线' && onItemTap(item)"
|
||||||
>
|
>
|
||||||
<view class="item-title">
|
<view class="item-title">
|
||||||
<view class="title-text">{{ item.title }}</view>
|
<view class="title-text">{{ item.agriName }}</view>
|
||||||
<text :class="['tag','tag-status',{'tag-manual':item.status==='手动模式'}]" v-if="item.status">{{ item.status }}</text>
|
<text :class="['tag','tag-status',{'tag-manual':item.workMode==='手动模式'}]" v-if="item.workMode">{{ item.workMode }}</text>
|
||||||
<text :class="['tag','tag-online',{'tag-offline':item.online==='离线'}]" v-if="item.online">{{ item.online }}</text>
|
<text :class="['tag','tag-online',{'tag-offline':item.online==='离线'}]" v-if="item.online">{{ item.online }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="item-subtitle">
|
<view class="item-subtitle">
|
||||||
{{ item.subtitle }}
|
{{ item.imei }}
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="item-tags">
|
<view class="item-tags">
|
||||||
|
|
@ -73,13 +75,17 @@
|
||||||
<script>
|
<script>
|
||||||
import ZPaging from "../uni_modules/z-paging/components/z-paging/z-paging.vue";
|
import ZPaging from "../uni_modules/z-paging/components/z-paging/z-paging.vue";
|
||||||
import CustomRefresher from "../components/custom-refresher/custom-refresher.vue";
|
import CustomRefresher from "../components/custom-refresher/custom-refresher.vue";
|
||||||
|
import {getAgriInfo} from "../api/system/assets/agri";
|
||||||
|
import store from "../store";
|
||||||
|
import TimeUtil from "../utils/TimeUtil";
|
||||||
|
import {getNewSpecialData} from "../api/data/specialData";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {CustomRefresher, ZPaging},
|
components: {CustomRefresher, ZPaging},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
listData: [],
|
listData: [],
|
||||||
page: 1,
|
datas: [],
|
||||||
pageSize: 6,
|
pageSize: 6,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
noMore: false,
|
noMore: false,
|
||||||
|
|
@ -134,8 +140,8 @@ export default {
|
||||||
}
|
}
|
||||||
return false*/
|
return false*/
|
||||||
},
|
},
|
||||||
onLoad() {
|
onShow() {
|
||||||
this.getListData()
|
this.refresh();
|
||||||
},
|
},
|
||||||
onReady() {
|
onReady() {
|
||||||
|
|
||||||
|
|
@ -149,21 +155,15 @@ export default {
|
||||||
// 这里可以添加跳转到添加大棚页面的逻辑
|
// 这里可以添加跳转到添加大棚页面的逻辑
|
||||||
// uni.navigateTo({ url: '/pages/add-agri/add-agri' })
|
// uni.navigateTo({ url: '/pages/add-agri/add-agri' })
|
||||||
},
|
},
|
||||||
search(res) {
|
change(e) {
|
||||||
uni.showToast({
|
this.input(this.searchValue)
|
||||||
title: '搜索:' + res.value,
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
input(res) {
|
input(res) {
|
||||||
|
this.listData = this.datas.filter(item =>
|
||||||
|
item[this.value === 0 ? 'agriName' : 'imei'].includes(res));
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel(res) {
|
cancel(res) {
|
||||||
uni.showToast({
|
this.listData = [...this.datas]
|
||||||
title: '点击取消,输入值为:' + res.value,
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
/*trigger(e) {
|
/*trigger(e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
|
@ -186,68 +186,108 @@ export default {
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
},*/
|
},*/
|
||||||
|
|
||||||
// 模拟获取列表数据
|
// 模拟获取列表数据
|
||||||
getListData(isRefresh = false) {
|
getListData() {
|
||||||
if (isRefresh) {
|
this.noMore = false;
|
||||||
this.page = 1
|
this.listData = [];
|
||||||
this.noMore = false
|
if (this.isLoading || this.noMore) return;
|
||||||
this.listData = []
|
this.isLoading = true;
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isLoading || this.noMore) return
|
getAgriInfo().then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
this.isLoading = true
|
// 1. 用 map 处理数据,返回新数组,避免 forEach 返回 undefined
|
||||||
|
// 2. 区分刷新/加载更多,更新 listData
|
||||||
// 模拟接口请求
|
this.listData = response.data.map(item => {
|
||||||
setTimeout(() => {
|
// 返回完整的列表项结构(和模板中需要的字段对应)
|
||||||
const start = (this.page - 1) * this.pageSize
|
return {
|
||||||
const end = this.page * this.pageSize
|
...item,
|
||||||
const newData = []
|
deviceStatus: TimeUtil.isLessThanSpecifiedSeconds(item.time, 90) ? '在线' : '离线',
|
||||||
|
online: TimeUtil.isLessThanSpecifiedSeconds(item.time, 90) ? '在线' : '离线', // 兼容模板中的 online 字段
|
||||||
for (let i = start; i < end && i < this.total; i++) {
|
agriName: item.agriName || '未知大棚',
|
||||||
// 新增:给每条数据加唯一id,优化v-for的key
|
imei: `设备编号:${item.imei || '未知'}`,
|
||||||
newData.push({
|
workMode: item.workMode === 0 ? '手动模式' : '自动模式',
|
||||||
id: `item-${i}`,
|
temp1: item.temp1,
|
||||||
title: `设备名称${i + 1}`,
|
temp2: item.temp2,
|
||||||
subtitle: `设备编号:SN-${10000 + i}`,
|
temp3: item.temp3,
|
||||||
status: i % 2 === 0 ? "自动模式" : "手动模式",
|
temp4: item.temp4
|
||||||
online: i % 3 === 0 ? "在线" : "离线",
|
};
|
||||||
temp1: Math.floor(Math.random() * 51), // 0-50随机温度
|
});
|
||||||
temp2: Math.floor(Math.random() * 51),
|
|
||||||
temp3: Math.floor(Math.random() * 51),
|
|
||||||
temp4: Math.floor(Math.random() * 51)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
}).catch(err => {
|
||||||
this.listData = isRefresh ? newData : [...this.listData, ...newData]
|
console.error("获取大棚信息失败:", err);
|
||||||
this.page++
|
uni.showToast({ title: "数据加载失败", icon: "none" });
|
||||||
this.isLoading = false
|
}).finally(() => {
|
||||||
|
// 4. 无论成功失败,都结束加载状态
|
||||||
if (this.listData.length >= this.total) {
|
this.isLoading = false;
|
||||||
this.noMore = true
|
// 5. 判断是否还有更多数据
|
||||||
|
this.noMore = this.listData.length >= this.total;
|
||||||
|
if (store.getters && store.getters.name === 'admin') {
|
||||||
|
this.getNewSpecialData();
|
||||||
}
|
}
|
||||||
}, 0)
|
this.datas = [...this.listData]
|
||||||
|
this.$refs.index.complete();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
// 加载更多(防抖:避免快速上拉重复触发)
|
// 加载更多(防抖:避免快速上拉重复触发)
|
||||||
loadMore() {
|
loadMore() {
|
||||||
if (!this.isLoading && !this.noMore) {
|
if (!this.isLoading && !this.noMore) {
|
||||||
// 延迟触发,避免滚动到底部瞬间多次调用
|
|
||||||
setTimeout(() => {
|
|
||||||
this.getListData()
|
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
|
this.getListData();
|
||||||
this.$refs.paging.complete();
|
|
||||||
},
|
},
|
||||||
// 点击列表项
|
// 点击列表项
|
||||||
onItemTap(item) {
|
onItemTap(item) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: `点击了:${item.title}`,
|
title: `点击了:${item.agriName}`,
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
getNewSpecialData() {
|
||||||
|
getNewSpecialData().then(response => {
|
||||||
|
if (response.code === 200 && response.data) {
|
||||||
|
this.makeSpecialData(response.data);
|
||||||
|
}
|
||||||
|
this.datas = [...this.listData]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
makeSpecialData: function (msgData){
|
||||||
|
const online = TimeUtil.isLessThanSpecifiedSeconds(msgData.ts,90)?'在线':'离线';
|
||||||
|
this.listData.push(
|
||||||
|
{
|
||||||
|
agriName: "八方南棚",
|
||||||
|
imei: "A",
|
||||||
|
temp1: msgData.temp1,
|
||||||
|
temp2: msgData.temp2,
|
||||||
|
temp3: msgData.temp3,
|
||||||
|
temp4: msgData.temp4,
|
||||||
|
online:online
|
||||||
|
},
|
||||||
|
{
|
||||||
|
agriName: "九方春棚",
|
||||||
|
imei: "B",
|
||||||
|
temp1: msgData.temp5,
|
||||||
|
temp2: msgData.temp6,
|
||||||
|
temp3: msgData.temp7,
|
||||||
|
temp4: msgData.temp8,
|
||||||
|
online:online
|
||||||
|
},
|
||||||
|
{
|
||||||
|
agriName: "十二方棚",
|
||||||
|
imei: "C",
|
||||||
|
temp1: msgData.temp9,
|
||||||
|
temp2: msgData.temp10,
|
||||||
|
temp3: msgData.temp11,
|
||||||
|
temp4: msgData.temp12,
|
||||||
|
online:online
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -289,6 +329,7 @@ page {
|
||||||
/* 新增:点击态样式,双端统一 */
|
/* 新增:点击态样式,双端统一 */
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
box-shadow: 8rpx 5rpx 10rpx #999;
|
box-shadow: 8rpx 5rpx 10rpx #999;
|
||||||
|
pointer-events: auto !important;
|
||||||
}
|
}
|
||||||
.list-item:active {
|
.list-item:active {
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
|
|
@ -341,13 +382,13 @@ page {
|
||||||
|
|
||||||
.item-tags {
|
.item-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16rpx;
|
gap: 12rpx;
|
||||||
flex-wrap: wrap; /* 新增:标签过多时换行,避免溢出 */
|
flex-wrap: wrap; /* 新增:标签过多时换行,避免溢出 */
|
||||||
margin: 14rpx 0;
|
margin: 14rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
font-size: 22rpx;
|
font-size: 23rpx;
|
||||||
padding: 4rpx 12rpx;
|
padding: 4rpx 12rpx;
|
||||||
border-radius: 4rpx;
|
border-radius: 4rpx;
|
||||||
display: inline-block; /* 修复H5端padding不生效问题 */
|
display: inline-block; /* 修复H5端padding不生效问题 */
|
||||||
|
|
@ -422,6 +463,7 @@ page {
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
/* 可选:添加透明度 */
|
/* 可选:添加透明度 */
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
pointer-events: none !important; /* 禁止所有交互 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 禁用状态下的子元素也置灰 */
|
/* 禁用状态下的子元素也置灰 */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
* 时间差判断工具类
|
||||||
|
* 核心功能:判断目标时间与当前时间的差值是否小于指定秒数
|
||||||
|
* 支持的目标时间格式:毫秒级时间戳、秒级时间戳、Date对象、合法时间字符串(如 "2026-02-12 10:00:00")
|
||||||
|
*/
|
||||||
|
class TimeUtil {
|
||||||
|
/**
|
||||||
|
* 判断目标时间距离当前时间是否小于指定秒数
|
||||||
|
* @param {String|Number|Date} targetTime 目标时间(支持多格式)
|
||||||
|
* @param {Number} seconds 指定秒数(需为非负数字)
|
||||||
|
* @returns {Boolean} true=时间差小于指定秒数;false=时间差大于/等于指定秒数或参数异常
|
||||||
|
* @throws {Error} 当秒数参数不合法时抛出明确异常
|
||||||
|
*/
|
||||||
|
static isLessThanSpecifiedSeconds(targetTime, seconds) {
|
||||||
|
// 1. 严格校验秒数参数
|
||||||
|
if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) {
|
||||||
|
throw new Error(`指定秒数不合法:${seconds},请传入非负数字(如 90、60)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 转换目标时间为毫秒级时间戳(内部私有方法处理多格式兼容)
|
||||||
|
const targetTimestamp = this._parseToMillisecondTimestamp(targetTime);
|
||||||
|
if (targetTimestamp === null) return false; // 时间格式不合法时返回false
|
||||||
|
|
||||||
|
// 3. 计算时间差(绝对值:兼容目标时间在当前时间之前/之后的场景)
|
||||||
|
const nowTimestamp = Date.now();
|
||||||
|
const timeDiffMs = Math.abs(nowTimestamp - targetTimestamp);
|
||||||
|
|
||||||
|
// 4. 转换指定秒数为毫秒,判断是否小于(核心逻辑)
|
||||||
|
const specifiedMs = seconds * 1000;
|
||||||
|
return timeDiffMs < specifiedMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私有方法:统一转换任意时间格式为毫秒级时间戳
|
||||||
|
* @param {String|Number|Date} time 待转换的时间
|
||||||
|
* @returns {Number|null} 合法返回毫秒级时间戳,不合法返回null
|
||||||
|
*/
|
||||||
|
static _parseToMillisecondTimestamp(time) {
|
||||||
|
let timestamp;
|
||||||
|
|
||||||
|
// 情况1:传入的是数字(秒级/毫秒级时间戳)
|
||||||
|
if (typeof time === 'number') {
|
||||||
|
// 区分秒级戳(10位)和毫秒级戳(13位)
|
||||||
|
timestamp = time.toString().length <= 10 ? time * 1000 : time;
|
||||||
|
// 校验时间戳有效性
|
||||||
|
if (isNaN(timestamp) || timestamp < 0) {
|
||||||
|
console.error(`时间戳不合法:${time},需为非负数字`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况2:传入的是Date对象
|
||||||
|
else if (time instanceof Date) {
|
||||||
|
timestamp = time.getTime();
|
||||||
|
if (isNaN(timestamp)) {
|
||||||
|
console.error(`Date对象不合法:${time}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况3:传入的是时间字符串(如 "2026-02-12 10:00:00"、"2026/02/12")
|
||||||
|
else if (typeof time === 'string') {
|
||||||
|
const date = new Date(time);
|
||||||
|
timestamp = date.getTime();
|
||||||
|
if (isNaN(timestamp)) {
|
||||||
|
console.error(`时间字符串格式不合法:${time},请使用如 "2026-02-12 10:00:00" 的格式`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 情况4:不支持的类型
|
||||||
|
else {
|
||||||
|
console.error(`不支持的时间类型:${typeof time},仅支持数字、Date对象、字符串`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default TimeUtil;
|
||||||
|
|
||||||
|
// -------------------------- 测试用例 --------------------------
|
||||||
|
// // 测试1:目标时间是90秒前(判断是否小于100秒 → true)
|
||||||
|
// const test1 = TimeUtil.isLessThanSpecifiedSeconds(Date.now() - 90 * 1000, 100);
|
||||||
|
// console.log('测试1:', test1); // true
|
||||||
|
//
|
||||||
|
// // 测试2:目标时间是100秒前(判断是否小于90秒 → false)
|
||||||
|
// const test2 = TimeUtil.isLessThanSpecifiedSeconds(Date.now() - 100 * 1000, 90);
|
||||||
|
// console.log('测试2:', test2); // false
|
||||||
|
//
|
||||||
|
// // 测试3:传入时间字符串(判断是否小于60秒 → false)
|
||||||
|
// const test3Time = "2026-02-12 09:00:00";
|
||||||
|
// const test3 = TimeUtil.isLessThanSpecifiedSeconds(test3Time, 60);
|
||||||
|
// console.log('测试3:', test3); // false
|
||||||
|
//
|
||||||
|
// // 测试4:传入秒级时间戳(1740000000 → 转换为毫秒级,判断是否小于30秒)
|
||||||
|
// const test4 = TimeUtil.isLessThanSpecifiedSeconds(1740000000, 30);
|
||||||
|
// console.log('测试4:', test4); // 取决于当前时间,大概率false
|
||||||
|
//
|
||||||
|
// // 测试5:非法秒数(会抛出异常)
|
||||||
|
// try {
|
||||||
|
// TimeUtil.isLessThanSpecifiedSeconds(Date.now(), -10);
|
||||||
|
// } catch (e) {
|
||||||
|
// console.log('测试5(异常):', e.message); // 指定秒数不合法:-10,请传入非负数字
|
||||||
|
// }
|
||||||
Loading…
Reference in New Issue