Commit 19bc7b16c288841054b8807daa384631d6359210

Authored by xp.Huang
2 parents 32c068e5 ab30c83c

Merge branch 'main_dev'

# Conflicts:
#	pages/alarm/alarm.vue
Showing 47 changed files with 2581 additions and 1944 deletions

Too many changes to show.

To preserve performance only 47 of 106 files are displayed.

1 <script> 1 <script>
2 import store from '@/store'; 2 import store from '@/store';
3 - // import {  
4 - // scene  
5 - // } from '@/config/common';  
6 3
7 export default { 4 export default {
8 //设置全局变量,解绑时从这里取 5 //设置全局变量,解绑时从这里取
@@ -10,27 +7,12 @@ @@ -10,27 +7,12 @@
10 openId: '' 7 openId: ''
11 }, 8 },
12 onLaunch(e) { 9 onLaunch(e) {
13 - //取出缓存数据  
14 - // store.commit('setCacheData');  
15 - //获取二维码信息  
16 - // scene(e);  
17 // #ifdef APP-PLUS 10 // #ifdef APP-PLUS
18 uni.reLaunch({ 11 uni.reLaunch({
19 url: '/pages/index/splash' 12 url: '/pages/index/splash'
20 }) 13 })
21 // #endif 14 // #endif
22 }, 15 },
23 - onShow(e) {  
24 - // #ifdef MP  
25 - // mpUpData(); //检测小程序更新  
26 - // #endif  
27 - // #ifdef APP-PLUS  
28 - // uni.reLaunch({  
29 - // url: '/pages/index/splash'  
30 - // })  
31 - // #endif  
32 - },  
33 - onHide() {},  
34 onUnload(){ 16 onUnload(){
35 uni.setStorageSync('getConfiguration', { 17 uni.setStorageSync('getConfiguration', {
36 isConfiguration: false 18 isConfiguration: false
  1 +<template>
  2 + <view class="alarm-detail-page">
  3 + <!-- 公共组件-每个页面必须引入 -->
  4 + <public-module></public-module>
  5 + <view class="alarm-detail-column">
  6 + <view class="u-flex detail-column">
  7 + <view class="column-line" v-for="(item,index) in alarmDetail" :key="index">
  8 + <view class="column">
  9 + <text class="text-org-bold">{{item.label}}</text>
  10 + <text class="text-device-muted text-clip alarm-text"
  11 + :style="{color:hasColor.includes(item.label)?'#DE4437':''}">
  12 + {{item.label===hasColor[0]? setAlarmStatus(item.value):item.label===hasColor[1]?setAlarmSeverity(item.value):
  13 + item.label==='触发值:'?formatAlarmValueText:item.label==='触发条件:'?formatAlarmConditionText
  14 + :item.label==='触发属性:'?formatAttrText:item.label==='告警设备:'?formatDeviceText:item.value}}
  15 + </text>
  16 + </view>
  17 + <view class="bottom-line"></view>
  18 + </view>
  19 + </view>
  20 + </view>
  21 + <!-- #ifdef MP -->
  22 + <view class="handle-result text-org-bold" style="">处理结果</view>
  23 + <view class="hanle-main">
  24 + <u--form :label-style="{ 'font-size': '0rpx' }" style="padding-left: 26rpx;" labelPosition="left"
  25 + :model="formModel" ref="form1">
  26 + <u-form-item label="." prop="result" ref="item3">
  27 + <view style="position: relative;left: -60rpx;">
  28 + <u--textarea border="none" height="96" placeholder="请输入处理结果" v-model="formModel.result" count>
  29 + </u--textarea>
  30 + </view>
  31 + </u-form-item>
  32 + </u--form>
  33 + </view>
  34 + <!-- #endif -->
  35 + <!-- #ifdef APP-PLUS -->
  36 + <view class="handle-result text-org-bold">处理结果</view>
  37 + <view class="hanle-main">
  38 + <view>
  39 + <u--textarea border="none" height="96" placeholder="请输入处理结果" v-model="formModel.result" count>
  40 + </u--textarea>
  41 + </view>
  42 + </view>
  43 + <!-- #endif -->
  44 + <view class="bottom-button">
  45 + <view v-if="handleText.includes(alarmDetail[8].value)" class="u-flex button-item">
  46 + <u-button @click="handleSubmit" type="primary" shape="circle" text="处理"></u-button>
  47 + </view>
  48 + <view v-if="clearText.includes(alarmDetail[8].value)" class="u-flex button-item">
  49 + <u-button @click="handleRemove" type="error" shape="circle" text="清除"></u-button>
  50 + </view>
  51 + </view>
  52 + </view>
  53 +</template>
  54 +
  55 +<script>
  56 + import {
  57 + mapActions
  58 + } from 'vuex'
  59 + import api from '@/api/index.js'
  60 + import {
  61 + alarmSeverity,
  62 + alarmStatus,
  63 + operationNumberOrDate,
  64 + operationString,
  65 + operationBoolean
  66 + } from '@/pages/alarm/config/data.js';
  67 + import {
  68 + useShowToast,
  69 + useNavigateBack
  70 + } from '@/plugins/utils.js'
  71 +
  72 + export default {
  73 + data() {
  74 + return {
  75 + handleText: ['ACTIVE_UNACK', 'CLEARED_UNACK'],
  76 + clearText: [ 'ACTIVE_ACK'],
  77 + hasColor: ['告警级别:', '告警状态:'],
  78 + alarmSeverity,
  79 + alarmStatus,
  80 + operationNumberOrDate,
  81 + operationString,
  82 + operationBoolean,
  83 + formModel: {
  84 + result: ''
  85 + },
  86 + detailId: '',
  87 + alarmDetail: [],
  88 + formatDeviceText: '',
  89 + formatAlarmValueText: '',
  90 + formatAlarmConditionText: '',
  91 + formatAttrText:'',
  92 + };
  93 + },
  94 + async onLoad(e) {
  95 + if (e.data !== null) {
  96 + let params = JSON.parse(decodeURIComponent(e.data));
  97 + const {deviceName,severity,organizationName,details,type,createdTime,status,id} = params
  98 +
  99 + this.detailId = id
  100 + this.alarmDetail = [{label: '告警场景:',value: type},{label: '告警级别:',value: severity},{label: '所属组织:',value: organizationName},{label: '告警设备:',value: ''},{label:'触发属性:',value:''},{label: '触发条件:',value: ''},{label: '触发值:',value: ''},{label: '告警时间:',value: createdTime},{label: '告警状态:',value: status},]
  101 +
  102 + const keys = Object.keys(details)
  103 + const dataFormat = await this.handleAlarmDetailFormat(keys);
  104 + this.formatAlarmDevice(details,dataFormat)
  105 + this.formatAlarmValue(details,dataFormat)
  106 + this.formatAlarmCondition(details,dataFormat)
  107 + this.formatAttr(details,dataFormat)
  108 + }
  109 + // 隐藏原生的tabbar
  110 + uni.hideTabBar();
  111 + },
  112 + methods: {
  113 + ...mapActions(['updateBadgeTotal']),
  114 + setAlarmStatus(value) {
  115 + return this.alarmSeverity.find(item => item.value === value).label
  116 + },
  117 + setAlarmSeverity(value) {
  118 + return this.alarmStatus.find(item => item.value === value).label
  119 + },
  120 + returnPrevPage(title) {
  121 + useShowToast(title)
  122 + let pages = getCurrentPages(); //获取所有页面栈实例列表
  123 + let nowPage = pages[pages.length - 1]; //当前页页面实例
  124 + let prevPage = pages[pages.length - 2]; //上一页页面实例
  125 + prevPage.$vm.detailStatus = true;
  126 + },
  127 + async handleSubmit() {
  128 + if (this.formModel.result == '') return uni.$u.toast('请输入处理结果');
  129 + const res = await api.alarmApi.postAlarmAckApi(this.detailId)
  130 + if (res == '') {
  131 + this.returnPrevPage('处理成功~')
  132 + setTimeout(() => {
  133 + useNavigateBack(1)
  134 + }, 500);
  135 + }
  136 + },
  137 + async handleRemove() {
  138 + const res = await api.alarmApi.postAlarmClearApi(this.detailId)
  139 + if (res == '') {
  140 + this.returnPrevPage('清除成功~')
  141 + setTimeout(async () => {
  142 + useNavigateBack(1)
  143 + const res = await uni.$u.http.get('/yt/homepage/app?login=true');
  144 + if (res) {
  145 + //异步实时更新告警徽标数
  146 + await this.updateBadgeTotal(res.totalAlarm?.activedAlarm);
  147 + }
  148 + }, 500);
  149 + }
  150 + },
  151 + //触发值处理
  152 + formatAlarmValue(e,dataFormat) {
  153 + const keys = Object.keys(e)
  154 + const values = keys.reduce((acc, curr) => {
  155 + const items = e[curr]?.triggerData
  156 + dataFormat.forEach((dataItem => {
  157 + if (dataItem.tbDeviceId === curr) {
  158 + if(!items?.realValue) return
  159 + acc.push(
  160 + `${items.realValue}`
  161 + )
  162 + }
  163 + }))
  164 + return acc
  165 + }, [])
  166 + this.formatAlarmValueText = values.join(',')
  167 + },
  168 + //触发条件处理
  169 + formatAlarmCondition(e) {
  170 + const keys = Object.keys(e)
  171 + const values = keys.reduce((acc, curr) => {
  172 + acc.push({
  173 + login: e[curr]?.triggerData?.logic,
  174 + logicValue: e[curr]?.triggerData?.logicValue
  175 + })
  176 + return acc
  177 + }, [])
  178 + const operation = [...operationNumberOrDate, ...operationString, ...operationBoolean]
  179 + const format = values.map(item => {
  180 + const findOperation = operation.find(findItem => findItem.value === item.login)?.symbol
  181 + return findOperation + item.logicValue
  182 + })
  183 + this.formatAlarmConditionText = format.filter(Boolean).join(',')
  184 + },
  185 + // 触发属性
  186 + formatAttr(e,dataFormat){
  187 + const keys = Object.keys(e)
  188 + const values = keys.reduce((acc, curr) => {
  189 + const items = e[curr]?.triggerData
  190 + dataFormat.forEach((dataItem => {
  191 + if (dataItem.tbDeviceId === curr) {
  192 + const findAttribute = dataItem.attribute.find(findItem => findItem.identifier === items?.key)
  193 + if(!findAttribute?.name && !items?.key) return
  194 + acc.push(`${findAttribute?.name || items?.key || ' '}`)
  195 + }
  196 + }))
  197 + return acc
  198 + }, [])
  199 + this.formatAttrText = values.join(',')
  200 + },
  201 + //告警设备处理
  202 + async formatAlarmDevice(e,dataFormat) {
  203 + if (!dataFormat) this.formatDeviceText = ''
  204 + if (Array.isArray(dataFormat) && dataFormat.length === 0) this.formatDeviceText = ''
  205 + this.formatDeviceText = dataFormat.map(item => item.name).join(',')
  206 + },
  207 + async handleAlarmDetailFormat(keys) {
  208 + const temp = [];
  209 + for (let item of keys) {
  210 + if (item === 'key' || item === 'data') return; //旧数据则终止
  211 + const deviceDetailRes = await api.deviceApi.getDeviceDetail(item);
  212 + const { deviceProfileId } = deviceDetailRes;
  213 + if (!deviceProfileId) return;
  214 + const attributeRes = await api.deviceApi.getAttribute(deviceProfileId);
  215 + const dataFormat = this.handleDataFormat(deviceDetailRes, attributeRes);
  216 + temp.push(dataFormat);
  217 + }
  218 + return temp;
  219 + },
  220 + handleDataFormat(deviceDetail, attributes) {
  221 + const { name,tbDeviceId } = deviceDetail;
  222 + const attribute = attributes.map((item) => ({
  223 + identifier: item.identifier,
  224 + name: item.name,
  225 + detail: item.detail
  226 + }));
  227 + return {
  228 + name,
  229 + tbDeviceId,
  230 + attribute,
  231 + };
  232 + }
  233 + }
  234 + };
  235 +</script>
  236 +
  237 +<style lang="scss" scoped>
  238 + @import './static/alarmDetail.scss';
  239 +
  240 + /deep/ .u-button--primary {
  241 + background-color: #377dff !important;
  242 + border-color: #377dff !important;
  243 + }
  244 +</style>
alarm-subpackage/alarm-detail/static/alarmDetail.scss renamed from alarmSubPage/alarmDetailPage/static/alarmDetail.scss
@@ -6,57 +6,67 @@ @@ -6,57 +6,67 @@
6 .alarm-detail-column { 6 .alarm-detail-column {
7 border-radius: 10px; 7 border-radius: 10px;
8 width: 688rpx; 8 width: 688rpx;
9 - height: 573rpx; 9 + height: 780rpx;
10 background-color: #ffffff; 10 background-color: #ffffff;
11 .detail-column { 11 .detail-column {
12 - height: 573rpx;  
13 - justify-content: space-between; 12 + width:750rpx;
14 flex-direction: column; 13 flex-direction: column;
15 align-items: center; 14 align-items: center;
16 - .column {  
17 - flex-direction: row; 15 + .column-line{
18 display:flex; 16 display:flex;
19 - // justify-content: space-between;  
20 - margin-top: 10rpx;  
21 - line-height: 68rpx;  
22 - width: 614rpx;  
23 - height: 90rpx;  
24 - text-align: left;  
25 - border-bottom: 0.1rpx solid #f0f0f0;  
26 - .device-name{  
27 - width:222rpx;  
28 - }  
29 - .text {  
30 - color: #333333;  
31 - font-size: 15px;  
32 - }  
33 - .image {  
34 - width: 30rpx;  
35 - height: 30rpx;  
36 - }  
37 - .text-alarm-level {  
38 - color: #333333;  
39 - font-size: 14px;  
40 - }  
41 - .text-alarm-level-lg {  
42 - color: #333333;  
43 - font-size: 15px;  
44 - }  
45 - .text-alarm-lg {  
46 - color: #666666;  
47 - font-size: 14px;  
48 - }  
49 - .text-alarm-status {  
50 - color: #de4437;  
51 - font-size: 14px; 17 + flex-direction: column;
  18 + .column {
  19 + display:flex;
  20 + margin-top: 10rpx;
  21 + line-height: 68rpx;
  22 + width:700rpx;
  23 + height: 74rpx;
  24 + align-items: center;
  25 + .device-name{
  26 + width:222rpx;
  27 + }
  28 + .alarm-text{
  29 + width:460rpx;
  30 + overflow-x: scroll;
  31 + }
  32 +
  33 + .text {
  34 + color: #333333;
  35 + font-size: 15px;
  36 + }
  37 + .image {
  38 + width: 30rpx;
  39 + height: 30rpx;
  40 + }
  41 + .text-alarm-level {
  42 + color: #333333;
  43 + font-size: 14px;
  44 + }
  45 + .text-alarm-level-lg {
  46 + color: #333333;
  47 + font-size: 15px;
  48 + }
  49 + .text-alarm-lg {
  50 + color: #666666;
  51 + font-size: 14px;
  52 + }
  53 + .text-alarm-status {
  54 + color: #de4437;
  55 + font-size: 14px;
  56 + }
52 } 57 }
  58 + .bottom-line{
  59 + border-bottom: 0.1rpx solid #f0f0f0;
  60 + width:650rpx;
  61 + }
53 } 62 }
  63 +
54 } 64 }
55 } 65 }
56 .handle-result { 66 .handle-result {
57 color: #333333; 67 color: #333333;
58 font-size: 15px; 68 font-size: 15px;
59 - margin-top: 20rpx; 69 + margin-top: 32rpx;
60 } 70 }
61 .hanle-main { 71 .hanle-main {
62 margin-top: 30rpx; 72 margin-top: 30rpx;
@@ -65,3 +75,13 @@ @@ -65,3 +75,13 @@
65 height: 273rpx; 75 height: 273rpx;
66 background-color: #ffffff; 76 background-color: #ffffff;
67 } 77 }
  78 +.bottom-button{
  79 + margin-top: 44rpx;
  80 + display: flex;
  81 + align-items: center;
  82 + gap:40rpx;
  83 + justify-content: center;
  84 + .button-item{
  85 + width: 260rpx;
  86 + }
  87 +}
1 -<template>  
2 - <view class="alarm-detail-page">  
3 - <!-- 公共组件-每个页面必须引入 -->  
4 - <public-module></public-module>  
5 - <view class="alarm-detail-column">  
6 - <view class="u-flex detail-column">  
7 - <view class="u-flex column">  
8 - <text  
9 - class="text-clip device-name text-org-bold">{{ list.deviceName == null ? '暂无数据' : list.deviceName }}</text>  
10 - <image class="image" src="/static/alarm-device.png"></image>  
11 - </view>  
12 - <view class="column">  
13 - <text class="text-org-bold ">告警级别:</text>  
14 - <text class="text-device-muted" style="color:#DE4437">  
15 - {{  
16 - list.severity == 'CRITICAL'  
17 - ? '危险'  
18 - : list.severity == 'MAJOR'  
19 - ? '重要'  
20 - : list.severity == 'MINOR'  
21 - ? '次要'  
22 - : list.severity == 'WARNING'  
23 - ? '警告'  
24 - : '不确定'  
25 - }}  
26 - </text>  
27 - </view>  
28 - <view class="column">  
29 - <text class="text-org-bold">所属组织:</text>  
30 - <text  
31 - class="text-device-muted">{{ list.organizationName == null ? '暂无数据' : list.organizationName }}</text>  
32 - </view>  
33 - <view class="column">  
34 - <text class="text-org-bold">告警值:</text>  
35 - <text  
36 - class="text-device-muted text-clip">{{ list.details == null ? '暂无数据' : formatDetailText(list.details.data) }}</text>  
37 - </view>  
38 - <view class="column">  
39 - <text class="text-org-bold">告警场景:</text>  
40 - <text  
41 - class="text-device-muted text-clip">{{ list.type == null ? '暂无数据' : list.type }}</text>  
42 - </view>  
43 - <view class="column">  
44 - <text class="text-org-bold">告警时间:</text>  
45 - <text class="text-device-muted">{{ list.createdTime }}</text>  
46 - </view>  
47 - <view class="column">  
48 - <text class="text-org-bold">告警状态:</text>  
49 - <text class="text-device-muted" style="color: #DE4437;">  
50 - {{  
51 - list.status == 'CLEARED_UNACK'  
52 - ? '清除未确认'  
53 - : list.status == 'ACTIVE_UNACK'  
54 - ? '激活未确认'  
55 - : list.status == 'CLEARED_ACK'  
56 - ? '清除已确认'  
57 - : '激活已确认'  
58 - }}  
59 - </text>  
60 - </view>  
61 - </view>  
62 - </view>  
63 - <!-- #ifdef MP -->  
64 - <view class="handle-result text-org-bold" style="">处理结果</view>  
65 - <view class="hanle-main">  
66 - <u--form :label-style="{ 'font-size': '0rpx' }" style="padding-left: 26rpx;" labelPosition="left"  
67 - :model="formModel" ref="form1">  
68 - <u-form-item label="." prop="result" ref="item3">  
69 - <view style="position: relative;left: -60rpx;">  
70 - <u--textarea border="none" height="96" placeholder="请输入处理结果" v-model="formModel.result" count>  
71 - </u--textarea>  
72 - </view>  
73 - </u-form-item>  
74 - </u--form>  
75 - </view>  
76 - <!-- #endif -->  
77 - <!-- #ifdef APP-PLUS -->  
78 - <view class="handle-result text-org-bold">处理结果</view>  
79 - <view class="hanle-main">  
80 - <view>  
81 - <u--textarea border="none" height="96" placeholder="请输入处理结果" v-model="formModel.result" count>  
82 - </u--textarea>  
83 - </view>  
84 - </view>  
85 - <!-- #endif -->  
86 - <view style="margin-top: 44rpx;display: flex;align-items: center;justify-content: space-between;">  
87 - <view :style="[  
88 - { position: list.status !== 'CLEARED_ACK' && list.status !== 'ACTIVE_ACK' ? 'relative' : '' },  
89 - { right: list.status !== 'CLEARED_ACK' && list.status !== 'ACTIVE_ACK' ? '-210rpx' : '' }  
90 - ]" v-if="list.status !== 'CLEARED_ACK' && list.status !== 'ACTIVE_ACK'" class="u-flex" style="width: 260rpx">  
91 - <u-button @click="handleSubmit" type="primary" shape="circle" text="处理"></u-button>  
92 - </view>  
93 - <view style="width: 30rpx;"></view>  
94 - <view  
95 - :style="[{ position: list.status == 'ACTIVE_ACK' ? 'relative' : '' }, { right: list.status == 'ACTIVE_ACK' ? '207rpx' : '' }]"  
96 - v-if="list.status == 'ACTIVE_ACK'" class="u-flex" style="width: 260rpx">  
97 - <u-button @click="handleRemove" type="error" shape="circle" text="清除"></u-button>  
98 - </view>  
99 - </view>  
100 - </view>  
101 -</template>  
102 -  
103 -<script>  
104 - import {  
105 - mapActions  
106 - } from 'vuex'  
107 - import api from '@/api/index.js'  
108 -  
109 - export default {  
110 - data() {  
111 - return {  
112 - formModel: {  
113 - result: ''  
114 - },  
115 - list: {}  
116 - };  
117 - },  
118 - onLoad(e) {  
119 - if (e.data !== null) {  
120 - let params = JSON.parse(e.data);  
121 - this.list = params;  
122 - }  
123 - // 隐藏原生的tabbar  
124 - uni.hideTabBar();  
125 - },  
126 - methods: {  
127 - //处理  
128 - async handleSubmit() {  
129 - if (this.formModel.result == '') return uni.$u.toast('请输入处理结果');  
130 - const res = await api.alarmApi.postAlarmAckApi(this.list.id)  
131 - if (res == '') {  
132 - uni.showToast({  
133 - title: '处理成功~',  
134 - icon: 'none'  
135 - });  
136 - let pages = getCurrentPages(); //获取所有页面栈实例列表  
137 - let nowPage = pages[pages.length - 1]; //当前页页面实例  
138 - let prevPage = pages[pages.length - 2]; //上一页页面实例  
139 - prevPage.$vm.detailStatus = true;  
140 - setTimeout(() => {  
141 - uni.navigateBack({  
142 - delta: 1  
143 - });  
144 - }, 500);  
145 - }  
146 - },  
147 - // 清除  
148 - async handleRemove() {  
149 - const res = await api.alarmApi.postAlarmClearApi(this.list.id)  
150 - if (res == '') {  
151 - uni.showToast({  
152 - title: '清除成功~',  
153 - icon: 'none'  
154 - });  
155 - let pages = getCurrentPages(); //获取所有页面栈实例列表  
156 - let nowPage = pages[pages.length - 1]; //当前页页面实例  
157 - let prevPage = pages[pages.length - 2]; //上一页页面实例  
158 - prevPage.$vm.detailStatus = true;  
159 - setTimeout(async () => {  
160 - uni.navigateBack({  
161 - delta: 1  
162 - });  
163 - const res = await uni.$u.http.get('/yt/homepage/app?login=true');  
164 - if (res) {  
165 - //异步实时更新告警徽标数  
166 - await this.updateBadgeTotal(res.totalAlarm?.activedAlarm);  
167 - }  
168 - }, 500);  
169 - }  
170 - },  
171 - ...mapActions(['updateBadgeTotal']),  
172 - formatDetailText(e) {  
173 - //去除字符串双引号  
174 - const jsonStr = JSON.stringify(e);  
175 - const str = jsonStr.substring(1, jsonStr.length - 1);  
176 - const newStr = str.replace(/\"/g, '');  
177 - return newStr.slice(0,26);  
178 - }  
179 - }  
180 - };  
181 -</script>  
182 -  
183 -<style lang="scss" scoped>  
184 - @import './static/alarmDetail.scss';  
185 -  
186 - /deep/ .u-button--primary {  
187 - background-color: #377dff !important;  
188 - border-color: #377dff !important;  
189 - }  
190 -</style>  
@@ -11,6 +11,30 @@ const getDeviceApi = (urlParams, data) => { @@ -11,6 +11,30 @@ const getDeviceApi = (urlParams, data) => {
11 return uni.$u.http.post(`/yt/device?page=${page}&pageSize=${pageSize}`, data); 11 return uni.$u.http.post(`/yt/device?page=${page}&pageSize=${pageSize}`, data);
12 }; 12 };
13 13
14 -export default {  
15 - getDeviceApi 14 +// 设备详情
  15 +const getDeviceDetail = (id) => {
  16 + return uni.$u.http.get(`/yt/device/${id}`);
  17 +};
  18 +
  19 +//设备属性
  20 +const getAttribute = (deviceProfileId) => {
  21 + return uni.$u.http.get(`/yt/device/attributes/${deviceProfileId}`);
  22 +};
  23 +
  24 +//命令下发
  25 +const issueCommand = (type, tbDeviceId, data) => {
  26 + return uni.$u.http.post(`/rpc/${type==='OneWay'?'oneway':'twoway'}/${tbDeviceId}`, data)
16 } 27 }
  28 +
  29 +//获取命令下发记录
  30 +const getRpcRecord = (params) => {
  31 + return uni.$u.http.get('/yt/rpc', params);
  32 +};
  33 +
  34 +export default {
  35 + getDeviceApi,
  36 + getDeviceDetail,
  37 + getAttribute,
  38 + issueCommand,
  39 + getRpcRecord
  40 +}
@@ -20,9 +20,22 @@ const getConfigurationApi = (params = {}) => { @@ -20,9 +20,22 @@ const getConfigurationApi = (params = {}) => {
20 .get('/yt/configuration/center', params) 20 .get('/yt/configuration/center', params)
21 } 21 }
22 22
  23 +// 获取看板分页API
  24 +const getVisualBoardApi = (params) => {
  25 + return uni.$u.http.get('yt/data_board',params)
  26 +}
  27 +
  28 +//获取组织列表
  29 +const getMeOrgListApi = () => {
  30 + return uni.$u.http
  31 + .get('/yt/organization/me/list')
  32 +}
  33 +
23 export default { 34 export default {
24 getHomeStatisticsApi, 35 getHomeStatisticsApi,
25 getCameraApi, 36 getCameraApi,
26 byCameraIdGetDetailApi, 37 byCameraIdGetDetailApi,
27 - getConfigurationApi 38 + getConfigurationApi,
  39 + getMeOrgListApi,
  40 + getVisualBoardApi
28 } 41 }
@@ -3,12 +3,18 @@ const getPlateCustomApi = () => { @@ -3,12 +3,18 @@ const getPlateCustomApi = () => {
3 return uni.$u.http.get("/yt/app_design/get") 3 return uni.$u.http.get("/yt/app_design/get")
4 }; 4 };
5 5
6 -//第三方账号绑定API 6 +//第三方账号登录API
7 const getThirdLoginApi = (code) => { 7 const getThirdLoginApi = (code) => {
8 return uni.$u.http.get(`/yt/third/login/${code}`) 8 return uni.$u.http.get(`/yt/third/login/${code}`)
9 }; 9 };
10 10
11 -//获取个人信息API 11 +//第三方账号绑定API
  12 +const postThirdLoginApi = (data = {}) => {
  13 + return uni.$u.http
  14 + .post("/yt/third/bind", data)
  15 +};
  16 +
  17 +//设置个人信息API
12 const setUserInfoApi = () => { 18 const setUserInfoApi = () => {
13 return uni.$u.http.get("/yt/user/me/info") 19 return uni.$u.http.get("/yt/user/me/info")
14 }; 20 };
@@ -52,6 +58,18 @@ const deleteBindApi = (data = {}) => { @@ -52,6 +58,18 @@ const deleteBindApi = (data = {}) => {
52 return uni.$u.http.delete('/yt/third', data) 58 return uni.$u.http.delete('/yt/third', data)
53 } 59 }
54 60
  61 +//获取手机验证码API
  62 +const postPhoneCodeApi = (phone, data = {}) => {
  63 + return uni.$u.http
  64 + .post(`/yt/noauth/send_login_code/${phone}`, data)
  65 +}
  66 +
  67 +//手机登录API
  68 +const postPhoneLoginApi = (data = {}) => {
  69 + return uni.$u.http
  70 + .post('/yt/auth/code/login', data)
  71 +}
  72 +
55 export default { 73 export default {
56 getPlateCustomApi, 74 getPlateCustomApi,
57 getThirdLoginApi, 75 getThirdLoginApi,
@@ -62,5 +80,8 @@ export default { @@ -62,5 +80,8 @@ export default {
62 postCodeApi, 80 postCodeApi,
63 postResetCodeApi, 81 postResetCodeApi,
64 postPersonalInfoApi, 82 postPersonalInfoApi,
65 - deleteBindApi 83 + deleteBindApi,
  84 + postPhoneCodeApi,
  85 + postPhoneLoginApi,
  86 + postThirdLoginApi
66 } 87 }
  1 +<template>
  2 + <view class="header-org" @click="$emit('openOrg')">
  3 + <view class="org-item">
  4 + <view class="u-flex org-contact"><text class="text">组织关系</text></view>
  5 + <view class="u-flex org-device">
  6 + <image class="device-image" :src="imageSrc"></image>
  7 + <text class="device-text">{{title}} {{ total }}</text>
  8 + </view>
  9 + </view>
  10 + <view class="org-item">
  11 + <image class="image" src="/static/arrow-right.png"></image>
  12 + </view>
  13 + </view>
  14 +</template>
  15 +
  16 +<script>
  17 + export default {
  18 + props: {
  19 + total: Number,
  20 + imageSrc: String,
  21 + title:String
  22 + }
  23 + }
  24 +</script>
  25 +
  26 +<style lang="scss" scoped>
  27 + .header-org {
  28 + width: 750rpx;
  29 + height: 150rpx;
  30 + margin-top: 1rpx;
  31 + background-color: #fff;
  32 + display: flex;
  33 + flex-direction: row;
  34 + justify-content: space-between;
  35 + position: fixed;
  36 + // z-index: 999999;
  37 + // top: 75rpx;
  38 +
  39 + .org-item {
  40 + width: 350rpx;
  41 + height: 200rpx;
  42 +
  43 + .org-contact {
  44 + flex-direction: row;
  45 + margin-top: 26rpx;
  46 + margin-left: 15rpx;
  47 +
  48 + .text {
  49 + color: #333333;
  50 + font-size: 15px;
  51 + margin-left: 14rpx;
  52 + }
  53 + }
  54 +
  55 + .org-device {
  56 + margin-top: 23rpx;
  57 + margin-left: 15rpx;
  58 + flex-direction: row;
  59 +
  60 + .device-image {
  61 + margin-left: 14rpx;
  62 + width: 30rpx;
  63 + height: 30rpx;
  64 + }
  65 +
  66 + .device-text {
  67 + margin-left: 10rpx;
  68 + color: #666666;
  69 + font-size: 12px;
  70 + }
  71 + }
  72 +
  73 + .image {
  74 + width: 6px;
  75 + height: 10px;
  76 + float: right;
  77 + margin-right: 34rpx;
  78 + margin-top: 37rpx;
  79 + }
  80 + }
  81 + }
  82 +</style>
  1 +<template>
  2 + <u-sticky>
  3 + <view class="device-top">
  4 + <view class="search">
  5 + <view>
  6 + <view class="search-left">
  7 + <slot></slot>
  8 + </view>
  9 + </view>
  10 + <view @click="$emit('openSearchDialog')" class="search-right">
  11 + <text>筛选</text>
  12 + <image src="../../static/shaixuan.png" />
  13 + </view>
  14 + </view>
  15 + <u-line />
  16 + <view class="org">
  17 + <u-cell @click="$emit('openOrg')" isLink title="组织关系" :border="false">
  18 + <view slot="label" class="label" style="display: flex; align-items: center;margin-top: 20rpx;">
  19 + <image src="../../static/org.png" style="width: 24rpx;height: 28rpx;"></image>
  20 + <view style="margin-left: 10rpx; color: #666;">
  21 + {{totalText}}
  22 + <text style="margin-left: 20rpx;">{{ total }}</text>
  23 + </view>
  24 + </view>
  25 + </u-cell>
  26 + </view>
  27 + </view>
  28 + </u-sticky>
  29 +</template>
  30 +
  31 +<script>
  32 + export default {
  33 + props: {
  34 + total: Number,
  35 + totalText: [Number, String]
  36 + }
  37 + }
  38 +</script>
  39 +
  40 +<style lang="scss" scoped>
  41 + /deep/ .u-button--primary {
  42 + background-color: #377dff !important;
  43 + border-color: #377dff !important;
  44 + }
  45 +
  46 + /deep/ .u-button--info {
  47 + background-color: #e3e3e5 !important;
  48 + border-color: #e3e3e5 !important;
  49 + }
  50 +
  51 + /deep/ .u-cell__right-icon-wrap {
  52 + margin-top: -55rpx !important;
  53 + }
  54 +
  55 + /deep/ .uni-calendar--fixed {
  56 + bottom: 172rpx !important;
  57 + }
  58 +
  59 + .pop-no-scroll {
  60 + overflow: hidden;
  61 + position: fixed;
  62 + height: 100%;
  63 + width: 100%;
  64 + }
  65 +
  66 + .device-top {
  67 + padding: 10rpx 30rpx;
  68 + background-color: #fff;
  69 +
  70 + .search {
  71 + display: flex;
  72 + justify-content: space-between;
  73 + padding-bottom: 10rpx;
  74 +
  75 + .search-left {
  76 + width: 580rpx;
  77 + background-color: #f8f9fa;
  78 + border-radius: 200rpx;
  79 + }
  80 +
  81 + .search-right {
  82 + display: flex;
  83 + align-items: center;
  84 +
  85 + text {
  86 + color: #333;
  87 + font-size: 14px;
  88 + }
  89 +
  90 + image {
  91 + width: 40rpx;
  92 + height: 40rpx;
  93 + }
  94 + }
  95 + }
  96 + }
  97 +</style>
@@ -228,7 +228,6 @@ export default { @@ -228,7 +228,6 @@ export default {
228 if (pageLen == 1 && !mainPagePath.includes(currentPages[0].route)) { 228 if (pageLen == 1 && !mainPagePath.includes(currentPages[0].route)) {
229 this.firstPage = true; 229 this.firstPage = true;
230 this.iconLeft = 'home'; 230 this.iconLeft = 'home';
231 - console.log(this.firstPage, 'this.firstPage');  
232 } 231 }
233 }, 232 },
234 methods: { 233 methods: {
@@ -273,7 +272,6 @@ export default { @@ -273,7 +272,6 @@ export default {
273 //设置手机状态栏颜色 272 //设置手机状态栏颜色
274 settingColor() { 273 settingColor() {
275 let navColor = this.navFontColor; 274 let navColor = this.navFontColor;
276 - console.log(navColor, 'settingColor');  
277 let frontColor = '#000000'; 275 let frontColor = '#000000';
278 if (whiteList.includes(navColor)) { 276 if (whiteList.includes(navColor)) {
279 frontColor = '#ffffff'; 277 frontColor = '#ffffff';
@@ -5,14 +5,16 @@ import { @@ -5,14 +5,16 @@ import {
5 * 服务端配置项 5 * 服务端配置项
6 * baseUrl 服务端 api地址 6 * baseUrl 服务端 api地址
7 * baseDrawioUrl 组态地址 注意端口 7 * baseDrawioUrl 组态地址 注意端口
  8 + * baseVisualUrl 看板地址
8 * baseWebSocketUrl 服务端 websocket地址 9 * baseWebSocketUrl 服务端 websocket地址
9 * socketPrefix websocket前缀 ((https, wss),( http, ws)) 10 * socketPrefix websocket前缀 ((https, wss),( http, ws))
10 */ 11 */
11 -  
12 -const baseUrl = "https://demo.thingskit.com/api";  
13 -const baseDrawioUrl = "https://demo.thingskit.com/thingskit-scada";  
14 -const baseWebSocketUrl = "demo.thingskit.com";  
15 -const socketPrefix = "wss"; 12 +const baseUrl = "http://222.180.200.114:48080/api";
  13 +const baseVisualUrl = "http://222.180.200.114:9527"
  14 +// const baseVisualUrl = "http://192.168.1.4:8083"
  15 +const baseDrawioUrl = "http://222.180.200.114:9527/thingskit-scada";
  16 +const baseWebSocketUrl = "222.180.200.114:48080";
  17 +const socketPrefix = "ws";
16 18
17 let systemInfo = { 19 let systemInfo = {
18 ...getTabbarHeight(), 20 ...getTabbarHeight(),
@@ -42,6 +44,7 @@ systemInfo.platform = "plus"; @@ -42,6 +44,7 @@ systemInfo.platform = "plus";
42 const courtConfig = { 44 const courtConfig = {
43 publicAppId: "", //公众号appId 45 publicAppId: "", //公众号appId
44 baseUrl, //服务端地址 46 baseUrl, //服务端地址
  47 + baseVisualUrl,//服务端看板地址
45 baseDrawioUrl, //服务端组态地址 48 baseDrawioUrl, //服务端组态地址
46 baseWebSocketUrl, //服务端websocket地址 49 baseWebSocketUrl, //服务端websocket地址
47 socketPrefix, //websocket前缀 50 socketPrefix, //websocket前缀
@@ -51,4 +54,4 @@ const courtConfig = { @@ -51,4 +54,4 @@ const courtConfig = {
51 sk: "", 54 sk: "",
52 }, 55 },
53 }; 56 };
54 -export default Object.assign({}, courtConfig); 57 +export default Object.assign({}, courtConfig);
@@ -103,11 +103,11 @@ uni.$u.http.interceptors.response.use( @@ -103,11 +103,11 @@ uni.$u.http.interceptors.response.use(
103 const routers = getCurrentPages(); 103 const routers = getCurrentPages();
104 const currentRoute = routers[routers.length - 1].route; 104 const currentRoute = routers[routers.length - 1].route;
105 const isLoginPage = currentRoute.includes( 105 const isLoginPage = currentRoute.includes(
106 - "publicLoginSubPage/public/login" 106 + "login-subpackage/public/login"
107 ); 107 );
108 !isLoginPage && 108 !isLoginPage &&
109 uni.reLaunch({ 109 uni.reLaunch({
110 - url: "/publicLoginSubPage/public/login", 110 + url: "/login-subpackage/public/login",
111 }); 111 });
112 // 清空登录信息 112 // 清空登录信息
113 store.commit("emptyUserInfo"); 113 store.commit("emptyUserInfo");
  1 +//上传图片大小
  2 +const UPLOAD_FILE_SIZE = 5242880
  3 +
  4 +export {
  5 + UPLOAD_FILE_SIZE
  6 +}
device-subpackage/device-detail/api/index.js renamed from deviceSubPage/deviceDetailPage/api/index.js
1 -// 获取某个Key的历史数据  
2 -const getUrlParams = (params) => {  
3 - return Object.keys(params).reduce((prev, next, currentIndex) => {  
4 - if (params[next]) {  
5 - return prev += `${!currentIndex ? '?' : '&'}${next}=${params[next]}`  
6 - }  
7 - return prev  
8 - }, '')  
9 -}  
10 -  
11 -export function getHistoryData(params) {  
12 - let {  
13 - entityId  
14 - } = params  
15 - params = getUrlParams(params)  
16 - return uni.$u.http.get(  
17 - `/plugins/telemetry/DEVICE/${entityId}/values/timeseries${params}`  
18 - );  
19 -}  
20 -  
21 -  
22 -  
23 -// 获取当前设备的key  
24 -export function getDeviceKeys(id) {  
25 - return uni.$u.http.get(`/plugins/telemetry/DEVICE/${id}/keys/timeseries`);  
26 -};  
27 -  
28 -export function issueCommand(type, tbDeviceId, data) {  
29 - return uni.$u.http.post(`/rpc/${type==='OneWay'?'oneway':'twoway'}/${tbDeviceId}`, data) 1 +// 获取某个Key的历史数据
  2 +const getUrlParams = (params) => {
  3 + return Object.keys(params).reduce((prev, next, currentIndex) => {
  4 + if (params[next]) {
  5 + return prev += `${!currentIndex ? '?' : '&'}${next}=${params[next]}`
  6 + }
  7 + return prev
  8 + }, '')
30 } 9 }
  10 +
  11 +export function getHistoryData(params) {
  12 + let {
  13 + entityId
  14 + } = params
  15 + params = getUrlParams(params)
  16 + return uni.$u.http.get(
  17 + `/plugins/telemetry/DEVICE/${entityId}/values/timeseries${params}`
  18 + );
  19 +}
  20 +
  21 +
  22 +
  23 +// 获取当前设备的key
  24 +export function getDeviceKeys(id) {
  25 + return uni.$u.http.get(`/plugins/telemetry/DEVICE/${id}/keys/timeseries`);
  26 +};
  1 +<template>
  2 + <view class="alert-page">
  3 + <!-- 告警头部 -->
  4 + <view class="filter-button" @click="openSearchDialog">
  5 + <text>筛选</text>
  6 + <image src="/static/shaixuan.png" />
  7 + </view>
  8 + <!-- 告警分页 -->
  9 + <mescroll-uni height="700rpx;" ref="mescrollRef" @init="mescrollInit" :up="upOption" :down="downOption"
  10 + @down="downCallback" @up="upCallback">
  11 + <alarm-item :list="list" @openAlertDetail="openAlertDetail"></alarm-item>
  12 + <mescroll-empty v-if="!list.length" />
  13 + </mescroll-uni>
  14 + <view style="height: 20rpx"></view>
  15 + <!-- 告警筛选 -->
  16 + <alarm-popup ref="alarmPopupRef" :show="show" @close="close" @queryCondition="getQueryCondition"></alarm-popup>
  17 + </view>
  18 +</template>
  19 +<script>
  20 + import MescrollMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js';
  21 + import api from '@/api/index.js'
  22 + import alarmItem from '@/pages/alarm/components/alarm-item.vue'
  23 + import alarmPopup from '@/pages/alarm/components/alarm-popup.vue'
  24 + import {
  25 + useNavigateTo
  26 + } from '@/plugins/utils.js'
  27 +
  28 + export default {
  29 + mixins: [MescrollMixin],
  30 + components: {
  31 + alarmItem,
  32 + alarmPopup
  33 + },
  34 + props: {
  35 + deviceId: {
  36 + type: String,
  37 + default: ''
  38 + }
  39 + },
  40 + data() {
  41 + return {
  42 + show: false,
  43 + list: [],
  44 + total: 0,
  45 + downOption: {
  46 + auto: true //是否在初始化后,自动执行downCallback; 默认true
  47 + },
  48 + upOption: {
  49 + auto: false // 不自动加载
  50 + },
  51 + page: {
  52 + num: 0,
  53 + size: 10
  54 + },
  55 + conditions: {},
  56 + };
  57 + },
  58 + methods: {
  59 + disabledScroll() {
  60 + return;
  61 + },
  62 + getQueryCondition(value) {
  63 + this.conditions = value
  64 + this.conditions.deviceId = this.deviceId
  65 + this.loadData(1, this.conditions);
  66 + this.close()
  67 + },
  68 + resetQuery() {
  69 + this.page.num = 1;
  70 + this.$nextTick(() => {
  71 + this.$refs.alarmPopupRef.resetQuery()
  72 + })
  73 + this.conditions = {}
  74 + },
  75 + downCallback() {
  76 + this.list = [];
  77 + this.page.num = 1;
  78 + this.resetQuery();
  79 + this.loadData(this.page.num, {
  80 + deviceId: this.deviceId
  81 + });
  82 + },
  83 + upCallback() {
  84 + const deviceId = {
  85 + deviceId: this.deviceId
  86 + }
  87 + const condition = Object.values(this.conditions)
  88 + if (condition.length === 0) {
  89 + this.page.num += 1;
  90 + this.loadData(this.page.num, {
  91 + ...deviceId
  92 + });
  93 + } else if (condition.filter(Boolean).length > 0) {
  94 + this.page.num += 1;
  95 + this.loadData(this.page.num, {
  96 + ...this.conditions,
  97 + ...deviceId
  98 + });
  99 + } else {
  100 + this.page.num += 1;
  101 + this.loadData(this.page.num, {
  102 + ...deviceId
  103 + });
  104 + }
  105 + },
  106 + async loadData(page, param) {
  107 + let that = this;
  108 + let params = {
  109 + page,
  110 + pageSize: 10,
  111 + ...param
  112 + };
  113 + const res = await api.alarmApi.getAlarmApi({
  114 + params,
  115 + custom: {
  116 + load: false
  117 + }
  118 + })
  119 + if (!res) return
  120 + uni.stopPullDownRefresh();
  121 + that.mescroll.endByPage(res.items.length, res.total); //必传参数(当前页的数据个数, 总页数)
  122 + that.alarmTotal = res.total;
  123 + if (page == 1) {
  124 + that.list = res.items;
  125 + } else {
  126 + that.list = that.list.concat(res.items);
  127 + }
  128 + },
  129 + openSearchDialog() {
  130 + this.show = true;
  131 + this.resetQuery();
  132 + },
  133 + close() {
  134 + this.show = false;
  135 + },
  136 + openAlertDetail(e) {
  137 + useNavigateTo('/alarm-subpackage/alarm-detail/alarm-detail?data=', e)
  138 + }
  139 + }
  140 + };
  141 +</script>
  142 +
  143 +<style lang="scss" scoped>
  144 + .filter-button {
  145 + margin: 0 20rpx;
  146 + font-size: 12px;
  147 + width: 160rpx;
  148 + height: 64rpx;
  149 + border-radius: 32rpx;
  150 + display: flex;
  151 + justify-content: center;
  152 + align-items: center;
  153 + background: #f0f1f2;
  154 + color: #666;
  155 +
  156 + image {
  157 + width: 28rpx;
  158 + height: 28rpx;
  159 + margin-left: 4rpx;
  160 + }
  161 + }
  162 +</style>
device-subpackage/device-detail/components/basic-info.vue renamed from deviceSubPage/deviceDetailPage/tabDetail/basicInfo.vue
1 -<template>  
2 - <view class="basic-page">  
3 - <!-- 公共组件-每个页面必须引入 -->  
4 - <public-module />  
5 - <view class="basic-title">  
6 - <view class="u-flex">  
7 - <view style="padding-left: 32rpx;">  
8 - <u-icon v-if="deviceDetail.deviceInfo.longitude !== ''" @click="handleClick" name="map-fill">  
9 - </u-icon>  
10 - </view>  
11 - <view class="text-clip" style="margin-left: 20rpx;width:370rpx">  
12 - {{ deviceDetail.alias? deviceDetail.alias: deviceDetail.name }}  
13 - </view>  
14 - <view style="margin-left: 20rpx; font-size: 14px;"  
15 - :style="{ color: deviceDetail.deviceState === 'INACTIVE' ? '#666' : deviceDetail.deviceState === 'ONLINE' ? '#377DFF' : '#DE4437' }">  
16 - {{ deviceStatus }}  
17 - </view>  
18 - </view>  
19 - <view style="margin-right: 20rpx;"  
20 - v-if="deviceDetail.deviceState === 'ONLINE' && deviceDetail.deviceType !== 'SENSOR'">  
21 - <!-- #ifdef MP -->  
22 - <u-button type="primary" shape="circle" size="mini" text="下发命令" @click="showModalBtn" />  
23 - <!-- #endif -->  
24 - <!-- #ifdef APP-PLUS -->  
25 - <view class="cu-item" @tap="showModal" data-target="Modal">  
26 - <text>下发命令</text>  
27 - </view>  
28 - <!-- #endif -->  
29 - </view>  
30 - </view>  
31 - <view class="detail">  
32 - <view class="detail-item">  
33 - <view class="detail-label">设备编号</view>  
34 - <view class="detail-value">{{ deviceDetail.sn }}</view>  
35 - </view>  
36 - <u-line length="90%" margin="0 auto"></u-line>  
37 - <view class="detail-item">  
38 - <view class="detail-label">设备类型</view>  
39 - <view class="detail-value">{{ deviceType }}</view>  
40 - </view>  
41 - <u-line length="90%" margin="0 auto"></u-line>  
42 - <view class="detail-item">  
43 - <view class="detail-label">所属组织</view>  
44 - <view class="detail-value">{{ deviceDetail.organizationDTO.name }}</view>  
45 - </view>  
46 - <u-line length="90%" margin="0 auto"></u-line>  
47 - <view class="detail-item">  
48 - <view class="detail-label">最后连接时间</view>  
49 - <view class="detail-value">{{ formatLastOnlineTime }}</view>  
50 - </view>  
51 - <u-line length="90%" margin="0 auto"></u-line>  
52 - <view class="detail-item">  
53 - <view class="detail-label">是否告警</view>  
54 - <view class="detail-value">{{ alarmStatus }}</view>  
55 - </view>  
56 - <u-line length="90%" margin="0 auto"></u-line>  
57 - <view class="detail-item">  
58 - <view class="detail-label">设备描述</view>  
59 - <view class="detail-value">{{ deviceDetail.description }}</view>  
60 - </view>  
61 - </view>  
62 - <!-- 下发命令 -->  
63 - <!-- #ifdef APP-PLUS -->  
64 - <view v-show="showNodal" class="cu-modal" :class="modalName=='Modal'?'show':''">  
65 - <view class="cu-dialog" style="">  
66 - <view class="modal_main">  
67 - <view class='nav-list margin-top'>  
68 - <view style="width: 100%; padding: 0 30rpx;">  
69 - <view style="text-align: center; font-weight:700;margin-top: 40rpx;">命令下发</view>  
70 - <view style="height: 20rpx;"></view>  
71 - <view class="u-flex">  
72 - <text  
73 - style="color: #333; font-size: 14px;font-weight:700;margin-right: 30rpx;">下发类型:</text>  
74 - <view>  
75 - <radio-group style="display: flex;" @change="radioChange">  
76 - <label class="uni-list-cell uni-list-cell-pd" v-for="(item, index) in items"  
77 - :key="item.value">  
78 - <view style="display: flex">  
79 - <view style="margin-left: 10rpx;">  
80 - <radio :value="item.value" :checked="index === current" />  
81 - </view>  
82 - <view style="width:10rpx"></view>  
83 - <view style="margin-left: 10rpx;">{{item.name}}</view>  
84 - </view>  
85 - </label>  
86 - </radio-group>  
87 - </view>  
88 - </view>  
89 - <view style="margin-top: 15rpx">  
90 - <view class="cusAppplusContent">  
91 - <textarea v-model="inputCommandVal" placeholder="请输入下发内容(json格式)" />  
92 - </view>  
93 - </view>  
94 - <view class="button-group">  
95 - <view>  
96 - <view class="cusAppplusCancelBtn" @click="cancelCommand"><text  
97 - style="color: #333333">取消</text>  
98 - </view>  
99 - </view>  
100 - <view>  
101 - <view class="cusAppplusConfrimBtn" @click="confirmCommand">  
102 - <text style="color:white">确认</text>  
103 - </view>  
104 - </view>  
105 - </view>  
106 - <view style="height:30rpx"></view>  
107 - </view>  
108 - </view>  
109 - </view>  
110 - </view>  
111 - </view>  
112 - <!-- #endif -->  
113 - <!-- #ifdef MP -->  
114 - <u-modal :show="showModel" closeOnClickOverlay :showConfirmButton="false" width="720rpx" @close="hiddenModal"  
115 - @touchmove.stop.prevent="disabledScroll">  
116 - <view style="width: 100%; padding: 0 30rpx;">  
117 - <view style="text-align: center; font-weight:700;margin-bottom: 40rpx;">命令下发</view>  
118 - <view class="u-flex">  
119 - <text style="color: #333; font-size: 14px;font-weight:700;margin-right: 30rpx;">下发类型:</text>  
120 -  
121 - <u-radio-group v-model="commandType" placement="row">  
122 - <u-radio activeColor="#3388FF" label="单向" name="OneWay"></u-radio>  
123 - <view style="margin: 0 20rpx;"></view>  
124 - <u-radio activeColor="#3388FF" label="双向" name="TwoWay"></u-radio>  
125 - </u-radio-group>  
126 - </view>  
127 - <view style="margin-top: 28rpx;width: 100%;">  
128 - <div class="u-flex u-row-between">  
129 - <u--textarea :placeholder="`请输入下发内容${isShowTCP?'(字符串格式)':'(json格式)'}`" v-model="inputCommandVal" />  
130 - <u-icon v-if="!isShowTCP" @click="handleCopy(copyTextValue)" name="question-circle" color="#2979ff" size="28"  
131 - class="ml-10">  
132 - </u-icon>  
133 - </div>  
134 - </view>  
135 -  
136 - <view class="button-group">  
137 - <view>  
138 - <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="取消"  
139 - @click="cancelCommand"></u-button>  
140 - </view>  
141 - <view>  
142 - <u-button color="#3388ff" shape="circle" text="确认" @click="confirmCommand"></u-button>  
143 - </view>  
144 - </view>  
145 - </view>  
146 - </u-modal>  
147 - <!-- #endif -->  
148 - </view>  
149 -</template>  
150 -  
151 -<script>  
152 - import {  
153 - formatToDate  
154 - } from '@/plugins/utils.js';  
155 - import {  
156 - issueCommand  
157 - } from '../api/index.js';  
158 - export default {  
159 - props: {  
160 - deviceDetail: {  
161 - type: Object,  
162 - default: () => ({})  
163 - }  
164 - },  
165 - data() {  
166 - return {  
167 - copyTextValue: {  
168 - "method": "methodThingskit",  
169 - "params": {  
170 - "pin": 7,  
171 - "value": 1  
172 - }  
173 - },  
174 - showNodal: false,  
175 - items: [{  
176 - value: 'OneWay',  
177 - name: '单向',  
178 - checked: 'true'  
179 - },  
180 - {  
181 - value: 'TwoWay',  
182 - name: '双向'  
183 - },  
184 - ],  
185 - current: 0,  
186 - modalName: null,  
187 - showModel: false,  
188 - commandType: 'OneWay',  
189 - commandValue: {},  
190 - inputCommandVal: '',  
191 - isShowTCP:false,//用于下发命令时判断是否是TCP/UDP  
192 - };  
193 - },  
194 - computed: {  
195 - deviceStatus(){  
196 - return this.deviceDetail.deviceState === 'INACTIVE' ? '待激活' : this.deviceDetail.deviceState === 'ONLINE' ? '在线' : '离线'  
197 - },  
198 - deviceType() {  
199 - return this.deviceDetail.deviceType === 'DIRECT_CONNECTION' ?  
200 - '直连设备' :  
201 - this.deviceDetail.deviceType === 'GATEWAY' ?  
202 - '网关设备' :  
203 - this.deviceDetail.deviceType === 'SENSOR' ?  
204 - '网关子设备' :  
205 - '';  
206 - },  
207 - alarmStatus() {  
208 - return this.deviceDetail.alarmStatus === '0' ? '否' : '是';  
209 - },  
210 - formatLastOnlineTime() {  
211 - return formatToDate(Number(this.deviceDetail.lastOnlineTime), 'YYYY-MM-DD HH:mm:ss');  
212 - }  
213 - },  
214 - onLoad(e) {  
215 - // 隐藏原生的tabbar  
216 - uni.hideTabBar();  
217 - },  
218 - mounted() {},  
219 - beforeCreate() {  
220 - this.modalName = null  
221 - },  
222 - methods: {  
223 - handleCopy(value) {  
224 - uni.showModal({  
225 - content: JSON.stringify(value),  
226 - confirmText: '复制内容',  
227 - showCancel:false,  
228 - success: () => {  
229 - uni.setClipboardData({  
230 - data: JSON.stringify(value),  
231 - success: () => {  
232 - uni.showToast({  
233 - title: '复制成功'  
234 - })  
235 - }  
236 - });  
237 - },  
238 -  
239 - });  
240 - },  
241 - radioChange: function(evt) {  
242 - for (let i = 0; i < this.items.length; i++) {  
243 - if (this.items[i].value === evt.detail.value) {  
244 - this.current = i;  
245 - break;  
246 - }  
247 - }  
248 - this.commandType = evt.detail.value  
249 - },  
250 - handleClick() {  
251 - const data = {  
252 - longitude: this.deviceDetail.deviceInfo.longitude || 0,  
253 - latitude: this.deviceDetail.deviceInfo.latitude || 0  
254 - };  
255 - uni.navigateTo({  
256 - url: '/deviceSubPage/deviceDetailPage/devicePosition?data=' + JSON.stringify(data)  
257 - });  
258 - },  
259 - showModal(e) {  
260 - this.modalName = e.currentTarget.dataset.target  
261 - const {transportType} = this.deviceDetail.deviceProfile  
262 - this.isShowTCP = transportType=='TCP'?true:false  
263 - this.showNodal = true  
264 - },  
265 - showModalBtn() {  
266 - this.showModel = true;  
267 - this.inputCommandVal = '';  
268 - const {transportType} = this.deviceDetail.deviceProfile  
269 - this.isShowTCP = transportType=='TCP'?true:false  
270 - },  
271 - disabledScroll() {  
272 - return;  
273 - },  
274 - hiddenModal() {  
275 - this.showModel = false;  
276 - this.inputCommandVal = '';  
277 - // #ifdef APP-PLUS  
278 - this.modalName = null  
279 - this.showNodal = false  
280 - // #endif  
281 - },  
282 - async confirmCommand() {  
283 - try {  
284 - this.commandValue.method = 'methodThingskit';  
285 - this.commandValue.persistent = true;  
286 - this.commandValue.additionalInfo = {  
287 - cmdType: 'API'  
288 - };  
289 - if(this.isShowTCP){//TCP的格式只能是字符串  
290 - const zg = /^[0-9a-zA-Z]*$/  
291 - if(!zg.test(this.inputCommandVal)) {  
292 - uni.$u.toast('输入的内容只能是字母和数字的组合')  
293 - return  
294 - }  
295 - this.commandValue.params = this.inputCommandVal  
296 - }else{  
297 - const commandJsonValue = JSON.parse(this.inputCommandVal);  
298 - this.commandValue.params = commandJsonValue  
299 - }  
300 - await issueCommand(this.commandType, this.deviceDetail.tbDeviceId, this.commandValue);  
301 - this.hiddenModal();  
302 - uni.$u.toast('下发成功~');  
303 - } catch (e) {  
304 - uni.$u.toast('下发失败~');  
305 - }  
306 - },  
307 - cancelCommand() {  
308 - this.hiddenModal();  
309 - // #ifdef APP-PLUS  
310 - this.modalName = null  
311 - this.showNodal = false  
312 - // #endif  
313 - }  
314 - }  
315 - };  
316 -</script>  
317 -  
318 -<style lang="scss" scoped>  
319 - @import url('../styles/modal.css');  
320 -  
321 - .cusAppplusContent {  
322 - width: 625rpx;  
323 - height: 400rpx;  
324 - background: #FFFFFF;  
325 - box-shadow: 2px 2px 4px 0px rgba(0, 0, 0, 0.03);  
326 - border-radius: 10px;  
327 -  
328 - }  
329 -  
330 - .cusAppplusCancelBtn {  
331 - background: #e3e3e5;  
332 - border-radius: 38rpx;  
333 - height: 85rpx;  
334 - line-height: 85rpx  
335 - }  
336 -  
337 - .cusAppplusConfrimBtn {  
338 - background: #3388ff;  
339 - border-radius: 38rpx;  
340 - height: 85rpx;  
341 - line-height: 85rpx  
342 - }  
343 -  
344 - .cu-item {  
345 - background: #3388FF;  
346 - border-radius: 12px;  
347 - width: 120rpx;  
348 - height: 48rpx;  
349 - text-align: center;  
350 - line-height: 40rpx;  
351 -  
352 - text {  
353 - font-size: 12px;  
354 - font-family: PingFangSC-Regular, PingFang SC;  
355 - font-weight: 400;  
356 - color: #FFFFFF;  
357 - }  
358 - }  
359 -  
360 -  
361 -  
362 - .basic-page {  
363 - padding: 0 30rpx;  
364 -  
365 - .basic-title {  
366 - display: flex;  
367 - justify-content: space-between;  
368 - align-items: center;  
369 - height: 140rpx;  
370 - background-color: #fff;  
371 - border-radius: 20rpx;  
372 - }  
373 -  
374 - .detail {  
375 - background-color: #fff;  
376 - margin-top: 30rpx;  
377 - border-radius: 20rpx;  
378 - width: 690rpx;  
379 -  
380 - .detail-item {  
381 - padding: 30rpx;  
382 - display: flex;  
383 - align-items: center;  
384 -  
385 - .detail-label {  
386 - color: #333;  
387 - font-size: 15px;  
388 - }  
389 -  
390 - .detail-value {  
391 - color: #666;  
392 - font-size: 14px;  
393 - margin-left: 30rpx;  
394 - }  
395 - }  
396 - }  
397 - }  
398 -  
399 - /deep/ .u-modal__content {  
400 - padding: 30rpx 0 !important;  
401 - }  
402 -  
403 - .button-group {  
404 - display: flex;  
405 - margin-top: 40rpx;  
406 - justify-content: space-between;  
407 -  
408 - view {  
409 - width: 300rpx;  
410 - }  
411 - }  
412 -</style> 1 +<template>
  2 + <view class="basic-page">
  3 + <!-- 公共组件-每个页面必须引入 -->
  4 + <public-module></public-module>
  5 + <!-- 基础信息头部 -->
  6 + <view class="basic-header">
  7 + <view class="u-flex">
  8 + <view class="pl-3">
  9 + <u-icon v-if="deviceDetail.deviceInfo.longitude !== ''" @click="handleClick"
  10 + name="map-fill"></u-icon>
  11 + </view>
  12 + <view class="basic-text text-clip ml-2">
  13 + {{ deviceDetail.alias ? deviceDetail.alias : deviceDetail.name }}
  14 + </view>
  15 + <view class="basic-text-status ml-2" :style="{ color: formatTextStatus(deviceDetail.deviceState) }">
  16 + {{ deviceStatus }}
  17 + </view>
  18 + </view>
  19 + <!-- 命令下发 设备在线并且不是网关子设备 -->
  20 + <view class="mr-2" v-if="deviceDetail.deviceState === 'ONLINE' && deviceDetail.deviceType !== 'SENSOR'">
  21 + <!-- #ifdef MP -->
  22 + <u-button type="primary" shape="circle" size="mini" text="下发命令" @click="handleMpShowModal" />
  23 + <!-- #endif -->
  24 + <!-- #ifdef APP-PLUS -->
  25 + <view class="cu-item" @tap="handleAppShowModal" data-target="Modal">
  26 + <text>下发命令</text>
  27 + </view>
  28 + <!-- #endif -->
  29 + </view>
  30 + </view>
  31 + <!-- 设备详情 -->
  32 + <view class="detail">
  33 + <view class="detail-item">
  34 + <view class="detail-label">设备编号</view>
  35 + <view class="detail-value">{{ deviceDetail.sn }}</view>
  36 + </view>
  37 + <u-line length="90%" margin="0 auto"></u-line>
  38 + <view class="detail-item">
  39 + <view class="detail-label">设备类型</view>
  40 + <view class="detail-value">{{ deviceType }}</view>
  41 + </view>
  42 + <u-line length="90%" margin="0 auto"></u-line>
  43 + <view class="detail-item">
  44 + <view class="detail-label">所属组织</view>
  45 + <view class="detail-value">{{ deviceDetail.organizationDTO.name }}</view>
  46 + </view>
  47 + <u-line length="90%" margin="0 auto"></u-line>
  48 + <view class="detail-item">
  49 + <view class="detail-label">最后连接时间</view>
  50 + <view class="detail-value">{{ formatLastOnlineTime }}</view>
  51 + </view>
  52 + <u-line length="90%" margin="0 auto"></u-line>
  53 + <view class="detail-item">
  54 + <view class="detail-label">是否告警</view>
  55 + <view class="detail-value">{{ alarmStatus }}</view>
  56 + </view>
  57 + <u-line length="90%" margin="0 auto"></u-line>
  58 + <view class="detail-item">
  59 + <view class="detail-label">设备描述</view>
  60 + <view class="detail-value">{{ deviceDetail.description }}</view>
  61 + </view>
  62 + </view>
  63 + <!-- 命令下发 -->
  64 + <!-- #ifdef APP-PLUS -->
  65 + <!-- 原生弹窗 封装成子组件无效 -->
  66 + <view v-show="showNativeModal" class="cu-modal" :class="modalName == 'Modal' ? 'show' : ''">
  67 + <view class="cu-dialog">
  68 + <view class="app-command-content">
  69 + <view class="app-command-text">
  70 + <text>命令下发</text>
  71 + </view>
  72 + <view class="app-command-type">
  73 + <text>下发类型</text>
  74 + <view class="mr-2">
  75 + <radio-group @change="radioChange" class="flex mr-1">
  76 + <label v-for="(item, index) in commandTypeList" :key="item.value">
  77 + <view class="flex">
  78 + <view class="ml-1">
  79 + <radio :value="item.value" :checked="index === current" />
  80 + </view>
  81 + <view style="width:10rpx"></view>
  82 + <view class="ml-1">{{item.name}}</view>
  83 + </view>
  84 + </label>
  85 + </radio-group>
  86 + </view>
  87 + </view>
  88 + <view class="app-command-body">
  89 + <textarea class="app-command-textarea" v-model="inputCommandContent"
  90 + :placeholder="`请输入下发内容${isShowTCP?'(字符串格式)':'(json格式)'}`" />
  91 + <u-icon @click="handleCopy(copyTextValue)" v-if="!isShowTCP" name="question-circle"
  92 + color="#2979ff" size="28" class="ml-10">
  93 + </u-icon>
  94 + </view>
  95 + <view class="app-command-buttons">
  96 + <view class="cancel-button" @click="cancelCommand"><text class="cancel-text">取消</text></view>
  97 + <view @click="handleAppCommand" class="confrim-button"><text class="confrim-text">确认</text>
  98 + </view>
  99 + </view>
  100 + </view>
  101 + </view>
  102 + </view>
  103 + <!-- #endif -->
  104 + <!-- #ifdef MP -->
  105 + <!-- u-modal在app端弹窗层级无法覆盖背景色 -->
  106 + <mp-command-issuance ref="mpCommandIssuanceRef" :isShowTCP="isShowTCP" :showModal="mpShowModal"
  107 + @hideModal="hideMpModal" @cancelCommand="cancelCommand"
  108 + @confirmCommand="confirmCommand"></mp-command-issuance>
  109 + <!-- #endif -->
  110 + </view>
  111 +</template>
  112 +
  113 +<script>
  114 + import {formatToDate} from '@/plugins/utils.js';
  115 + import {issueCommand} from '../api/index.js';
  116 + import api from '@/api/index.js';
  117 + import mpCommandIssuance from './mp-command-issuance.vue';
  118 + import {commandTypeList} from '../config/data.js'
  119 + import {useShowModal} from '@/plugins/utils.js'
  120 +
  121 + export default {
  122 + components: {
  123 + mpCommandIssuance,
  124 + },
  125 + props: {
  126 + deviceDetail: {
  127 + type: Object,
  128 + default: () => ({})
  129 + }
  130 + },
  131 + data() {
  132 + return {
  133 + showNativeModal: false,
  134 + current: 0,
  135 + commandTypeList,
  136 + commandTypeStr: 'OneWay',
  137 + inputCommandContent: '',
  138 + mpShowModal: false,
  139 + commandValue: {},
  140 + isShowTCP: false, //用于下发命令时判断是否是TCP/UDP
  141 + modalName: null,
  142 + copyTextValue: {
  143 + "method": "methodThingskit",
  144 + "params": {
  145 + "pin": 7,
  146 + "value": 1
  147 + }
  148 + }
  149 + };
  150 + },
  151 + computed: {
  152 + deviceStatus() {
  153 + return this.deviceDetail.deviceState === 'INACTIVE' ? '待激活' : this.deviceDetail.deviceState === 'ONLINE' ?
  154 + '在线' : '离线';
  155 + },
  156 + deviceType() {
  157 + return this.deviceDetail.deviceType === 'DIRECT_CONNECTION' ?
  158 + '直连设备' :
  159 + this.deviceDetail.deviceType === 'GATEWAY' ?
  160 + '网关设备' :
  161 + this.deviceDetail.deviceType === 'SENSOR' ?
  162 + '网关子设备' :
  163 + '';
  164 + },
  165 + alarmStatus() {
  166 + return this.deviceDetail.alarmStatus === '0' ? '否' : '是';
  167 + },
  168 + formatLastOnlineTime() {
  169 + return formatToDate(Number(this.deviceDetail.lastOnlineTime), 'YYYY-MM-DD HH:mm:ss');
  170 + }
  171 + },
  172 + beforeCreate() {
  173 + this.modalName = null
  174 + },
  175 + onLoad() {
  176 + // 隐藏原生的tabbar
  177 + uni.hideTabBar();
  178 + this.modalName = null
  179 + },
  180 + methods: {
  181 + handleCopy(value) {
  182 + useShowModal(JSON.stringify(value), '命令下发', '复制内容').then(res => {
  183 + uni.setClipboardData({
  184 + data: JSON.stringify(value),
  185 + success: () => {
  186 + uni.showToast({
  187 + title: '复制成功'
  188 + })
  189 + }
  190 + });
  191 + })
  192 + },
  193 + radioChange: function(evt) {
  194 + for (let i = 0; i < this.commandTypeList.length; i++) {
  195 + if (this.items[i].value === evt.detail.value) {
  196 + this.current = i;
  197 + break;
  198 + }
  199 + }
  200 + this.commandTypeStr = evt.detail.value
  201 + },
  202 + formatTextStatus(deviceState) {
  203 + return deviceState === 'INACTIVE' ? '#666' : deviceState === 'ONLINE' ? '#377DFF' : '#DE4437';
  204 + },
  205 + handleClick() {
  206 + const data = {
  207 + longitude: this.deviceDetail.deviceInfo.longitude || 0,
  208 + latitude: this.deviceDetail.deviceInfo.latitude || 0
  209 + };
  210 + uni.navigateTo({
  211 + url: '/device-subpackage/device-detail/device-position?data=' + JSON.stringify(data)
  212 + });
  213 + },
  214 + disabledScroll() {
  215 + return;
  216 + },
  217 + handleAppShowModal(e) {
  218 + this.modalName = e.currentTarget.dataset.target;
  219 + this.showNativeModal = true
  220 + },
  221 + handleMpShowModal() {
  222 + const {
  223 + transportType
  224 + } = this.deviceDetail.deviceProfile;
  225 + this.isShowTCP = transportType == 'TCP' ? true : false;
  226 + this.mpShowModal = true;
  227 + },
  228 + hideMpModal() {
  229 + this.mpShowModal = false;
  230 + },
  231 + hideAppModal() {
  232 + this.modalName = null;
  233 + this.showNativeModal = false
  234 + },
  235 + confirmCommand(commandType, inputCommandVal) {
  236 + this.handleCommand(commandType, inputCommandVal)
  237 + },
  238 + cancelCommand() {
  239 + this.hideMpModal();
  240 + this.hideAppModal();
  241 + this.commandTypeStr = 'OneWay'
  242 + this.inputCommandContent = ''
  243 + this.$nextTick(() => {
  244 + this.$refs.mpCommandIssuanceRef.reset()
  245 + })
  246 + },
  247 + handleAppCommand() {
  248 + this.handleCommand(this.commandTypeStr, this.inputCommandContent)
  249 + },
  250 + async handleCommand(commandType, inputCommandVal) {
  251 + if (!inputCommandVal) return uni.$u.toast('请输入下发内容~');
  252 + if (this.isShowTCP) {
  253 + //TCP的格式只能是字符串
  254 + const zg = /^[0-9a-zA-Z]*$/;
  255 + if (!zg.test(inputCommandVal)) {
  256 + uni.$u.toast('输入的内容只能是字母和数字的组合');
  257 + return;
  258 + }
  259 + this.commandValue.params = inputCommandVal;
  260 + } else {
  261 + this.commandValue.params = JSON.parse(inputCommandVal);
  262 + }
  263 + this.commandValue.persistent = true;
  264 + this.commandValue.additionalInfo = {
  265 + cmdType: 'API'
  266 + };
  267 + this.commandValue.method = 'methodThingskit';
  268 + this.commandValue.params = inputCommandVal;
  269 + await api.deviceApi.issueCommand(commandType, this.deviceDetail.tbDeviceId, this.commandValue);
  270 + this.cancelCommand();
  271 + uni.$u.toast('下发成功~');
  272 +
  273 + },
  274 +
  275 + }
  276 + };
  277 +</script>
  278 +
  279 +<style lang="scss" scoped>
  280 + @import url('../static/modal.css');
  281 +
  282 + .app-command-content {
  283 + .app-command-text {
  284 + display: flex;
  285 + justify-content: center;
  286 + align-items: center;
  287 +
  288 + text {
  289 + font-weight: 700;
  290 + }
  291 + }
  292 +
  293 + .app-command-type {
  294 + display: flex;
  295 + margin-top: 20rpx;
  296 + margin-left: 20rpx;
  297 +
  298 + text {
  299 + font-weight: 700;
  300 + }
  301 + }
  302 +
  303 + .app-command-body {
  304 + display: flex;
  305 + align-items: center;
  306 + justify-content: space-between;
  307 + margin-top: 20rpx;
  308 + margin-left: 20rpx;
  309 +
  310 + .app-command-textarea {
  311 + width: 625rpx;
  312 + height: 400rpx;
  313 + background: #FFFFFF;
  314 + box-shadow: 2px 2px 4px 0px rgba(0, 0, 0, 0.03);
  315 + border-radius: 10px;
  316 + }
  317 + }
  318 +
  319 + .app-command-buttons {
  320 + display: flex;
  321 + align-items: center;
  322 + justify-content: space-evenly;
  323 + height: 85rpx;
  324 + margin-top: 20rpx;
  325 + margin-bottom: 20rpx;
  326 +
  327 + .cancel-button {
  328 + width: 300rpx;
  329 + background: #e3e3e5;
  330 + height: 85rpx;
  331 + border-radius: 38rpx;
  332 + line-height: 85rpx;
  333 +
  334 + .cancel-text {
  335 + color: #333333
  336 + }
  337 + }
  338 +
  339 + .confrim-button {
  340 + width: 300rpx;
  341 + background: #3388ff;
  342 + border-radius: 38rpx;
  343 + height: 85rpx;
  344 + line-height: 85rpx;
  345 +
  346 + .confrim-text {
  347 + color: white
  348 + }
  349 + }
  350 + }
  351 + }
  352 +
  353 + .basic-page {
  354 + padding: 0 30rpx;
  355 +
  356 + .basic-header {
  357 + display: flex;
  358 + justify-content: space-between;
  359 + align-items: center;
  360 + height: 140rpx;
  361 + background-color: #fff;
  362 + border-radius: 20rpx;
  363 +
  364 + .basic-text {
  365 + width: 370rpx;
  366 + }
  367 +
  368 + .cu-item {
  369 + background: #3388ff;
  370 + border-radius: 12px;
  371 + width: 120rpx;
  372 + height: 48rpx;
  373 + text-align: center;
  374 + line-height: 40rpx;
  375 +
  376 + text {
  377 + font-size: 12px;
  378 + font-family: PingFangSC-Regular, PingFang SC;
  379 + font-weight: 400;
  380 + color: #ffffff;
  381 + }
  382 + }
  383 +
  384 + .basic-text-status {
  385 + font-size: 14px;
  386 + }
  387 + }
  388 +
  389 + .detail {
  390 + background-color: #fff;
  391 + margin-top: 30rpx;
  392 + border-radius: 20rpx;
  393 + width: 690rpx;
  394 +
  395 + .detail-item {
  396 + padding: 30rpx;
  397 + display: flex;
  398 + align-items: center;
  399 +
  400 + .detail-label {
  401 + color: #333;
  402 + font-size: 15px;
  403 + }
  404 +
  405 + .detail-value {
  406 + color: #666;
  407 + font-size: 14px;
  408 + margin-left: 30rpx;
  409 + }
  410 + }
  411 + }
  412 + }
  413 +
  414 + /deep/ .u-modal__content {
  415 + padding: 30rpx 0 !important;
  416 + }
  417 +</style>
device-subpackage/device-detail/components/command-detail.vue renamed from deviceSubPage/deviceDetailPage/tabDetail/CommandDetail.vue
1 -<template>  
2 - <view class="command-detail">  
3 - <view class="detail-top">{{ commandDetail.deviceName }}</view>  
4 - <view class="detail">  
5 - <view class="detail-item">  
6 - <view class="detail-label">设备类型</view>  
7 - <view class="detail-value">{{ deviceType }}</view>  
8 - </view>  
9 - <u-line length="90%" margin="0 auto"></u-line>  
10 - <view class="detail-item">  
11 - <view class="detail-label">设备编号</view>  
12 - <view class="detail-value">{{ commandDetail.deviceSn }}</view>  
13 - </view>  
14 - <u-line length="90%" margin="0 auto"></u-line>  
15 - <view class="detail-item">  
16 - <view class="detail-label">所属组织</view>  
17 - <view class="detail-value">{{ commandDetail.organizationName }}</view>  
18 - </view>  
19 - <u-line length="90%" margin="0 auto"></u-line>  
20 - <view class="detail-item">  
21 - <view class="detail-label">命令下发时间</view>  
22 - <view class="detail-value">{{ format(commandDetail.createTime) }}</view>  
23 - </view>  
24 - <u-line length="90%" margin="0 auto"></u-line>  
25 - <view class="detail-item">  
26 - <view class="detail-label">命令类型</view>  
27 - <view class="detail-value">{{ commandDetail.additionalInfo.cmdType===1?'服务':'自定义' }}</view>  
28 - </view>  
29 - <u-line length="90%" margin="0 auto" v-if="commandDetail.additionalInfo.cmdType"></u-line>  
30 - <view class="detail-item">  
31 - <view class="detail-label">响应类型</view>  
32 - <view class="detail-value">{{ commandDetail.request.oneway ? '单向' : '双向' }}</view>  
33 - </view>  
34 - <u-line length="90%" margin="0 auto"></u-line>  
35 - <view class="detail-item">  
36 - <view class="detail-label">命令状态</view>  
37 - <view class="detail-value">{{ commandDetail.statusName }}</view>  
38 - </view>  
39 - <u-line length="90%" margin="0 auto"></u-line>  
40 - <view class="detail-item" v-if="!commandDetail.request.oneway">  
41 - <view class="detail-label">响应结果</view>  
42 - <view class="detail-value">{{ commandDetail.response?JSON.stringify(commandDetail.response):'无' }}</view>  
43 - </view>  
44 - <!-- <view class="detail-item" v-if="!commandDetail.request.oneway">  
45 - <view class="detail-label">响应失败内容</view>  
46 - <view class="detail-value" style="width: 400rpx;" v-if="commandDetail.response.status!=='SUCCESS'">  
47 - <u--textarea placeholder="响应失败内容" v-model="failContent" />  
48 - </view>  
49 - </view> -->  
50 - </view>  
51 - <view class="command">命令内容</view>  
52 - <u-textarea :value="formatValue(commandDetail.request.body)" disabled></u-textarea>  
53 - <view style="height: 50rpx;"></view>  
54 - </view>  
55 -</template>  
56 -  
57 -<script>  
58 - import {  
59 - formatToDate  
60 - } from '@/plugins/utils.js';  
61 - export default {  
62 - data() {  
63 - return {  
64 - commandDetail: {},  
65 - failContent: ""  
66 - };  
67 - },  
68 - computed: {  
69 - deviceType() {  
70 - return this.commandDetail.deviceType === 'DIRECT_CONNECTION' ?  
71 - '直连设备' :  
72 - this.commandDetail.deviceType === 'GATEWAY' ?  
73 - '网关设备' :  
74 - this.commandDetail.deviceType === 'SENSOR' ?  
75 - '网关子设备' :  
76 - '';  
77 - }  
78 - },  
79 - methods: {  
80 - format(date) {  
81 - return formatToDate(date, 'YYYY-MM-DD HH:mm:ss');  
82 - },  
83 - formatValue(value) {  
84 - try {  
85 - const val = JSON.parse(value['params']);  
86 - //微信小程序端object无法显示,格式化为字符串  
87 - const stringifyVal = JSON.stringify(val['params'])  
88 - const formatVal = stringifyVal  
89 - .replace(/\\"/g, '"')  
90 - .replace(/]"/g, ']')  
91 - .replace(/"\[/g, '[');  
92 - return formatVal  
93 - } catch (e) {  
94 - console.error("命令记录页面格式化无返回值", e);  
95 - return value['params']  
96 - }  
97 - }  
98 - },  
99 - onLoad(options) {  
100 - const {  
101 - data  
102 - } = options;  
103 - this.commandDetail = JSON.parse(data);  
104 - if (this.commandDetail.response.status === 'SUCCESS') return  
105 - this.failContent = JSON.stringify(this.commandDetail.response.error)  
106 - }  
107 - };  
108 -</script>  
109 -  
110 -<style lang="scss" scoped>  
111 - .command-detail {  
112 - padding: 0 30rpx;  
113 - height: 100vh;  
114 - background-color: #f8f9fa;  
115 -  
116 - .detail-top {  
117 - height: 118rpx;  
118 - width: 690rpx;  
119 - display: flex;  
120 - align-items: center;  
121 - background-color: #fff;  
122 - color: #333;  
123 - border-radius: 20rpx;  
124 - font-size: 15px;  
125 - margin-top: 30rpx;  
126 - padding: 30rpx;  
127 - }  
128 -  
129 - .detail {  
130 - background-color: #fff;  
131 - margin-top: 30rpx;  
132 - border-radius: 20rpx;  
133 - width: 690rpx;  
134 -  
135 - .detail-item {  
136 - padding: 30rpx;  
137 - display: flex;  
138 - align-items: center;  
139 -  
140 - .detail-label {  
141 - color: #333;  
142 - font-size: 15px;  
143 - }  
144 -  
145 - .detail-value {  
146 - color: #666;  
147 - font-size: 14px;  
148 - margin-left: 30rpx;  
149 - }  
150 - }  
151 - }  
152 -  
153 - .command {  
154 - margin: 30rpx 0;  
155 - }  
156 - }  
157 -</style> 1 +<template>
  2 + <view class="command-detail">
  3 + <view class="detail-top">{{ commandDetail.deviceName }}</view>
  4 + <view class="detail">
  5 + <view class="detail-item">
  6 + <view class="detail-label">设备类型</view>
  7 + <view class="detail-value">{{ deviceType }}</view>
  8 + </view>
  9 + <u-line length="90%" margin="0 auto"></u-line>
  10 + <view class="detail-item">
  11 + <view class="detail-label">设备编号</view>
  12 + <view class="detail-value">{{ commandDetail.deviceSn }}</view>
  13 + </view>
  14 + <u-line length="90%" margin="0 auto"></u-line>
  15 + <view class="detail-item">
  16 + <view class="detail-label">所属组织</view>
  17 + <view class="detail-value">{{ commandDetail.organizationName }}</view>
  18 + </view>
  19 + <u-line length="90%" margin="0 auto"></u-line>
  20 + <view class="detail-item">
  21 + <view class="detail-label">命令下发时间</view>
  22 + <view class="detail-value">{{ format(commandDetail.createTime) }}</view>
  23 + </view>
  24 + <u-line length="90%" margin="0 auto"></u-line>
  25 + <view class="detail-item">
  26 + <view class="detail-label">命令类型</view>
  27 + <view class="detail-value">{{ commandDetail.additionalInfo.cmdType===1?'服务':'自定义' }}</view>
  28 + </view>
  29 + <u-line length="90%" margin="0 auto" v-if="commandDetail.additionalInfo.cmdType"></u-line>
  30 + <view class="detail-item">
  31 + <view class="detail-label">响应类型</view>
  32 + <view class="detail-value">{{ commandDetail.request.oneway ? '单向' : '双向' }}</view>
  33 + </view>
  34 + <u-line length="90%" margin="0 auto"></u-line>
  35 + <view class="detail-item">
  36 + <view class="detail-label">命令状态</view>
  37 + <view class="detail-value">{{ commandDetail.statusName }}</view>
  38 + </view>
  39 + <u-line length="90%" margin="0 auto"></u-line>
  40 + <view class="detail-item" v-if="!commandDetail.request.oneway">
  41 + <view class="detail-label">响应结果</view>
  42 + <view class="detail-value">{{ commandDetail.response?JSON.stringify(commandDetail.response):'无' }}
  43 + </view>
  44 + </view>
  45 + </view>
  46 + <view class="command">命令内容</view>
  47 + <u-textarea :value="formatValue(commandDetail.request.body)" disabled></u-textarea>
  48 + <view style="height: 50rpx;"></view>
  49 + </view>
  50 +</template>
  51 +
  52 +<script>
  53 + import {
  54 + formatToDate
  55 + } from '@/plugins/utils.js';
  56 + export default {
  57 + data() {
  58 + return {
  59 + commandDetail: {},
  60 + failContent: ""
  61 + };
  62 + },
  63 + computed: {
  64 + deviceType() {
  65 + return this.commandDetail.deviceType === 'DIRECT_CONNECTION' ?
  66 + '直连设备' :
  67 + this.commandDetail.deviceType === 'GATEWAY' ?
  68 + '网关设备' :
  69 + this.commandDetail.deviceType === 'SENSOR' ?
  70 + '网关子设备' :
  71 + '';
  72 + }
  73 + },
  74 + methods: {
  75 + format(date) {
  76 + return formatToDate(date, 'YYYY-MM-DD HH:mm:ss');
  77 + },
  78 + formatValue(value) {
  79 + try {
  80 + const val = JSON.parse(value['params']);
  81 + //微信小程序端object无法显示,格式化为字符串
  82 + const stringifyVal = JSON.stringify(val['params'])
  83 + const formatVal = stringifyVal
  84 + .replace(/\\"/g, '"')
  85 + .replace(/]"/g, ']')
  86 + .replace(/"\[/g, '[');
  87 + return formatVal
  88 + } catch (e) {
  89 + return value['params']
  90 + }
  91 + }
  92 + },
  93 + onLoad(options) {
  94 + const { data } = options;
  95 + this.commandDetail = JSON.parse(decodeURIComponent(data));
  96 + if (this.commandDetail.response.status === 'SUCCESS') return
  97 + this.failContent = JSON.stringify(this.commandDetail.response.error)
  98 + }
  99 + };
  100 +</script>
  101 +
  102 +<style lang="scss" scoped>
  103 + @import "../static/command-detail.scss";
  104 +</style>
  1 +<template>
  2 + <view class="command-record">
  3 + <!-- 命令记录筛选-->
  4 + <view class="filter-button" @click="openSearchDialog">
  5 + <text>筛选</text>
  6 + <image src="/static/shaixuan.png" />
  7 + </view>
  8 + <!-- 命令记录分页 -->
  9 + <mescroll-uni ref="mescrollRef" @init="mescrollInit" :down="downOption" @down="downCallback" @up="upCallback"
  10 + height="700px">
  11 + <view @click="openCommandDetail(item)" class="list-item" v-for="(item, index) in list" :key="index">
  12 + <view class="item">
  13 + <view class="item-first">
  14 + <text>{{ item.deviceName }}</text>
  15 + <!-- 业务 单向是没有响应状态 -->
  16 + <view v-if="!item.request.oneway">
  17 + <view class="item-right item-success" v-if="item.response">响应成功</view>
  18 + <view class="item-right item-fail" v-else>响应失败</view>
  19 + </view>
  20 + </view>
  21 + <view>
  22 + 命令类型:
  23 + <text class="ml-16">{{ item.additionalInfo.cmdType===1?'服务':'自定义' }}</text>
  24 + </view>
  25 + <view v-if="item.statusName">
  26 + 命令状态:
  27 + <text :style="{color:formatCommandStatus(item.status)}" class="ml-16">
  28 + {{ item.statusName }}
  29 + </text>
  30 + </view>
  31 + <view class="item-first">
  32 + <view>
  33 + 响应类型:
  34 + <text class="ml-16">{{ !item.request.oneway?'双向':'单向' }}</text>
  35 + </view>
  36 + <view class="time">{{ format(item.createTime) }}</view>
  37 + </view>
  38 + </view>
  39 + </view>
  40 + </mescroll-uni>
  41 + <!-- 命令记录弹窗筛选 -->
  42 + <u-popup @close="close" closeable bgColor="#fff" :show="show" mode="bottom" :round="20">
  43 + <view class="filter" @touchmove.stop.prevent="disabledScroll">
  44 + <view class="filter-title"><text>筛选条件</text></view>
  45 + <query-item :filterList="issueStatus" title="下发状态"
  46 + @clickTag="currentIndex => handleClickTag(currentIndex, issueStatus)"></query-item>
  47 + <view class="flex-column">
  48 + <view class="mt-3 command-time-text">命令下发时间</view>
  49 + <view class="mt-3">
  50 + <uni-datetime-picker return-type="timestamp" v-model="range" type="datetimerange"
  51 + rangeSeparator="至" />
  52 + </view>
  53 + </view>
  54 + <view class="h-30"></view>
  55 + <view class="button-group">
  56 + <view>
  57 + <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="重置"
  58 + @click="resetFilter"></u-button>
  59 + </view>
  60 + <view>
  61 + <u-button color="#3388ff" shape="circle" text="确认" @click="confirmFilter"></u-button>
  62 + </view>
  63 + </view>
  64 + </view>
  65 + </u-popup>
  66 + </view>
  67 +</template>
  68 +<script>
  69 + import queryItem from '@/pages/device/components/query-item.vue';
  70 + import MescrollMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js';
  71 + import {
  72 + formatToDate
  73 + } from '@/plugins/utils.js';
  74 + import {
  75 + debounce
  76 + } from '@/plugins/throttle.js';
  77 + import {
  78 + issueStatus
  79 + } from '../config/data.js'
  80 + import api from '@/api/index.js'
  81 + import {
  82 + useNavigateTo
  83 + } from '@/plugins/utils.js'
  84 +
  85 + export default {
  86 + mixins: [MescrollMixin],
  87 + components: {
  88 + queryItem
  89 + },
  90 + props: {
  91 + tbDeviceId: {
  92 + type: String,
  93 + default: ''
  94 + }
  95 + },
  96 + data() {
  97 + return {
  98 + show: false,
  99 + list: [],
  100 + total: 0,
  101 + range: [],
  102 + formTime: {
  103 + status: '',
  104 + startTime: '',
  105 + endTime: ''
  106 + },
  107 + status: '',
  108 + issueStatus,
  109 + downOption: {
  110 + auto: false //是否在初始化后,自动执行downCallback; 默认true
  111 + },
  112 + page: {
  113 + num: 0,
  114 + size: 10
  115 + }
  116 + };
  117 + },
  118 + methods: {
  119 + formatCommandStatus(status) {
  120 + return status == 'EXPIRED' ?
  121 + 'red' :
  122 + status == 'DELIVERED' ?
  123 + 'blue' :
  124 + status == 'QUEUED' ?
  125 + '#00C9A7' :
  126 + status == 'TIMEOUT' ?
  127 + 'red' :
  128 + status == 'SENT' ?
  129 + '#00C9A7' : ''
  130 + },
  131 + downCallback() {
  132 + for (let i in this.formTime) Reflect.set(this.formTime, i, '')
  133 + this.list = [];
  134 + this.page.num = 1;
  135 + this.loadData(this.page.num, {
  136 + tbDeviceId: this.tbDeviceId
  137 + });
  138 + },
  139 + format(date) {
  140 + return formatToDate(date, 'YYYY-MM-DD HH:mm:ss');
  141 + },
  142 + disabledScroll() {
  143 + return;
  144 + },
  145 + upCallback() {
  146 + const tbDeviceId = {
  147 + tbDeviceId: this.tbDeviceId
  148 + }
  149 + const condition = Object.values(this.formTime)
  150 + if (condition.length === 0) {
  151 + this.page.num += 1;
  152 + this.loadData(this.page.num, {
  153 + ...tbDeviceId
  154 + });
  155 + } else if (condition.filter(Boolean).length > 0) {
  156 + this.page.num += 1;
  157 + this.loadData(this.page.num, {
  158 + ...this.formTime,
  159 + ...tbDeviceId
  160 + });
  161 + } else {
  162 + this.page.num += 1;
  163 + this.loadData(this.page.num, {
  164 + ...tbDeviceId
  165 + });
  166 + }
  167 + },
  168 + async loadData(pageNo, params = {}) {
  169 + let httpData = {
  170 + ...params,
  171 + page: pageNo,
  172 + pageSize: 10
  173 + };
  174 + const res = await api.deviceApi.getRpcRecord({
  175 + params: httpData,
  176 + custom: {
  177 + load: false
  178 + }
  179 + })
  180 + if (!res) return
  181 + this.total = res.total;
  182 + uni.stopPullDownRefresh();
  183 + this.mescroll.endByPage(res.items.length, res.total);
  184 + if (pageNo == 1) {
  185 + this.list = res.items;
  186 + } else {
  187 + this.list = this.list.concat(res.items);
  188 + }
  189 + },
  190 + handleClickTag(currentIndex, list) {
  191 + list.map((item, index) => {
  192 + item.checked = index === currentIndex;
  193 + });
  194 + },
  195 + resetFilter() {
  196 + const {
  197 + issueStatus
  198 + } = this;
  199 + issueStatus.forEach(item => item.checked = false)
  200 + issueStatus[0].checked = true
  201 + },
  202 + close() {
  203 + this.show = false;
  204 + },
  205 + openSearchDialog() {
  206 + this.show = true;
  207 + this.resetFilter()
  208 + this.range = []
  209 + for (let i in this.formTime) Reflect.set(this.formTime, i, '')
  210 + },
  211 + hideKeyboard() {
  212 + uni.hideKeyboard();
  213 + },
  214 + confirmFilter() {
  215 + this.formTime.startTime = this.range[0]
  216 + this.formTime.endTime = this.range[1]
  217 + const issueStatus = this.issueStatus.find(item => item.checked);
  218 + this.formTime.status = issueStatus.type ? issueStatus.type : undefined,
  219 + this.loadData(1, {
  220 + tbDeviceId: this.tbDeviceId,
  221 + ...this.formTime
  222 + });
  223 + this.show = false;
  224 + },
  225 + openCommandDetail(item) {
  226 + useNavigateTo('/device-subpackage/device-detail/components/command-detail?data=', item)
  227 + }
  228 + }
  229 + };
  230 +</script>
  231 +
  232 +
  233 +<style lang="scss" scoped>
  234 + @import "../static/command-record.scss";
  235 +</style>
device-subpackage/device-detail/components/history-data.vue renamed from deviceSubPage/deviceDetailPage/tabDetail/historyData.vue
1 -<template>  
2 - <view class="historyData">  
3 - <!-- 公共组件-每个页面必须引入 -->  
4 - <public-module></public-module>  
5 - <view class="historyData-top">  
6 - <u-form :label-style="{ 'font-size': '0rpx' }">  
7 - <u-form-item @click="openCalendar">  
8 - <u-input v-model="timeData.selectTime" disabled disabledColor="#fff" placeholder="请选择日期"  
9 - border="none" suffixIcon="arrow-down">  
10 - <template slot="prefix">  
11 - <image class="icon" src="../../../static/can-der.png"></image>  
12 - </template>  
13 - </u-input>  
14 - </u-form-item>  
15 - <u-form-item @click="openTimeGap">  
16 - <u-input v-model="timeData.getTimeGap" disabled disabledColor="#fff" placeholder="请选择时间区间"  
17 - border="none" suffixIcon="arrow-down">  
18 - <template slot="prefix">  
19 - <image class="icon" src="../../../static/time.png"></image>  
20 - </template>  
21 - </u-input>  
22 - </u-form-item>  
23 - <u-form-item @click="openAvg">  
24 - <u-input shape="circle" v-model="aggText" placeholder="请选择数据聚合功能" disabled disabledColor="#377DFF0D"  
25 - suffixIcon="arrow-down" />  
26 - </u-form-item>  
27 - <u-form-item @click="openTimeGap" v-if="limitFlag">  
28 - <view class="u-flex">  
29 - <text>最大条数</text>  
30 - <u-number-box style="margin-left:30rpx" class="ml-10" v-model="timeData.limit" :min="7"  
31 - :max="50000"></u-number-box>  
32 - </view>  
33 - </u-form-item>  
34 - <u-form-item @click="openType">  
35 - <u-input shape="circle" v-model="timeData.getType" placeholder="请选择属性" disabled  
36 - disabledColor="#377DFF0D" suffixIcon="arrow-down" />  
37 - </u-form-item>  
38 - </u-form>  
39 -  
40 - <view class="charts-box" v-show="historyData.length">  
41 - <qiun-data-charts type="area" canvas2d canvasId="daskujdhasljkdcnzjkdfhuoqwlqwjhkdsamjczxnmdasd123321"  
42 - :chartData="chartData" :ontouch="true"  
43 - :opts="{ xAxis: { disabled: true, itemCount: 6, scrollShow: true }, legend: { show: false }, enableScroll: true }" />  
44 - </view>  
45 - <mescroll-empty v-if="!historyData.length" />  
46 - </view>  
47 - <view class="historyData-bottom" v-show="historyData.length">  
48 - <view class="table">  
49 - <view class="tr bg-w" v-if="historyData.length">  
50 - <view class="th">变量值</view>  
51 - <view class="th">更新时间</view>  
52 - </view>  
53 - <view class="tr bg-g" :class="{ odd: index % 2 === 0 }" v-for="(item, index) in historyData"  
54 - :key="index">  
55 - <view class="td">{{ item.value }}</view>  
56 - <view class="td">{{ item.ts }}</view>  
57 - </view>  
58 - </view>  
59 - </view>  
60 - <u-calendar :show="showCalendar" :defaultDate="defaultDate" closeOnClickOverlay mode="range" startText="开始时间"  
61 - endText="结束时间" confirmDisabledText="请选择日期" :minDate="minDate" :maxDate="maxDate" @confirm="calendarConfirm"  
62 - @close="calendarClose"></u-calendar>  
63 - <u-picker :show="showTimeGap" :columns="columns" keyName="label" closeOnClickOverlay @confirm="confirmTimeGap"  
64 - @cancel="cancelTimeGap" @close="cancelTimeGap" :defaultIndex="[3]"></u-picker>  
65 - <u-picker :show="showSelectType" :columns="keys" closeOnClickOverlay @confirm="confirmTypeGap"  
66 - @cancel="cancelTypeGap" @close="cancelTypeGap"></u-picker>  
67 - <u-picker :show="showSelectAvg" :columns="avgColumns" keyName="label" closeOnClickOverlay  
68 - @confirm="confirmAvgGap" @cancel="showSelectAvg=false" @close="showSelectAvg=false"></u-picker>  
69 - </view>  
70 -</template>  
71 -  
72 -<script>  
73 - import fTabbar from '@/components/module/f-tabbar/f-tabbar';  
74 - import qiunDataCharts from '@/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue';  
75 - import {  
76 - getHistoryData  
77 - } from '../api/index.js';  
78 - import {  
79 - formatToDate  
80 - } from '@/plugins/utils.js';  
81 - const d = new Date();  
82 - const year = d.getFullYear();  
83 - let month = d.getMonth() + 1;  
84 - month = month < 10 ? `0${month}` : month;  
85 - const date = d.getDate();  
86 - export default {  
87 - components: {  
88 - fTabbar,  
89 - qiunDataCharts  
90 - },  
91 - props: {  
92 - keys: {  
93 - type: Array,  
94 - default: () => []  
95 - },  
96 - yesterday: {  
97 - type: String,  
98 - default: ''  
99 - },  
100 - today: {  
101 - type: String,  
102 - default: ''  
103 - },  
104 - timeDiff: {  
105 - type: String,  
106 - default: ''  
107 - },  
108 - historyData: {  
109 - type: Array,  
110 - default: () => []  
111 - },  
112 - entityId: {  
113 - type: String,  
114 - required: true  
115 - },  
116 - start: {  
117 - type: String,  
118 - required: true  
119 - },  
120 - end: {  
121 - type: String,  
122 - required: true  
123 - }  
124 - },  
125 - data() {  
126 - return {  
127 - limitFlag: true,  
128 - avgColumns: [  
129 - [{  
130 - label: '最小值',  
131 - value: 'MIN'  
132 - }, {  
133 - label: '最大值',  
134 - value: 'MAX'  
135 - },  
136 - {  
137 - label: '平均值',  
138 - value: 'AVG'  
139 - },  
140 - {  
141 - label: '求和',  
142 - value: 'SUM'  
143 - },  
144 - {  
145 - label: '计数',  
146 - value: 'COUNT'  
147 - },  
148 - {  
149 - label: '空',  
150 - value: 'NONE'  
151 - },  
152 - ]  
153 - ],  
154 - startTs: this.start,  
155 - endTs: this.end,  
156 - showCalendar: false,  
157 - showTimeGap: false,  
158 - showSelectType: false,  
159 - showSelectAvg: false,  
160 - minDate: `${year}-${month - 1}-${date}`,  
161 - maxDate: `${year}-${month}-${date + 1}`,  
162 - defaultDate: [this.yesterday, this.today],  
163 - chartData: {  
164 - categories: this.historyData.length && this.historyData.map(item => item.ts),  
165 - series: [{  
166 - name: this.keys[0][0],  
167 - data: this.historyData.length && this.historyData.map(item => Number(item.value))  
168 - }]  
169 - },  
170 - columns: [  
171 - [{  
172 - label: '5分钟',  
173 - value: 300000  
174 - },  
175 - {  
176 - label: '10分钟',  
177 - value: 600000  
178 - },  
179 - {  
180 - label: '15分钟',  
181 - value: 900000  
182 - },  
183 - {  
184 - label: '30分钟',  
185 - value: 1800000  
186 - },  
187 - {  
188 - label: '1小时',  
189 - value: 3600000  
190 - },  
191 - {  
192 - label: '2小时',  
193 - value: 7200000  
194 - }  
195 - ]  
196 - ],  
197 - timeData: {  
198 - selectTime: this.yesterday + ' 至 ' + this.today,  
199 - getTimeGap: this.timeDiff,  
200 - getType: this.keys[0][0],  
201 - limit: 7,  
202 - agg: 'NONE'  
203 - },  
204 - aggText: '空'  
205 - };  
206 - },  
207 - watch: {  
208 - historyData(newValue) {  
209 - if (!newValue.length) {  
210 - this.chartData.categories = [];  
211 - this.chartData.series = [];  
212 - } else {  
213 - this.chartData.categories = newValue.map(item => item.ts);  
214 - this.chartData.series = [{  
215 - name: this.keys[0][0],  
216 - data: newValue.map(item => Number(item.value))  
217 - }];  
218 - }  
219 - }  
220 - },  
221 - methods: {  
222 - // 动态生成Columns  
223 - generateColumns(value) {  
224 - if (value < 604800000) {  
225 - // 小于7天  
226 - return [  
227 - [{  
228 - label: '5分钟',  
229 - value: 300000  
230 - },  
231 - {  
232 - label: '10分钟',  
233 - value: 600000  
234 - },  
235 - {  
236 - label: '15分钟',  
237 - value: 900000  
238 - },  
239 - {  
240 - label: '30分钟',  
241 - value: 1800000  
242 - },  
243 - {  
244 - label: '1小时',  
245 - value: 3600000  
246 - },  
247 - {  
248 - label: '2小时',  
249 - value: 7200000  
250 - }  
251 - ]  
252 - ];  
253 - } else if (value < 2592000000) {  
254 - // 小于30天  
255 - return [  
256 - [{  
257 - label: '30分钟',  
258 - value: 1800000  
259 - },  
260 - {  
261 - label: '1小时',  
262 - value: 3600000  
263 - },  
264 - {  
265 - label: '2小时',  
266 - value: 7200000  
267 - },  
268 - {  
269 - label: '5小时',  
270 - value: 18000000  
271 - },  
272 - {  
273 - label: '10小时',  
274 - value: 36000000  
275 - },  
276 - {  
277 - label: '12小时',  
278 - value: 43200000  
279 - },  
280 - {  
281 - label: '1天',  
282 - value: 86400000  
283 - }  
284 - ]  
285 - ];  
286 - } else if (value >= 2592000000) {  
287 - // 大于30天  
288 - return [  
289 - [{  
290 - label: '2小时',  
291 - value: 7200000  
292 - },  
293 - {  
294 - label: '5小时',  
295 - value: 18000000  
296 - },  
297 - {  
298 - label: '10小时',  
299 - value: 36000000  
300 - },  
301 - {  
302 - label: '12小时',  
303 - value: 43200000  
304 - },  
305 - {  
306 - label: '1天',  
307 - value: 86400000  
308 - }  
309 - ]  
310 - ];  
311 - }  
312 - },  
313 - openCalendar() {  
314 - this.showCalendar = true;  
315 - },  
316 - openTimeGap() {  
317 - this.showTimeGap = true;  
318 - },  
319 - openType() {  
320 - this.showSelectType = true;  
321 - },  
322 - openAvg() {  
323 - this.showSelectAvg = true  
324 - },  
325 - calendarConfirm(date) {  
326 - this.showCalendar = false;  
327 - this.timeData.selectTime = `${date[0]} 至 ${date[date.length - 1]}`;  
328 - // 选择的日期时间差(时间戳)  
329 - const timeDiff = formatToDate(date[date.length - 1], 'x') - formatToDate(date[0], 'x');  
330 - const genColumns = this.generateColumns(timeDiff);  
331 - this.columns = genColumns;  
332 - this.timeData.getTimeGap = '';  
333 - this.timeData.getType = '';  
334 - this.startTs = formatToDate(date[0], 'x');  
335 - // 最后时间的最后一秒  
336 - this.endTs = formatToDate(`${date[date.length - 1]} 23:59:59`, 'x');  
337 - },  
338 - calendarClose() {  
339 - this.showCalendar = false;  
340 - },  
341 - confirmTimeGap(time) {  
342 - this.showTimeGap = false;  
343 - this.timeData.getTimeGap = time.value[0].label;  
344 - this.timeData.getType = '';  
345 - },  
346 -  
347 - cancelTimeGap() {  
348 - this.showTimeGap = false;  
349 - },  
350 - confirmAvgGap(e) {  
351 - this.timeData.agg = e.value[0].value  
352 - this.aggText = e.value[0].label  
353 - if (e.value[0].value === 'NONE') {  
354 - this.limitFlag = true  
355 - this.timeData.limit = 7  
356 - } else {  
357 - this.timeData.limit = null  
358 - this.limitFlag = false  
359 - }  
360 - this.showSelectAvg = false  
361 - },  
362 - async confirmTypeGap(time) {  
363 - this.showSelectType = false;  
364 - this.timeData.getType = time.value[0];  
365 - const interval = this.columns[0].find(item => item.label === this.timeData.getTimeGap);  
366 - const data = await getHistoryData({  
367 - startTs: this.startTs,  
368 - endTs: this.endTs,  
369 - keys: this.timeData.getType,  
370 - interval: this.limitFlag ? null : interval.value,  
371 - entityId: this.entityId,  
372 - limit: this.timeData.limit,  
373 - agg: this.timeData.agg  
374 - });  
375 - this.$emit('update', data[this.timeData.getType]);  
376 - },  
377 - cancelTypeGap() {  
378 - this.showSelectType = false;  
379 - }  
380 - }  
381 - };  
382 -</script>  
383 -  
384 -<style lang="scss" scoped>  
385 - .charts-box {  
386 - width: 100%;  
387 - height: 550rpx;  
388 - }  
389 -  
390 - .historyData {  
391 - margin: 30rpx;  
392 -  
393 - .historyData-top {  
394 - padding: 30rpx;  
395 - background-color: #fff;  
396 - // height: 870rpx;  
397 - border-radius: 20rpx;  
398 -  
399 - .icon {  
400 - width: 28rpx;  
401 - height: 28rpx;  
402 - margin-right: 15rpx;  
403 - }  
404 - }  
405 -  
406 - .historyData-bottom {  
407 - margin-top: 30rpx;  
408 - background-color: #fff;  
409 - border-radius: 20rpx;  
410 -  
411 - .table {  
412 - border: 0px solid darkgray;  
413 -  
414 - .tr {  
415 - display: flex;  
416 - width: 100%;  
417 - justify-content: center;  
418 - height: 3rem;  
419 - align-items: center;  
420 -  
421 - .th {  
422 - display: flex;  
423 - justify-content: center;  
424 - align-items: center;  
425 - width: 50%;  
426 - color: #333;  
427 - font-weight: 500;  
428 - }  
429 -  
430 - .td {  
431 - color: #999;  
432 - width: 50%;  
433 - display: flex;  
434 - justify-content: center;  
435 - text-align: center;  
436 - }  
437 - }  
438 - }  
439 - }  
440 - }  
441 -  
442 - .odd {  
443 - background-color: #f9fcff;  
444 - } 1 +<template>
  2 + <view class="historyData">
  3 + <!-- 公共组件-每个页面必须引入 -->
  4 + <public-module></public-module>
  5 + <view class="historyData-top">
  6 + <u-form :label-style="{ 'font-size': '0rpx' }">
  7 + <u-form-item @click="openCalendar">
  8 + <u-input v-model="timeData.selectTime" disabled disabledColor="#fff" placeholder="请选择日期"
  9 + border="none" suffixIcon="arrow-down">
  10 + <template slot="prefix">
  11 + <image class="icon" src="../../../static/can-der.png"></image>
  12 + </template>
  13 + </u-input>
  14 + </u-form-item>
  15 + <u-form-item @click="openTimeGap">
  16 + <u-input v-model="timeData.getTimeGap" disabled disabledColor="#fff" placeholder="请选择时间区间"
  17 + border="none" suffixIcon="arrow-down">
  18 + <template slot="prefix">
  19 + <image class="icon" src="../../../static/time.png"></image>
  20 + </template>
  21 + </u-input>
  22 + </u-form-item>
  23 + <u-form-item @click="openAvg">
  24 + <u-input shape="circle" v-model="aggText" placeholder="请选择数据聚合功能" disabled disabledColor="#377DFF0D"
  25 + suffixIcon="arrow-down" />
  26 + </u-form-item>
  27 + <u-form-item @click="openTimeGap" v-if="limitFlag">
  28 + <view class="u-flex">
  29 + <text>最大条数</text>
  30 + <u-number-box style="margin-left:30rpx" class="ml-10" v-model="timeData.limit" :min="7"
  31 + :max="50000"></u-number-box>
  32 + </view>
  33 + </u-form-item>
  34 + <u-form-item @click="openType">
  35 + <u-input shape="circle" v-model="timeData.getType" placeholder="请选择属性" disabled
  36 + disabledColor="#377DFF0D" suffixIcon="arrow-down" />
  37 + </u-form-item>
  38 + </u-form>
  39 +
  40 + <view class="charts-box" v-show="historyData.length">
  41 + <qiun-data-charts type="area" canvas2d canvasId="daskujdhasljkdcnzjkdfhuoqwlqwjhkdsamjczxnmdasd123321"
  42 + :chartData="chartData" :ontouch="true"
  43 + :opts="{ xAxis: { disabled: true, itemCount: 6, scrollShow: true }, legend: { show: false }, enableScroll: true }" />
  44 + </view>
  45 + <mescroll-empty v-if="!historyData.length" />
  46 + </view>
  47 + <view class="historyData-bottom" v-show="historyData.length">
  48 + <view class="table">
  49 + <view class="tr bg-w" v-if="historyData.length">
  50 + <view class="th">变量值</view>
  51 + <view class="th">更新时间</view>
  52 + </view>
  53 + <view class="tr bg-g" :class="{ odd: index % 2 === 0 }" v-for="(item, index) in historyData"
  54 + :key="index">
  55 + <view class="td">{{ item.value }}</view>
  56 + <view class="td">{{ item.ts }}</view>
  57 + </view>
  58 + </view>
  59 + </view>
  60 + <u-calendar :show="showCalendar" :defaultDate="defaultDate" closeOnClickOverlay mode="range" startText="开始时间"
  61 + endText="结束时间" confirmDisabledText="请选择日期" :minDate="minDate" :maxDate="maxDate" @confirm="calendarConfirm"
  62 + @close="calendarClose"></u-calendar>
  63 + <u-picker :show="showTimeGap" :columns="columns" keyName="label" closeOnClickOverlay @confirm="confirmTimeGap"
  64 + @cancel="cancelTimeGap" @close="cancelTimeGap" :defaultIndex="[3]"></u-picker>
  65 + <u-picker :show="showSelectType" :columns="keys" closeOnClickOverlay @confirm="confirmTypeGap"
  66 + @cancel="cancelTypeGap" @close="cancelTypeGap"></u-picker>
  67 + <u-picker :show="showSelectAvg" :columns="avgColumns" keyName="label" closeOnClickOverlay
  68 + @confirm="confirmAvgGap" @cancel="showSelectAvg=false" @close="showSelectAvg=false"></u-picker>
  69 + </view>
  70 +</template>
  71 +
  72 +<script>
  73 + import fTabbar from '@/components/module/f-tabbar/f-tabbar';
  74 + import qiunDataCharts from './uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue';
  75 + import {
  76 + getHistoryData
  77 + } from '../api/index.js';
  78 + import {
  79 + formatToDate
  80 + } from '@/plugins/utils.js';
  81 + const d = new Date();
  82 + const year = d.getFullYear();
  83 + let month = d.getMonth() + 1;
  84 + month = month < 10 ? `0${month}` : month;
  85 + const date = d.getDate();
  86 + export default {
  87 + components: {
  88 + fTabbar,
  89 + qiunDataCharts
  90 + },
  91 + props: {
  92 + keys: {
  93 + type: Array,
  94 + default: () => []
  95 + },
  96 + yesterday: {
  97 + type: String,
  98 + default: ''
  99 + },
  100 + today: {
  101 + type: String,
  102 + default: ''
  103 + },
  104 + timeDiff: {
  105 + type: String,
  106 + default: ''
  107 + },
  108 + historyData: {
  109 + type: Array,
  110 + default: () => []
  111 + },
  112 + entityId: {
  113 + type: String,
  114 + required: true
  115 + },
  116 + start: {
  117 + type: String,
  118 + required: true
  119 + },
  120 + end: {
  121 + type: String,
  122 + required: true
  123 + }
  124 + },
  125 + data() {
  126 + return {
  127 + limitFlag: true,
  128 + avgColumns: [
  129 + [{
  130 + label: '最小值',
  131 + value: 'MIN'
  132 + }, {
  133 + label: '最大值',
  134 + value: 'MAX'
  135 + },
  136 + {
  137 + label: '平均值',
  138 + value: 'AVG'
  139 + },
  140 + {
  141 + label: '求和',
  142 + value: 'SUM'
  143 + },
  144 + {
  145 + label: '计数',
  146 + value: 'COUNT'
  147 + },
  148 + {
  149 + label: '空',
  150 + value: 'NONE'
  151 + },
  152 + ]
  153 + ],
  154 + startTs: this.start,
  155 + endTs: this.end,
  156 + showCalendar: false,
  157 + showTimeGap: false,
  158 + showSelectType: false,
  159 + showSelectAvg: false,
  160 + minDate: `${year}-${month - 1}-${date}`,
  161 + maxDate: `${year}-${month}-${date + 1}`,
  162 + defaultDate: [this.yesterday, this.today],
  163 + chartData: {
  164 + categories: this.historyData.length && this.historyData.map(item => item.ts),
  165 + series: [{
  166 + name: this.keys[0][0],
  167 + data: this.historyData.length && this.historyData.map(item => Number(item.value))
  168 + }]
  169 + },
  170 + columns: [
  171 + [{
  172 + label: '5分钟',
  173 + value: 300000
  174 + },
  175 + {
  176 + label: '10分钟',
  177 + value: 600000
  178 + },
  179 + {
  180 + label: '15分钟',
  181 + value: 900000
  182 + },
  183 + {
  184 + label: '30分钟',
  185 + value: 1800000
  186 + },
  187 + {
  188 + label: '1小时',
  189 + value: 3600000
  190 + },
  191 + {
  192 + label: '2小时',
  193 + value: 7200000
  194 + }
  195 + ]
  196 + ],
  197 + timeData: {
  198 + selectTime: this.yesterday + ' 至 ' + this.today,
  199 + getTimeGap: this.timeDiff,
  200 + getType: this.keys[0][0],
  201 + limit: 7,
  202 + agg: 'NONE'
  203 + },
  204 + aggText: '空'
  205 + };
  206 + },
  207 + watch: {
  208 + historyData(newValue) {
  209 + if (!newValue.length) {
  210 + this.chartData.categories = [];
  211 + this.chartData.series = [];
  212 + } else {
  213 + this.chartData.categories = newValue.map(item => item.ts);
  214 + this.chartData.series = [{
  215 + name: this.keys[0][0],
  216 + data: newValue.map(item => Number(item.value))
  217 + }];
  218 + }
  219 + }
  220 + },
  221 + methods: {
  222 + // 动态生成Columns
  223 + generateColumns(value) {
  224 + if (value < 604800000) {
  225 + // 小于7天
  226 + return [
  227 + [{
  228 + label: '5分钟',
  229 + value: 300000
  230 + },
  231 + {
  232 + label: '10分钟',
  233 + value: 600000
  234 + },
  235 + {
  236 + label: '15分钟',
  237 + value: 900000
  238 + },
  239 + {
  240 + label: '30分钟',
  241 + value: 1800000
  242 + },
  243 + {
  244 + label: '1小时',
  245 + value: 3600000
  246 + },
  247 + {
  248 + label: '2小时',
  249 + value: 7200000
  250 + }
  251 + ]
  252 + ];
  253 + } else if (value < 2592000000) {
  254 + // 小于30天
  255 + return [
  256 + [{
  257 + label: '30分钟',
  258 + value: 1800000
  259 + },
  260 + {
  261 + label: '1小时',
  262 + value: 3600000
  263 + },
  264 + {
  265 + label: '2小时',
  266 + value: 7200000
  267 + },
  268 + {
  269 + label: '5小时',
  270 + value: 18000000
  271 + },
  272 + {
  273 + label: '10小时',
  274 + value: 36000000
  275 + },
  276 + {
  277 + label: '12小时',
  278 + value: 43200000
  279 + },
  280 + {
  281 + label: '1天',
  282 + value: 86400000
  283 + }
  284 + ]
  285 + ];
  286 + } else if (value >= 2592000000) {
  287 + // 大于30天
  288 + return [
  289 + [{
  290 + label: '2小时',
  291 + value: 7200000
  292 + },
  293 + {
  294 + label: '5小时',
  295 + value: 18000000
  296 + },
  297 + {
  298 + label: '10小时',
  299 + value: 36000000
  300 + },
  301 + {
  302 + label: '12小时',
  303 + value: 43200000
  304 + },
  305 + {
  306 + label: '1天',
  307 + value: 86400000
  308 + }
  309 + ]
  310 + ];
  311 + }
  312 + },
  313 + openCalendar() {
  314 + this.showCalendar = true;
  315 + },
  316 + openTimeGap() {
  317 + this.showTimeGap = true;
  318 + },
  319 + openType() {
  320 + this.showSelectType = true;
  321 + },
  322 + openAvg() {
  323 + this.showSelectAvg = true
  324 + },
  325 + calendarConfirm(date) {
  326 + this.showCalendar = false;
  327 + this.timeData.selectTime = `${date[0]} 至 ${date[date.length - 1]}`;
  328 + // 选择的日期时间差(时间戳)
  329 + const timeDiff = formatToDate(date[date.length - 1], 'x') - formatToDate(date[0], 'x');
  330 + const genColumns = this.generateColumns(timeDiff);
  331 + this.columns = genColumns;
  332 + this.timeData.getTimeGap = '';
  333 + this.timeData.getType = '';
  334 + this.startTs = formatToDate(date[0], 'x');
  335 + // 最后时间的最后一秒
  336 + this.endTs = formatToDate(`${date[date.length - 1]} 23:59:59`, 'x');
  337 + },
  338 + calendarClose() {
  339 + this.showCalendar = false;
  340 + },
  341 + confirmTimeGap(time) {
  342 + this.showTimeGap = false;
  343 + this.timeData.getTimeGap = time.value[0].label;
  344 + this.timeData.getType = '';
  345 + },
  346 +
  347 + cancelTimeGap() {
  348 + this.showTimeGap = false;
  349 + },
  350 + confirmAvgGap(e) {
  351 + this.timeData.agg = e.value[0].value
  352 + this.aggText = e.value[0].label
  353 + if (e.value[0].value === 'NONE') {
  354 + this.limitFlag = true
  355 + this.timeData.limit = 7
  356 + } else {
  357 + this.timeData.limit = null
  358 + this.limitFlag = false
  359 + }
  360 + this.showSelectAvg = false
  361 + },
  362 + async confirmTypeGap(time) {
  363 + this.showSelectType = false;
  364 + this.timeData.getType = time.value[0];
  365 + const interval = this.columns[0].find(item => item.label === this.timeData.getTimeGap);
  366 + const data = await getHistoryData({
  367 + startTs: this.startTs,
  368 + endTs: this.endTs,
  369 + keys: this.timeData.getType,
  370 + interval: this.limitFlag ? null : interval.value,
  371 + entityId: this.entityId,
  372 + limit: this.timeData.limit,
  373 + agg: this.timeData.agg
  374 + });
  375 + this.$emit('update', data[this.timeData.getType]);
  376 + },
  377 + cancelTypeGap() {
  378 + this.showSelectType = false;
  379 + }
  380 + }
  381 + };
  382 +</script>
  383 +
  384 +<style lang="scss" scoped>
  385 + .charts-box {
  386 + width: 100%;
  387 + height: 550rpx;
  388 + }
  389 +
  390 + .historyData {
  391 + margin: 30rpx;
  392 +
  393 + .historyData-top {
  394 + padding: 30rpx;
  395 + background-color: #fff;
  396 + // height: 870rpx;
  397 + border-radius: 20rpx;
  398 +
  399 + .icon {
  400 + width: 28rpx;
  401 + height: 28rpx;
  402 + margin-right: 15rpx;
  403 + }
  404 + }
  405 +
  406 + .historyData-bottom {
  407 + margin-top: 30rpx;
  408 + background-color: #fff;
  409 + border-radius: 20rpx;
  410 +
  411 + .table {
  412 + border: 0px solid darkgray;
  413 +
  414 + .tr {
  415 + display: flex;
  416 + width: 100%;
  417 + justify-content: center;
  418 + height: 3rem;
  419 + align-items: center;
  420 +
  421 + .th {
  422 + display: flex;
  423 + justify-content: center;
  424 + align-items: center;
  425 + width: 50%;
  426 + color: #333;
  427 + font-weight: 500;
  428 + }
  429 +
  430 + .td {
  431 + color: #999;
  432 + width: 50%;
  433 + display: flex;
  434 + justify-content: center;
  435 + text-align: center;
  436 + }
  437 + }
  438 + }
  439 + }
  440 + }
  441 +
  442 + .odd {
  443 + background-color: #f9fcff;
  444 + }
445 </style> 445 </style>
  1 +<template>
  2 + <view class="mp-u-modal">
  3 + <u-modal :mask-close-able="true" :show="showModal" closeOnClickOverlay :showConfirmButton="false"
  4 + @close="$emit('hideModal')" @touchmove.stop.prevent="disabledScroll" z-index="99999">
  5 + <view class="w-100 modal-content">
  6 + <view class="header-title">命令下发</view>
  7 + <view class="u-flex">
  8 + <text class="type-text">下发类型:</text>
  9 + <u-radio-group v-model="commandType" placement="row">
  10 + <u-radio activeColor="#3388FF" label="单向" name="OneWay"></u-radio>
  11 + <view style="margin: 0 20rpx;"></view>
  12 + <u-radio activeColor="#3388FF" label="双向" name="TwoWay"></u-radio>
  13 + </u-radio-group>
  14 + </view>
  15 + <view class="content-body">
  16 + <div class="u-flex u-row-between">
  17 + <u--textarea :placeholder="`请输入下发内容${isShowTCP?'(字符串格式)':'(json格式)'}`"
  18 + v-model="inputCommandVal" />
  19 + <u-icon v-if="!isShowTCP" @click="handleCopy(copyTextValue)" name="question-circle"
  20 + color="#2979ff" size="28" class="ml-10">
  21 + </u-icon>
  22 + </div>
  23 + </view>
  24 + <view class="button-group">
  25 + <view>
  26 + <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="取消"
  27 + @click="cancelCommand"></u-button>
  28 + </view>
  29 + <view>
  30 + <u-button color="#3388ff" shape="circle" text="确认" @click="confirmCommand"></u-button>
  31 + </view>
  32 + </view>
  33 + </view>
  34 + </u-modal>
  35 + </view>
  36 +</template>
  37 +
  38 +<script>
  39 + import {
  40 + useShowModal
  41 + } from '@/plugins/utils.js'
  42 +
  43 + export default {
  44 + props: {
  45 + showModal: Boolean,
  46 + isShowTCP: Boolean
  47 + },
  48 + data() {
  49 + return {
  50 + current: 0,
  51 + commandType: 'OneWay',
  52 + inputCommandVal: '',
  53 + copyTextValue: {
  54 + "method": "methodThingskit",
  55 + "params": {
  56 + "pin": 7,
  57 + "value": 1
  58 + }
  59 + }
  60 + }
  61 + },
  62 + methods: {
  63 + cancelCommand() {
  64 + this.$emit('cancelCommand')
  65 + },
  66 + confirmCommand() {
  67 + this.$emit('confirmCommand', this.commandType, this.inputCommandVal)
  68 + },
  69 + handleCopy(value) {
  70 + useShowModal(JSON.stringify(value), '命令下发', '复制内容').then(res => {
  71 + uni.setClipboardData({
  72 + data: JSON.stringify(value),
  73 + success: () => {
  74 + uni.showToast({
  75 + title: '复制成功'
  76 + })
  77 + }
  78 + });
  79 + })
  80 + },
  81 + reset() {
  82 + this.commandType = 'OneWay'
  83 + this.inputCommandVal = ''
  84 + }
  85 + }
  86 + }
  87 +</script>
  88 +
  89 +<style lang="scss" scoped>
  90 + .modal-content {
  91 + width: 720rpx;
  92 + padding: 0 30rpx;
  93 + background-color: white;
  94 +
  95 + .header-title {
  96 + text-align: center;
  97 + font-weight: 700;
  98 + margin-bottom: 40rpx;
  99 + }
  100 +
  101 + .type-text {
  102 + color: #333;
  103 + font-size: 14px;
  104 + font-weight: 700;
  105 + margin-right: 30rpx;
  106 + }
  107 +
  108 + .content-body {
  109 + margin-top: 28rpx;
  110 + width: 100%;
  111 + }
  112 +
  113 + .button-group {
  114 + display: flex;
  115 + margin-top: 40rpx;
  116 + justify-content: space-between;
  117 +
  118 + view {
  119 + width: 300rpx;
  120 + }
  121 + }
  122 + }
  123 +</style>
  1 +<template>
  2 + <view class="realtime-page">
  3 + <view class="item" v-for="(item, index) in recordList" :key="index">
  4 + <view class="item-top">
  5 + <view>{{ item.key }}</view>
  6 + <view class="item-value">{{ item.value }}</view>
  7 + </view>
  8 + <view class="item-time">{{ item.time }}</view>
  9 + </view>
  10 + <mescroll-empty v-if="!recordList.length" />
  11 + </view>
  12 +</template>
  13 +
  14 +<script>
  15 + export default {
  16 + props: {
  17 + recordList: {
  18 + type: Array,
  19 + default: () => []
  20 + }
  21 + }
  22 + };
  23 +</script>
  24 +
  25 +<style lang="scss" scoped>
  26 + @import "../static/realtime-data.scss";
  27 +</style>
  1 +const list = [{
  2 + name: "基础信息",
  3 + },
  4 + {
  5 + name: "实时数据",
  6 + },
  7 + {
  8 + name: "历史数据",
  9 + },
  10 + {
  11 + name: "告警记录",
  12 + },
  13 +]
  14 +
  15 +const issueStatus = [{
  16 + checked: true,
  17 + name: '全部',
  18 + type: ''
  19 + },
  20 + {
  21 + checked: false,
  22 + name: '队列中',
  23 + type: 'QUEUED'
  24 + },
  25 + {
  26 + checked: false,
  27 + name: '已发送',
  28 + type: 'SENT'
  29 + },
  30 + {
  31 + checked: false,
  32 + name: '发送成功',
  33 + type: 'DELIVERED'
  34 + },
  35 + {
  36 + checked: false,
  37 + name: '响应成功',
  38 + type: 'SUCCESSFUL'
  39 + },
  40 + {
  41 + checked: false,
  42 + name: '超时',
  43 + type: 'TIMEOUT'
  44 + },
  45 + {
  46 + checked: false,
  47 + name: '已过期',
  48 + type: 'EXPIRED'
  49 + },
  50 + {
  51 + checked: false,
  52 + name: '响应失败',
  53 + type: 'FAILED'
  54 + },
  55 + {
  56 + checked: false,
  57 + name: '已删除',
  58 + type: 'DELETED'
  59 + }
  60 +]
  61 +
  62 +const commandTypeList = [{
  63 + value: 'OneWay',
  64 + name: '单向',
  65 + },
  66 + {
  67 + value: 'TwoWay',
  68 + name: '双向'
  69 + },
  70 +]
  71 +
  72 +
  73 +export {
  74 + list,
  75 + issueStatus,
  76 + commandTypeList
  77 +}
device-subpackage/device-detail/device-detail.vue renamed from deviceSubPage/deviceDetailPage/deviceDetail.vue
1 -<template>  
2 - <view class="device-detail-page">  
3 - <!-- 公共组件-每个页面必须引入 -->  
4 - <public-module></public-module>  
5 - <u-sticky bgColor="#fff">  
6 - <u-tabs :list="list" :current="currentTab" @click="handleTabClick" :activeStyle="{  
7 - fontWeight: 'bold',  
8 - color: '#333',  
9 - }" :inactiveStyle="{  
10 - color: '#999',  
11 - }" :scrollable="isScrollable" />  
12 - </u-sticky>  
13 - <view style="margin-top: 30rpx">  
14 - <basicInfo v-show="currentTab == 0" :deviceDetail="deviceDetail" />  
15 - <realTimeData v-show="currentTab === 1" :recordList="recordList" />  
16 - <historyData v-if="currentTab === 2" :keys="keys" :yesterday="yesterday" :today="today" :timeDiff="timeDiff"  
17 - :historyData="historyData" :entityId="entityId" :start="startTs" :end="endTs" @update="handleUpdate" />  
18 - <alarmHistory v-show="currentTab === 3" :deviceId="deviceId" />  
19 - <commondRecord v-if="currentTab === 4" :tbDeviceId="entityId" />  
20 - </view>  
21 - </view>  
22 -</template>  
23 -  
24 -<script>  
25 - import fTabbar from "@/components/module/f-tabbar/f-tabbar";  
26 - import basicInfo from "./tabDetail/basicInfo.vue";  
27 - import realTimeData from "./tabDetail/realtimeData.vue";  
28 - import alarmHistory from "./tabDetail/alarmHistory.vue";  
29 - import historyData from "./tabDetail/historyData.vue";  
30 - import commondRecord from "./tabDetail/CommandRecord.vue";  
31 - import {  
32 - getDeviceKeys,  
33 - getHistoryData  
34 - } from "./api/index.js";  
35 - import {  
36 - formatToDate  
37 - } from "@/plugins/utils.js";  
38 - import MescrollCompMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js";  
39 - import moment from "moment";  
40 - import base from "@/config/baseUrl.js";  
41 -  
42 - export default {  
43 - mixins: [MescrollCompMixin],  
44 - components: {  
45 - fTabbar,  
46 - basicInfo,  
47 - realTimeData,  
48 - alarmHistory,  
49 - historyData,  
50 - commondRecord,  
51 - },  
52 - data() {  
53 - return {  
54 - list: [{  
55 - name: "基础信息",  
56 - },  
57 - {  
58 - name: "实时数据",  
59 - },  
60 - {  
61 - name: "历史数据",  
62 - },  
63 - {  
64 - name: "告警记录",  
65 - },  
66 - ],  
67 - currentTab: 0,  
68 - deviceId: "",  
69 - deviceDetail: {},  
70 - keys: [],  
71 - yesterday: "",  
72 - today: "",  
73 - timeDiff: "",  
74 - historyData: [],  
75 - entityId: "",  
76 - startTs: "",  
77 - endTs: "",  
78 - recordList: [],  
79 - isScrollable: false,  
80 - attrList: []  
81 - };  
82 - },  
83 - onUnload() {  
84 - // 页面关闭时,销毁webSocket连接,否则第二次会存在连接不到的情况  
85 - uni.closeSocket();  
86 - },  
87 - async onLoad(options) {  
88 - const {  
89 - id,  
90 - alarmStatus,  
91 - lastOnlineTime,  
92 - tbDeviceId,  
93 - deviceProfileId  
94 - } =  
95 - options;  
96 - this.deviceId = id;  
97 - const res = await uni.$u.http.get(`/yt/device/${id}`);  
98 - this.deviceDetail = {  
99 - ...res,  
100 - alarmStatus,  
101 - lastOnlineTime,  
102 - };  
103 -  
104 - // 设备类型不是网关子设备的添加一个命令记录的选项卡  
105 - if (this.deviceDetail.deviceType !== "SENSOR") {  
106 - this.list.push({  
107 - name: "命令记录",  
108 - });  
109 - }  
110 - this.isScrollable = this.list.length > 4;  
111 - if (res.deviceProfileId) {  
112 - const getAttrList = await uni.$u.http.get(  
113 - `/yt/device/attributes/${res.deviceProfileId}`  
114 - );  
115 - if (Array.isArray(getAttrList)) {  
116 - this.attrList = getAttrList.map(m => {  
117 - return m.identifier  
118 - })  
119 - }  
120 - }  
121 - // 连接webSockte  
122 - const socketTask = uni.connectSocket({  
123 - url: `${base.socketPrefix}://${base.baseWebSocketUrl}/api/ws/plugins/telemetry?token=` +  
124 - uni.getStorageSync("userInfo").isToken, //仅为示例,并非真实接口地址。  
125 - complete: () => {},  
126 - });  
127 - uni.onSocketOpen((header) => {  
128 - socketTask.send({  
129 - data: JSON.stringify({  
130 - attrSubCmds: [],  
131 - tsSubCmds: [{  
132 - entityType: "DEVICE",  
133 - entityId: tbDeviceId,  
134 - scope: "LATEST_TELEMETRY",  
135 - cmdId: 1,  
136 - keys: this.attrList.join(','),  
137 - }, ],  
138 - historyCmds: [],  
139 - entityDataCmds: [],  
140 - entityDataUnsubscribeCmds: [],  
141 - alarmDataCmds: [],  
142 - alarmDataUnsubscribeCmds: [],  
143 - entityCountCmds: [],  
144 - entityCountUnsubscribeCmds: [],  
145 - }),  
146 - success() {},  
147 - });  
148 - });  
149 - socketTask.onMessage((msg) => {  
150 - const {  
151 - data  
152 - } = JSON.parse(msg.data);  
153 - const newArray = [];  
154 - for (const key in data) {  
155 - const [time, value] = data[key].flat(1);  
156 - let obj = {  
157 - key,  
158 - time,  
159 - value,  
160 - };  
161 - if (this.recordList.length === 0) {  
162 - this.recordList.unshift(obj);  
163 - } else {  
164 - newArray.push(obj);  
165 - }  
166 - }  
167 - newArray.forEach((item) => {  
168 - let flag = false;  
169 - this.recordList.forEach((item1) => {  
170 - if (item1.key === item.key) {  
171 - item1.value = item.value;  
172 - item1.time = item.time;  
173 - flag = true;  
174 - }  
175 - });  
176 - if (!flag) {  
177 - this.recordList.unshift(item);  
178 - }  
179 - });  
180 - this.recordList = this.recordList.map((item) => {  
181 - return {  
182 - ...item,  
183 - time: formatToDate(item.time, "YYYY-MM-DD HH:mm:ss"),  
184 - };  
185 - });  
186 - });  
187 -  
188 - const keys = await getDeviceKeys(tbDeviceId);  
189 - this.keys = [keys];  
190 - // 昨天  
191 - this.yesterday = moment().subtract(1, "days").format("YYYY-MM-DD");  
192 - // 今天  
193 - this.today = moment().format("YYYY-MM-DD");  
194 - // 开始时间  
195 - this.startTs = moment().subtract(1, "days").format("x");  
196 - // 结束时间  
197 - this.endTs = moment().format("x");  
198 - this.entityId = tbDeviceId;  
199 -  
200 - const data = await getHistoryData({  
201 - entityId: tbDeviceId,  
202 - startTs: this.startTs,  
203 - endTs: this.endTs,  
204 - keys: keys[0],  
205 - // interval: 1800000,  
206 - limit: 7,  
207 - agg: 'NONE'  
208 - });  
209 - this.timeDiff = "30分钟";  
210 - if (!Object.keys(data).length) return;  
211 -  
212 - this.historyData = data[keys[0]].map((item) => {  
213 - return {  
214 - value: item.value,  
215 - ts: formatToDate(item.ts, "YYYY-MM-DD HH:mm:ss"),  
216 - };  
217 - });  
218 - },  
219 - methods: {  
220 - handleTabClick({  
221 - index  
222 - }) {  
223 - this.currentTab = index;  
224 - },  
225 - handleUpdate(data, e) {  
226 - if (!Array.isArray(data)) {  
227 - this.historyData = [];  
228 - return;  
229 - }  
230 - this.historyData = data.map((item) => {  
231 - return {  
232 - value: item.value,  
233 - ts: formatToDate(item.ts, "YYYY-MM-DD HH:mm:ss"),  
234 - };  
235 - });  
236 - },  
237 - },  
238 - };  
239 -</script>  
240 -  
241 -<style lang="scss" scoped>  
242 - .device-detail-page {  
243 - height: 100vh;  
244 - background-color: #f8f9fa;  
245 - }  
246 -</style> 1 +<template>
  2 + <view class="device-detail-page">
  3 + <!-- 公共组件-每个页面必须引入 -->
  4 + <public-module></public-module>
  5 + <u-sticky :bgColor="bgColor">
  6 + <u-tabs :list="list" :current="currentTab" @click="handleTabClick" :activeStyle="activeColor"
  7 + :inactiveStyle="inActiveColor" :scrollable="isScrollable" />
  8 + </u-sticky>
  9 + <view class="mt-3">
  10 + <basic-info v-show="currentTab == 0" :deviceDetail="deviceDetail" />
  11 + <realtime-data v-show="currentTab === 1" :recordList="recordList" />
  12 + <history-data v-if="currentTab === 2" :keys="keys" :yesterday="yesterday" :today="today"
  13 + :timeDiff="timeDiff" :historyData="historyData" :entityId="entityId" :start="startTs" :end="endTs"
  14 + @update="handleUpdate" />
  15 + <alarm-history v-show="currentTab === 3" :deviceId="deviceId" />
  16 + <commond-record v-if="currentTab === 4" :tbDeviceId="entityId" />
  17 + </view>
  18 + </view>
  19 +</template>
  20 +
  21 +<script>
  22 + import fTabbar from "@/components/module/f-tabbar/f-tabbar";
  23 + import basicInfo from "./components/basic-info.vue";
  24 + import realtimeData from "./components/realtime-data.vue";
  25 + import alarmHistory from "./components/alarm-history.vue";
  26 + import historyData from "./components/history-data.vue";
  27 + import commondRecord from "./components/command-record.vue";
  28 + import { getDeviceKeys,getHistoryData } from "./api/index.js";
  29 + import {formatToDate} from "@/plugins/utils.js";
  30 + import MescrollCompMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js";
  31 + import moment from "moment";
  32 + import base from "@/config/baseUrl.js";
  33 + import { list } from './config/data.js'
  34 + import api from '@/api'
  35 +
  36 + export default {
  37 + mixins: [MescrollCompMixin],
  38 + components: {
  39 + fTabbar,
  40 + basicInfo,
  41 + realtimeData,
  42 + alarmHistory,
  43 + historyData,
  44 + commondRecord,
  45 + },
  46 + data() {
  47 + return {
  48 + bgColor: '#fff',
  49 + activeColor: {
  50 + fontWeight: 'bold',
  51 + color: '#333',
  52 + },
  53 + inActiveColor: {
  54 + color: '#999',
  55 + },
  56 + list,
  57 + currentTab: 0,
  58 + deviceId: "",
  59 + deviceDetail: {},
  60 + keys: [],
  61 + yesterday: "",
  62 + today: "",
  63 + timeDiff: "",
  64 + historyData: [],
  65 + entityId: "",
  66 + startTs: "",
  67 + endTs: "",
  68 + recordList: [], //实时数据
  69 + isScrollable: false,
  70 + attrList: []
  71 + };
  72 + },
  73 + onUnload() {
  74 + // 页面关闭时,销毁webSocket连接,否则第二次会存在连接不到的情况
  75 + uni.closeSocket();
  76 + },
  77 + async onLoad(options) {
  78 + const {id,alarmStatus,lastOnlineTime,tbDeviceId,deviceProfileId} = options;
  79 + this.deviceId = id;
  80 + const res = await api.deviceApi.getDeviceDetail(this.deviceId)
  81 + if(!res) return
  82 + this.deviceDetail = {
  83 + ...res,
  84 + alarmStatus,
  85 + lastOnlineTime,
  86 + };
  87 + // 设备类型不是网关子设备的添加一个命令记录的选项卡
  88 + if (this.deviceDetail.deviceType !== "SENSOR") {
  89 + this.list.push({
  90 + name: "命令记录",
  91 + });
  92 + const res = new Map()
  93 + this.list = this.list.filter((item) => !res.has(item.name) && res.set(item.name, 1))
  94 + } else {
  95 + this.list = this.list.filter(item => item.name !=='命令记录')
  96 + }
  97 + this.isScrollable = this.list.length > 4;
  98 + if (res.deviceProfileId) {
  99 + const getAttrList = await api.deviceApi.getAttribute(res.deviceProfileId)
  100 + if (Array.isArray(getAttrList)) {
  101 + this.attrList = getAttrList.map(m => {
  102 + return m.identifier
  103 + })
  104 + }
  105 + }
  106 + // 连接webSockte
  107 + const socketTask = uni.connectSocket({
  108 + url: `${base.socketPrefix}://${base.baseWebSocketUrl}/api/ws/plugins/telemetry?token=` + uni.getStorageSync("userInfo").isToken, //仅为示例,并非真实接口地址。
  109 + complete: () => {},
  110 + });
  111 + uni.onSocketOpen((header) => {
  112 + socketTask.send({
  113 + data: JSON.stringify({
  114 + attrSubCmds: [],
  115 + tsSubCmds: [{
  116 + entityType: "DEVICE",
  117 + entityId: tbDeviceId,
  118 + scope: "LATEST_TELEMETRY",
  119 + cmdId: 1,
  120 + keys: this.attrList.join(','),
  121 + }, ],
  122 + historyCmds: [],
  123 + entityDataCmds: [],
  124 + entityDataUnsubscribeCmds: [],
  125 + alarmDataCmds: [],
  126 + alarmDataUnsubscribeCmds: [],
  127 + entityCountCmds: [],
  128 + entityCountUnsubscribeCmds: [],
  129 + }),
  130 + success() {},
  131 + });
  132 + });
  133 + socketTask.onMessage((msg) => {
  134 + const { data } = JSON.parse(msg.data);
  135 + const newArray = [];
  136 + for (const key in data) {
  137 + const [time, value] = data[key].flat(1);
  138 + let obj = { key,time,value, };
  139 + if (this.recordList.length === 0) {
  140 + this.recordList.unshift(obj);
  141 + } else {
  142 + newArray.push(obj);
  143 + }
  144 + }
  145 + newArray.forEach((item) => {
  146 + let flag = false;
  147 + this.recordList.forEach((item1) => {
  148 + if (item1.key === item.key) {
  149 + item1.value = item.value;
  150 + item1.time = item.time;
  151 + flag = true;
  152 + }
  153 + });
  154 + if (!flag) {
  155 + this.recordList.unshift(item);
  156 + }
  157 + });
  158 + this.recordList = this.recordList.map((item) => {
  159 + return {
  160 + ...item,
  161 + time: formatToDate(item.time, "YYYY-MM-DD HH:mm:ss"),
  162 + };
  163 + });
  164 + });
  165 + const keys = await getDeviceKeys(tbDeviceId);
  166 + this.keys = [keys];
  167 + // 昨天
  168 + this.yesterday = moment().subtract(1, "days").format("YYYY-MM-DD");
  169 + // 今天
  170 + this.today = moment().format("YYYY-MM-DD");
  171 + // 开始时间
  172 + this.startTs = moment().subtract(1, "days").format("x");
  173 + // 结束时间
  174 + this.endTs = moment().format("x");
  175 + this.entityId = tbDeviceId;
  176 + const data = await getHistoryData({
  177 + entityId: tbDeviceId,
  178 + startTs: this.startTs,
  179 + endTs: this.endTs,
  180 + keys: keys[0],
  181 + // interval: 1800000,
  182 + limit: 7,
  183 + agg: 'NONE'
  184 + });
  185 + this.timeDiff = "30分钟";
  186 + if (!Object.keys(data).length) return;
  187 + this.historyData = data[keys[0]].map((item) => {
  188 + return {
  189 + value: item.value,
  190 + ts: formatToDate(item.ts, "YYYY-MM-DD HH:mm:ss"),
  191 + };
  192 + });
  193 + },
  194 + methods: {
  195 + handleTabClick({
  196 + index
  197 + }) {
  198 + this.currentTab = index;
  199 + },
  200 + handleUpdate(data, e) {
  201 + if (!Array.isArray(data)) {
  202 + this.historyData = [];
  203 + return;
  204 + }
  205 + this.historyData = data.map((item) => {
  206 + return {
  207 + value: item.value,
  208 + ts: formatToDate(item.ts, "YYYY-MM-DD HH:mm:ss"),
  209 + };
  210 + });
  211 + },
  212 + },
  213 + };
  214 +</script>
  215 +
  216 +<style lang="scss" scoped>
  217 + .device-detail-page {
  218 + height: 100vh;
  219 + background-color: #f8f9fa;
  220 + }
  221 +</style>
device-subpackage/device-detail/device-position.vue renamed from deviceSubPage/deviceDetailPage/devicePosition.vue
  1 +.command-detail {
  2 + padding: 5rpx 30rpx;
  3 + height: 100vh;
  4 + background-color: #f8f9fa;
  5 +
  6 + .detail-top {
  7 + height: 118rpx;
  8 + width: 690rpx;
  9 + display: flex;
  10 + align-items: center;
  11 + background-color: #fff;
  12 + color: #333;
  13 + border-radius: 20rpx;
  14 + font-size: 15px;
  15 + margin-top: 30rpx;
  16 + padding: 30rpx;
  17 + }
  18 +
  19 + .detail {
  20 + background-color: #fff;
  21 + margin-top: 30rpx;
  22 + border-radius: 20rpx;
  23 + width: 690rpx;
  24 +
  25 + .detail-item {
  26 + padding: 30rpx;
  27 + display: flex;
  28 + align-items: center;
  29 +
  30 + .detail-label {
  31 + color: #333;
  32 + font-size: 15px;
  33 + }
  34 +
  35 + .detail-value {
  36 + color: #666;
  37 + font-size: 14px;
  38 + margin-left: 30rpx;
  39 + }
  40 + }
  41 + }
  42 +
  43 + .command {
  44 + margin: 30rpx 0;
  45 + }
  46 + }
  1 +.command-record {
  2 + padding: 0 30rpx;
  3 + background: #f8f9fa;
  4 +
  5 + .filter-button {
  6 + font-size: 12px;
  7 + width: 160rpx;
  8 + height: 64rpx;
  9 + border-radius: 32rpx;
  10 + display: flex;
  11 + justify-content: center;
  12 + align-items: center;
  13 + background: #f0f1f2;
  14 + color: #666;
  15 +
  16 + image {
  17 + width: 28rpx;
  18 + height: 28rpx;
  19 + margin-left: 4rpx;
  20 + }
  21 + }
  22 + }
  23 +
  24 + .list-item {
  25 + width: 690rpx;
  26 + background-color: #fff;
  27 + border-radius: 20rpx;
  28 + margin: 20rpx auto;
  29 + color: #333;
  30 +
  31 + .item {
  32 + .delivered-color {
  33 + color: blue;
  34 + }
  35 +
  36 + padding: 30rpx;
  37 +
  38 + view {
  39 + font-size: 14px;
  40 + margin-bottom: 10rpx;
  41 + }
  42 +
  43 + .time {
  44 + margin-top: 20rpx;
  45 + color: #999;
  46 + }
  47 +
  48 + .item-first {
  49 + display: flex;
  50 + justify-content: space-between;
  51 + align-items: center;
  52 + font-size: 15px;
  53 + font-weight: 500;
  54 + align-items: center;
  55 +
  56 + .item-right {
  57 + display: flex;
  58 + justify-content: center;
  59 + align-items: center;
  60 + width: 104rpx;
  61 + height: 36rpx;
  62 + font-size: 10px;
  63 + border-radius: 20rpx;
  64 + }
  65 +
  66 + .item-fail {
  67 + color: #848383;
  68 + background-color: #84838325;
  69 + }
  70 +
  71 + .item.success {
  72 + color: #00c9a7;
  73 + background-color: #00c9a725;
  74 + }
  75 + }
  76 + }
  77 + }
  78 +
  79 + .filter {
  80 + padding: 0 30rpx;
  81 +
  82 + .filter-title {
  83 + text-align: center;
  84 + margin-top: 14px;
  85 + font-size: 16px;
  86 + font-weight: 700;
  87 + }
  88 +
  89 + .button-group {
  90 + display: flex;
  91 + margin-top: 40rpx;
  92 + justify-content: space-between;
  93 +
  94 + view {
  95 + width: 330rpx;
  96 + }
  97 + }
  98 +
  99 + .command-time-text {
  100 + color: #333;
  101 + font-size: 14px;
  102 + font-weight: 700;
  103 + }
  104 + }
device-subpackage/device-detail/static/modal.css renamed from deviceSubPage/deviceDetailPage/styles/modal.css
  1 +/* ==================
  2 +模态窗口 采用colorsui的部分样式
  3 +==================== */
  4 +
1 .cu-modal { 5 .cu-modal {
2 position: fixed; 6 position: fixed;
3 top: 0; 7 top: 0;
@@ -18,7 +22,7 @@ @@ -18,7 +22,7 @@
18 } 22 }
19 23
20 .cu-modal::before { 24 .cu-modal::before {
21 - content: "\200B"; 25 + content: '\200B';
22 display: inline-block; 26 display: inline-block;
23 height: 100%; 27 height: 100%;
24 vertical-align: middle; 28 vertical-align: middle;
@@ -89,8 +93,8 @@ @@ -89,8 +93,8 @@
89 transform: translateX(0%); 93 transform: translateX(0%);
90 } 94 }
91 95
92 -.cu-modal .cu-dialog>.cu-bar:first-child .action { 96 +.cu-modal .cu-dialog > .cu-bar:first-child .action {
93 min-width: 100rpx; 97 min-width: 100rpx;
94 margin-right: 0; 98 margin-right: 0;
95 min-height: 100rpx; 99 min-height: 100rpx;
96 -} 100 +}
device-subpackage/device-detail/static/realtime-data.scss renamed from deviceSubPage/deviceDetailPage/tabDetail/realtimeData.vue
1 -<template>  
2 - <view class="realtime-page">  
3 - <view class="item" v-for="(item, index) in recordList" :key="index">  
4 - <view class="item-top">  
5 - <view>{{ item.key }}</view>  
6 - <view class="item-value">{{ item.value }}</view>  
7 - </view>  
8 - <view class="item-time">{{ item.time }}</view>  
9 - </view>  
10 - <mescroll-empty v-if="!recordList.length" />  
11 - </view>  
12 -</template>  
13 -  
14 -<script>  
15 -export default {  
16 - props: {  
17 - recordList: {  
18 - type: Array,  
19 - default: () => []  
20 - }  
21 - }  
22 -};  
23 -</script>  
24 -  
25 -<style lang="scss" scoped>  
26 -.realtime-page {  
27 - .item {  
28 - margin: 30rpx;  
29 - padding: 30rpx;  
30 - border-radius: 20rpx;  
31 - background-color: #fff;  
32 - height: 160rpx;  
33 - width: 690rpx;  
34 - .item-top {  
35 - display: flex;  
36 - justify-content: space-between;  
37 - color: #333;  
38 - font-size: 16px;  
39 - font-family: PingFangSC-Medium, PingFang SC;  
40 - font-weight: bold;  
41 - .item-value {  
42 - font-weight: bold;  
43 - }  
44 - }  
45 - .item-time {  
46 - margin-top: 4rpx;  
47 - font-size: 13px;  
48 - color: #999;  
49 - }  
50 - }  
51 -}  
52 -</style> 1 +.realtime-page {
  2 + .item {
  3 + margin: 30rpx;
  4 + padding: 30rpx;
  5 + border-radius: 20rpx;
  6 + background-color: #fff;
  7 + height: 160rpx;
  8 + width: 690rpx;
  9 + .item-top {
  10 + display: flex;
  11 + justify-content: space-between;
  12 + color: #333;
  13 + font-size: 16px;
  14 + font-family: PingFangSC-Medium, PingFang SC;
  15 + font-weight: bold;
  16 + .item-value {
  17 + font-weight: bold;
  18 + }
  19 + }
  20 + .item-time {
  21 + margin-top: 4rpx;
  22 + font-size: 13px;
  23 + color: #999;
  24 + }
  25 + }
  26 +}
1 -<template>  
2 - <!-- 单向没有响应失败状态 -->  
3 - <!-- 响应类型 -->  
4 - <view class="command-record">  
5 - <view class="filter-button" @click="openSearchDialog">  
6 - <text>筛选</text>  
7 - <image src="../../../static/shaixuan.png" />  
8 - </view>  
9 -  
10 - <mescroll-uni ref="mescrollRef" @init="mescrollInit" :down="downOption" @down="downCallback" @up="upCallback"  
11 - height="700px">  
12 - <view @click="openCommandDetail(item)" class="list-item" v-for="(item, index) in list" :key="index">  
13 - <view class="item">  
14 - <view class="item-first">  
15 - <text>{{ item.deviceName }}</text>  
16 - <view v-if="!item.request.oneway">  
17 - <view class="item-right item-success" v-if="item.response">响应成功</view>  
18 - <view class="item-right item-fail" v-else>响应失败</view>  
19 - </view>  
20 - </view>  
21 - <view>  
22 - 命令类型:  
23 - <text style="margin-left: 16rpx;">{{ item.additionalInfo.cmdType===1?'服务':'自定义' }}</text>  
24 - </view>  
25 - <view v-if="item.statusName">  
26 - 命令状态:  
27 - <text :style="{  
28 - color:  
29 - item.status == 'EXPIRED'  
30 - ? 'red'  
31 - : item.status == 'DELIVERED'  
32 - ? 'blue'  
33 - : item.status == 'QUEUED'  
34 - ? '#00C9A7'  
35 - : item.status == 'TIMEOUT'  
36 - ? 'red'  
37 - : item.status == 'SENT'  
38 - ? '#00C9A7'  
39 - : ''  
40 - }" style="margin-left: 16rpx;">  
41 - {{ item.statusName }}  
42 - </text>  
43 - </view>  
44 - <view class="item-first">  
45 - <view>  
46 - 响应类型:  
47 - <text style="margin-left: 16rpx;">{{ !item.request.oneway?'双向':'单向' }}</text>  
48 - </view>  
49 - <view class="time">{{ format(item.createTime) }}</view>  
50 - </view>  
51 - </view>  
52 - </view>  
53 - </mescroll-uni>  
54 - <!-- 告警筛选 -->  
55 - <u-popup @close="close" closeable bgColor="#fff" :show="show" mode="bottom" :round="20"  
56 - @touchmove.stop.prevent="disabledScroll">  
57 - <view class="filter" @touchmove.stop.prevent="disabledScroll">  
58 - <view class="filter-title"><text>筛选条件</text></view>  
59 - <FilterItem :filterList="issueStatus" title="下发状态"  
60 - @clickTag="currentIndex => handleClickTag(currentIndex, issueStatus)"></FilterItem>  
61 - <view class="button-group">  
62 - <view>  
63 - <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="重置"  
64 - @click="resetFilter"></u-button>  
65 - </view>  
66 - <view>  
67 - <u-button color="#3388ff" shape="circle" text="确认" @click="confirmFilter"></u-button>  
68 - </view>  
69 - </view>  
70 - </view>  
71 - </u-popup>  
72 - <u-calendar :show="showCalendar" mode="range" @confirm="calendarConfirm" @close="calendarClose" startText="开始时间"  
73 - endText="结束时间" confirmDisabledText="请选择日期" :formatter="formatter"></u-calendar>  
74 - </view>  
75 -</template>  
76 -<script>  
77 - import FilterItem from '@/pages/device/FilterItem.vue';  
78 - import MescrollMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js';  
79 - import {  
80 - formatToDate  
81 - } from '@/plugins/utils.js';  
82 - import {  
83 - debounce  
84 - } from '@/plugins/throttle.js';  
85 - export default {  
86 - mixins: [MescrollMixin],  
87 - components: {  
88 - FilterItem  
89 - },  
90 - props: {  
91 - tbDeviceId: {  
92 - type: String,  
93 - default: ''  
94 - }  
95 - },  
96 - data() {  
97 - return {  
98 - show: false,  
99 - list: [],  
100 - total: '',  
101 - timeData: {  
102 - selectTime: '',  
103 - getTimeGap: ''  
104 - },  
105 - showCalendar: false,  
106 - issueStatus: [{  
107 - checked: true,  
108 - name: '全部',  
109 - type: ''  
110 - },  
111 - {  
112 - checked: false,  
113 - name: '响应成功',  
114 - type: 'SUCCESSFUL'  
115 - },  
116 - {  
117 - checked: false,  
118 - name: '发送成功',  
119 - type: 'DELIVERED'  
120 - },  
121 - {  
122 - checked: false,  
123 - name: '已过期',  
124 - type: 'EXPIRED'  
125 - },  
126 - {  
127 - checked: false,  
128 - name: '响应失败',  
129 - type: 'FAILED'  
130 - }  
131 - ],  
132 - downOption: {  
133 - auto: false //是否在初始化后,自动执行downCallback; 默认true  
134 - },  
135 - page: {  
136 - num: 0,  
137 - size: 10  
138 - }  
139 - };  
140 - },  
141 - methods: {  
142 - /*下拉刷新的回调 */  
143 - downCallback() {  
144 - //联网加载数据  
145 - this.list = [];  
146 - this.page.num = 1;  
147 - this.loadData(this.page.num, {  
148 - tbDeviceId: this.tbDeviceId  
149 - });  
150 - },  
151 - format(date) {  
152 - return formatToDate(date, 'YYYY-MM-DD HH:mm:ss');  
153 - },  
154 - disabledScroll() {  
155 - return;  
156 - },  
157 - /*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */  
158 - upCallback() {  
159 - //联网加载数据  
160 - this.page.num += 1;  
161 - this.loadData(this.page.num, {  
162 - tbDeviceId: this.tbDeviceId  
163 - });  
164 - },  
165 - //获取告警数据  
166 - loadData(pageNo, params = {}) {  
167 - let httpData = {  
168 - ...params,  
169 - page: pageNo,  
170 - pageSize: 10  
171 - };  
172 - uni.$u.http  
173 - .get('/yt/rpc', {  
174 - params: httpData,  
175 - custom: {  
176 - load: false  
177 - }  
178 - })  
179 - .then(res => {  
180 - this.total = res.total;  
181 - uni.stopPullDownRefresh();  
182 - //方法一(推荐): 后台接口有返回列表的总页数 totalPage  
183 - this.mescroll.endByPage(res.items.length, res.total); //必传参数(当前页的数据个数, 总页数)  
184 - if (pageNo == 1) {  
185 - this.list = res.items;  
186 - } else {  
187 - this.list = this.list.concat(res.items);  
188 - }  
189 - })  
190 - .catch(() => {  
191 - //联网失败, 结束加载  
192 - this.mescroll.endErr();  
193 - });  
194 - },  
195 - handleClickTag(currentIndex, list) {  
196 - list.map((item, index) => {  
197 - item.checked = index === currentIndex;  
198 - });  
199 - },  
200 - resetFilter() {  
201 - const {  
202 - issueStatus  
203 - } = this;  
204 - issueStatus.forEach(item => item.map((item, index) => (item.checked = index === 0)));  
205 - },  
206 - close() {  
207 - this.show = false;  
208 - },  
209 - openSearchDialog() {  
210 - this.show = true;  
211 - },  
212 - hideKeyboard() {  
213 - uni.hideKeyboard();  
214 - },  
215 - calendarConfirm(e) {  
216 - this.showCalendar = false;  
217 - this.timeData.selectTime = `${e[0]} / ${e[e.length - 1]}`;  
218 - },  
219 - confirmFilter() {  
220 - const issueStatus = this.issueStatus.find(item => item.checked);  
221 - this.loadData(1, {  
222 - status: issueStatus.type ? issueStatus.type : undefined,  
223 - tbDeviceId: this.tbDeviceId  
224 - });  
225 - this.show = false;  
226 - },  
227 - calendarClose() {  
228 - this.showCalendar = false;  
229 - },  
230 - openCommandDetail(item) {  
231 - uni.navigateTo({  
232 - url: '/deviceSubPage/deviceDetailPage/tabDetail/CommandDetail?data=' + JSON.stringify(item)  
233 - });  
234 - }  
235 - }  
236 - };  
237 -</script>  
238 -  
239 -<style lang="scss" scoped>  
240 - .command-record {  
241 - padding: 0 30rpx;  
242 - background: #f8f9fa;  
243 -  
244 - .filter-button {  
245 - font-size: 12px;  
246 - width: 160rpx;  
247 - height: 64rpx;  
248 - border-radius: 32rpx;  
249 - display: flex;  
250 - justify-content: center;  
251 - align-items: center;  
252 - background: #f0f1f2;  
253 - color: #666;  
254 -  
255 - image {  
256 - width: 28rpx;  
257 - height: 28rpx;  
258 - margin-left: 4rpx;  
259 - }  
260 - }  
261 - }  
262 -  
263 - .list-item {  
264 - width: 690rpx;  
265 - background-color: #fff;  
266 - border-radius: 20rpx;  
267 - margin: 20rpx auto;  
268 - color: #333;  
269 -  
270 - .item {  
271 - .delivered-color {  
272 - color: blue;  
273 - }  
274 -  
275 - padding: 30rpx;  
276 -  
277 - view {  
278 - font-size: 14px;  
279 - margin-bottom: 10rpx;  
280 - }  
281 -  
282 - .time {  
283 - margin-top: 20rpx;  
284 - color: #999;  
285 - }  
286 -  
287 - .item-first {  
288 - display: flex;  
289 - justify-content: space-between;  
290 - align-items: center;  
291 - font-size: 15px;  
292 - font-weight: 500;  
293 - align-items: center;  
294 -  
295 - .item-right {  
296 - display: flex;  
297 - justify-content: center;  
298 - align-items: center;  
299 - width: 104rpx;  
300 - height: 36rpx;  
301 - font-size: 10px;  
302 - border-radius: 20rpx;  
303 - }  
304 -  
305 - .item-fail {  
306 - color: #848383;  
307 - background-color: #84838325;  
308 - }  
309 -  
310 - .item.success {  
311 - color: #00c9a7;  
312 - background-color: #00c9a725;  
313 - }  
314 - }  
315 - }  
316 - }  
317 -  
318 - .filter {  
319 - padding: 0 30rpx;  
320 -  
321 - .filter-title {  
322 - text-align: center;  
323 - margin-top: 14px;  
324 - font-size: 16px;  
325 - font-weight: 700;  
326 - }  
327 -  
328 - .button-group {  
329 - display: flex;  
330 - margin-top: 40rpx;  
331 - justify-content: space-between;  
332 -  
333 - view {  
334 - width: 330rpx;  
335 - }  
336 - }  
337 - }  
338 -</style>