2024-12-09 14:16:57 +08:00

751 lines
22 KiB
Vue
Raw 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>
<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>