2025-05-22 16:37:43 +08:00

858 lines
24 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="home-wrap">
<nav-bar @messageSent="handleMessage"></nav-bar>
<!-- #ifdef H5-->
<view class="container-wrap h5">
<u-sticky zIndex="98" offset-top="-44">
<view class="top-wrap">
<view class="swiper-wrap">
<view class="swiper-item">
<weather></weather>
</view>
</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS || MP-WEIXIN-->
<view class="container-wrap">
<u-sticky zIndex="98">
<view class="top-wrap">
<view class="swiper-wrap-weixin">
<view class="swiper-item">
<weather></weather>
</view>
</view>
<!-- #endif -->
<view class="notice-wrap">
<u-notice-bar scroll="false"
:text="Object.keys(notices).length !==0 ? `最新通知:${notices.noticeTitle}` : '暂无'"
mode="closable" color="#486ff2" bgColor="#F0F9FF"
@click="handleNotice"></u-notice-bar>
</view>
<view class="tab-wrap" v-if="token !== null || token !== ''">
<u-tabs :list="groupList" :scrollable="true" lineWidth="40" lineHeight="2"
lineColor="transparent" :duration="100"
:activeStyle="{ fontSize: '36rpx', color: '#486FF2', fontWeight: 'bold' }"
@change="handleTabChange">
<view v-if="token !== null || token !== ''" slot="right" class="add-btn"
@tap="handleTopPopOpen">
<u-icon name="plus-circle-fill" size="23" color='#486FF2' bold></u-icon>
</view>
</u-tabs>
</view>
</view>
</u-sticky>
<view class="device-wrap" v-if="token != null && token != ''">
<view class="content-wrap">
<view class="item-wrap" v-for="(item, index) in deviceList" :key="index">
<view class="card"
:style="{margin:(index%2==0?'12rpx 12rpx 12rpx 30rpx':'12rpx 30rpx 12rpx 12rpx')}"
@tap="gotoDeviceDetail(item)">
<div class="share-wrap" v-if="item.isOwner === 0">
<u--image src="/static/common/share.png" mode="aspectFill" width="20"
height="25rpx"></u--image>
</div>
<view class="title">{{item.deviceName}}</view>
<view class="top">
<view class="img-wrap">
<u--image v-if="item.imgUrl" :src="item.imageUrl" radius="10"
mode="aspectFill" width="96rpx" height="96rpx">
<view slot="error" style="font-size: 12px;">
{{$tt('home.errorLoading')}}
</view>
<template v-slot:loading>
<u-loading-icon></u-loading-icon>
</template>
</u--image>
<u--image v-else-if="item.deviceType === 2"
src="/static/common/gatewa.png" radius="10" mode="aspectFill"
width="96rpx" height="96rpx">
</u--image>
<u--image v-else-if="item.deviceType === 3"
src="/static/common/video.png" radius="10" mode="aspectFill"
width="96rpx" height="96rpx">
</u--image>
<u--image v-else src="/static/common/device.png" radius="10"
mode="aspectFill" width="96rpx" height="96rpx">
</u--image>
</view>
<view class="right-wrap">
<view class="status-wrap">
<u--text v-if="item.status == 3 && item.rssi >= '-55'" lines="1"
prefixIcon="/static/common/wifi_4.png"
iconStyle="margin-right:6rpx;" size="12"
:text="statusTxt(item.status)"></u--text>
<u--text
v-else-if="item.status == 3 && item.rssi >= '-70' && item.rssi < '-55'"
lines="1" prefixIcon="/static/common/wifi_3.png"
iconStyle="margin-right:6rpx;" size="12"
:text="statusTxt(item.status)"></u--text>
<u--text
v-else-if="item.status == 3 && item.rssi >= '-85' && item.rssi < '-70'"
lines="1" prefixIcon="/static/common/wifi_2.png"
iconStyle="margin-right:6rpx;" size="12"
:text="statusTxt(item.status)"></u--text>
<u--text
v-else-if="item.status == 3 && item.rssi >= '-100' && item.rssi < '-85'"
lines="1" prefixIcon="/static/common/wifi_1.png"
iconStyle="margin-right:6rpx;" size="12"
:text="statusTxt(item.status)"></u--text>
<u--text v-else lines="1" prefixIcon="/static/common/wifi_0.png"
iconStyle="margin-right:6rpx;" size="12"
:text="statusTxt(item.status)">
</u--text>
</view>
<view class="shadow-wrap">
<u--text lines="1" prefixIcon="/static/common/shadow_active.png"
iconStyle="margin-right:6rpx;" size="12"
v-if="item.isShadow == 1" :text="$tt('home.shadow')">
</u--text>
<u--text lines="1" prefixIcon="/static/common/shadow_disable.png"
iconStyle="margin-right:6rpx;" size="12" v-else
:text="$tt('home.shadow')"></u--text>
</view>
</view>
</view>
</view>
</view>
</view>
<u-empty mode="data" :show="total === 0" marginTop="60"
:text="$tt('scene.emptyData')"></u-empty>
<u-loadmore :status="loadStatus" v-if="total > queryParams.pageSize"
:loading-text="$tt('scene.tryingToLoad')" :loadmoreText="$tt('scene.gentlyPullUp')"
:nomoreText="$tt('scene.emptyData')" marginTop="20" />
</view>
<view class="token-null" v-if="token == null || token == ''">
<u-empty mode="data" :show="!token" marginTop="60"
:text="$tt('timing.emptyNull')"></u-empty>
<u-button type="primary" @click="() => openAlert = true"
customStyle="width:400rpx;margin-top:60rpx;">{{$tt('home.createBtn')}}</u-button>
</view>
</view>
<view class="other">
<u-popup :show="isShow" @close="handleTopPopClose" mode="top" :safeAreaInsetTop="true"
round="10" style="margin-top: 40px;">
<u-status-bar></u-status-bar>
<view style="padding:20px 0 10px 0;">
<!-- #ifdef MP-WEIXIN || H5 -->
<u-grid :border="false" col="3">
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<u-grid :border="false" col="3">
<!-- #endif -->
<u-grid-item>
<!-- 配网添加 -->
<u-icon name="/static/common/network.png" size="25" color="#fff"
:label="$tt('home.netWork')" labelPos="bottom" labelSize="15"
space="10px" @click="gotoAddDevice()"
customStyle="background-color:#f56c6c;border-radius:3px;padding:10px;"></u-icon>
</u-grid-item>
<u-grid-item>
<!-- 扫码添加-->
<u-icon name="/static/common/scan.png" size="25" :label="$tt('home.qrCode')"
labelPos="bottom" labelSize="15" space="10px" @click="openScan"
customStyle="background-color:#3c9cff;border-radius:3px;padding:10px;"></u-icon>
</u-grid-item>
<u-grid-item>
<!-- 关联添加 -->
<u-icon name="/static/common/relate.png" size="25"
:label="$tt('home.association')" labelPos="bottom" labelSize="15"
space="10px" @click="gotoRelateDevice()"
customStyle="background-color:#f9ae3d;border-radius:3px;padding:10px;"></u-icon>
</u-grid-item>
</u-grid>
<view>
<u-row>
<!-- #ifdef APP-PLUS -->
<u-col :span="4">
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || H5 -->
<u-col :span="4">
<!-- #endif -->
<u--text type="info" :text="$tt('home.wifi-type')" size="12"
customStyle="padding:10px 15px;">
</u--text>
</u-col>
<!-- #ifdef APP-PLUS -->
<u-col :span="4">
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || H5 -->
<u-col :span="4">
<!-- #endif -->
<u--text type="info" :text="$tt('home.networksDevices')"
size="12" customStyle="padding:10px 15px;">
</u--text>
</u-col>
<!-- #ifdef APP-PLUS -->
<u-col :span="4">
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || H5 -->
<u-col :span="4">
<!-- #endif -->
<u--text type="info"
:text="$tt('home.supportBatchOperations')" size="12"
customStyle="padding:10px 15px;"></u--text>
</u-col>
</u-row>
</view>
</view>
</u-popup>
<u-modal :show="modal.show" :content="modal.content" @confirm="confirm" @cancel="cancel"
:showConfirmButton="modal.showConfirmButton" showCancelButton></u-modal>
<u-loading-page style="z-index: 98" :loading="loading" bg-color="#eef3f7"
loadingText="iot-xcwl.cn"></u-loading-page>
<u-modal :show="openAlert" :content="$tt('home.content')" @confirm="gotoLogin"
@cancel="() => openAlert = false" showCancelButton></u-modal>
</view>
</view>
</template>
<script>
import projectConfig from '@/env.config.js';
import navBar from './navBar.vue'
import weather from '@/components/weather/index.vue';
import {
getNoticeList
} from '@/apis/modules/notice';
import {
getGroupList
} from '@/apis/modules/group';
import {
listDeviceShort,
deviceRelateUser
} from '@/apis/modules/device';
export default {
beforeRouteEnter(to, from, next) {
// 在进入路由前执行一些操作,例如加载数据等
next(vm => {
// 这里可以访问组件实例 `vm`
vm.onLoad() // 执行加载操作
})
},
components: {
weather,
navBar
},
data() {
return {
token: '',
groupList: [], // 分组列表
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
groupId: 0,
deviceName: null
},
loadStatus: 'loadmore', // 加载更多
deviceList: [], // 设备列表
total: 0, // 总条数
isShow: false, // 顶部弹出层
// 扫码模态窗
modal: {
show: false,
showConfirmButton: false,
content: ''
},
//提示需要登录框
openAlert: false,
scanJson: {}, // 扫码获取的Json
loading: false,
notices: {}, // 最新公告
};
},
onLoad() {
this.getToken();
if (this.token != '' && this.token != null) {
this.connectMqtt();
this.getDatas();
this.getNoticeDatas();
}
this.groupList = [{
name: this.$tt('home.all'),
id: 0
}];
uni.$on('refreshData', () => {
this.getDatas();
})
},
onShow() {
// 小程序tabBar导航国际化特殊性
// #ifdef MP-WEIXIN || APP-PLUS
uni.setTabBarItem({
index: 0,
text: this.$tt('navBar.home'),
})
uni.setTabBarItem({
index: 1,
text: this.$tt('navBar.scene'),
})
uni.setTabBarItem({
index: 2,
text: this.$tt('navBar.alert'),
})
uni.setTabBarItem({
index: 3,
text: this.$tt('navBar.news'),
})
uni.setTabBarItem({
index: 4,
text: this.$tt('navBar.user'),
})
// #endif
},
methods: {
// 分享给朋友
onShareAppMessage() {
return {
title: this.$tt('home.welcome'),
path: '/pages/tabBar/home/index',
imageUrl: '/static/common/logo_blue.png'
};
},
// 分享到朋友圈
onShareTimeline() {
return {
title: this.$tt('home.welcome'),
query: '/pages/tabBar/home/index',
imageUrl: '/static/common/logo_blue.png'
};
},
// 获取通知列表数据
// getNoticeDatas() {
// this.loading = true;
// getNoticeList(this.queryParams).then(res => {
// if (res.code === 200) {
// this.notices = res.rows[0];
// }
// });
// },
getNoticeDatas() {
getNoticeList(this.queryParams).then(res => {
this.notices = res?.rows?.[0] || {}; // 确保始终为对象
}).catch(() => {
this.notices = {};
});
},
//接受子组件导航栏传递的搜索信息
handleMessage(msg) {
this.deviceList = []
this.queryParams.deviceName = msg;
console.log('需要搜索的数据为', this.queryParams)
this.getDevices()
},
getToken() {
// 本地缓存获取token
this.token = uni.getStorageSync('token');
// vuex存储token
uni.$u.vuex('vuex_token', this.token);
},
// 连接Mqtt消息服务器
async connectMqtt() {
if (this.$mqttTool.client == null) {
await this.$mqttTool.connect(this.vuex_token);
}
this.mqttCallback();
this.getDatas();
},
// Mqtt回调处理
mqttCallback() {
this.$mqttTool.client.on('message', (topic, message, buffer) => {
let topics = topic.split('/');
let productId = topics[1];
let deviceNum = topics[2];
message = JSON.parse(message.toString());
if (!message) {
return;
}
if (topics[3] == 'status') {
console.log('接收到【设备状态】主题:', topic);
console.log('接收到【设备状态】内容:', message);
// 更新列表中设备的状态
for (let i = 0; i < this.deviceList.length; i++) {
if (this.deviceList[i].serialNumber == deviceNum) {
this.deviceList[i].status = message.status;
this.deviceList[i].isShadow = message.isShadow;
this.deviceList[i].rssi = message.rssi;
return;
}
}
}
});
},
// 订阅消息
mqttSubscribe(list) {
// 订阅当前页面设备状态和实时监测
let topics = [];
for (let i = 0; i < list.length; i++) {
let topicStatus = '/' + list[i].productId + '/' + list[i].serialNumber + '/status/post';
topics.push(topicStatus);
}
this.$mqttTool.subscribe(topics);
},
// 获取数据
getDatas() {
this.getGroups();
this.getDevices();
},
// 获取分组列表
getGroups() {
getGroupList({
userId: this.profile.userId
}).then(res => {
if (res.code === 200) {
this.groupList = [{
name: this.$tt('home.all'),
id: 0
}];
if (res.rows.length !== 0) {
for (let i = 0; i < res.rows.length; i++) {
this.groupList.push({
name: res.rows[i].groupName,
id: res.rows[i].groupId
});
}
}
}
});
},
// 获取设备列表
getDevices() {
this.loading = true;
listDeviceShort(this.queryParams).then(response => {
let {
code,
rows,
total
} = response;
if (code === 200) {
rows = rows.map(item => {
item.imageUrl = item.imgUrl !== null && item.imgUrl !== '' ? projectConfig
.baseUrl + item.imgUrl : item.imgUrl;
return item;
});
if (this.queryParams.pageNum == 1) {
this.deviceList = rows;
} else {
this.deviceList = this.deviceList.concat(rows);
}
this.total = total;
const {
pageNum,
pageSize
} = this.queryParams;
this.loadStatus = total > pageNum * pageSize ? 'loadmore' : 'nomore';
// 订阅消息
if (this.deviceList && this.deviceList.length > 0) {
this.mqttSubscribe(this.deviceList);
}
}
this.loading = false;
uni.stopPullDownRefresh();
});
},
getProfileInfo() {
// 调用用户信息接口
this.$api.common.getProfile().then(res => {
//存储用户信息,TODO 需要调用一次,不然其他页面调用返回空
uni.$u.vuex('profile', res.data);
this.profile;
}).catch(err => {
this.$u.toast(err.msg);
});
},
// 设备分组改变事件
handleTabChange(item) {
this.queryParams.groupId = item.id;
this.queryParams.pageNum = 1;
this.getDevices();
},
// 设备状态
statusTxt(status) {
let txt = '';
switch (status) {
case 1:
txt = this.$tt('home.notActive');
break;
case 2:
txt = this.$tt('home.disabled');
break;
case 3:
txt = this.$tt('home.onLine');
break;
case 4:
txt = this.$tt('home.offline');
break;
}
return txt;
},
// 跳转详情
gotoDeviceDetail(item) {
const {
deviceId,
protocolCode
} = item;
uni.navigateTo({
url: '/pagesA/home/device/index?deviceId=' + deviceId + '&protocolCode=' + protocolCode
});
},
// 未登录跳转登录页
gotoLogin() {
uni.$u.route('/pages/login/index');
this.openAlert = false;
},
// 打开顶部弹窗
handleTopPopOpen() {
this.isShow = true;
},
// 关闭顶部弹窗
handleTopPopClose() {
this.isShow = false;
},
// 添加设备
gotoAddDevice() {
if (this.token != '' && this.token != null) {
this.isShow = false;
uni.navigateTo({
url: '/pagesA/list/home/deviceAdd'
});
} else {
this.openAlert = true;
}
},
// 关联设备
gotoRelateDevice() {
if (this.token != '' && this.token != null) {
this.isShow = false;
uni.navigateTo({
url: '/pagesA/list/home/deviceRelate'
});
} else {
this.openAlert = true;
}
},
// 下拉刷新
onPullDownRefresh() {
this.list = [];
this.queryParams.pageNum = 1;
this.getDatas(); //重新获取数据
},
// 上拉加载
onReachBottom() {
this.queryParams.pageNum = this.queryParams.pageNum + 1;
if ((this.queryParams.pageNum - 1) * this.queryParams.pageSize >= this.total) {
this.loadStatus = 'nomore';
} else {
this.loadStatus = 'loading';
this.getDevices();
}
},
/***************************************扫码关联设备***********************************************/
// 模态窗确定
confirm() {
this.cancel();
let form = {
deviceNumberAndProductIds: [{
deviceNumber: this.scanJson.deviceNumber,
productId: this.scanJson.productId
}],
userId: this.profile.userId
};
deviceRelateUser(form).then(res => {
if (res.code == 200) {
uni.showToast({
icon: 'success',
title: this.$tt('user.saveSuccess')
});
this.getDatas();
this.isShow = false;
} else {
this.modal = {
show: true,
showConfirmButton: false,
content: res.msg
};
}
});
},
// 模态窗取消
cancel() {
this.modal = {
show: false,
showConfirmButton: false,
content: ''
};
},
// 扫码
async openScan() {
if (this.token != '' && this.token != null) {
// #ifndef MP-WEIXIN || APP-PLUS
uni.showToast({
icon: 'none',
title: this.$tt('user.scanning')
});
return;
// #endif
// 权限问题app 需要做权限说明
let onlyFromCamera = false;
// #ifdef APP-PLUS
onlyFromCamera = true;
let result = await this.$store.dispatch("permission/requestPermissions", 'CAMERA');
if (result !== 1) return;
// #endif
this.startScanCode(onlyFromCamera);
} else {
this.openAlert = true;
}
},
startScanCode(onlyFromCamera) {
uni.scanCode({
onlyFromCamera, // 是否允许从相册扫码
success: res => {
console.log('条码类型:' + res.scanType);
console.log('条码内容:' + res.result);
if (res.result.substr(0, 1) != '{') {
console.log('坑点:解析二维码后第一个位置包含一个特殊字符,大部分编译器和调试工具识别不了这个特殊字符');
res.result = res.result.substring(1);
}
// 解析JSON
try {
this.scanJson = JSON.parse(res.result);
// type=1 代表扫码关联设备
if (this.scanJson.type == 1) {
this.modal = {
show: true,
showConfirmButton: true,
content: '【' + this.$tt('home.serialNumber') +
':' + this.scanJson.deviceNumber + ',' + this.$tt(
'home.productName') +
':' + this
.scanJson.productName + '】' + this.$tt('home.sureAdd')
};
return;
}
uni.showToast({
icon: 'none',
title: this.$tt('user.parseQrCode')
});
} catch (error) {
uni.showToast({
icon: 'none',
title: this.$tt('user.errorParseQRcode')
});
}
}
});
},
// 调整到对应公告
handleNotice() {
uni.navigateTo({
url: '/pagesB/user/message/detail?noticeId=' + this.notices.noticeId
});
}
}
};
</script>
<style lang="scss">
page {
height: 100%;
background: $uni-bg-color-grey;
}
</style>
<style lang="scss" scoped>
.home-wrap {
padding: 0;
margin: 0;
// 不要设置height会影响u-sticky 算uviewbug吧
//height: 100%;
.h5 {
// padding-bottom: calc(52px + var(--status-bar-height));
}
.container-wrap {
// padding-top: calc(52px + var(--status-bar-height));
// height: 100%;
.top-wrap {
background: $uni-bg-color-grey;
.swiper-wrap {
height: 10.72rem;
padding: 80rpx 30rpx 20rpx;
background: linear-gradient(130deg, #486FF2 0%, rgba(78, 147, 246, 0.78) 100%);
box-shadow: 4px 4px 8px 0px rgba(0, 93, 253, 0.14);
border-radius: 0 0 60rpx 60rpx;
align-items: start;
.swiper-item {
margin-top: 200rpx;
display: block;
width: 18.4rem;
height: 11rem;
margin-top: 100rpx;
}
}
.swiper-wrap-weixin {
height: 430rpx;
padding: 80rpx 30rpx 60rpx;
background: linear-gradient(130deg, #486FF2 0%, rgba(78, 147, 246, 0.78) 100%);
box-shadow: 4px 4px 8px 0px rgba(0, 93, 253, 0.14);
border-radius: 0 0 60rpx 60rpx;
align-items: start;
.swiper-item {
margin-top: 200rpx;
display: block;
height: 400rpx;
margin-top: 160rpx;
}
}
.notice-wrap {
margin: 3rem 1rem 0;
border-radius: 8px;
width: 674rpx;
height: 64rpx;
::v-deep .u-notice-bar {
border: 2rpx solid #FFFFFF;
border-radius: 20rpx;
}
}
// #ifdef H5
.tab-wrap {
padding: 0 12rpx;
margin: 30rpx 0 5rpx;
.add-btn {
padding: 0 16rpx;
background: $uni-bg-color-grey;
}
}
// #endif
// #ifdef APP-PLUS || MP-WEIXIN
.tab-wrap {
padding: 0 14rpx;
margin: 30rpx 0 0;
.add-btn {
padding: 0 14rpx;
background: $uni-bg-color-grey;
}
}
// #endif
}
.device-wrap {
background: $uni-bg-color-grey;
display: flex;
flex-direction: column;
justify-content: center;
padding-bottom: 120rpx;
.content-wrap {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: flex-start;
width: 100%;
.item-wrap {
width: 50%;
.card {
background: #FFFFFF;
box-shadow: 0rem 0.43rem 0.43rem 0rem rgba(0, 0, 0, 0.04);
position: relative;
padding: 20rpx;
background-color: #fafafa; // 更浅的背景颜色
border-radius: 16rpx;
border: 2rpx solid #d3e0f5;
overflow: hidden;
.circle-one {
position: absolute;
// z-index: 1;
border-radius: 50%;
width: 90rpx;
height: 90rpx;
top: -20rpx;
left: -25rpx;
background-color: rgba(220, 235, 248, 0.8); // 浅蓝色
}
.share-wrap {
position: absolute;
right: 8rpx;
top: 12rpx;
}
.top {
display: flex;
flex-direction: row;
align-items: center;
// justify-content: space-between;
.img-wrap {
border-radius: 10rpx;
// border: 3rpx dashed #819ac0;
padding: 6rpx;
}
.right-wrap {
margin-right: 25rpx;
margin-left: 50rpx;
.status-wrap {
margin-top: 15rpx;
}
.shadow-wrap {
margin-top: 10rpx;
}
}
}
.title {
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 26rpx;
color: #000000;
line-height: 36rpx;
text-align: left;
font-style: normal;
text-transform: none;
white-space: nowrap;
overflow: hidden;
margin: 0 10rpx 20rpx;
text-overflow: ellipsis;
}
}
}
}
}
.token-null {
background: $uni-bg-color-grey;
display: flex;
flex-direction: column;
justify-content: center;
padding-bottom: 120rpx;
}
}
}
</style>