feat(network): 项目初始化、请求封装,主包部分页面开发和接口对接等

- 完成登录、配置、统计、设备、我的、修改密码页开发
- 初始化项目tabbar图标、默认图片等
- 重构请求封装,项目配置等
This commit is contained in:
fhysy 2025-04-25 14:06:05 +08:00
parent 648f3c4ca9
commit 39b5e3774b
25 changed files with 1404 additions and 318 deletions

View File

@ -10,14 +10,14 @@
if(!configList || configList.length == 0){
let configList = [{
id:this.$u.guid(32),
protocol:'https://',
address:'cloud.iot-fast.com',
protocol:'http://',
address:'192.168.1.17:8848',
}]
// #ifdef APP-PLUS
configList.push({
id:this.$u.guid(32),
protocol:'http://',
address:'192.168.21.22',
address:'192.168.1.17:8848',
})
// #endif
uni.setStorageSync('configList',configList)

View File

@ -1,9 +1,9 @@
{
"name" : "物联网可视化",
"appid" : "__UNI__604B8F1",
"name" : "德润物联网",
"appid" : "__UNI__B7FE99B",
"description" : "",
"versionName" : "1.0.34",
"versionCode" : 1034,
"versionName" : "1.0.0",
"versionCode" : 100,
"transformPx" : false,
/* 5+App */
"app-plus" : {
@ -135,7 +135,7 @@
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx27c271b0cb420015",
"appid" : "wxc1dd0068f0f85026",
"setting" : {
"urlCheck" : false,
"postcss" : true,

View File

@ -14,7 +14,7 @@ export default {
getAppConfig(data){
return new Promise((resolve, reject) => {
request.customRequest({
url: '/admin/admin/site/config',
url: '/system/config/front',
method: 'GET',
},data)
.then((res) =>{
@ -58,7 +58,7 @@ export default {
passwordLogin(data){
return new Promise((resolve, reject) => {
request.customRequest({
url: '/user/admin/site/accountLogin',
url: '/authorize/login',
method: 'POST',
},data)
.then((res) =>{
@ -68,6 +68,20 @@ export default {
})
})
},
// 获取用户信息
userLogout(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/user-token/reset',
method: 'GET',
},data)
.then((res) =>{
resolve(res);
}).catch(err =>{
reject(err);
})
})
},
// 发送短信验证码
sendSms(data){
@ -103,7 +117,7 @@ export default {
getUserInfo(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/user/admin/member/info',
url: '/user/detail',
method: 'GET',
},data)
.then((res) =>{
@ -134,7 +148,22 @@ export default {
updatePwd(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/user/admin/member/updatePwd',
url: '/user/passwd',
method: 'PUT',
},data)
.then((res) =>{
resolve(res);
}).catch(err =>{
reject(err);
})
})
},
// 看板统计查询
getDashboardSelect(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/dashboard/_multi',
method: 'POST',
},data)
.then((res) =>{

View File

@ -1,6 +1,21 @@
import request from "../../request.js"
export default {
// 获取设备数
getDeviceInstanceCount(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/device/instance/_count',
method: 'GET',
},data)
.then((res) =>{
resolve(res);
}).catch(err =>{
reject(err);
})
})
},
// 获取设备统计数量
getDeviceCount(data){
return new Promise((resolve, reject) => {
@ -20,8 +35,8 @@ export default {
getProductList(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/iot/admin/product/list',
method: 'GET',
url: '/device-product/_query/no-paging',
method: 'POST',
},data)
.then((res) =>{
resolve(res);
@ -35,7 +50,7 @@ export default {
getDeviceList(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/iot/admin/device/list',
url: '/device-instance/_query',
method: 'POST',
},data)
.then((res) =>{
@ -136,12 +151,25 @@ export default {
})
})
},
// 告警级别列表
// 获取告警记录列表
getAlarmList(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/alarm/record/_query',
method: 'POST',
},data)
.then((res) =>{
resolve(res);
}).catch(err =>{
reject(err);
})
})
},
// 获取默认告警级别
getAlarmLevelList(data){
return new Promise((resolve, reject) => {
request.TokenRequest({
url: '/iot/admin/alarm/level/list',
url: '/alarm/config/default/level',
method: 'GET',
},data)
.then((res) =>{
@ -151,6 +179,20 @@ export default {
})
})
},
// // 告警级别列表
// getAlarmLevelList(data){
// return new Promise((resolve, reject) => {
// request.TokenRequest({
// url: '/iot/admin/alarm/level/list',
// method: 'GET',
// },data)
// .then((res) =>{
// resolve(res);
// }).catch(err =>{
// reject(err);
// })
// })
// },
// 概览页统计(图表)
getAlarmRecordStat(data){

View File

@ -14,8 +14,8 @@ if(process.env.NODE_ENV === 'development'){
// wsUrl = "ws://iotos-ui-dev.iotos.192.168.10.243.nip.io:32764"
// configurationUrl = "http://192.168.18.139:8855"
// configurationhtmlUrl = "http://hceditor-2d-dev.hceditor.192.168.10.243.nip.io:30405"
url = 'https://miot.gkiiot.com'
wsUrl = 'wss://miot.gkiiot.com'
url = 'http://192.168.1.17:8848'
wsUrl = 'ws://192.168.1.17:8848'
configurationUrl = 'https://2d.gkiiot.com/prod-api'
configurationhtmlUrl = 'https://2d.gkiiot.com'
}else{

View File

@ -94,7 +94,7 @@ const TokenRequest = (opts, data) => {
data: data,
method: opts.method,
header: {
'Authorization': tokenKey,
'x-access-token': tokenKey,
},
}
let promise = new Promise(function(resolve, reject) {
@ -102,7 +102,7 @@ const TokenRequest = (opts, data) => {
(res) => {
// console.log("请求返回",res)
// 令牌过期关闭所有页面跳转登录页
if(res[1].data.code==61){
if(res[1].data.status==401){
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none',
@ -114,7 +114,7 @@ const TokenRequest = (opts, data) => {
setTimeout(()=>{
uni.reLaunch({url:'/pages/tabbar/login'});
},1500)
}else if(res[1].data.code>0){
}else if(res[1].data.status!=200){
uni.showToast({
title: res[1].data.message,
icon: 'none',

View File

@ -29,6 +29,20 @@
// "navigationStyle":"custom"
// }
// },
{
"path": "pages/tabbar/dashboard",
"style": {
"navigationBarTitleText": "统计看板",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/tabbar/device",
"style": {
"navigationBarTitleText": "设备",
"navigationBarBackgroundColor": "#FFFFFF"
}
},
{
"path": "pages/tabbar/new",
"style": {
@ -343,15 +357,15 @@
},
// #endif
"list": [{
"pagePath": "pages/tabbar/home",
"iconPath": "static/image/tabbar/home.png",
"selectedIconPath": "static/image/tabbar/home-active.png",
"text": "首页"
"pagePath": "pages/tabbar/dashboard",
"iconPath": "static/image/tabbar/dashboard.png",
"selectedIconPath": "static/image/tabbar/dashboard-active.png",
"text": "统计"
},{
"pagePath": "pages/tabbar/workbench",
"iconPath": "static/image/tabbar/workbench.png",
"selectedIconPath": "static/image/tabbar/workbench-active.png",
"text": "工作台"
"pagePath": "pages/tabbar/device",
"iconPath": "static/image/tabbar/iot.png",
"selectedIconPath": "static/image/tabbar/iot-active.png",
"text": "设备"
},
// #ifdef MP-WEIXIN
{

View File

@ -56,13 +56,13 @@
submit(){
if(this.verification()){
let params={
id:this.infoData.id,
// id:this.infoData.id,
newPassword:this.newPassword,
oldPassword:this.oldPassword,
}
this.$api.updatePwd(params).then(res => {
console.log("submit",res);
if(res.code==0){
if(res.status == 200){
this.$u.toast(res.message);
this.$store.commit('setTokenKey', '');
uni.$u.toast('退出登录成功');

View File

@ -1,46 +1,91 @@
<template>
<view class="config-box">
<view class="config-list" v-if="configList.length">
<view class="config-item" v-for="(item,index) in configList" :key="item.id" @click="changeConfig(index)">
<!-- <u-icon v-if="configIndex==index" class="icon-checkbox-mark" name="checkbox-mark" color="#ff0000" size="50"></u-icon> -->
<view class="acitve-box" v-if="configIndex==index">
已选择
</view>
<view class="congfig-txt">
{{item.protocol+item.address}}
</view>
<view class="operate-box">
<view class="iconfont icon-bianji1 color-blue" @click.stop="openEdit(index)"></view>
<view class="iconfont icon-shanchu color-red" @click.stop="delConfig(index)"></view>
<view class="container">
<!-- 域名列表 -->
<view class="domain-list" v-if="configList.length">
<view
class="domain-item"
:class="{'domain-item-active': configIndex === index}"
v-for="(item, index) in configList"
:key="item.id"
@click="changeConfig(index)"
>
<view class="domain-item-content">
<!-- 单选按钮 -->
<view class="radio-wrapper">
<u-radio-group v-model="configIndex">
<u-radio
:name="index"
size="40"
shape="circle"
activeColor="#2979ff"
></u-radio>
</u-radio-group>
</view>
<!-- 协议标签 -->
<view :class="['protocol-tag', item.protocol.includes('https') ? 'https' : 'http']">
<text>{{ item.protocol.includes('https') ? 'HTTPS' : 'HTTP' }}</text>
</view>
<!-- 域名 -->
<view class="domain-name">
<text>{{ item.address }}</text>
</view>
<!-- 操作按钮 -->
<view @click.stop="openEdit(index)" class="domain-item-btn">
<u-icon name="edit-pen" color="#2979ff" size="40"></u-icon>
</view>
<view @click.stop="delConfig(index)" class="domain-item-btn">
<u-icon name="trash" color="#fa3534" size="40"></u-icon>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<u-empty margin-top="200" v-else text="没有域名,请添加" mode="list"></u-empty>
<view class="btn-box">
<button type="primary" @tap="openAdd">添加域名</button>
<!-- 添加域名按钮 -->
<view class="add-button-wrapper">
<u-button
type="primary"
@click="openAdd"
>+ 添加域名</u-button>
</view>
<u-modal v-model="show" width="700" title="新增/修改域名" ref="uModal" :show-cancel-button="true" :mask-close-able="true" @confirm="addConfig">
<!-- 添加/编辑域名弹窗 -->
<u-modal v-model="show" width="700" :title="form.id ? '编辑域名' : '添加域名'" ref="uModal" :show-cancel-button="true" :mask-close-able="true" @confirm="addConfig">
<view class="slot-content">
<!-- 网络协议选择 -->
<view class="form-item">
<view class="form-label">
网络协议
网络协议
</view>
<view class="form-value" @click="protocolShow=true">
{{form.protocol?form.protocol:'请选择'}}
<view class="iconfont icon-xiangyou1"></view>
{{ form.protocol ? form.protocol : '请选择' }}
<view class="arrow-icon">
<u-icon name="arrow-down" size="28"></u-icon>
</view>
</view>
</view>
<!-- 域名输入 -->
<view class="form-item">
<view class="form-label">
域名或ip
域名
</view>
<view class="form-value">
<input type="text" v-model="form.address" placeholder="请输入地址如:192.168.0.1">
<input type="text" v-model="form.address" placeholder="请输入域名">
</view>
</view>
</view>
</u-modal>
<!-- 协议选择器 -->
<u-select v-model="protocolShow" :list="protocolList" @confirm="confirmProtocol"></u-select>
<!-- 提示框 -->
<u-toast ref="uToast" />
</view>
</template>
@ -49,122 +94,122 @@
export default {
data() {
return {
configIndex:0,
configList:[],
form:{
id:'',
protocol:'',
address:'',
configIndex: 0,
configList: [],
form: {
id: '',
protocol: '',
address: '',
},
protocolShow:false,
protocolList:[
{
value: 'http://',
label: 'http://'
},
protocolShow: false,
protocolList: [
{
value: 'https://',
label: 'https://'
label: 'HTTPS'
},
{
value: 'http://',
label: 'HTTP'
}
],
show:false,
show: false,
};
},
onLoad() {
// 使https
// #ifdef MP-WEIXIN
this.protocolList = [
{
value: 'https://',
label: 'https://'
}
]
// this.protocolList = [
// {
// value: 'https://',
// label: 'HTTPS'
// }
// ]
// #endif
this.getConfigList();
},
methods:{
getConfigList(){
methods: {
getConfigList() {
uni.getStorage({
key:'configList',
key: 'configList',
}).then(res => {
//
if(res.length==2){
if (res.length == 2) {
this.configList = res[1].data;
}
return uni.getStorage({
key:'configIndex',
key: 'configIndex',
})
}).then(res => {
console.log("configIndex",res)
if(res.length==2){
console.log("configIndex", res)
if (res.length == 2) {
this.configIndex = res[1].data;
}
})
},
changeConfig(i){
console.log("index",i)
if(i !== this.configIndex){
changeConfig(i) {
console.log("index", i)
if (i !== this.configIndex) {
this.configIndex = i;
uni.setStorage({
key:'configIndex',
key: 'configIndex',
data: i,
}).then(res => {
console.log("修改索引")
})
}
},
verification(){
if(!this.form.protocol){
verification() {
if (!this.form.protocol) {
this.$refs.uToast.show({
title: '请先选择协议!',
type: 'error'
})
return false;
}else if(!this.form.address){
} else if (!this.form.address) {
this.$refs.uToast.show({
title: '请先输入地址',
type: 'error',
})
return false;
}else if(!this.$u.test.url(this.form.protocol+this.form.address)){
} else if (!this.$u.test.url(this.form.protocol + this.form.address)) {
this.$refs.uToast.show({
title: '请确认输入地址正确',
type: 'error',
})
return false;
}else{
} else {
return true;
}
},
openAdd(){
if(this.configList.length == 0){
openAdd() {
if (this.configList.length == 0) {
this.form = {
id:'',
protocol:'https://',
address:'cloud.iot-fast.com',
id: '',
protocol: 'http://',
address: '192.168.1.17:8848',
}
}else{
} else {
this.form = {
id:'',
protocol:'',
address:'',
id: '',
protocol: '',
address: '',
}
}
this.show = true;
},
addConfig(){
console.log("添加页面",this.form)
if(this.verification()){
if(this.form.id===''){
addConfig() {
console.log("添加页面", this.form)
if (this.verification()) {
if (this.form.id === '') {
//
this.configList.push({
id:this.$u.guid(32),
protocol:this.form.protocol,
address:this.form.address,
id: this.$u.guid(32),
protocol: this.form.protocol,
address: this.form.address,
})
uni.setStorage({
key:'configList',
key: 'configList',
data: this.configList,
}).then(res => {
this.$refs.uToast.show({
@ -172,9 +217,9 @@
type: 'success',
})
})
if(this.configList.length==1){
if (this.configList.length == 1) {
uni.setStorage({
key:'configIndex',
key: 'configIndex',
data: 0,
}).then(res => {
console.log("添加第一个域名索引为0")
@ -182,18 +227,18 @@
}
this.show = false;
this.getConfigList()
console.log("configList",this.configList)
}else{
console.log("configList", this.configList)
} else {
//
this.configList.map(item=>{
if(item.id == this.form.id){
item.protocol=this.form.protocol;
item.address=this.form.address;
this.configList.map(item => {
if (item.id == this.form.id) {
item.protocol = this.form.protocol;
item.address = this.form.address;
}
return item;
})
uni.setStorage({
key:'configList',
key: 'configList',
data: this.configList,
}).then(res => {
this.$refs.uToast.show({
@ -204,28 +249,29 @@
this.show = false;
this.getConfigList()
}
}else{
} else {
this.$refs.uModal.clearLoading();
}
},
confirmProtocol(e){
confirmProtocol(e) {
this.form.protocol = e[0].value;
},
openEdit(index){
this.form = this.configList[index];
openEdit(index) {
console.log("编辑",index)
this.form = JSON.parse(JSON.stringify(this.configList[index]));
this.show = true;
},
delConfig(index){
delConfig(index) {
uni.showModal({
title: '提示',
content: '确认要删除域名吗?',
success: (res)=>{
success: (res) => {
if (res.confirm) {
console.log('用户点击确定');
this.configList.splice(index, 1);
uni.setStorage({
key:'configList',
key: 'configList',
data: this.configList,
}).then(res => {
this.$refs.uToast.show({
@ -233,19 +279,19 @@
type: 'success',
})
})
if(this.configIndex<index){
}else{
if(this.configIndex>index){
if (this.configIndex < index) {
} else {
if (this.configIndex > index) {
this.configIndex = this.configIndex - 1;
}else if(this.configIndex==index){
if(this.configList.length==0){
} else if (this.configIndex == index) {
if (this.configList.length == 0) {
this.configIndex = -1;
}else{
} else {
this.configIndex = 0;
}
}
uni.setStorage({
key:'configIndex',
key: 'configIndex',
data: this.configIndex,
}).then(res => {
console.log("修改索引")
@ -261,129 +307,130 @@
}
}
</script>
<style>
page{
background: #f5f5f5;
}
</style>
<style lang="scss" scoped>
.color-blue{
color: $mainColor;
page {
background-color: #F5F7FA;
}
.color-red{
color: red;
.container {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #F5F7FA;
padding: 20rpx;
}
.config-box{
.config-list{
.config-item{
background: #fff;
// border-radius: 10rpx;
padding: 30rpx 20rpx;
padding-left: 70rpx;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
border-bottom: 1px solid #f5f5f5;
overflow: hidden;
.acitve-box{
z-index: 888;
position: absolute;
top: -16rpx;
left: -76rpx;
width: 200rpx;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
transform: rotate(-40deg) ;
padding-top: 30rpx;
color: #fff;
background: $mainColor;
font-size: 26rpx;
}
.icon-checkbox-mark{
position: absolute;
top:20rpx;
left:10rpx;
}
.operate-box{
display: flex;
align-items: center;
.iconfont{
font-weight: bold;
margin-left: 20rpx;
font-size: 40rpx;
}
}
}
}
.domain-list {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-bottom: 120rpx; //
}
.slot-content{
padding: 20rpx 30rpx;
.form-item{
.domain-item {
background-color: #ffffff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
&-content {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10rpx;
padding: 35rpx 30rpx;
}
&-active {
background-color: #f0f7ff;
border: 1rpx solid #2979ff;
}
.domain-item-btn{
margin-left: 30rpx;
}
}
.radio-wrapper {
margin-right: 0rpx;
}
.protocol-tag {
padding: 4rpx 12rpx;
border-radius: 6rpx;
margin-right: 20rpx;
font-size: 26rpx;
&.https {
background-color: rgba(41, 121, 255, 0.1);
.form-label{
width: 170rpx;
text {
color: #2979ff;
}
.form-value{
flex: 1;
background: #f0f0f0;
border-radius: 8rpx;
height: 70rpx;
display: flex;
align-items: center;
padding-right: 20rpx;
input{
width: 100%;
padding-left: 20rpx;
}
}
&:first-child{
.form-value{
// justify-content: flex-end;
padding-left: 20rpx;
position: relative;
padding-right: 40rpx;
.iconfont{
position: absolute;
right: 6rpx;
top: 19rpx;
font-size: 37rpx;
color: #8a8c90;
}
}
}
&.http {
background-color: rgba(255, 152, 0, 0.1);
text {
color: #ff9800;
}
}
}
.btn-box{
position: fixed;
width: 100%;
height: 130rpx;
padding: 10rpx;
padding-bottom: 20rpx;
box-sizing: border-box;
bottom: 0;
.domain-name {
flex: 1;
text {
font-size: 28rpx;
}
}
.action-buttons {
display: flex;
align-items: center;
justify-content: space-around;
background-color: #fff;
button{
width: 95%;
background-color: $mainColor;
color: #fff;
}
.bg-grey{
background: #e7e7e7;
color: #8a8c90;
gap: 30rpx;
view {
padding: 10rpx;
}
}
</style>
.add-button-wrapper {
padding: 30rpx 20rpx;
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.slot-content {
padding: 20rpx 30rpx;
}
.form-item {
margin-bottom: 30rpx;
.form-label {
font-size: 28rpx;
color: #666;
margin-bottom: 20rpx;
}
.form-value {
background-color: #f5f5f5;
border-radius: 8rpx;
height: 80rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
position: relative;
input {
width: 100%;
height: 100%;
}
.arrow-icon {
position: absolute;
right: 20rpx;
}
}
}
</style>

458
pages/tabbar/dashboard.vue Normal file
View File

@ -0,0 +1,458 @@
<template>
<view class="container">
<!-- 设备统计卡片 -->
<view class="stat-card">
<view class="card-header">
<view class="header-left">
<u-icon name="grid" size="34" color="#666"></u-icon>
<text class="header-title">设备统计</text>
</view>
<view class="header-right" @click="viewDeviceDetails">
<text class="detail-text">查看详情</text>
<u-icon name="arrow-right" size="28" color="#999"></u-icon>
</view>
</view>
<!-- 设备数量统计 -->
<view class="stat-row">
<view class="stat-item">
<text class="stat-value blue">{{deviceInfo.deviceNum || 0}}</text>
<text class="stat-label">设备数量</text>
</view>
<view class="stat-item">
<text class="stat-value blue">{{deviceInfo.deviceOnlineNum || 0}}</text>
<text class="stat-label">在线数</text>
</view>
<view class="stat-item">
<text class="stat-value blue">{{deviceInfo.deviceOfflineNum || 0}}</text>
<text class="stat-label">离线数</text>
</view>
</view>
<!-- 设备消息量统计 -->
<view class="stat-row">
<view class="stat-item">
<text class="stat-value blue">{{deviceInfo.todayMessageNum || 0}}</text>
<text class="stat-label">今日设备消息量</text>
</view>
<view class="stat-item">
<text class="stat-value blue">{{deviceInfo.thisMonthMessageNum || 0}}</text>
<text class="stat-label">当月设备消息量</text>
</view>
</view>
</view>
<!-- 告警统计卡片 -->
<view class="stat-card">
<view class="card-header">
<view class="header-left">
<u-icon name="bell" size="34" color="#666"></u-icon>
<text class="header-title">告警统计</text>
</view>
<view class="header-right" @click="viewAlarmDetails">
<text class="detail-text">查看详情</text>
<u-icon name="arrow-right" size="28" color="#999"></u-icon>
</view>
</view>
<!-- 告警数量统计 -->
<view class="stat-row">
<view class="stat-item">
<text class="stat-value red">{{alarmInfo.todayMessageNum || 0}}</text>
<text class="stat-label">今日告警数</text>
</view>
<view class="stat-item">
<text class="stat-value red">{{alarmInfo.thisMonthMessageNum || 0}}</text>
<text class="stat-label">当月告警数</text>
</view>
</view>
<!-- 最新告警列表 -->
<view class="alarm-list">
<view class="alarm-list-title">最新告警列表</view>
<!-- 告警项 -->
<u-empty v-if="alarmList.length == 0" margin-top="50" text="告警列表为空" mode="list"></u-empty>
<view v-else class="alarm-item" v-for="(item, index) in alarmList" :key="index">
<view class="alarm-info">
<text class="alarm-name">{{ item.alarmName }}</text>
<text class="alarm-status">{{ item.state.text }}</text>
</view>
<view class="alarm-detail">
<view :class="['alarm-level', 'alarm-level-' + item.level]">{{ getAlarmName(item.level) }}</view>
<text class="alarm-time">{{$u.timeFormat(item.time, 'yyyy-mm-dd hh:MM:ss')}}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
deviceInfo:{
deviceNum:0,
deviceOnlineNum:0,
deviceOfflineNum:0,
todayMessageNum:0,
thisMonthMessageNum:0,
},
alarmInfo:{
todayMessageNum:0,
thisMonthMessageNum:0,
},
alarmLevelList:[
{
level: 1,
title: "超紧急"
},
{
level: 2,
title: "紧急"
},
{
level: 3,
title: "严重"
},
{
level: 4,
title: "一般"
},
{
level: 5,
title: "提醒"
}
],
alarmList: []
};
},
onLoad() {
this.getAlarmLevelList();
},
onShow() {
this.getDeviceInfo();
this.getDeviceMessageCount();
this.getAlarmMessageCount();
this.getAlarmList();
},
methods: {
getAlarmName(level) {
const foundItem = this.alarmLevelList.find(item => item.level === level);
return foundItem ? foundItem.title : '';
},
getAlarmLevelList(){
this.$api.iotsApi.getAlarmLevelList().then(res => {
if(res.status == 200){
this.alarmLevelList = res.result.levels || [];
}else{
this.$u.toast(res.message);
}
}, error => {
this.$u.toast('服务器开小差了呢,请您稍后再试')
})
},
getDeviceInfo(){
this.getDeviceCount('deviceNum',{})
this.getDeviceCount('deviceOnlineNum',{'terms[0].column':'state','terms[0].value':'online'})
this.getDeviceCount('deviceOfflineNum',{'terms[0].column':'state','terms[0].value':'offline'})
},
viewDeviceDetails() {
uni.switchTab({
url: '/pages/tabbar/device'
});
},
viewAlarmDetails() {
uni.navigateTo({
url: '/pages/alarm/statistics-detail'
});
},
getDeviceCount(type,params){
this.$api.iotsApi.getDeviceInstanceCount(params).then(res => {
if(res.status == 200){
this.deviceInfo[type] = res.result;
}else{
this.$u.toast(res.message);
}
}, error => {
this.$u.toast('服务器开小差了呢,请您稍后再试')
})
},
getDeviceMessageCount(){
this.$api.getDashboardSelect([
{
"dashboard": "device",
"object": "message",
"measurement": "quantity",
"dimension": "agg",
"group": "oneday",
"params": {
"time": "1d",
"format": "yyyy-MM-dd",
"from": "now-1d"
}
},
{
"dashboard": "device",
"object": "message",
"measurement": "quantity",
"dimension": "agg",
"group": "thisMonth",
"params": {
"time": "1M",
"format": "yyyy-MM",
"limit": 1,
"from": "now-1M"
}
}
]).then(res => {
if(res.status == 200){
if(res.result && Array.isArray(res.result)){
res.result.forEach((item)=>{
if(item.group === 'oneday'){
this.deviceInfo.todayMessageNum = item.data.value || 0;
}else if(item.group === 'thisMonth'){
this.deviceInfo.thisMonthMessageNum = item.data.value || 0;
}
})
}
}else{
this.$u.toast(res.message);
}
}, error => {
this.$u.toast('服务器开小差了呢,请您稍后再试')
})
},
getAlarmMessageCount(){
this.$api.getDashboardSelect([
{
"dashboard": "alarm",
"object": "record",
"measurement": "trend",
"dimension": "agg",
"group": "today",
"params": {
"time": "1d",
"format": "HH:mm:ss",
"from": "2025-04-24 00:00:00",
"to": "now"
}
},
{
"dashboard": "alarm",
"object": "record",
"measurement": "trend",
"dimension": "agg",
"group": "thisMonth",
"params": {
"time": "1M",
"format": "yyyy-MM",
"limit": 1,
"from": "now-1M"
}
}
]).then(res => {
if(res.status == 200){
if(res.result && Array.isArray(res.result)){
res.result.forEach((item)=>{
if(item.group === 'today'){
this.alarmInfo.todayMessageNum = item.data.value || 0;
}else if(item.group === 'thisMonth'){
this.alarmInfo.thisMonthMessageNum = item.data.value || 0;
}
})
}
}else{
this.$u.toast(res.message);
}
}, error => {
this.$u.toast('服务器开小差了呢,请您稍后再试')
})
},
getAlarmList(){
this.$api.iotsApi.getAlarmList({
"pageIndex": 0,
"pageSize": 5,
"sorts": [
{
"name": "alarmTime",
"order": "desc"
}
],
"terms": [
{
"terms": [
{
"type": "and",
"value": "%warning%",
"termType": "like",
"column": "state"
}
]
}
]
}).then(res => {
if(res.status == 200){
this.alarmList = res.result.data || [];
}else{
this.$u.toast(res.message);
}
}, error => {
this.$u.toast('服务器开小差了呢,请您稍后再试')
})
},
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: #f5f5f5;
padding: 20rpx;
min-height: 100%;
}
.stat-card {
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 2px solid #F9F9F9;
.header-left {
display: flex;
align-items: center;
.header-title {
font-size: 32rpx;
font-weight: 500;
margin-left: 10rpx;
color: #333;
}
}
.header-right {
display: flex;
align-items: center;
.detail-text {
font-size: 26rpx;
color: #999;
margin-right: 6rpx;
}
}
}
.stat-row {
display: flex;
justify-content: space-around;
margin-bottom: 20rpx;
padding: 30rpx;
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
.stat-value {
font-size: 40rpx;
font-weight: bold;
margin-bottom: 10rpx;
&.blue {
color: #2979ff;
}
&.red {
color: #ff5252;
}
}
.stat-label {
font-size: 24rpx;
color: #666;
}
}
}
.alarm-list {
padding: 30rpx;
.alarm-list-title {
font-size: 28rpx;
font-weight: 500;
color: #333;
margin-bottom: 20rpx;
}
.alarm-item {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.alarm-info {
.alarm-name {
font-size: 28rpx;
color: #333;
margin-bottom: 10rpx;
display: block;
}
.alarm-status {
font-size: 24rpx;
color: #999;
}
}
.alarm-detail {
text-align: right;
.alarm-level {
display: inline-block;
padding: 4rpx 16rpx;
border-radius: 6rpx;
font-size: 22rpx;
margin-bottom: 10rpx;
color: #fff;
&.alarm-level-1 {
background-color: #E50012;
}
&.alarm-level-2 {
background-color: #FF9457;
}
&.alarm-level-3 {
background-color: #FABD47;
}
&.alarm-level-4 {
background-color: #999999;
}
&.alarm-level-5 {
background-color: #BBBBBB;
}
}
.alarm-time {
font-size: 22rpx;
color: #999;
display: block;
}
}
}
}
</style>

405
pages/tabbar/device.vue Normal file
View File

@ -0,0 +1,405 @@
<template>
<view class="device-list-box">
<!-- 菜单 -->
<view class="device-search">
<u-dropdown class="sl-filter" border-bottom>
<u-dropdown-item v-model="productId" title="所属产品" height="750" :options="productList" @change="searchChange"></u-dropdown-item>
<u-dropdown-item v-model="deviceType" title="设备类型" :options="deviceTypeList" @change="searchChange"></u-dropdown-item>
</u-dropdown>
<u-search placeholder="请输入设备名称/编号搜索" class="uni-search-bar" v-model="searchVal" @custom="searchChange" @clear="searchChange" @search="searchChange"></u-search>
<u-tabs :list="tabsList" bar-width="187" :is-scroll="false" :current="tabIndex" @change="tabChange"></u-tabs>
</view>
<mescroll-body ref="mescrollRef" top="260" @init="mescrollInit" @down="downCallback" :up="upOption" @up="upCallback">
<view class="device-box">
<view class="device-info">
<view class="name">设备总数: <text style="font-size: 24rpx;">{{total}}</text></view>
</view>
<!-- 设备列表 -->
<view class="device-list">
<view class="device-item" v-for="(item, index) in dataList" :key="index" @click="goDetail(item)">
<!-- 设备名称和类型区域 -->
<view class="device-header">
<view class="device-title">{{ item.productName }}</view>
</view>
<!-- 设备图片和信息区域 -->
<view class="device-content">
<!-- 设备图片 -->
<view class="device-img-box">
<image v-if="item.photoUrl" :src="item.photoUrl" mode="aspectFit"></image>
<!-- #ifdef MP-WEIXIN -->
<image v-else src="http://static.drgyen.com/app/iot-ui-app/image/device/device-card.png" mode="aspectFit"></image>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<image v-else src="/static/app-plus/image/device/device-card.png" mode="aspectFit"></image>
<!-- #endif -->
</view>
<!-- 设备信息和状态 -->
<view class="device-info-box">
<!-- 设备状态 -->
<view class="device-status-dot" :class="item.state.value">
<text>{{ item.state.text }}</text>
</view>
<!-- 设备类型 -->
<view class="device-type">{{ item.deviceType.text }}</view>
</view>
</view>
<!-- 设备名称 -->
<view class="device-name-box">
<text class="device-name">{{ item.name }}</text>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js"
export default {
mixins: [MescrollMixin],
components: {
},
data() {
return {
upOption:{
noMoreSize: 4,
textNoMore: '---- 已经到底啦 ----',
empty:{
tip: '~ 搜索无数据 ~' //
}
},
tabbarIndex:1,
productId:null,
deviceType:null,
searchVal: '',
tabIndex: 0, // tab
productList:[],
deviceTypeList:[
{
label: '全部设备',
value: null,
},
{
label: '直连设备',
value: 'device',
},
{
label: '网关子设备',
value: 'childrenDevice',
},
{
label: '网关设备',
value: 'gateway',
}
],
tabsList:[{
name: '全部',
value: null
}, {
name: '禁用',
value: 'notActive'
}, {
name: '在线',
value: 'online'
}, {
name: '离线',
value: 'offline'
}],
dataList: [],
total: 0,
imgPath:'',
}
},
onLoad() {
this.getConfigPath();
this.getProductList();
this.getUserInfo();
},
methods: {
getUserInfo(){
this.$api.getUserInfo({}).then(res => {
console.log("获取用户信息",res)
if(res.status == 200){
uni.setStorageSync('userInfo',res.result)
}
}, error => {
})
},
goDetail(obj){
// console.log("",obj)
uni.navigateTo({
url:'./device-detail?id=' + obj.id + '&devName=' + obj.devName
})
},
getConfigPath(){
let configList = uni.getStorageSync('configList');
let configIndex = uni.getStorageSync('configIndex');
this.imgPath = configList[configIndex].protocol + configList[configIndex].address;
},
getDeviceTypeLabel(type) {
const typeMap = {
'device': '直连设备',
'childrenDevice': '网关子设备',
'gateway': '网关设备'
};
return typeMap[type] || '网关设备';
},
//
tabChange(e) {
this.tabIndex = e;
this.mescroll.resetUpScroll() //
},
searchChange(e) {
// this.searchVal = res.value
this.dataList = []// ,
this.mescroll.resetUpScroll() //
},
getProductList(){
this.$api.iotsApi.getProductList({paging:false}).then(res => {
if(res.status == 200 && res.result.length>0){
let productList = res.result.map((item)=>{
return {
label:item.name,
value:item.id
}
})
this.productList = [{
label: '全部产品',
value: null,
},...productList];
}else{
this.productList = [{
label: '全部产品',
value: null,
}];
}
}, error => {
})
},
upCallback(page) {
let pageNum = page.num-1;
let params = {
"pageIndex": pageNum,
"pageSize": page.size,
"sorts": [
{
"name": "createTime",
"order": "desc"
},
{
"name": "name",
"order": "desc"
}
],
"terms": [{
"terms": [
{ "type": "and", "column": "productId", "termType": "eq", "value": this.productId },
{ "type": "and", "column": "state", "termType": "eq", "value": this.tabsList[this.tabIndex].value },
{ "type": "and", "column": "deviceType", "termType": "eq", "value": this.deviceType }
]
},
// id/name
{
"terms": [
{ "type": "or", "column": "id", "termType": "like", "value": "%"+this.searchVal+"%" },
{ "type": "or", "column": "name", "termType": "like", "value": "%"+this.searchVal+"%" }
]
}],
}
this.$api.iotsApi.getDeviceList(params).then(res => {
if(res.status === 200){
let curPageData = res.result && res.result.data;
let curPageLen = curPageData && curPageData.length || 0;
let totalSize = res.result.total;
this.$nextTick(()=>{
// ;
this.mescroll.endBySize(curPageLen, totalSize);
})
//
if(page.num == 1) this.dataList = []; //
this.dataList = curPageLen > 0 && this.dataList.concat(curPageData); //
this.total = totalSize;
}else{
this.$u.toast(res.msg);
this.mescroll.endErr();
}
}, error => {
console.error("API Error:", error);
uni.showToast({
title: '请求失败,请稍后重试',
icon: 'none'
});
this.mescroll.endErr();
})
}
}
}
</script>
<style>
page{
background-color: #f5f5f5;
}
</style>
<style lang="scss" scoped>
/deep/.u-search{
padding: 19rpx 20rpx;
border-bottom: 1px solid #f4f6f8;
}
.device-list-box{
width: 100%;
height: 100%;
background-color: #f5f5f5;
.device-search{
position: fixed;
top: 0;
left: 0;
box-sizing: border-box;
height: 260rpx;
background-color: #fff;
width: 100%;
z-index: 999;
}
.device-box{
.device-info{
text-align: right;
padding: 20rpx;
padding-bottom: 0;
.name{
font-size: 28rpx;
font-weight: bold;
color: #333333;
}
}
.device-list {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
.device-item {
width: 345rpx;
background: #fff;
border-radius: 20rpx;
margin-bottom: 20rpx;
padding: 20rpx;
box-sizing: border-box;
&:nth-child(2n-1) {
margin-right: 20rpx;
}
//
.device-header {
margin-bottom: 15rpx;
.device-title {
font-size: 24rpx;
color: #666;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
//
.device-content {
display: flex;
align-items: center;
margin-bottom: 15rpx;
.device-img-box {
width: 120rpx;
height: 120rpx;
display: flex;
justify-content: center;
align-items: center;
image {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.device-info-box {
flex: 1;
padding-left: 36rpx;
display: flex;
flex-direction: column;
justify-content: center;
//
.device-status-dot {
display: flex;
align-items: center;
font-size: 24rpx;
margin-bottom: 10rpx;
&::before {
content: '';
display: inline-block;
width: 12rpx;
height: 12rpx;
border-radius: 50%;
margin-right: 6rpx;
}
&.online {
color: #10CC70;
&::before {
background-color: #10CC70;
}
}
&.offline {
color: #E50012;
&::before {
background-color: #E50012;
}
}
&.notActive {
color: #FF9000;
&::before {
background-color: #FF9000;
}
}
}
//
.device-type {
font-size: 24rpx;
color: #666;
}
}
}
//
.device-name-box {
.device-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
display: block;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -46,7 +46,7 @@
</view>
</view>
</view>
<view class="my-item" @click="goItem('none')">
<!-- <view class="my-item" @click="goItem('none')">
<view class="my-item-left">
<view class="my-item-icon color-blue">
<view class="iconfont icon-mobile"></view>
@ -63,7 +63,7 @@
<view class="iconfont icon-xiangyou1"></view>
</view>
</view>
</view>
</view> -->
<view class="my-item">
<view class="my-item-left">
<view class="my-item-icon color-yellow">
@ -172,12 +172,13 @@
},
onShow() {
let userInfo = uni.getStorageSync('userInfo');
console.log("userInfo",userInfo)
if(userInfo){
this.userInfo = {
name:userInfo.username,
company:userInfo.realName,
name:userInfo.name,
company:userInfo.realName || '物联网平台',
imgPath:userInfo.avatar,
phone:phoneHide(userInfo.mobile)
// phone:phoneHide(userInfo.telephone)
}
}
},
@ -238,16 +239,32 @@
if (res.confirm) {
console.log('用户点击确定');
// this.$store.commit('logout');
this.$store.commit('setTokenKey', '');
uni.$u.toast('退出登录成功');
uni.removeStorageSync('token')
uni.removeStorageSync('expire')
setTimeout(()=>{
uni.reLaunch({
url:"../tabbar/login"
})
return;
},200)
this.$api.userLogout({}).then(res => {
if(res.status == 200){
this.$store.commit('setTokenKey', '');
uni.$u.toast('退出登录成功');
uni.removeStorageSync('token')
uni.removeStorageSync('expire')
setTimeout(()=>{
uni.reLaunch({
url:"../tabbar/login"
})
return;
},200)
}
}, error => {
this.$store.commit('setTokenKey', '');
uni.$u.toast('退出登录成功');
uni.removeStorageSync('token')
uni.removeStorageSync('expire')
setTimeout(()=>{
uni.reLaunch({
url:"../tabbar/login"
})
return;
},200)
})
} else if (res.cancel) {
console.log('用户点击取消');
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

BIN
static/image/tabbar/iot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 527 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 493 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 510 B