751 lines
22 KiB
Vue
Raw Permalink Normal View History

2024-12-09 14:16:57 +08:00
<template>
<page-meta>
<navigation-bar :title="$tt('common.fastbee')" background-color="#007AFF">
</navigation-bar>
</page-meta>
<view class="home-wrap">
<NavBar @messageSent="handleMessage"></NavBar>
<!-- #ifdef H5-->
<view class="container-wrap h5">
<u-sticky zIndex="98">
<view class="top-wrap">
<view class="swiper-wrap">
<swiper class="swiper-item" circular :indicator-dots="true">
<swiper-item>
<weather></weather>
</swiper-item>
</swiper>
</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS || MP-WEIXIN-->
<view class="container-wrap ">
<u-sticky zIndex="98">
<view class="top-wrap">
<view class="swiper-wrap-weixin">
<swiper class="swiper-item" circular :indicator-dots="true">
<swiper-item>
<weather></weather>
</swiper-item>
<!-- <swiper-item>
<u--image :showLoading="true" src="/static/home/fastbee.png" width="100%"
height="334rpx" radius="10"></u--image>
</swiper-item> -->
</swiper>
</view>
<!-- #endif -->
<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: '#3c9cff', fontWeight: 'bold' }"
@change="handleTabChange">
<view v-if="token !== null || token !== ''" slot="right" class="add-btn"
@tap="handleTopPopOpen">
<u-icon name="plus-circle-fill" size="24" color='#3c9cff' 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?'15rpx 15rpx 15rpx 30rpx':'15rpx 30rpx 15rpx 15rpx')}"
@tap="gotoDeviceDetail(item)">
<div style="height:25rpx;">
<u--image v-if="item.isOwner===0" src="/static/home/device/share.png"
mode="aspectFill" width="20" height="25rpx"></u--image>
</div>
<view class="top">
<view class="img-wrap">
<u--image v-if="item.imgUrl" :src="item.imageUrl" radius="10"
mode="aspectFill" width="20" height="20">
<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/gateway.png" radius="10" mode="aspectFill"
width="55" height="55">
</u--image>
<u--image v-else-if="item.deviceType === 3"
src="/static/common/video.png" radius="10" mode="aspectFill"
width="55" height="55">
</u--image>
<u--image v-else src="/static/common/device.png" radius="10"
mode="aspectFill" width="55" height="55">
</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/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/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/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/wifi_1.png"
iconStyle="margin-right:6rpx;" size="12"
:text="statusTxt(item.status)"></u--text>
<u--text v-else lines="1" prefixIcon="/static/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/state_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/state.png"
iconStyle="margin-right:6rpx;" size="12" v-else
:text="$tt('home.shadow')"></u--text>
</view>
</view>
</view>
<view class="title">{{item.deviceName}}</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;">创建设备</u-button>
</view>
</view>
<view class="other">
<u-popup :show="isShow" @close="handleTopPopClose" mode="top" round="10">
<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/ap.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/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/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="XCWL.cn"></u-loading-page>
<u-modal :show="openAlert" content="该功能需要登录才可使用,是否去登录?" @confirm="gotoLogin"
@cancel="() => openAlert = false" showCancelButton></u-modal>
</view>
</view>
</template>
<script>
import projectConfig from '@/env.config.js';
import NavBar from '@/components/NavBar/NavBar.vue'
import weather from '@/components/weather/index.vue';
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,
};
},
onLoad () {
this.getToken();
if (this.token != '' && this.token != null) {
this.connectMqtt();
this.getDatas();
}
this.groupList = [{
name: this.$tt('home.all'),
id: 0
}];
},
onShow () {
//小程序tabBar导航国际化特殊性
// #ifdef MP-WEIXIN
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: {
//接受子组件导航栏传递的搜索信息
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('/pagesB/login/waitLogin');
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.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')
});
}
}
});
}
}
};
</script>
<style>
page {
height: 100%;
background: #eef3f7;
}
</style>
<style lang="scss" scoped>
.home-wrap {
// 不要设置height会影响u-sticky 算uviewbug吧
//height: 100%;
.h5 {
padding-top: calc(52px + var(--status-bar-height));
}
.container-wrap {
//padding-top: calc(52px + var(--status-bar-height));
background: #eef3f7;
// height: 100%;
.top-wrap {
background: #eef3f7;
.swiper-wrap {
padding: 30rpx;
padding-top: calc(40rpx + var(--status-bar-height));
.swiper-item {
display: block;
height: 334rpx;
}
}
.swiper-wrap-weixin {
padding: 30rpx;
padding-top: calc(165rpx + var(--status-bar-height));
.swiper-item {
display: block;
height: 334rpx;
}
}
.tab-wrap {
padding: 0 12rpx;
.add-btn {
padding: 0 12rpx;
background: #eef3f7;
}
}
}
.device-wrap {
background: #eef3f7;
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 {
position: relative;
box-shadow: 0 8rpx 12rpx rgba(0, 79, 159, 0.2); // 更加柔和的阴影
padding: 20rpx;
background-color: #fafafa; // 更浅的背景颜色
border-radius: 35rpx;
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); // 浅蓝色
}
.top {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.img-wrap {
border-radius: 10rpx;
// border: 3rpx dashed #819ac0;
padding: 6rpx;
background-color: #eaf2fc;
}
.right-wrap {
margin-right: 25rpx;
.status-wrap {
margin-top: 15rpx;
}
.shadow-wrap {
margin-top: 10rpx;
}
}
}
.title {
margin-top: 20rpx;
font-size: 30rpx;
padding: 10rpx;
font-family: 'Roboto', sans-serif;
color: #496d89;
white-space: nowrap;
overflow: hidden;
// border-bottom: 2rpx dotted #7fa1ce; // 增加实线边框
text-overflow: ellipsis;
text-shadow: 1rpx 1rpx 2rpx rgba(0, 0, 0, 0.1);
}
}
}
}
}
.token-null {
background: #eef3f7;
display: flex;
flex-direction: column;
justify-content: center;
padding-bottom: 120rpx;
}
}
}
</style>