Commit 84c365f3917c72548f1ee9f123e0ed7b02fb6fd2

Authored by fengwotao
1 parent 594624ad

pref: app、mp端 优化告警和设备代码

Showing 21 changed files with 2277 additions and 2498 deletions

Too many changes to show.

To preserve performance only 21 of 40 files are displayed.

alarm-subpackage/alarm-detail/alarm-detail.vue renamed from alarmSubPage/alarmDetailPage/alarmDetail.vue
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==='告警设备:'?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[7].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[7].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_UNACK', '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   - };
92   - },
93   - onLoad(e) {
94   - if (e.data !== null) {
95   - let params = JSON.parse(decodeURIComponent(e.data));
96   - const {
97   - deviceName,
98   - severity,
99   - organizationName,
100   - details,
101   - type,
102   - createdTime,
103   - status,
104   - id
105   - } = params
106   - this.detailId = id
107   - this.alarmDetail = [{
108   - label: '告警场景:',
109   - value: type
110   - },
111   - {
112   - label: '告警级别:',
113   - value: severity
114   - },
115   - {
116   - label: '所属组织:',
117   - value: organizationName
118   - },
119   - {
120   - label: '告警设备:',
121   - value: ''
122   - },
123   - {
124   - label: '告警条件:',
125   - value: ''
126   - },
127   - {
128   - label: '告警值:',
129   - value: ''
130   - },
131   - {
132   - label: '告警时间:',
133   - value: createdTime
134   - },
135   - {
136   - label: '告警状态:',
137   - value: status
138   - },
139   - ]
140   - this.formatAlarmDevice(details)
141   - this.formatAlarmValue(details)
142   - this.formatAlarmCondition(details)
143   - }
144   - // 隐藏原生的tabbar
145   - uni.hideTabBar();
146   - },
147   - methods: {
148   - ...mapActions(['updateBadgeTotal']),
149   - setAlarmStatus(value) {
150   - return this.alarmSeverity.find(item => item.value === value).label
151   - },
152   - setAlarmSeverity(value) {
153   - return this.alarmStatus.find(item => item.value === value).label
154   - },
155   - returnPrevPage(title) {
156   - useShowToast(title)
157   - let pages = getCurrentPages(); //获取所有页面栈实例列表
158   - let nowPage = pages[pages.length - 1]; //当前页页面实例
159   - let prevPage = pages[pages.length - 2]; //上一页页面实例
160   - prevPage.$vm.detailStatus = true;
161   - },
162   - async handleSubmit() {
163   - if (this.formModel.result == '') return uni.$u.toast('请输入处理结果');
164   - const res = await api.alarmApi.postAlarmAckApi(this.detailId)
165   - if (res == '') {
166   - this.returnPrevPage('处理成功~')
167   - setTimeout(() => {
168   - useNavigateBack(1)
169   - }, 500);
170   - }
171   - },
172   - async handleRemove() {
173   - const res = await api.alarmApi.postAlarmClearApi(this.detailId)
174   - if (res == '') {
175   - this.returnPrevPage('清除成功~')
176   - setTimeout(async () => {
177   - useNavigateBack(1)
178   - const res = await uni.$u.http.get('/yt/homepage/app?login=true');
179   - if (res) {
180   - //异步实时更新告警徽标数
181   - await this.updateBadgeTotal(res.totalAlarm?.activedAlarm);
182   - }
183   - }, 500);
184   - }
185   - },
186   - async formatAlarmValue(e) {
187   - const keys = Object.keys(e)
188   - const dataFormat = await this.handleAlarmDetailFormat(keys);
189   - const values = keys.reduce((acc, curr) => {
190   - dataFormat.forEach((dataItem => {
191   - if (dataItem.tbDeviceId === curr) {
192   - const findAttribute = dataItem.attribute.find(findItem => findItem
193   - .identifier === e[curr].key)
194   - acc.push(
195   - `${findAttribute.name}:${e[curr].realValue}${findAttribute.detail?.dataType?.specs?.unit?.key}`
196   - )
197   - }
198   - }))
199   - return acc
200   - }, [])
201   - this.formatAlarmValueText = values.join(',')
202   - },
203   - formatAlarmCondition(e) {
204   - const keys = Object.keys(e)
205   - const values = keys.reduce((acc, curr) => {
206   - acc.push({
207   - login: e[curr].logic,
208   - logicValue: e[curr].logicValue
209   - })
210   - return acc
211   - }, [])
212   - const operation = [...operationNumberOrDate, ...operationString, ...operationBoolean]
213   - const format = values.map(item => {
214   - const findOperation = operation.find(findItem => findItem.value === item.login)?.symbol
215   - return findOperation + item.logicValue
216   - })
217   - this.formatAlarmConditionText = format
218   - },
219   - async formatAlarmDevice(e) {
220   - const keys = Object.keys(e)
221   - const dataFormat = await this.handleAlarmDetailFormat(keys);
222   - this.formatDeviceText = dataFormat.map(item => item.name).join(',')
223   - },
224   - async handleAlarmDetailFormat(keys) {
225   - const temp = [];
226   - for (let item of keys) {
227   - if (item === 'key' || item === 'data') return; //旧数据则终止
228   - const deviceDetailRes = await api.deviceApi.getDeviceDetail(item);
229   - const {
230   - deviceProfileId
231   - } = deviceDetailRes;
232   - if (!deviceProfileId) return;
233   - const attributeRes = await api.deviceApi.getAttribute(deviceProfileId);
234   - const dataFormat = this.handleDataFormat(deviceDetailRes, attributeRes);
235   - temp.push(dataFormat);
236   - }
237   - return temp;
238   - },
239   - handleDataFormat(deviceDetail, attributes) {
240   - const {
241   - name,
242   - tbDeviceId
243   - } = deviceDetail;
244   - const attribute = attributes.map((item) => ({
245   - identifier: item.identifier,
246   - name: item.name,
247   - detail: item.detail
248   - }));
249   - return {
250   - name,
251   - tbDeviceId,
252   - attribute,
253   - };
254   - }
255   - }
256   - };
257   -</script>
258   -
259   -<style lang="scss" scoped>
260   - @import './static/alarmDetail.scss';
261   -
262   - /deep/ .u-button--primary {
263   - background-color: #377dff !important;
264   - border-color: #377dff !important;
265   - }
  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==='告警设备:'?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[7].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[7].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_UNACK', '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 + };
  92 + },
  93 + onLoad(e) {
  94 + if (e.data !== null) {
  95 + let params = JSON.parse(decodeURIComponent(e.data));
  96 + const {
  97 + deviceName,
  98 + severity,
  99 + organizationName,
  100 + details,
  101 + type,
  102 + createdTime,
  103 + status,
  104 + id
  105 + } = params
  106 + this.detailId = id
  107 + this.alarmDetail = [{
  108 + label: '告警场景:',
  109 + value: type
  110 + },
  111 + {
  112 + label: '告警级别:',
  113 + value: severity
  114 + },
  115 + {
  116 + label: '所属组织:',
  117 + value: organizationName
  118 + },
  119 + {
  120 + label: '告警设备:',
  121 + value: ''
  122 + },
  123 + {
  124 + label: '告警条件:',
  125 + value: ''
  126 + },
  127 + {
  128 + label: '告警值:',
  129 + value: ''
  130 + },
  131 + {
  132 + label: '告警时间:',
  133 + value: createdTime
  134 + },
  135 + {
  136 + label: '告警状态:',
  137 + value: status
  138 + },
  139 + ]
  140 + this.formatAlarmDevice(details)
  141 + this.formatAlarmValue(details)
  142 + this.formatAlarmCondition(details)
  143 + }
  144 + // 隐藏原生的tabbar
  145 + uni.hideTabBar();
  146 + },
  147 + methods: {
  148 + ...mapActions(['updateBadgeTotal']),
  149 + setAlarmStatus(value) {
  150 + return this.alarmSeverity.find(item => item.value === value).label
  151 + },
  152 + setAlarmSeverity(value) {
  153 + return this.alarmStatus.find(item => item.value === value).label
  154 + },
  155 + returnPrevPage(title) {
  156 + useShowToast(title)
  157 + let pages = getCurrentPages(); //获取所有页面栈实例列表
  158 + let nowPage = pages[pages.length - 1]; //当前页页面实例
  159 + let prevPage = pages[pages.length - 2]; //上一页页面实例
  160 + prevPage.$vm.detailStatus = true;
  161 + },
  162 + async handleSubmit() {
  163 + if (this.formModel.result == '') return uni.$u.toast('请输入处理结果');
  164 + const res = await api.alarmApi.postAlarmAckApi(this.detailId)
  165 + if (res == '') {
  166 + this.returnPrevPage('处理成功~')
  167 + setTimeout(() => {
  168 + useNavigateBack(1)
  169 + }, 500);
  170 + }
  171 + },
  172 + async handleRemove() {
  173 + const res = await api.alarmApi.postAlarmClearApi(this.detailId)
  174 + if (res == '') {
  175 + this.returnPrevPage('清除成功~')
  176 + setTimeout(async () => {
  177 + useNavigateBack(1)
  178 + const res = await uni.$u.http.get('/yt/homepage/app?login=true');
  179 + if (res) {
  180 + //异步实时更新告警徽标数
  181 + await this.updateBadgeTotal(res.totalAlarm?.activedAlarm);
  182 + }
  183 + }, 500);
  184 + }
  185 + },
  186 + async formatAlarmValue(e) {
  187 + const keys = Object.keys(e)
  188 + const dataFormat = await this.handleAlarmDetailFormat(keys);
  189 + const values = keys.reduce((acc, curr) => {
  190 + dataFormat.forEach((dataItem => {
  191 + if (dataItem.tbDeviceId === curr) {
  192 + const findAttribute = dataItem.attribute.find(findItem => findItem
  193 + .identifier === e[curr].key)
  194 + acc.push(
  195 + `${findAttribute.name}:${e[curr].realValue}${findAttribute.detail?.dataType?.specs?.unit?.key}`
  196 + )
  197 + }
  198 + }))
  199 + return acc
  200 + }, [])
  201 + this.formatAlarmValueText = values.join(',')
  202 + },
  203 + formatAlarmCondition(e) {
  204 + const keys = Object.keys(e)
  205 + const values = keys.reduce((acc, curr) => {
  206 + acc.push({
  207 + login: e[curr].logic,
  208 + logicValue: e[curr].logicValue
  209 + })
  210 + return acc
  211 + }, [])
  212 + const operation = [...operationNumberOrDate, ...operationString, ...operationBoolean]
  213 + const format = values.map(item => {
  214 + const findOperation = operation.find(findItem => findItem.value === item.login)?.symbol
  215 + return findOperation + item.logicValue
  216 + })
  217 + this.formatAlarmConditionText = format
  218 + },
  219 + async formatAlarmDevice(e) {
  220 + const keys = Object.keys(e)
  221 + const dataFormat = await this.handleAlarmDetailFormat(keys);
  222 + this.formatDeviceText = dataFormat.map(item => item.name).join(',')
  223 + },
  224 + async handleAlarmDetailFormat(keys) {
  225 + const temp = [];
  226 + for (let item of keys) {
  227 + if (item === 'key' || item === 'data') return; //旧数据则终止
  228 + const deviceDetailRes = await api.deviceApi.getDeviceDetail(item);
  229 + const {
  230 + deviceProfileId
  231 + } = deviceDetailRes;
  232 + if (!deviceProfileId) return;
  233 + const attributeRes = await api.deviceApi.getAttribute(deviceProfileId);
  234 + const dataFormat = this.handleDataFormat(deviceDetailRes, attributeRes);
  235 + temp.push(dataFormat);
  236 + }
  237 + return temp;
  238 + },
  239 + handleDataFormat(deviceDetail, attributes) {
  240 + const {
  241 + name,
  242 + tbDeviceId
  243 + } = deviceDetail;
  244 + const attribute = attributes.map((item) => ({
  245 + identifier: item.identifier,
  246 + name: item.name,
  247 + detail: item.detail
  248 + }));
  249 + return {
  250 + name,
  251 + tbDeviceId,
  252 + attribute,
  253 + };
  254 + }
  255 + }
  256 + };
  257 +</script>
  258 +
  259 +<style lang="scss" scoped>
  260 + @import './static/alarmDetail.scss';
  261 +
  262 + /deep/ .u-button--primary {
  263 + background-color: #377dff !important;
  264 + border-color: #377dff !important;
  265 + }
266 266 </style>
\ No newline at end of file
... ...
alarm-subpackage/alarm-detail/static/alarmDetail.scss renamed from alarmSubPage/alarmDetailPage/static/alarmDetail.scss
... ... @@ -17,13 +17,24 @@ const getDeviceDetail = (id) => {
17 17 };
18 18
19 19 //设备属性
20   - const getAttribute = (deviceProfileId) => {
21   - return uni.$u.http.get(`/yt/device/attributes/${deviceProfileId}`);
  20 +const getAttribute = (deviceProfileId) => {
  21 + return uni.$u.http.get(`/yt/device/attributes/${deviceProfileId}`);
22 22 };
23 23
  24 +//命令下发
  25 +const issueCommand = (type, tbDeviceId, data) => {
  26 + return uni.$u.http.post(`/rpc/${type==='OneWay'?'oneway':'twoway'}/${tbDeviceId}`, data)
  27 +}
  28 +
  29 +//获取命令下发记录
  30 +const getRpcRecord = (params) => {
  31 + return uni.$u.http.get('/yt/rpc', params);
  32 +};
24 33
25 34 export default {
26 35 getDeviceApi,
27 36 getDeviceDetail,
28   - getAttribute
29   -}
  37 + getAttribute,
  38 + issueCommand,
  39 + getRpcRecord
  40 +}
\ No newline at end of file
... ...
... ... @@ -103,11 +103,11 @@ uni.$u.http.interceptors.response.use(
103 103 const routers = getCurrentPages();
104 104 const currentRoute = routers[routers.length - 1].route;
105 105 const isLoginPage = currentRoute.includes(
106   - "publicLoginSubPage/public/login"
  106 + "login-subpackage/public/login"
107 107 );
108 108 !isLoginPage &&
109 109 uni.reLaunch({
110   - url: "/publicLoginSubPage/public/login",
  110 + url: "/login-subpackage/public/login",
111 111 });
112 112 // 清空登录信息
113 113 store.commit("emptyUserInfo");
... ...
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="700px" 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 FilterItem from '@/pages/device/components/query-item.vue';
  21 + import MescrollMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js';
  22 + import api from '@/api/index.js'
  23 + import alarmItem from '@/pages/alarm/components/alarm-item.vue'
  24 + import alarmPopup from '@/pages/alarm/components/alarm-popup.vue'
  25 + import {
  26 + useNavigateTo
  27 + } from '@/plugins/utils.js'
  28 +
  29 + export default {
  30 + mixins: [MescrollMixin],
  31 + components: {
  32 + FilterItem,
  33 + alarmItem,
  34 + alarmPopup
  35 + },
  36 + props: {
  37 + deviceId: {
  38 + type: String,
  39 + default: ''
  40 + }
  41 + },
  42 + data() {
  43 + return {
  44 + show: false,
  45 + list: [],
  46 + total: 0,
  47 + downOption: {
  48 + auto: true //是否在初始化后,自动执行downCallback; 默认true
  49 + },
  50 + upOption: {
  51 + auto: false // 不自动加载
  52 + },
  53 + page: {
  54 + num: 0,
  55 + size: 10
  56 + },
  57 + conditions: {},
  58 + };
  59 + },
  60 + methods: {
  61 + disabledScroll() {
  62 + return;
  63 + },
  64 + getQueryCondition(value) {
  65 + this.conditions = value
  66 + this.conditions.deviceId = this.deviceId
  67 + this.loadData(1, this.conditions);
  68 + this.close()
  69 + },
  70 + resetQuery() {
  71 + this.page.num = 1;
  72 + this.$refs.alarmPopupRef.resetQuery()
  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>
\ No newline at end of file
... ...
  1 +<template>
  2 +</template>
  3 +
  4 +<script>
  5 + export default {
  6 +
  7 + }
  8 +</script>
  9 +<style>
  10 +</style>
\ No newline at end of file
... ...
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="请输入下发内容(json格式)" v-model="inputCommandVal" />
130   - <u-icon @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   - };
192   - },
193   - computed: {
194   - deviceStatus(){
195   - return this.deviceDetail.deviceState === 'INACTIVE' ? '待激活' : this.deviceDetail.deviceState === 'ONLINE' ? '在线' : '离线'
196   - },
197   - deviceType() {
198   - return this.deviceDetail.deviceType === 'DIRECT_CONNECTION' ?
199   - '直连设备' :
200   - this.deviceDetail.deviceType === 'GATEWAY' ?
201   - '网关设备' :
202   - this.deviceDetail.deviceType === 'SENSOR' ?
203   - '网关子设备' :
204   - '';
205   - },
206   - alarmStatus() {
207   - return this.deviceDetail.alarmStatus === '0' ? '否' : '是';
208   - },
209   - formatLastOnlineTime() {
210   - return formatToDate(Number(this.deviceDetail.lastOnlineTime), 'YYYY-MM-DD HH:mm:ss');
211   - }
212   - },
213   - onLoad(e) {
214   - // 隐藏原生的tabbar
215   - uni.hideTabBar();
216   - },
217   - mounted() {},
218   - beforeCreate() {
219   - this.modalName = null
220   - },
221   - methods: {
222   - handleCopy(value) {
223   - uni.showModal({
224   - content: JSON.stringify(value),
225   - confirmText: '复制内容',
226   - showCancel:false,
227   - success: () => {
228   - uni.setClipboardData({
229   - data: JSON.stringify(value),
230   - success: () => {
231   - uni.showToast({
232   - title: '复制成功'
233   - })
234   - }
235   - });
236   - },
237   -
238   - });
239   - },
240   - radioChange: function(evt) {
241   - for (let i = 0; i < this.items.length; i++) {
242   - if (this.items[i].value === evt.detail.value) {
243   - this.current = i;
244   - break;
245   - }
246   - }
247   - this.commandType = evt.detail.value
248   - },
249   - handleClick() {
250   - const data = {
251   - longitude: this.deviceDetail.deviceInfo.longitude || 0,
252   - latitude: this.deviceDetail.deviceInfo.latitude || 0
253   - };
254   - uni.navigateTo({
255   - url: '/deviceSubPage/deviceDetailPage/devicePosition?data=' + JSON.stringify(data)
256   - });
257   - },
258   - showModal(e) {
259   - this.modalName = e.currentTarget.dataset.target
260   - this.showNodal = true
261   - },
262   - showModalBtn() {
263   - this.showModel = true;
264   - this.inputCommandVal = '';
265   - },
266   - disabledScroll() {
267   - return;
268   - },
269   - hiddenModal() {
270   - this.showModel = false;
271   - this.inputCommandVal = '';
272   - // #ifdef APP-PLUS
273   - this.modalName = null
274   - this.showNodal = false
275   - // #endif
276   - },
277   - async confirmCommand() {
278   - try {
279   - const commandJsonValue = JSON.parse(this.inputCommandVal);
280   - this.commandValue.persistent = true;
281   - this.commandValue.additionalInfo = {
282   - cmdType: 'API'
283   - };
284   - this.commandValue.method = 'methodThingskit';
285   - this.commandValue.params = commandJsonValue
286   - await issueCommand(this.commandType, this.deviceDetail.tbDeviceId, this.commandValue);
287   - this.hiddenModal();
288   - uni.$u.toast('下发成功~');
289   - } catch (e) {
290   - uni.$u.toast('下发失败~');
291   - }
292   - },
293   - cancelCommand() {
294   - this.hiddenModal();
295   - // #ifdef APP-PLUS
296   - this.modalName = null
297   - this.showNodal = false
298   - // #endif
299   - }
300   - }
301   - };
302   -</script>
303   -
304   -<style lang="scss" scoped>
305   - @import url('../styles/modal.css');
306   -
307   - .cusAppplusContent {
308   - width: 625rpx;
309   - height: 400rpx;
310   - background: #FFFFFF;
311   - box-shadow: 2px 2px 4px 0px rgba(0, 0, 0, 0.03);
312   - border-radius: 10px;
313   -
314   - }
315   -
316   - .cusAppplusCancelBtn {
317   - background: #e3e3e5;
318   - border-radius: 38rpx;
319   - height: 85rpx;
320   - line-height: 85rpx
321   - }
322   -
323   - .cusAppplusConfrimBtn {
324   - background: #3388ff;
325   - border-radius: 38rpx;
326   - height: 85rpx;
327   - line-height: 85rpx
328   - }
329   -
330   - .cu-item {
331   - background: #3388FF;
332   - border-radius: 12px;
333   - width: 120rpx;
334   - height: 48rpx;
335   - text-align: center;
336   - line-height: 40rpx;
337   -
338   - text {
339   - font-size: 12px;
340   - font-family: PingFangSC-Regular, PingFang SC;
341   - font-weight: 400;
342   - color: #FFFFFF;
343   - }
344   - }
345   -
346   -
347   -
348   - .basic-page {
349   - padding: 0 30rpx;
350   -
351   - .basic-title {
352   - display: flex;
353   - justify-content: space-between;
354   - align-items: center;
355   - height: 140rpx;
356   - background-color: #fff;
357   - border-radius: 20rpx;
358   - }
359   -
360   - .detail {
361   - background-color: #fff;
362   - margin-top: 30rpx;
363   - border-radius: 20rpx;
364   - width: 690rpx;
365   -
366   - .detail-item {
367   - padding: 30rpx;
368   - display: flex;
369   - align-items: center;
370   -
371   - .detail-label {
372   - color: #333;
373   - font-size: 15px;
374   - }
375   -
376   - .detail-value {
377   - color: #666;
378   - font-size: 14px;
379   - margin-left: 30rpx;
380   - }
381   - }
382   - }
383   - }
384   -
385   - /deep/ .u-modal__content {
386   - padding: 30rpx 0 !important;
387   - }
388   -
389   - .button-group {
390   - display: flex;
391   - margin-top: 40rpx;
392   - justify-content: space-between;
393   -
394   - view {
395   - width: 300rpx;
396   - }
397   - }
398   -</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" name="map-fill">
  10 + </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 + <u-button type="primary" shape="circle" size="mini" text="下发命令" @click="handleAppShowModal" />
  26 + <!-- #endif -->
  27 + </view>
  28 + </view>
  29 + <!-- 设备详情 -->
  30 + <view class="detail">
  31 + <view class="detail-item">
  32 + <view class="detail-label">设备编号</view>
  33 + <view class="detail-value">{{ deviceDetail.sn }}</view>
  34 + </view>
  35 + <u-line length="90%" margin="0 auto"></u-line>
  36 + <view class="detail-item">
  37 + <view class="detail-label">设备类型</view>
  38 + <view class="detail-value">{{ deviceType }}</view>
  39 + </view>
  40 + <u-line length="90%" margin="0 auto"></u-line>
  41 + <view class="detail-item">
  42 + <view class="detail-label">所属组织</view>
  43 + <view class="detail-value">{{ deviceDetail.organizationDTO.name }}</view>
  44 + </view>
  45 + <u-line length="90%" margin="0 auto"></u-line>
  46 + <view class="detail-item">
  47 + <view class="detail-label">最后连接时间</view>
  48 + <view class="detail-value">{{ formatLastOnlineTime }}</view>
  49 + </view>
  50 + <u-line length="90%" margin="0 auto"></u-line>
  51 + <view class="detail-item">
  52 + <view class="detail-label">是否告警</view>
  53 + <view class="detail-value">{{ alarmStatus }}</view>
  54 + </view>
  55 + <u-line length="90%" margin="0 auto"></u-line>
  56 + <view class="detail-item">
  57 + <view class="detail-label">设备描述</view>
  58 + <view class="detail-value">{{ deviceDetail.description }}</view>
  59 + </view>
  60 + </view>
  61 + <!-- 命令下发 -->
  62 + <!-- #ifdef APP-PLUS -->
  63 + <app-command-issuance :showModal="appShowModal"></app-command-issuance>
  64 + <!-- #endif -->
  65 + <!-- #ifdef MP -->
  66 + <!-- u-modal在app端弹窗层级无法覆盖 -->
  67 + <mp-command-issuance :isShowTCP="isShowTCP" :showModal="mpShowModal" @hideModal="hideMpModal"
  68 + @cancelCommand="cancelCommand" @confirmCommand="confirmCommand"></mp-command-issuance>
  69 + <!-- #endif -->
  70 + </view>
  71 +</template>
  72 +
  73 +<script>
  74 + import {
  75 + formatToDate
  76 + } from '@/plugins/utils.js';
  77 + import {
  78 + issueCommand
  79 + } from '../api/index.js';
  80 + import api from '@/api/index.js'
  81 + import mpCommandIssuance from './mp-command-issuance.vue'
  82 + import appCommandIssuance from './app-command-issuance.vue'
  83 + import nextModal from '@/components/module/next-modal_1.0.2/components/next-modal/next-modal.vue'
  84 +
  85 + export default {
  86 + components: {
  87 + mpCommandIssuance,
  88 + appCommandIssuance,
  89 + nextModal
  90 + },
  91 + props: {
  92 + deviceDetail: {
  93 + type: Object,
  94 + default: () => ({})
  95 + }
  96 + },
  97 + data() {
  98 + return {
  99 + mpShowModal: false,
  100 + appShowModal: false,
  101 + commandValue: {},
  102 + isShowTCP: false, //用于下发命令时判断是否是TCP/UDP
  103 + };
  104 + },
  105 + computed: {
  106 + deviceStatus() {
  107 + return this.deviceDetail.deviceState === 'INACTIVE' ? '待激活' : this.deviceDetail.deviceState === 'ONLINE' ?
  108 + '在线' : '离线'
  109 + },
  110 + deviceType() {
  111 + return this.deviceDetail.deviceType === 'DIRECT_CONNECTION' ?
  112 + '直连设备' :
  113 + this.deviceDetail.deviceType === 'GATEWAY' ?
  114 + '网关设备' :
  115 + this.deviceDetail.deviceType === 'SENSOR' ?
  116 + '网关子设备' :
  117 + '';
  118 + },
  119 + alarmStatus() {
  120 + return this.deviceDetail.alarmStatus === '0' ? '否' : '是';
  121 + },
  122 + formatLastOnlineTime() {
  123 + return formatToDate(Number(this.deviceDetail.lastOnlineTime), 'YYYY-MM-DD HH:mm:ss');
  124 + }
  125 + },
  126 + onLoad() {
  127 + // 隐藏原生的tabbar
  128 + uni.hideTabBar();
  129 + },
  130 + methods: {
  131 + formatTextStatus(deviceState) {
  132 + return deviceState === 'INACTIVE' ? '#666' : deviceState === 'ONLINE' ? '#377DFF' : '#DE4437'
  133 + },
  134 + handleClick() {
  135 + const data = {
  136 + longitude: this.deviceDetail.deviceInfo.longitude || 0,
  137 + latitude: this.deviceDetail.deviceInfo.latitude || 0
  138 + };
  139 + uni.navigateTo({
  140 + url: '/deviceSubPage/deviceDetailPage/devicePosition?data=' + JSON.stringify(data)
  141 + });
  142 + },
  143 + disabledScroll() {
  144 + return;
  145 + },
  146 + handleAppShowModal() {
  147 + this.appShowModal = true
  148 + },
  149 + handleMpShowModal() {
  150 + const {
  151 + transportType
  152 + } = this.deviceDetail.deviceProfile
  153 + this.isShowTCP = transportType == 'TCP' ? true : false
  154 + this.mpShowModal = true;
  155 + },
  156 + hideMpModal() {
  157 + this.mpShowModal = false;
  158 + },
  159 + hideAppModal() {
  160 + this.appShowModal = false
  161 + },
  162 + async confirmCommand(commandType, inputCommandVal) {
  163 + try {
  164 + if (this.isShowTCP) { //TCP的格式只能是字符串
  165 + const zg = /^[0-9a-zA-Z]*$/
  166 + if (!zg.test(inputCommandVal)) {
  167 + uni.$u.toast('输入的内容只能是字母和数字的组合')
  168 + return
  169 + }
  170 + this.commandValue.params = inputCommandVal
  171 + } else {
  172 + this.commandValue.params = JSON.parse(inputCommandVal);
  173 + }
  174 + if (!commandJsonValue) return uni.$u.toast('请输入下发内容~');
  175 + this.commandValue.persistent = true;
  176 + this.commandValue.additionalInfo = {
  177 + cmdType: 'API'
  178 + };
  179 + this.commandValue.method = 'methodThingskit';
  180 + this.commandValue.params = commandJsonValue
  181 + await api.deviceApi.issueCommand(commandType, this.deviceDetail.tbDeviceId, this.commandValue);
  182 + this.cancelCommand();
  183 + uni.$u.toast('下发成功~');
  184 + } catch (e) {
  185 + uni.$u.toast('下发失败~');
  186 + }
  187 + },
  188 + cancelCommand() {
  189 + this.hideMpModal();
  190 + this.hideAppModal()
  191 + }
  192 + }
  193 + };
  194 +</script>
  195 +
  196 +<style lang="scss" scoped>
  197 + .basic-page {
  198 + padding: 0 30rpx;
  199 +
  200 + .basic-header {
  201 + display: flex;
  202 + justify-content: space-between;
  203 + align-items: center;
  204 + height: 140rpx;
  205 + background-color: #fff;
  206 + border-radius: 20rpx;
  207 +
  208 + .basic-text {
  209 + width: 370rpx;
  210 + }
  211 +
  212 + .cu-item {
  213 + background: #3388FF;
  214 + border-radius: 12px;
  215 + width: 120rpx;
  216 + height: 48rpx;
  217 + text-align: center;
  218 + line-height: 40rpx;
  219 +
  220 + text {
  221 + font-size: 12px;
  222 + font-family: PingFangSC-Regular, PingFang SC;
  223 + font-weight: 400;
  224 + color: #FFFFFF;
  225 + }
  226 + }
  227 +
  228 + .basic-text-status {
  229 + font-size: 14px;
  230 + }
  231 + }
  232 +
  233 + .detail {
  234 + background-color: #fff;
  235 + margin-top: 30rpx;
  236 + border-radius: 20rpx;
  237 + width: 690rpx;
  238 +
  239 + .detail-item {
  240 + padding: 30rpx;
  241 + display: flex;
  242 + align-items: center;
  243 +
  244 + .detail-label {
  245 + color: #333;
  246 + font-size: 15px;
  247 + }
  248 +
  249 + .detail-value {
  250 + color: #666;
  251 + font-size: 14px;
  252 + margin-left: 30rpx;
  253 + }
  254 + }
  255 + }
  256 + }
  257 +
  258 + /deep/ .u-modal__content {
  259 + padding: 30rpx 0 !important;
  260 + }
  261 +</style>
\ No newline at end of file
... ...
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 {
  95 + data
  96 + } = options;
  97 + this.commandDetail = JSON.parse(decodeURIComponent(data));
  98 + if (this.commandDetail.response.status === 'SUCCESS') return
  99 + this.failContent = JSON.stringify(this.commandDetail.response.error)
  100 + }
  101 + };
  102 +</script>
  103 +
  104 +<style lang="scss" scoped>
  105 + .command-detail {
  106 + padding: 0 30rpx;
  107 + height: 100vh;
  108 + background-color: #f8f9fa;
  109 +
  110 + .detail-top {
  111 + height: 118rpx;
  112 + width: 690rpx;
  113 + display: flex;
  114 + align-items: center;
  115 + background-color: #fff;
  116 + color: #333;
  117 + border-radius: 20rpx;
  118 + font-size: 15px;
  119 + margin-top: 30rpx;
  120 + padding: 30rpx;
  121 + }
  122 +
  123 + .detail {
  124 + background-color: #fff;
  125 + margin-top: 30rpx;
  126 + border-radius: 20rpx;
  127 + width: 690rpx;
  128 +
  129 + .detail-item {
  130 + padding: 30rpx;
  131 + display: flex;
  132 + align-items: center;
  133 +
  134 + .detail-label {
  135 + color: #333;
  136 + font-size: 15px;
  137 + }
  138 +
  139 + .detail-value {
  140 + color: #666;
  141 + font-size: 14px;
  142 + margin-left: 30rpx;
  143 + }
  144 + }
  145 + }
  146 +
  147 + .command {
  148 + margin: 30rpx 0;
  149 + }
  150 + }
  151 +</style>
\ No newline at end of file
... ...
device-subpackage/device-detail/components/command-record.vue renamed from deviceSubPage/deviceDetailPage/tabDetail/CommandRecord.vue
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/components/query-item.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>
  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 + @touchmove.stop.prevent="disabledScroll">
  44 + <view class="filter" @touchmove.stop.prevent="disabledScroll">
  45 + <view class="filter-title"><text>筛选条件</text></view>
  46 + <query-item :filterList="issueStatus" title="下发状态"
  47 + @clickTag="currentIndex => handleClickTag(currentIndex, issueStatus)"></query-item>
  48 + <view class="mt-3">
  49 + <uni-datetime-picker return-type="timestamp" v-model="range" type="datetimerange"
  50 + rangeSeparator="至" />
  51 + </view>
  52 + <view class="h-30"></view>
  53 + <view class="button-group">
  54 + <view>
  55 + <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="重置"
  56 + @click="resetFilter"></u-button>
  57 + </view>
  58 + <view>
  59 + <u-button color="#3388ff" shape="circle" text="确认" @click="confirmFilter"></u-button>
  60 + </view>
  61 + </view>
  62 + </view>
  63 + </u-popup>
  64 + </view>
  65 +</template>
  66 +<script>
  67 + import queryItem from '@/pages/device/components/query-item.vue';
  68 + import MescrollMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js';
  69 + import {
  70 + formatToDate
  71 + } from '@/plugins/utils.js';
  72 + import {
  73 + debounce
  74 + } from '@/plugins/throttle.js';
  75 + import {
  76 + issueStatus
  77 + } from '../config/data.js'
  78 + import api from '@/api/index.js'
  79 + import {
  80 + useNavigateTo
  81 + } from '@/plugins/utils.js'
  82 +
  83 + export default {
  84 + mixins: [MescrollMixin],
  85 + components: {
  86 + queryItem
  87 + },
  88 + props: {
  89 + tbDeviceId: {
  90 + type: String,
  91 + default: ''
  92 + }
  93 + },
  94 + data() {
  95 + return {
  96 + show: false,
  97 + list: [],
  98 + total: 0,
  99 + range: [],
  100 + formTime: {
  101 + status: '',
  102 + startTime: '',
  103 + endTime: ''
  104 + },
  105 + status: '',
  106 + issueStatus,
  107 + downOption: {
  108 + auto: false //是否在初始化后,自动执行downCallback; 默认true
  109 + },
  110 + page: {
  111 + num: 0,
  112 + size: 10
  113 + }
  114 + };
  115 + },
  116 + methods: {
  117 + formatCommandStatus(status) {
  118 + return status == 'EXPIRED' ?
  119 + 'red' :
  120 + status == 'DELIVERED' ?
  121 + 'blue' :
  122 + status == 'QUEUED' ?
  123 + '#00C9A7' :
  124 + status == 'TIMEOUT' ?
  125 + 'red' :
  126 + status == 'SENT' ?
  127 + '#00C9A7' : ''
  128 + },
  129 + downCallback() {
  130 + for (let i in this.formTime) Reflect.set(this.formTime, i, '')
  131 + this.list = [];
  132 + this.page.num = 1;
  133 + this.loadData(this.page.num, {
  134 + tbDeviceId: this.tbDeviceId
  135 + });
  136 + },
  137 + format(date) {
  138 + return formatToDate(date, 'YYYY-MM-DD HH:mm:ss');
  139 + },
  140 + disabledScroll() {
  141 + return;
  142 + },
  143 + upCallback() {
  144 + const tbDeviceId = {
  145 + tbDeviceId: this.tbDeviceId
  146 + }
  147 + const condition = Object.values(this.formTime)
  148 + if (condition.length === 0) {
  149 + this.page.num += 1;
  150 + this.loadData(this.page.num);
  151 + } else if (condition.filter(Boolean).length > 0) {
  152 + this.page.num += 1;
  153 + this.loadData(this.page.num, {
  154 + ...this.formTime,
  155 + ...tbDeviceId
  156 + });
  157 + } else {
  158 + this.page.num += 1;
  159 + this.loadData(this.page.num);
  160 + }
  161 + },
  162 + async loadData(pageNo, params = {}) {
  163 + let httpData = {
  164 + ...params,
  165 + page: pageNo,
  166 + pageSize: 10
  167 + };
  168 + const res = await api.deviceApi.getRpcRecord({
  169 + params: httpData,
  170 + custom: {
  171 + load: false
  172 + }
  173 + })
  174 + if (!res) return
  175 + this.total = res.total;
  176 + uni.stopPullDownRefresh();
  177 + this.mescroll.endByPage(res.items.length, res.total);
  178 + if (pageNo == 1) {
  179 + this.list = res.items;
  180 + } else {
  181 + this.list = this.list.concat(res.items);
  182 + }
  183 + },
  184 + handleClickTag(currentIndex, list) {
  185 + list.map((item, index) => {
  186 + item.checked = index === currentIndex;
  187 + });
  188 + },
  189 + resetFilter() {
  190 + const {
  191 + issueStatus
  192 + } = this;
  193 + issueStatus.forEach(item => item.checked = false)
  194 + issueStatus[0].checked = true
  195 + },
  196 + close() {
  197 + this.show = false;
  198 + },
  199 + openSearchDialog() {
  200 + this.show = true;
  201 + this.resetFilter()
  202 + this.range = []
  203 + for (let i in this.formTime) Reflect.set(this.formTime, i, '')
  204 + },
  205 + hideKeyboard() {
  206 + uni.hideKeyboard();
  207 + },
  208 + confirmFilter() {
  209 + this.formTime.startTime = this.range[0]
  210 + this.formTime.endTime = this.range[1]
  211 + const issueStatus = this.issueStatus.find(item => item.checked);
  212 + this.formTime.status = issueStatus.type ? issueStatus.type : undefined,
  213 + this.loadData(1, {
  214 + tbDeviceId: this.tbDeviceId,
  215 + ...this.formTime
  216 + });
  217 + this.show = false;
  218 + },
  219 + openCommandDetail(item) {
  220 + useNavigateTo('/device-subpackage/device-detail/components/command-detail?data=', item)
  221 + }
  222 + }
  223 + };
  224 +</script>
  225 +
  226 +<style lang="scss" scoped>
  227 + .command-record {
  228 + padding: 0 30rpx;
  229 + background: #f8f9fa;
  230 +
  231 + .filter-button {
  232 + font-size: 12px;
  233 + width: 160rpx;
  234 + height: 64rpx;
  235 + border-radius: 32rpx;
  236 + display: flex;
  237 + justify-content: center;
  238 + align-items: center;
  239 + background: #f0f1f2;
  240 + color: #666;
  241 +
  242 + image {
  243 + width: 28rpx;
  244 + height: 28rpx;
  245 + margin-left: 4rpx;
  246 + }
  247 + }
  248 + }
  249 +
  250 + .list-item {
  251 + width: 690rpx;
  252 + background-color: #fff;
  253 + border-radius: 20rpx;
  254 + margin: 20rpx auto;
  255 + color: #333;
  256 +
  257 + .item {
  258 + .delivered-color {
  259 + color: blue;
  260 + }
  261 +
  262 + padding: 30rpx;
  263 +
  264 + view {
  265 + font-size: 14px;
  266 + margin-bottom: 10rpx;
  267 + }
  268 +
  269 + .time {
  270 + margin-top: 20rpx;
  271 + color: #999;
  272 + }
  273 +
  274 + .item-first {
  275 + display: flex;
  276 + justify-content: space-between;
  277 + align-items: center;
  278 + font-size: 15px;
  279 + font-weight: 500;
  280 + align-items: center;
  281 +
  282 + .item-right {
  283 + display: flex;
  284 + justify-content: center;
  285 + align-items: center;
  286 + width: 104rpx;
  287 + height: 36rpx;
  288 + font-size: 10px;
  289 + border-radius: 20rpx;
  290 + }
  291 +
  292 + .item-fail {
  293 + color: #848383;
  294 + background-color: #84838325;
  295 + }
  296 +
  297 + .item.success {
  298 + color: #00c9a7;
  299 + background-color: #00c9a725;
  300 + }
  301 + }
  302 + }
  303 + }
  304 +
  305 + .filter {
  306 + padding: 0 30rpx;
  307 +
  308 + .filter-title {
  309 + text-align: center;
  310 + margin-top: 14px;
  311 + font-size: 16px;
  312 + font-weight: 700;
  313 + }
  314 +
  315 + .button-group {
  316 + display: flex;
  317 + margin-top: 40rpx;
  318 + justify-content: space-between;
  319 +
  320 + view {
  321 + width: 330rpx;
  322 + }
  323 + }
  324 + }
  325 +</style>
\ No newline at end of file
... ...
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 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 +
  23 + </div>
  24 + </view>
  25 + <view class="button-group">
  26 + <view>
  27 + <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="取消"
  28 + @click="cancelCommand"></u-button>
  29 + </view>
  30 + <view>
  31 + <u-button color="#3388ff" shape="circle" text="确认" @click="confirmCommand"></u-button>
  32 + </view>
  33 + </view>
  34 + </view>
  35 + </u-modal>
  36 + </view>
  37 +</template>
  38 +
  39 +<script>
  40 + import {
  41 + useShowModal
  42 + } from '@/plugins/utils.js'
  43 +
  44 + export default {
  45 + props: {
  46 + showModal: Boolean,
  47 + isShowTCP: Boolean
  48 + },
  49 + data() {
  50 + return {
  51 + current: 0,
  52 + commandType: 'OneWay',
  53 + inputCommandVal: '',
  54 + copyTextValue: {
  55 + "method": "methodThingskit",
  56 + "params": {
  57 + "pin": 7,
  58 + "value": 1
  59 + }
  60 + }
  61 + }
  62 + },
  63 + methods: {
  64 + cancelCommand() {
  65 + this.$emit('cancelCommand')
  66 + },
  67 + confirmCommand() {
  68 + this.$emit('confirmCommand', this.commandType, this.inputCommandVal)
  69 + },
  70 + handleCopy(value) {
  71 + useShowModal(JSON.stringify(value), '命令下发', '复制内容').then(res => {
  72 + uni.setClipboardData({
  73 + data: JSON.stringify(value),
  74 + success: () => {
  75 + uni.showToast({
  76 + title: '复制成功'
  77 + })
  78 + }
  79 + });
  80 + })
  81 + }
  82 + }
  83 + }
  84 +</script>
  85 +
  86 +<style lang="scss" scoped>
  87 + .modal-content {
  88 + width: 720rpx;
  89 + padding: 0 30rpx;
  90 + background-color: white;
  91 +
  92 + .header-title {
  93 + text-align: center;
  94 + font-weight: 700;
  95 + margin-bottom: 40rpx;
  96 + }
  97 +
  98 + .type-text {
  99 + color: #333;
  100 + font-size: 14px;
  101 + font-weight: 700;
  102 + margin-right: 30rpx;
  103 + }
  104 +
  105 + .content-body {
  106 + margin-top: 28rpx;
  107 + width: 100%;
  108 + }
  109 +
  110 + .button-group {
  111 + display: flex;
  112 + margin-top: 40rpx;
  113 + justify-content: space-between;
  114 +
  115 + view {
  116 + width: 300rpx;
  117 + }
  118 + }
  119 + }
  120 +</style>
\ No newline at end of file
... ...
device-subpackage/device-detail/components/realtime-data.vue 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   -}
  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 52 </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 +export {
  63 + list,
  64 + issueStatus
  65 +}
\ No newline at end of file
... ...
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 {
  29 + getDeviceKeys,
  30 + getHistoryData
  31 + } from "./api/index.js";
  32 + import {
  33 + formatToDate
  34 + } from "@/plugins/utils.js";
  35 + import MescrollCompMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js";
  36 + import moment from "moment";
  37 + import base from "@/config/baseUrl.js";
  38 + import {
  39 + list
  40 + } from './config/data.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 + bgColor: '#fff',
  55 + activeColor: {
  56 + fontWeight: 'bold',
  57 + color: '#333',
  58 + },
  59 + inActiveColor: {
  60 + color: '#999',
  61 + },
  62 + list,
  63 + currentTab: 0,
  64 + deviceId: "",
  65 + deviceDetail: {},
  66 + keys: [],
  67 + yesterday: "",
  68 + today: "",
  69 + timeDiff: "",
  70 + historyData: [],
  71 + entityId: "",
  72 + startTs: "",
  73 + endTs: "",
  74 + recordList: [], //实时数据
  75 + isScrollable: false,
  76 + attrList: []
  77 + };
  78 + },
  79 + onUnload() {
  80 + // 页面关闭时,销毁webSocket连接,否则第二次会存在连接不到的情况
  81 + uni.closeSocket();
  82 + },
  83 + async onLoad(options) {
  84 + const {
  85 + id,
  86 + alarmStatus,
  87 + lastOnlineTime,
  88 + tbDeviceId,
  89 + deviceProfileId
  90 + } =
  91 + options;
  92 + this.deviceId = id;
  93 + const res = await uni.$u.http.get(`/yt/device/${id}`);
  94 + this.deviceDetail = {
  95 + ...res,
  96 + alarmStatus,
  97 + lastOnlineTime,
  98 + };
  99 +
  100 + // 设备类型不是网关子设备的添加一个命令记录的选项卡
  101 + if (this.deviceDetail.deviceType !== "SENSOR") {
  102 + this.list.push({
  103 + name: "命令记录",
  104 + });
  105 + }
  106 + this.isScrollable = this.list.length > 4;
  107 + if (res.deviceProfileId) {
  108 + const getAttrList = await uni.$u.http.get(
  109 + `/yt/device/attributes/${res.deviceProfileId}`
  110 + );
  111 + if (Array.isArray(getAttrList)) {
  112 + this.attrList = getAttrList.map(m => {
  113 + return m.identifier
  114 + })
  115 + }
  116 + }
  117 + // 连接webSockte
  118 + const socketTask = uni.connectSocket({
  119 + url: `${base.socketPrefix}://${base.baseWebSocketUrl}/api/ws/plugins/telemetry?token=` +
  120 + uni.getStorageSync("userInfo").isToken, //仅为示例,并非真实接口地址。
  121 + complete: () => {},
  122 + });
  123 + uni.onSocketOpen((header) => {
  124 + socketTask.send({
  125 + data: JSON.stringify({
  126 + attrSubCmds: [],
  127 + tsSubCmds: [{
  128 + entityType: "DEVICE",
  129 + entityId: tbDeviceId,
  130 + scope: "LATEST_TELEMETRY",
  131 + cmdId: 1,
  132 + keys: this.attrList.join(','),
  133 + }, ],
  134 + historyCmds: [],
  135 + entityDataCmds: [],
  136 + entityDataUnsubscribeCmds: [],
  137 + alarmDataCmds: [],
  138 + alarmDataUnsubscribeCmds: [],
  139 + entityCountCmds: [],
  140 + entityCountUnsubscribeCmds: [],
  141 + }),
  142 + success() {},
  143 + });
  144 + });
  145 + socketTask.onMessage((msg) => {
  146 + const {
  147 + data
  148 + } = JSON.parse(msg.data);
  149 + const newArray = [];
  150 + for (const key in data) {
  151 + const [time, value] = data[key].flat(1);
  152 + let obj = {
  153 + key,
  154 + time,
  155 + value,
  156 + };
  157 + if (this.recordList.length === 0) {
  158 + this.recordList.unshift(obj);
  159 + } else {
  160 + newArray.push(obj);
  161 + }
  162 + }
  163 + newArray.forEach((item) => {
  164 + let flag = false;
  165 + this.recordList.forEach((item1) => {
  166 + if (item1.key === item.key) {
  167 + item1.value = item.value;
  168 + item1.time = item.time;
  169 + flag = true;
  170 + }
  171 + });
  172 + if (!flag) {
  173 + this.recordList.unshift(item);
  174 + }
  175 + });
  176 + this.recordList = this.recordList.map((item) => {
  177 + return {
  178 + ...item,
  179 + time: formatToDate(item.time, "YYYY-MM-DD HH:mm:ss"),
  180 + };
  181 + });
  182 + });
  183 +
  184 + const keys = await getDeviceKeys(tbDeviceId);
  185 + this.keys = [keys];
  186 + // 昨天
  187 + this.yesterday = moment().subtract(1, "days").format("YYYY-MM-DD");
  188 + // 今天
  189 + this.today = moment().format("YYYY-MM-DD");
  190 + // 开始时间
  191 + this.startTs = moment().subtract(1, "days").format("x");
  192 + // 结束时间
  193 + this.endTs = moment().format("x");
  194 + this.entityId = tbDeviceId;
  195 +
  196 + const data = await getHistoryData({
  197 + entityId: tbDeviceId,
  198 + startTs: this.startTs,
  199 + endTs: this.endTs,
  200 + keys: keys[0],
  201 + // interval: 1800000,
  202 + limit: 7,
  203 + agg: 'NONE'
  204 + });
  205 + this.timeDiff = "30分钟";
  206 + if (!Object.keys(data).length) return;
  207 +
  208 + this.historyData = data[keys[0]].map((item) => {
  209 + return {
  210 + value: item.value,
  211 + ts: formatToDate(item.ts, "YYYY-MM-DD HH:mm:ss"),
  212 + };
  213 + });
  214 + },
  215 + methods: {
  216 + handleTabClick({
  217 + index
  218 + }) {
  219 + this.currentTab = index;
  220 + },
  221 + handleUpdate(data, e) {
  222 + if (!Array.isArray(data)) {
  223 + this.historyData = [];
  224 + return;
  225 + }
  226 + this.historyData = data.map((item) => {
  227 + return {
  228 + value: item.value,
  229 + ts: formatToDate(item.ts, "YYYY-MM-DD HH:mm:ss"),
  230 + };
  231 + });
  232 + },
  233 + },
  234 + };
  235 +</script>
  236 +
  237 +<style lang="scss" scoped>
  238 + .device-detail-page {
  239 + height: 100vh;
  240 + background-color: #f8f9fa;
  241 + }
  242 +</style>
\ No newline at end of file
... ...
device-subpackage/device-detail/device-position.vue renamed from deviceSubPage/deviceDetailPage/devicePosition.vue
device-subpackage/device-detail/static/modal.css renamed from deviceSubPage/deviceDetailPage/styles/modal.css
1   -<template>
2   - <view class="alert-page">
3   - <view class="filter-button" @click="openSearchDialog">
4   - <text>筛选</text>
5   - <image src="../../../static/shaixuan.png" />
6   - </view>
7   -
8   - <mescroll-uni ref="mescrollRef" @init="mescrollInit" :down="downOption" @down="downCallback" @up="upCallback" height="700px">
9   - <view @click="openDeviceDetail(item)" class="list-item" v-for="(item, index) in list" :key="index">
10   - <view class="item">
11   - <view class="item-first">
12   - <text style="font-weight: bold;">{{ item.deviceName }}</text>
13   - <view class="item-right">
14   - <image
15   - :src="
16   - item.severity === 'CRITICAL'
17   - ? '../../../static/danger.png'
18   - : item.severity === 'MAJOR'
19   - ? '../../../static/major.png'
20   - : item.severity === 'MINOR'
21   - ? '../../../static/secondary.png'
22   - : item.severity === 'WARNING'
23   - ? '../../../static/warn.png'
24   - : '../../../static/noshue.png'
25   - "
26   - ></image>
27   - <text
28   - :style="{
29   - color:
30   - item.severity === 'CRITICAL'
31   - ? '#DE4437'
32   - : item.severity === 'MAJOR'
33   - ? '#DE7337'
34   - : item.severity === 'MINOR'
35   - ? '#FFC107'
36   - : item.severity === 'WARNING'
37   - ? '#FF1E0B'
38   - : '#00C9A7'
39   - }"
40   - >
41   - {{
42   - item.severity === 'CRITICAL'
43   - ? '危险'
44   - : item.severity === 'MAJOR'
45   - ? '重要'
46   - : item.severity === 'MINOR'
47   - ? '次要'
48   - : item.severity === 'WARNING'
49   - ? '警告'
50   - : '不确定'
51   - }}
52   - </text>
53   - </view>
54   - </view>
55   - <view>
56   - {{ Object.entries(item.details.data)[0][0] }} :
57   - <text style="font-weight: bold; margin-left:4rpx;">{{ Object.entries(item.details.data)[0][1] }}</text>
58   - </view>
59   - <view v-if="item.status">
60   - 告警状态:{{
61   - item.status === 'CLEARED_UNACK'
62   - ? '清除未确认'
63   - : item.status === 'CLEARED_ACK'
64   - ? '清除已确认'
65   - : item.status === 'ACTIVE_UNACK'
66   - ? '激活未确认'
67   - : '激活已确认'
68   - }}
69   - </view>
70   - <view class="time">{{ item.createdTime }}</view>
71   - </view>
72   - </view>
73   - </mescroll-uni>
74   - <!-- 告警筛选 -->
75   - <u-popup @close="close" closeable bgColor="#fff" :show="show" mode="bottom" :round="20" @touchmove.stop.prevent="disabledScroll">
76   - <view class="filter" @touchmove.stop.prevent="disabledScroll">
77   - <view class="filter-title"><text>筛选条件</text></view>
78   - <FilterItem :filterList="alarmStatus" title="告警状态" @clickTag="currentIndex => handleClickTag(currentIndex, alarmStatus)"></FilterItem>
79   - <FilterItem :filterList="typeStatus" title="设备类型" @clickTag="currentIndex => handleClickTag(currentIndex, typeStatus)"></FilterItem>
80   - <FilterItem :filterList="alarmLevelStatus" title="告警等级" @clickTag="currentIndex => handleClickTag(currentIndex, alarmLevelStatus)"></FilterItem>
81   - <FilterItem :filterList="timeStatus" title="选择时间" @clickTag="currentIndex => handleClickTag(currentIndex, timeStatus)"></FilterItem>
82   - <view class="button-group">
83   - <view><u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="重置" @click="resetFilter"></u-button></view>
84   - <view><u-button color="#3388ff" shape="circle" text="确认" @click="confirmFilter"></u-button></view>
85   - </view>
86   - </view>
87   - </u-popup>
88   - <u-calendar
89   - :show="showCalendar"
90   - mode="range"
91   - @confirm="calendarConfirm"
92   - @close="calendarClose"
93   - startText="开始时间"
94   - endText="结束时间"
95   - confirmDisabledText="请选择日期"
96   - ></u-calendar>
97   - </view>
98   -</template>
99   -<script>
100   -import FilterItem from '@/pages/device/components/query-item.vue';
101   -import MescrollMixin from '@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js';
102   -export default {
103   - mixins: [MescrollMixin],
104   - components: {
105   - FilterItem
106   - },
107   - props: {
108   - deviceId: {
109   - type: String,
110   - default: ''
111   - }
112   - },
113   - data() {
114   - return {
115   - show: false,
116   - list: [],
117   - total: '',
118   - timeData: {
119   - selectTime: '',
120   - getTimeGap: ''
121   - },
122   - showCalendar: false,
123   - alarmStatus: [
124   - {
125   - checked: true,
126   - name: '全部',
127   - type: ''
128   - },
129   - {
130   - checked: false,
131   - name: '激活未确认',
132   - type: 'ACTIVE_UNACK'
133   - },
134   - {
135   - checked: false,
136   - name: '激活已确认',
137   - type: 'ACTIVE_ACK'
138   - },
139   - {
140   - checked: false,
141   - name: '清除未确认',
142   - type: 'CLEARED_UNACK'
143   - },
144   - {
145   - checked: false,
146   - name: '清除已确认',
147   - type: 'CLEARED_ACK'
148   - }
149   - ],
150   - typeStatus: [
151   - {
152   - checked: true,
153   - name: '全部',
154   - type: ''
155   - },
156   - {
157   - checked: false,
158   - name: '网关设备',
159   - type: 'GATEWAY'
160   - },
161   - {
162   - checked: false,
163   - name: '网关子设备',
164   - type: 'SENSOR'
165   - },
166   - {
167   - checked: false,
168   - name: '直连设备',
169   - type: 'DIRECT_CONNECTION'
170   - }
171   - ],
172   - alarmLevelStatus: [
173   - {
174   - checked: true,
175   - name: '全部',
176   - type: ''
177   - },
178   - {
179   - checked: false,
180   - name: '危险',
181   - type: 'CRITICAL'
182   - },
183   - {
184   - checked: false,
185   - name: '重要',
186   - type: 'MAJOR'
187   - },
188   - {
189   - checked: false,
190   - name: '次要',
191   - type: 'MINOR'
192   - },
193   - {
194   - checked: false,
195   - name: '警告',
196   - type: 'WARNING'
197   - },
198   - {
199   - checked: false,
200   - name: '不确定',
201   - type: 'INDETERMINATE'
202   - }
203   - ],
204   - timeStatus: [
205   - {
206   - checked: true,
207   - name: '全部',
208   - type: ''
209   - },
210   - {
211   - checked: false,
212   - name: '30分钟',
213   - type: '1800000'
214   - },
215   - {
216   - checked: false,
217   - name: '一小时',
218   - type: '3600000'
219   - },
220   - {
221   - checked: false,
222   - name: '2小时',
223   - type: '7200000'
224   - },
225   - {
226   - checked: false,
227   - name: '近一天',
228   - type: '86400000'
229   - }
230   - ],
231   - downOption: {
232   - auto: false //是否在初始化后,自动执行downCallback; 默认true
233   - },
234   - page: {
235   - num: 0,
236   - size: 10
237   - }
238   - };
239   - },
240   - methods: {
241   - disabledScroll() {
242   - return;
243   - },
244   - /*下拉刷新的回调 */
245   - downCallback() {
246   - //联网加载数据
247   - this.list = [];
248   - this.page.num = 1;
249   - this.loadData(this.page.num, {
250   - deviceId: this.deviceId
251   - });
252   - },
253   - /*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
254   - upCallback() {
255   - //联网加载数据
256   - this.page.num += 1;
257   - this.loadData(this.page.num, {
258   - deviceId: this.deviceId
259   - });
260   - },
261   - //获取告警数据
262   - loadData(pageNo, params = {}) {
263   - let httpData = {
264   - ...params,
265   - page: pageNo,
266   - pageSize: 10
267   - };
268   - uni.$u.http
269   - .get('/yt/alarm', {
270   - params: httpData,
271   - custom: {
272   - load: false
273   - }
274   - })
275   - .then(res => {
276   - this.total = res.total;
277   - uni.stopPullDownRefresh();
278   - //方法一(推荐): 后台接口有返回列表的总页数 totalPage
279   - this.mescroll.endByPage(res.items.length, res.total); //必传参数(当前页的数据个数, 总页数)
280   - if (pageNo == 1) {
281   - this.list = res.items;
282   - } else {
283   - this.list = this.list.concat(res.items);
284   - }
285   - })
286   - .catch(() => {
287   - //联网失败, 结束加载
288   - this.mescroll.endErr();
289   - });
290   - },
291   - handleClickTag(currentIndex, list) {
292   - list.map((item, index) => {
293   - item.checked = index === currentIndex;
294   - });
295   - },
296   - resetFilter() {
297   - const { alarmStatus, typeStatus, alarmLevelStatus, timeStatus } = this;
298   - [alarmStatus, typeStatus, alarmLevelStatus, timeStatus].forEach(item => item.map((item, index) => (item.checked = index === 0)));
299   - },
300   - close() {
301   - this.show = false;
302   - },
303   - openSearchDialog() {
304   - this.show = true;
305   - },
306   - hideKeyboard() {
307   - uni.hideKeyboard();
308   - },
309   - calendarConfirm(e) {
310   - this.showCalendar = false;
311   - this.timeData.selectTime = `${e[0]} / ${e[e.length - 1]}`;
312   - },
313   - confirmFilter() {
314   - const alarmState = this.alarmStatus.find(item => item.checked);
315   - const typeState = this.typeStatus.find(item => item.checked);
316   - const alarmLevelState = this.alarmLevelStatus.find(item => item.checked);
317   - const timeState = this.timeStatus.find(item => item.checked);
318   - const endTs = Date.now();
319   - const startTs = endTs - timeState.type;
320   - this.loadData(1, {
321   - status: alarmState.type ? alarmState.type : undefined,
322   - deviceType: typeState.type ? typeState.type : undefined,
323   - severity: alarmLevelState.type ? alarmLevelState.type : undefined,
324   - startTime: timeState.type ? startTs : undefined,
325   - endTime: timeState.type ? endTs : undefined,
326   - deviceId: this.deviceId
327   - });
328   - this.show = false;
329   - },
330   - calendarClose() {
331   - this.showCalendar = false;
332   - },
333   - openDeviceDetail(item) {
334   - const { id, deviceName, severity, originatorType, details, createdTime, status } = item;
335   - let obj = {
336   - id,
337   - deviceName,
338   - severity,
339   - originatorType,
340   - details,
341   - createdTime,
342   - status
343   - };
344   - uni.navigateTo({
345   - url: '/alarmSubPage/alarmDetailPage/alarmDetail?data=' + JSON.stringify(obj)
346   - // url: '/' + JSON.stringify(obj)
347   - });
348   - }
349   - }
350   -};
351   -</script>
352   -
353   -<style lang="scss" scoped>
354   -.filter-button {
355   - font-size: 12px;
356   - width: 160rpx;
357   - height: 64rpx;
358   - border-radius: 32rpx;
359   - display: flex;
360   - justify-content: center;
361   - align-items: center;
362   - background: #f0f1f2;
363   - color: #666;
364   - image {
365   - width: 28rpx;
366   - height: 28rpx;
367   - margin-left: 4rpx;
368   - }
369   -}
370   -.alert-page {
371   - padding: 0 30rpx;
372   - .list-item {
373   - width: 690rpx;
374   - height: 262rpx;
375   - background-color: #fff;
376   - border-radius: 20rpx;
377   - margin: 20rpx auto;
378   - color: #333;
379   - .item {
380   - padding: 30rpx;
381   - view {
382   - font-size: 14px;
383   - margin-bottom: 10rpx;
384   - }
385   - .time {
386   - color: #999;
387   - }
388   - .item-first {
389   - display: flex;
390   - justify-content: space-between;
391   - font-size: 15px;
392   - font-weight: 500;
393   - align-items: center;
394   - .item-right {
395   - display: flex;
396   - align-items: center;
397   - image {
398   - width: 28rpx;
399   - height: 28rpx;
400   - margin-right: 10rpx;
401   - }
402   - }
403   - }
404   - }
405   - }
406   -}
407   -
408   -.filter {
409   - padding: 0 30rpx;
410   - .filter-title {
411   - text-align: center;
412   - margin-top: 14px;
413   - font-size: 16px;
414   - font-weight: 700;
415   - }
416   - .button-group {
417   - display: flex;
418   - margin-top: 40rpx;
419   - justify-content: space-between;
420   - view {
421   - width: 330rpx;
422   - }
423   - }
424   -}
425   -</style>
feedback-subpackage/feedback/feedback.vue renamed from feedBackSubPage/feedback/feedback.vue
feedback-subpackage/feedback/static/feedback.scss renamed from feedBackSubPage/feedback/static/feedback.scss
login-subpackage/other/code.vue renamed from publicLoginSubPage/other/code.vue
1   -<template>
2   - <view class="code-page">
3   - <!-- 公共组件-每个页面必须引入 -->
4   - <public-module></public-module>
5   - <view class="login-body">
6   - <view class="login-phone">
7   - <view class="phone-main">
8   - <text class="text">手机验证码登录</text>
9   - <view class="circleStyle"></view>
10   - </view>
11   - <view class="form-row"><u-input v-model="loginForm.phone" type="number" placeholder="请输入手机号码"
12   - border="bottom"></u-input></view>
13   - <view class="form-row">
14   - <u-input type="number" v-model="loginForm.verifyCode" placeholder="请输入验证码" border="bottom">
15   - <template slot="suffix">
16   - <view @click="getVerifyCode" class="verify-code">{{ codeText }}</view>
17   - </template>
18   - </u-input>
19   - </view>
20   - <button class="submit" size="default" @click="onSubmit"><text class="text">登录</text></button>
21   - <view class="u-flex account-style">
22   - <view class="content" @click="openAccountFunc">账号密码登录</view>
23   - </view>
24   - <view class="circleStyleBottom"></view>
25   - </view>
26   - </view>
27   - </view>
28   -</template>
29   -
30   -<script>
31   - var clear;
32   - import {
33   - mapState,
34   - mapMutations,
35   - mapActions
36   - } from 'vuex';
37   - import {
38   - useShowToast,
39   - useNavigateTo
40   - } from '@/plugins/utils.js'
41   - import api from '@/api'
42   -
43   - export default {
44   - data() {
45   - return {
46   - loginForm: {
47   - phone: '',
48   - verifyCode: ''
49   - },
50   - readonly: false,
51   - codeText: '发送验证码',
52   - };
53   - },
54   - methods: {
55   - ...mapMutations(['setUserInfo']),
56   - ...mapActions(['updateBadgeTotal']),
57   - //验证码按钮文字状态
58   - codeCountdownText() {
59   - const _this = this;
60   - this.readonly = true;
61   - this.codeText = '60s后重新获取';
62   - var s = 60;
63   - clear = setInterval(() => {
64   - s--;
65   - _this.codeText = s + 's后重新获取';
66   - if (s <= 0) {
67   - clearInterval(clear);
68   - _this.codeText = '发送验证码';
69   - _this.readonly = false;
70   - }
71   - }, 1000);
72   - },
73   - //获取验证码
74   - async getVerifyCode() {
75   - const phoneRegular = /^1\d{10}$/;
76   - if (this.readonly) {
77   - useShowToast('验证码已发送~')
78   - }
79   - if (!this.loginForm.phone) {
80   - return useShowToast('请输入手机号~')
81   - }
82   - if (!phoneRegular.test(this.loginForm.phone)) {
83   - return useShowToast('手机号格式不正确~')
84   - }
85   - // 获取验证码接口
86   - await api.loginApi.postPhoneCodeApi(this.loginForm.phone)
87   - this.codeCountdownText(); //开始倒计时
88   - },
89   - async onSubmit() {
90   - const phoneRegular = /^1\d{10}$/;
91   - const verifyCodeReg = /^\d{6}$/;
92   - const validateValue = Object.values(this.loginForm)
93   - if (!validateValue[0]) return uni.$u.toast("请输入手机号码~");
94   - if (!validateValue[1]) return uni.$u.toast("请输入验证码~");
95   - if (!phoneRegular.test(validateValue[0])) return uni.$u.toast("手机号格式不正确~");
96   - if (!verifyCodeReg.test(validateValue[1])) return uni.$u.toast("验证码格式不正确~");
97   - const res = await api.loginApi.postPhoneLoginApi(this.loginForm)
98   - if (res) {
99   - // 储存登录信息
100   - let tokenInfo = {
101   - refreshToken: res.refreshToken,
102   - isToken: res.token
103   - };
104   - let userInfo = {
105   - ...tokenInfo,
106   - token: true, //token用于判断是否登录
107   - isThirdLogin: false
108   - };
109   - if (userInfo.token) {
110   - this.setUserInfo(userInfo);
111   - }
112   - useShowToast('登录成功~').then(async (res) => {
113   - this.saveUserInfo();
114   - await this.getAlarmTotalData();
115   - useReLaunch("/pages/index/index")
116   - });
117   - }
118   - },
119   - async getAlarmTotalData() {
120   - const res = await await api.loginApi.getAlarmTotalApi()
121   - if (!res) return
122   - //异步实时更新告警徽标数
123   - this.updateBadgeTotal(res.totalAlarm?.activedAlarm);
124   - },
125   - async saveUserInfo() {
126   - //储存个人信息
127   - const res = await api.loginApi.setUserInfoApi()
128   - if (!res) return
129   - this.setUserInfo(res);
130   - },
131   - openAccountFunc() {
132   - useNavigateTo('../public/login')
133   - }
134   - }
135   - };
136   -</script>
137   -
138   -<style lang="scss" scoped>
139   - @import './static/code.scss';
  1 +<template>
  2 + <view class="code-page">
  3 + <!-- 公共组件-每个页面必须引入 -->
  4 + <public-module></public-module>
  5 + <view class="login-body">
  6 + <view class="login-phone">
  7 + <view class="phone-main">
  8 + <text class="text">手机验证码登录</text>
  9 + <view class="circleStyle"></view>
  10 + </view>
  11 + <view class="form-row"><u-input v-model="loginForm.phone" type="number" placeholder="请输入手机号码"
  12 + border="bottom"></u-input></view>
  13 + <view class="form-row">
  14 + <u-input type="number" v-model="loginForm.verifyCode" placeholder="请输入验证码" border="bottom">
  15 + <template slot="suffix">
  16 + <view @click="getVerifyCode" class="verify-code">{{ codeText }}</view>
  17 + </template>
  18 + </u-input>
  19 + </view>
  20 + <button class="submit" size="default" @click="onSubmit"><text class="text">登录</text></button>
  21 + <view class="u-flex account-style">
  22 + <view class="content" @click="openAccountFunc">账号密码登录</view>
  23 + </view>
  24 + <view class="circleStyleBottom"></view>
  25 + </view>
  26 + </view>
  27 + </view>
  28 +</template>
  29 +
  30 +<script>
  31 + var clear;
  32 + import {
  33 + mapState,
  34 + mapMutations,
  35 + mapActions
  36 + } from 'vuex';
  37 + import {
  38 + useShowToast,
  39 + useNavigateTo
  40 + } from '@/plugins/utils.js'
  41 + import api from '@/api'
  42 +
  43 + export default {
  44 + data() {
  45 + return {
  46 + loginForm: {
  47 + phone: '',
  48 + verifyCode: ''
  49 + },
  50 + readonly: false,
  51 + codeText: '发送验证码',
  52 + };
  53 + },
  54 + methods: {
  55 + ...mapMutations(['setUserInfo']),
  56 + ...mapActions(['updateBadgeTotal']),
  57 + //验证码按钮文字状态
  58 + codeCountdownText() {
  59 + const _this = this;
  60 + this.readonly = true;
  61 + this.codeText = '60s后重新获取';
  62 + var s = 60;
  63 + clear = setInterval(() => {
  64 + s--;
  65 + _this.codeText = s + 's后重新获取';
  66 + if (s <= 0) {
  67 + clearInterval(clear);
  68 + _this.codeText = '发送验证码';
  69 + _this.readonly = false;
  70 + }
  71 + }, 1000);
  72 + },
  73 + //获取验证码
  74 + async getVerifyCode() {
  75 + const phoneRegular = /^1\d{10}$/;
  76 + if (this.readonly) {
  77 + useShowToast('验证码已发送~')
  78 + }
  79 + if (!this.loginForm.phone) {
  80 + return useShowToast('请输入手机号~')
  81 + }
  82 + if (!phoneRegular.test(this.loginForm.phone)) {
  83 + return useShowToast('手机号格式不正确~')
  84 + }
  85 + // 获取验证码接口
  86 + await api.loginApi.postPhoneCodeApi(this.loginForm.phone)
  87 + this.codeCountdownText(); //开始倒计时
  88 + },
  89 + async onSubmit() {
  90 + const phoneRegular = /^1\d{10}$/;
  91 + const verifyCodeReg = /^\d{6}$/;
  92 + const validateValue = Object.values(this.loginForm)
  93 + if (!validateValue[0]) return uni.$u.toast("请输入手机号码~");
  94 + if (!validateValue[1]) return uni.$u.toast("请输入验证码~");
  95 + if (!phoneRegular.test(validateValue[0])) return uni.$u.toast("手机号格式不正确~");
  96 + if (!verifyCodeReg.test(validateValue[1])) return uni.$u.toast("验证码格式不正确~");
  97 + const res = await api.loginApi.postPhoneLoginApi(this.loginForm)
  98 + if (res) {
  99 + // 储存登录信息
  100 + let tokenInfo = {
  101 + refreshToken: res.refreshToken,
  102 + isToken: res.token
  103 + };
  104 + let userInfo = {
  105 + ...tokenInfo,
  106 + token: true, //token用于判断是否登录
  107 + isThirdLogin: false
  108 + };
  109 + if (userInfo.token) {
  110 + this.setUserInfo(userInfo);
  111 + }
  112 + useShowToast('登录成功~').then(async (res) => {
  113 + this.saveUserInfo();
  114 + await this.getAlarmTotalData();
  115 + useReLaunch("/pages/index/index")
  116 + });
  117 + }
  118 + },
  119 + async getAlarmTotalData() {
  120 + const res = await await api.loginApi.getAlarmTotalApi()
  121 + if (!res) return
  122 + //异步实时更新告警徽标数
  123 + this.updateBadgeTotal(res.totalAlarm?.activedAlarm);
  124 + },
  125 + async saveUserInfo() {
  126 + //储存个人信息
  127 + const res = await api.loginApi.setUserInfoApi()
  128 + if (!res) return
  129 + this.setUserInfo(res);
  130 + },
  131 + openAccountFunc() {
  132 + useNavigateTo('../public/login')
  133 + }
  134 + }
  135 + };
  136 +</script>
  137 +
  138 +<style lang="scss" scoped>
  139 + @import './static/code.scss';
140 140 </style>
\ No newline at end of file
... ...