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

View File

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

View File

@ -14,7 +14,7 @@ export default {
getAppConfig(data){ getAppConfig(data){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.customRequest({ request.customRequest({
url: '/admin/admin/site/config', url: '/system/config/front',
method: 'GET', method: 'GET',
},data) },data)
.then((res) =>{ .then((res) =>{
@ -58,7 +58,7 @@ export default {
passwordLogin(data){ passwordLogin(data){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.customRequest({ request.customRequest({
url: '/user/admin/site/accountLogin', url: '/authorize/login',
method: 'POST', method: 'POST',
},data) },data)
.then((res) =>{ .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){ sendSms(data){
@ -103,7 +117,7 @@ export default {
getUserInfo(data){ getUserInfo(data){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.TokenRequest({ request.TokenRequest({
url: '/user/admin/member/info', url: '/user/detail',
method: 'GET', method: 'GET',
},data) },data)
.then((res) =>{ .then((res) =>{
@ -134,7 +148,22 @@ export default {
updatePwd(data){ updatePwd(data){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.TokenRequest({ 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', method: 'POST',
},data) },data)
.then((res) =>{ .then((res) =>{

View File

@ -1,6 +1,21 @@
import request from "../../request.js" import request from "../../request.js"
export default { 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){ getDeviceCount(data){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -20,8 +35,8 @@ export default {
getProductList(data){ getProductList(data){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.TokenRequest({ request.TokenRequest({
url: '/iot/admin/product/list', url: '/device-product/_query/no-paging',
method: 'GET', method: 'POST',
},data) },data)
.then((res) =>{ .then((res) =>{
resolve(res); resolve(res);
@ -35,7 +50,7 @@ export default {
getDeviceList(data){ getDeviceList(data){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.TokenRequest({ request.TokenRequest({
url: '/iot/admin/device/list', url: '/device-instance/_query',
method: 'POST', method: 'POST',
},data) },data)
.then((res) =>{ .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){ getAlarmLevelList(data){
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request.TokenRequest({ request.TokenRequest({
url: '/iot/admin/alarm/level/list', url: '/alarm/config/default/level',
method: 'GET', method: 'GET',
},data) },data)
.then((res) =>{ .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){ 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" // wsUrl = "ws://iotos-ui-dev.iotos.192.168.10.243.nip.io:32764"
// configurationUrl = "http://192.168.18.139:8855" // configurationUrl = "http://192.168.18.139:8855"
// configurationhtmlUrl = "http://hceditor-2d-dev.hceditor.192.168.10.243.nip.io:30405" // configurationhtmlUrl = "http://hceditor-2d-dev.hceditor.192.168.10.243.nip.io:30405"
url = 'https://miot.gkiiot.com' url = 'http://192.168.1.17:8848'
wsUrl = 'wss://miot.gkiiot.com' wsUrl = 'ws://192.168.1.17:8848'
configurationUrl = 'https://2d.gkiiot.com/prod-api' configurationUrl = 'https://2d.gkiiot.com/prod-api'
configurationhtmlUrl = 'https://2d.gkiiot.com' configurationhtmlUrl = 'https://2d.gkiiot.com'
}else{ }else{

View File

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

View File

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

View File

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

View File

@ -1,46 +1,91 @@
<template> <template>
<view class="config-box"> <view class="container">
<view class="config-list" v-if="configList.length"> <!-- 域名列表 -->
<view class="config-item" v-for="(item,index) in configList" :key="item.id" @click="changeConfig(index)"> <view class="domain-list" v-if="configList.length">
<!-- <u-icon v-if="configIndex==index" class="icon-checkbox-mark" name="checkbox-mark" color="#ff0000" size="50"></u-icon> --> <view
<view class="acitve-box" v-if="configIndex==index"> class="domain-item"
已选择 :class="{'domain-item-active': configIndex === index}"
</view> v-for="(item, index) in configList"
<view class="congfig-txt"> :key="item.id"
{{item.protocol+item.address}} @click="changeConfig(index)"
</view> >
<view class="operate-box"> <view class="domain-item-content">
<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="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> </view>
</view> </view>
<!-- 空状态 -->
<u-empty margin-top="200" v-else text="没有域名,请添加" mode="list"></u-empty> <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> </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="slot-content">
<!-- 网络协议选择 -->
<view class="form-item"> <view class="form-item">
<view class="form-label"> <view class="form-label">
网络协议 网络协议
</view> </view>
<view class="form-value" @click="protocolShow=true"> <view class="form-value" @click="protocolShow=true">
{{form.protocol?form.protocol:'请选择'}} {{ form.protocol ? form.protocol : '请选择' }}
<view class="iconfont icon-xiangyou1"></view> <view class="arrow-icon">
<u-icon name="arrow-down" size="28"></u-icon>
</view>
</view> </view>
</view> </view>
<!-- 域名输入 -->
<view class="form-item"> <view class="form-item">
<view class="form-label"> <view class="form-label">
域名或ip 域名
</view> </view>
<view class="form-value"> <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> </view>
</view> </view>
</u-modal> </u-modal>
<!-- 协议选择器 -->
<u-select v-model="protocolShow" :list="protocolList" @confirm="confirmProtocol"></u-select> <u-select v-model="protocolShow" :list="protocolList" @confirm="confirmProtocol"></u-select>
<!-- 提示框 -->
<u-toast ref="uToast" /> <u-toast ref="uToast" />
</view> </view>
</template> </template>
@ -49,122 +94,122 @@
export default { export default {
data() { data() {
return { return {
configIndex:0, configIndex: 0,
configList:[], configList: [],
form:{ form: {
id:'', id: '',
protocol:'', protocol: '',
address:'', address: '',
}, },
protocolShow:false, protocolShow: false,
protocolList:[ protocolList: [
{
value: 'http://',
label: 'http://'
},
{ {
value: 'https://', value: 'https://',
label: 'https://' label: 'HTTPS'
},
{
value: 'http://',
label: 'HTTP'
} }
], ],
show:false, show: false,
}; };
}, },
onLoad() { onLoad() {
// 使https // 使https
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
this.protocolList = [ // this.protocolList = [
{ // {
value: 'https://', // value: 'https://',
label: 'https://' // label: 'HTTPS'
} // }
] // ]
// #endif // #endif
this.getConfigList(); this.getConfigList();
}, },
methods:{ methods: {
getConfigList(){ getConfigList() {
uni.getStorage({ uni.getStorage({
key:'configList', key: 'configList',
}).then(res => { }).then(res => {
// //
if(res.length==2){ if (res.length == 2) {
this.configList = res[1].data; this.configList = res[1].data;
} }
return uni.getStorage({ return uni.getStorage({
key:'configIndex', key: 'configIndex',
}) })
}).then(res => { }).then(res => {
console.log("configIndex",res) console.log("configIndex", res)
if(res.length==2){ if (res.length == 2) {
this.configIndex = res[1].data; this.configIndex = res[1].data;
} }
}) })
}, },
changeConfig(i){ changeConfig(i) {
console.log("index",i) console.log("index", i)
if(i !== this.configIndex){ if (i !== this.configIndex) {
this.configIndex = i; this.configIndex = i;
uni.setStorage({ uni.setStorage({
key:'configIndex', key: 'configIndex',
data: i, data: i,
}).then(res => { }).then(res => {
console.log("修改索引") console.log("修改索引")
}) })
} }
}, },
verification(){ verification() {
if(!this.form.protocol){ if (!this.form.protocol) {
this.$refs.uToast.show({ this.$refs.uToast.show({
title: '请先选择协议!', title: '请先选择协议!',
type: 'error' type: 'error'
}) })
return false; return false;
}else if(!this.form.address){ } else if (!this.form.address) {
this.$refs.uToast.show({ this.$refs.uToast.show({
title: '请先输入地址', title: '请先输入地址',
type: 'error', type: 'error',
}) })
return false; 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({ this.$refs.uToast.show({
title: '请确认输入地址正确', title: '请确认输入地址正确',
type: 'error', type: 'error',
}) })
return false; return false;
}else{ } else {
return true; return true;
} }
}, },
openAdd(){ openAdd() {
if(this.configList.length == 0){ if (this.configList.length == 0) {
this.form = { this.form = {
id:'', id: '',
protocol:'https://', protocol: 'http://',
address:'cloud.iot-fast.com', address: '192.168.1.17:8848',
} }
}else{ } else {
this.form = { this.form = {
id:'', id: '',
protocol:'', protocol: '',
address:'', address: '',
} }
} }
this.show = true; this.show = true;
}, },
addConfig(){ addConfig() {
console.log("添加页面",this.form) console.log("添加页面", this.form)
if(this.verification()){ if (this.verification()) {
if(this.form.id===''){ if (this.form.id === '') {
// //
this.configList.push({ this.configList.push({
id:this.$u.guid(32), id: this.$u.guid(32),
protocol:this.form.protocol, protocol: this.form.protocol,
address:this.form.address, address: this.form.address,
}) })
uni.setStorage({ uni.setStorage({
key:'configList', key: 'configList',
data: this.configList, data: this.configList,
}).then(res => { }).then(res => {
this.$refs.uToast.show({ this.$refs.uToast.show({
@ -172,9 +217,9 @@
type: 'success', type: 'success',
}) })
}) })
if(this.configList.length==1){ if (this.configList.length == 1) {
uni.setStorage({ uni.setStorage({
key:'configIndex', key: 'configIndex',
data: 0, data: 0,
}).then(res => { }).then(res => {
console.log("添加第一个域名索引为0") console.log("添加第一个域名索引为0")
@ -182,18 +227,18 @@
} }
this.show = false; this.show = false;
this.getConfigList() this.getConfigList()
console.log("configList",this.configList) console.log("configList", this.configList)
}else{ } else {
// //
this.configList.map(item=>{ this.configList.map(item => {
if(item.id == this.form.id){ if (item.id == this.form.id) {
item.protocol=this.form.protocol; item.protocol = this.form.protocol;
item.address=this.form.address; item.address = this.form.address;
} }
return item; return item;
}) })
uni.setStorage({ uni.setStorage({
key:'configList', key: 'configList',
data: this.configList, data: this.configList,
}).then(res => { }).then(res => {
this.$refs.uToast.show({ this.$refs.uToast.show({
@ -204,28 +249,29 @@
this.show = false; this.show = false;
this.getConfigList() this.getConfigList()
} }
}else{ } else {
this.$refs.uModal.clearLoading(); this.$refs.uModal.clearLoading();
} }
}, },
confirmProtocol(e){ confirmProtocol(e) {
this.form.protocol = e[0].value; this.form.protocol = e[0].value;
}, },
openEdit(index){ openEdit(index) {
this.form = this.configList[index]; console.log("编辑",index)
this.form = JSON.parse(JSON.stringify(this.configList[index]));
this.show = true; this.show = true;
}, },
delConfig(index){ delConfig(index) {
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: '确认要删除域名吗?', content: '确认要删除域名吗?',
success: (res)=>{ success: (res) => {
if (res.confirm) { if (res.confirm) {
console.log('用户点击确定'); console.log('用户点击确定');
this.configList.splice(index, 1); this.configList.splice(index, 1);
uni.setStorage({ uni.setStorage({
key:'configList', key: 'configList',
data: this.configList, data: this.configList,
}).then(res => { }).then(res => {
this.$refs.uToast.show({ this.$refs.uToast.show({
@ -233,19 +279,19 @@
type: 'success', type: 'success',
}) })
}) })
if(this.configIndex<index){ if (this.configIndex < index) {
}else{ } else {
if(this.configIndex>index){ if (this.configIndex > index) {
this.configIndex = this.configIndex - 1; this.configIndex = this.configIndex - 1;
}else if(this.configIndex==index){ } else if (this.configIndex == index) {
if(this.configList.length==0){ if (this.configList.length == 0) {
this.configIndex = -1; this.configIndex = -1;
}else{ } else {
this.configIndex = 0; this.configIndex = 0;
} }
} }
uni.setStorage({ uni.setStorage({
key:'configIndex', key: 'configIndex',
data: this.configIndex, data: this.configIndex,
}).then(res => { }).then(res => {
console.log("修改索引") console.log("修改索引")
@ -261,129 +307,130 @@
} }
} }
</script> </script>
<style>
page{
background: #f5f5f5;
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.color-blue{ page {
color: $mainColor; background-color: #F5F7FA;
}
.color-red{
color: red;
}
.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;
}
}
}
}
} }
.slot-content{ .container {
padding: 20rpx 30rpx; display: flex;
.form-item{ flex-direction: column;
min-height: 100vh;
background-color: #F5F7FA;
padding: 20rpx;
}
.domain-list {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-bottom: 120rpx; //
}
.domain-item {
background-color: #ffffff;
border-radius: 12rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
&-content {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; padding: 35rpx 30rpx;
margin-bottom: 10rpx; }
.form-label{ &-active {
width: 170rpx; background-color: #f0f7ff;
} border: 1rpx solid #2979ff;
.form-value{ }
flex: 1; .domain-item-btn{
background: #f0f0f0; margin-left: 30rpx;
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;
} .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);
text {
color: #2979ff;
}
}
&.http {
background-color: rgba(255, 152, 0, 0.1);
text {
color: #ff9800;
} }
} }
} }
.btn-box{ .domain-name {
position: fixed; flex: 1;
width: 100%;
height: 130rpx; text {
padding: 10rpx; font-size: 28rpx;
padding-bottom: 20rpx;
box-sizing: border-box;
bottom: 0;
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; .action-buttons {
display: flex;
gap: 30rpx;
view {
padding: 10rpx;
}
}
.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> </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>
</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-left">
<view class="my-item-icon color-blue"> <view class="my-item-icon color-blue">
<view class="iconfont icon-mobile"></view> <view class="iconfont icon-mobile"></view>
@ -63,7 +63,7 @@
<view class="iconfont icon-xiangyou1"></view> <view class="iconfont icon-xiangyou1"></view>
</view> </view>
</view> </view>
</view> </view> -->
<view class="my-item"> <view class="my-item">
<view class="my-item-left"> <view class="my-item-left">
<view class="my-item-icon color-yellow"> <view class="my-item-icon color-yellow">
@ -172,12 +172,13 @@
}, },
onShow() { onShow() {
let userInfo = uni.getStorageSync('userInfo'); let userInfo = uni.getStorageSync('userInfo');
console.log("userInfo",userInfo)
if(userInfo){ if(userInfo){
this.userInfo = { this.userInfo = {
name:userInfo.username, name:userInfo.name,
company:userInfo.realName, company:userInfo.realName || '物联网平台',
imgPath:userInfo.avatar, imgPath:userInfo.avatar,
phone:phoneHide(userInfo.mobile) // phone:phoneHide(userInfo.telephone)
} }
} }
}, },
@ -238,16 +239,32 @@
if (res.confirm) { if (res.confirm) {
console.log('用户点击确定'); console.log('用户点击确定');
// this.$store.commit('logout'); // this.$store.commit('logout');
this.$store.commit('setTokenKey', ''); this.$api.userLogout({}).then(res => {
uni.$u.toast('退出登录成功'); if(res.status == 200){
uni.removeStorageSync('token') this.$store.commit('setTokenKey', '');
uni.removeStorageSync('expire') uni.$u.toast('退出登录成功');
setTimeout(()=>{ uni.removeStorageSync('token')
uni.reLaunch({ uni.removeStorageSync('expire')
url:"../tabbar/login" setTimeout(()=>{
}) uni.reLaunch({
return; url:"../tabbar/login"
},200) })
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) { } else if (res.cancel) {
console.log('用户点击取消'); 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