Commit 1c2f21524b460b5e618280b89e13e7e507efc89a
Committed by
xp.Huang
1 parent
fe27c91c
feat: 设备命令下发添加服务调用
Showing
5 changed files
with
1048 additions
and
384 deletions
@@ -4,37 +4,47 @@ | @@ -4,37 +4,47 @@ | ||
4 | * data ((deviceProfileIds)) | 4 | * data ((deviceProfileIds)) |
5 | */ | 5 | */ |
6 | const getDeviceApi = (urlParams, data) => { | 6 | const getDeviceApi = (urlParams, data) => { |
7 | - const { | ||
8 | - page, | ||
9 | - pageSize, | ||
10 | - } = urlParams | ||
11 | - return uni.$u.http.post(`/yt/device?page=${page}&pageSize=${pageSize}`, data); | ||
12 | -}; | 7 | + const { page, pageSize } = urlParams |
8 | + return uni.$u.http.post(`/yt/device?page=${page}&pageSize=${pageSize}`, data) | ||
9 | +} | ||
13 | 10 | ||
14 | // 设备详情 | 11 | // 设备详情 |
15 | const getDeviceDetail = (id) => { | 12 | const getDeviceDetail = (id) => { |
16 | - return uni.$u.http.get(`/yt/device/${id}`); | ||
17 | -}; | 13 | + return uni.$u.http.get(`/yt/device/${id}`) |
14 | +} | ||
18 | 15 | ||
19 | //设备属性 | 16 | //设备属性 |
20 | const getAttribute = (deviceProfileId) => { | 17 | const getAttribute = (deviceProfileId) => { |
21 | - return uni.$u.http.get(`/yt/device/attributes/${deviceProfileId}`); | ||
22 | -}; | 18 | + return uni.$u.http.get(`/yt/device/attributes/${deviceProfileId}`) |
19 | +} | ||
23 | 20 | ||
24 | //命令下发 | 21 | //命令下发 |
25 | const issueCommand = (type, tbDeviceId, data) => { | 22 | const issueCommand = (type, tbDeviceId, data) => { |
26 | - return uni.$u.http.post(`/rpc/${type==='OneWay'?'oneway':'twoway'}/${tbDeviceId}`, data) | 23 | + return uni.$u.http.post(`/rpc/${type === 'OneWay' ? 'oneway' : 'twoway'}/${tbDeviceId}`, data) |
27 | } | 24 | } |
28 | 25 | ||
29 | //获取命令下发记录 | 26 | //获取命令下发记录 |
30 | const getRpcRecord = (params) => { | 27 | const getRpcRecord = (params) => { |
31 | - return uni.$u.http.get('/yt/rpc', params); | ||
32 | -}; | 28 | + return uni.$u.http.get('/yt/rpc', params) |
29 | +} | ||
30 | + | ||
31 | +// 获取设备状态 在线or离线 | ||
32 | +const getDeviceActiveTime = (entityId) => { | ||
33 | + return uni.$u.http.get(`/plugins/telemetry/DEVICE/${entityId}/values/attributes?keys=active`) | ||
34 | +} | ||
35 | + | ||
36 | +// 获取服务调用 | ||
37 | +const getModelServices = (params) => { | ||
38 | + const { deviceProfileId } = params | ||
39 | + return uni.$u.http.get(`/yt/things_model/get_services/${deviceProfileId}`) | ||
40 | +} | ||
33 | 41 | ||
34 | export default { | 42 | export default { |
35 | - getDeviceApi, | ||
36 | - getDeviceDetail, | ||
37 | - getAttribute, | ||
38 | - issueCommand, | ||
39 | - getRpcRecord | ||
40 | -} | ||
43 | + getDeviceApi, | ||
44 | + getDeviceDetail, | ||
45 | + getAttribute, | ||
46 | + issueCommand, | ||
47 | + getRpcRecord, | ||
48 | + getModelServices, | ||
49 | + getDeviceActiveTime, | ||
50 | +} |
@@ -6,8 +6,7 @@ | @@ -6,8 +6,7 @@ | ||
6 | <view class="basic-header"> | 6 | <view class="basic-header"> |
7 | <view class="u-flex"> | 7 | <view class="u-flex"> |
8 | <view class="pl-3"> | 8 | <view class="pl-3"> |
9 | - <u-icon v-if="deviceDetail.deviceInfo.longitude !== ''" @click="handleClick" | ||
10 | - name="map-fill"></u-icon> | 9 | + <u-icon v-if="deviceDetail.deviceInfo.longitude !== ''" @click="handleClick" name="map-fill"></u-icon> |
11 | </view> | 10 | </view> |
12 | <view class="basic-text text-clip ml-2"> | 11 | <view class="basic-text text-clip ml-2"> |
13 | {{ deviceDetail.alias ? deviceDetail.alias : deviceDetail.name }} | 12 | {{ deviceDetail.alias ? deviceDetail.alias : deviceDetail.name }} |
@@ -17,13 +16,13 @@ | @@ -17,13 +16,13 @@ | ||
17 | </view> | 16 | </view> |
18 | </view> | 17 | </view> |
19 | <!-- 命令下发 设备在线并且不是网关子设备 --> | 18 | <!-- 命令下发 设备在线并且不是网关子设备 --> |
20 | - <view class="mr-2" v-if="deviceDetail.deviceState === 'ONLINE' && deviceDetail.transportType!==deviceTypeNum.GBT"> | 19 | + <view class="mr-2" v-if="deviceDetail.deviceState === 'ONLINE' && deviceDetail.transportType !== deviceTypeNum.GBT"> |
21 | <!-- #ifdef MP --> | 20 | <!-- #ifdef MP --> |
22 | - <u-button type="primary" shape="circle" size="mini" text="下发命令" @click="handleMpShowModal" /> | 21 | + <u-button type="primary" shape="circle" size="mini" text="命令下发" @click="handleMpShowModal" /> |
23 | <!-- #endif --> | 22 | <!-- #endif --> |
24 | <!-- #ifdef APP-PLUS --> | 23 | <!-- #ifdef APP-PLUS --> |
25 | <view class="cu-item" @tap="handleAppShowModal" data-target="Modal"> | 24 | <view class="cu-item" @tap="handleAppShowModal" data-target="Modal"> |
26 | - <text>下发命令</text> | 25 | + <text>命令下发</text> |
27 | </view> | 26 | </view> |
28 | <!-- #endif --> | 27 | <!-- #endif --> |
29 | </view> | 28 | </view> |
@@ -79,7 +78,7 @@ | @@ -79,7 +78,7 @@ | ||
79 | <radio :value="item.value" :checked="index === current" /> | 78 | <radio :value="item.value" :checked="index === current" /> |
80 | </view> | 79 | </view> |
81 | <view style="width:10rpx"></view> | 80 | <view style="width:10rpx"></view> |
82 | - <view class="ml-1">{{item.name}}</view> | 81 | + <view class="ml-1">{{ item.name }}</view> |
83 | </view> | 82 | </view> |
84 | </label> | 83 | </label> |
85 | </radio-group> | 84 | </radio-group> |
@@ -87,9 +86,9 @@ | @@ -87,9 +86,9 @@ | ||
87 | </view> | 86 | </view> |
88 | <view class="app-command-body"> | 87 | <view class="app-command-body"> |
89 | <textarea class="app-command-textarea" v-model="inputCommandContent" | 88 | <textarea class="app-command-textarea" v-model="inputCommandContent" |
90 | - :placeholder="`请输入下发内容${isShowTCP?'(字符串格式)':'(json格式)'}`" /> | ||
91 | - <u-icon @click="handleCopy(copyTextValue)" v-if="!isShowTCP" name="question-circle" | ||
92 | - color="#2979ff" size="28" class="ml-10"> | 89 | + :placeholder="`请输入下发内容${isShowTCP ? '(字符串格式)' : '(json格式)'}`" /> |
90 | + <u-icon @click="handleCopy(copyTextValue)" v-if="!isShowTCP" name="question-circle" color="#2979ff" | ||
91 | + size="28" class="ml-10"> | ||
93 | </u-icon> | 92 | </u-icon> |
94 | </view> | 93 | </view> |
95 | <view class="app-command-buttons"> | 94 | <view class="app-command-buttons"> |
@@ -104,315 +103,326 @@ | @@ -104,315 +103,326 @@ | ||
104 | <!-- #ifdef MP --> | 103 | <!-- #ifdef MP --> |
105 | <!-- u-modal在app端弹窗层级无法覆盖背景色 --> | 104 | <!-- u-modal在app端弹窗层级无法覆盖背景色 --> |
106 | <mp-command-issuance ref="mpCommandIssuanceRef" :isShowTCP="isShowTCP" :showModal="mpShowModal" | 105 | <mp-command-issuance ref="mpCommandIssuanceRef" :isShowTCP="isShowTCP" :showModal="mpShowModal" |
107 | - @hideModal="hideMpModal" @cancelCommand="cancelCommand" | 106 | + :deviceDetail="deviceDetail" @hideModal="hideMpModal" @cancelCommand="cancelCommand" |
108 | @confirmCommand="confirmCommand"></mp-command-issuance> | 107 | @confirmCommand="confirmCommand"></mp-command-issuance> |
109 | <!-- #endif --> | 108 | <!-- #endif --> |
110 | </view> | 109 | </view> |
111 | </template> | 110 | </template> |
112 | 111 | ||
113 | <script> | 112 | <script> |
114 | - import {formatToDate} from '@/plugins/utils.js'; | ||
115 | - import api from '@/api/index.js'; | ||
116 | - import mpCommandIssuance from './mp-command-issuance.vue'; | ||
117 | - import {commandTypeList} from '../config/data.js' | ||
118 | - import {useShowModal} from '@/plugins/utils.js' | ||
119 | - import {deviceTypeNum} from '../config/data' | ||
120 | - | ||
121 | - export default { | ||
122 | - components: { | ||
123 | - mpCommandIssuance, | ||
124 | - }, | ||
125 | - props: { | ||
126 | - deviceDetail: { | ||
127 | - type: Object, | ||
128 | - default: () => ({}) | 113 | +import { formatToDate } from '@/plugins/utils.js'; |
114 | +import api from '@/api/index.js'; | ||
115 | +import mpCommandIssuance from './mp-command-issuance.vue'; | ||
116 | +import { commandTypeList } from '../config/data.js' | ||
117 | +import { useShowModal } from '@/plugins/utils.js' | ||
118 | +import { deviceTypeNum } from '../config/data' | ||
119 | +import deviceDetail from '../device-detail.vue'; | ||
120 | + | ||
121 | +export default { | ||
122 | + components: { | ||
123 | + mpCommandIssuance, | ||
124 | + }, | ||
125 | + props: { | ||
126 | + deviceDetail: { | ||
127 | + type: Object, | ||
128 | + default: () => ({}) | ||
129 | + } | ||
130 | + }, | ||
131 | + data() { | ||
132 | + return { | ||
133 | + showNativeModal: false, | ||
134 | + current: 0, | ||
135 | + commandTypeList, | ||
136 | + deviceTypeNum: deviceTypeNum, | ||
137 | + commandTypeStr: 'OneWay', | ||
138 | + inputCommandContent: '', | ||
139 | + mpShowModal: false, | ||
140 | + commandValue: {}, | ||
141 | + isShowTCP: false, //用于下发命令时判断是否是TCP/UDP | ||
142 | + modalName: null, | ||
143 | + copyTextValue: { | ||
144 | + "method": "methodThingskit", | ||
145 | + "params": { | ||
146 | + "pin": 7, | ||
147 | + "value": 1 | ||
148 | + } | ||
129 | } | 149 | } |
150 | + }; | ||
151 | + }, | ||
152 | + computed: { | ||
153 | + deviceStatus() { | ||
154 | + return this.deviceDetail.deviceState === 'INACTIVE' ? '待激活' : this.deviceDetail.deviceState === 'ONLINE' ? | ||
155 | + '在线' : '离线'; | ||
130 | }, | 156 | }, |
131 | - data() { | ||
132 | - return { | ||
133 | - showNativeModal: false, | ||
134 | - current: 0, | ||
135 | - commandTypeList, | ||
136 | - deviceTypeNum:deviceTypeNum, | ||
137 | - commandTypeStr: 'OneWay', | ||
138 | - inputCommandContent: '', | ||
139 | - mpShowModal: false, | ||
140 | - commandValue: {}, | ||
141 | - isShowTCP: false, //用于下发命令时判断是否是TCP/UDP | ||
142 | - modalName: null, | ||
143 | - copyTextValue: { | ||
144 | - "method": "methodThingskit", | ||
145 | - "params": { | ||
146 | - "pin": 7, | ||
147 | - "value": 1 | 157 | + deviceType() { |
158 | + return this.deviceDetail.deviceType === 'DIRECT_CONNECTION' ? | ||
159 | + '直连设备' : | ||
160 | + this.deviceDetail.deviceType === 'GATEWAY' ? | ||
161 | + '网关设备' : | ||
162 | + this.deviceDetail.deviceType === 'SENSOR' ? | ||
163 | + '网关子设备' : | ||
164 | + ''; | ||
165 | + }, | ||
166 | + alarmStatus() { | ||
167 | + return this.deviceDetail.alarmStatus === '0' ? '否' : '是'; | ||
168 | + }, | ||
169 | + formatLastOnlineTime() { | ||
170 | + return formatToDate(Number(this.deviceDetail.lastOnlineTime), 'YYYY-MM-DD HH:mm:ss'); | ||
171 | + } | ||
172 | + }, | ||
173 | + beforeCreate() { | ||
174 | + this.modalName = null | ||
175 | + }, | ||
176 | + onLoad() { | ||
177 | + // 隐藏原生的tabbar | ||
178 | + uni.hideTabBar(); | ||
179 | + this.modalName = null | ||
180 | + }, | ||
181 | + methods: { | ||
182 | + handleCopy(value) { | ||
183 | + useShowModal(JSON.stringify(value), '命令下发', '复制内容').then(res => { | ||
184 | + uni.setClipboardData({ | ||
185 | + data: JSON.stringify(value), | ||
186 | + success: () => { | ||
187 | + uni.showToast({ | ||
188 | + title: '复制成功' | ||
189 | + }) | ||
148 | } | 190 | } |
191 | + }); | ||
192 | + }) | ||
193 | + }, | ||
194 | + radioChange: function (evt) { | ||
195 | + for (let i = 0; i < this.commandTypeList.length; i++) { | ||
196 | + if (this.items[i].value === evt.detail.value) { | ||
197 | + this.current = i; | ||
198 | + break; | ||
149 | } | 199 | } |
200 | + } | ||
201 | + this.commandTypeStr = evt.detail.value | ||
202 | + }, | ||
203 | + formatTextStatus(deviceState) { | ||
204 | + return deviceState === 'INACTIVE' ? '#666' : deviceState === 'ONLINE' ? '#377DFF' : '#DE4437'; | ||
205 | + }, | ||
206 | + handleClick() { | ||
207 | + const data = { | ||
208 | + longitude: this.deviceDetail.deviceInfo.longitude || 0, | ||
209 | + latitude: this.deviceDetail.deviceInfo.latitude || 0 | ||
150 | }; | 210 | }; |
211 | + uni.navigateTo({ | ||
212 | + url: '/device-subpackage/device-detail/device-position?data=' + JSON.stringify(data) | ||
213 | + }); | ||
151 | }, | 214 | }, |
152 | - computed: { | ||
153 | - deviceStatus() { | ||
154 | - return this.deviceDetail.deviceState === 'INACTIVE' ? '待激活' : this.deviceDetail.deviceState === 'ONLINE' ? | ||
155 | - '在线' : '离线'; | ||
156 | - }, | ||
157 | - deviceType() { | ||
158 | - return this.deviceDetail.deviceType === 'DIRECT_CONNECTION' ? | ||
159 | - '直连设备' : | ||
160 | - this.deviceDetail.deviceType === 'GATEWAY' ? | ||
161 | - '网关设备' : | ||
162 | - this.deviceDetail.deviceType === 'SENSOR' ? | ||
163 | - '网关子设备' : | ||
164 | - ''; | ||
165 | - }, | ||
166 | - alarmStatus() { | ||
167 | - return this.deviceDetail.alarmStatus === '0' ? '否' : '是'; | ||
168 | - }, | ||
169 | - formatLastOnlineTime() { | ||
170 | - return formatToDate(Number(this.deviceDetail.lastOnlineTime), 'YYYY-MM-DD HH:mm:ss'); | ||
171 | - } | 215 | + disabledScroll() { |
216 | + return; | ||
172 | }, | 217 | }, |
173 | - beforeCreate() { | ||
174 | - this.modalName = null | 218 | + handleAppShowModal(e) { |
219 | + this.modalName = e.currentTarget.dataset.target; | ||
220 | + this.showNativeModal = true | ||
175 | }, | 221 | }, |
176 | - onLoad() { | ||
177 | - // 隐藏原生的tabbar | ||
178 | - uni.hideTabBar(); | ||
179 | - this.modalName = null | 222 | + handleMpShowModal() { |
223 | + const { | ||
224 | + transportType | ||
225 | + } = this.deviceDetail.deviceProfile; | ||
226 | + this.isShowTCP = transportType == 'TCP' ? true : false; | ||
227 | + this.mpShowModal = true; | ||
180 | }, | 228 | }, |
181 | - methods: { | ||
182 | - handleCopy(value) { | ||
183 | - useShowModal(JSON.stringify(value), '命令下发', '复制内容').then(res => { | ||
184 | - uni.setClipboardData({ | ||
185 | - data: JSON.stringify(value), | ||
186 | - success: () => { | ||
187 | - uni.showToast({ | ||
188 | - title: '复制成功' | ||
189 | - }) | ||
190 | - } | ||
191 | - }); | ||
192 | - }) | ||
193 | - }, | ||
194 | - radioChange: function(evt) { | ||
195 | - for (let i = 0; i < this.commandTypeList.length; i++) { | ||
196 | - if (this.items[i].value === evt.detail.value) { | ||
197 | - this.current = i; | ||
198 | - break; | ||
199 | - } | 229 | + hideMpModal() { |
230 | + this.mpShowModal = false; | ||
231 | + }, | ||
232 | + hideAppModal() { | ||
233 | + this.modalName = null; | ||
234 | + this.showNativeModal = false | ||
235 | + }, | ||
236 | + confirmCommand(commandType, callType, values) { | ||
237 | + this.handleCommand(commandType, callType, values) | ||
238 | + }, | ||
239 | + cancelCommand() { | ||
240 | + this.hideMpModal(); | ||
241 | + this.hideAppModal(); | ||
242 | + this.commandTypeStr = 'OneWay' | ||
243 | + this.inputCommandContent = '' | ||
244 | + this.$nextTick(() => { | ||
245 | + this.$refs.mpCommandIssuanceRef.reset() | ||
246 | + }) | ||
247 | + }, | ||
248 | + handleAppCommand() { | ||
249 | + this.handleCommand(this.commandTypeStr, this.inputCommandContent) | ||
250 | + }, | ||
251 | + async handleCommand(commandType, callType, values) { | ||
252 | + if (!values) return uni.$u.toast('请输入下发内容~'); | ||
253 | + if(callType=='TwoWay'){ | ||
254 | + const result = await api.deviceApi.getDeviceActiveTime(this.deviceDetail.tbDeviceId) | ||
255 | + const [firsetItem] = result || [] | ||
256 | + if (!firsetItem.value) { | ||
257 | + return uni.$u.toast('当前设备不在线~') | ||
200 | } | 258 | } |
201 | - this.commandTypeStr = evt.detail.value | ||
202 | - }, | ||
203 | - formatTextStatus(deviceState) { | ||
204 | - return deviceState === 'INACTIVE' ? '#666' : deviceState === 'ONLINE' ? '#377DFF' : '#DE4437'; | ||
205 | - }, | ||
206 | - handleClick() { | ||
207 | - const data = { | ||
208 | - longitude: this.deviceDetail.deviceInfo.longitude || 0, | ||
209 | - latitude: this.deviceDetail.deviceInfo.latitude || 0 | ||
210 | - }; | ||
211 | - uni.navigateTo({ | ||
212 | - url: '/device-subpackage/device-detail/device-position?data=' + JSON.stringify(data) | ||
213 | - }); | ||
214 | - }, | ||
215 | - disabledScroll() { | ||
216 | - return; | ||
217 | - }, | ||
218 | - handleAppShowModal(e) { | ||
219 | - this.modalName = e.currentTarget.dataset.target; | ||
220 | - this.showNativeModal = true | ||
221 | - }, | ||
222 | - handleMpShowModal() { | ||
223 | - const { | ||
224 | - transportType | ||
225 | - } = this.deviceDetail.deviceProfile; | ||
226 | - this.isShowTCP = transportType == 'TCP' ? true : false; | ||
227 | - this.mpShowModal = true; | ||
228 | - }, | ||
229 | - hideMpModal() { | ||
230 | - this.mpShowModal = false; | ||
231 | - }, | ||
232 | - hideAppModal() { | ||
233 | - this.modalName = null; | ||
234 | - this.showNativeModal = false | ||
235 | - }, | ||
236 | - confirmCommand(commandType, inputCommandVal) { | ||
237 | - this.handleCommand(commandType, inputCommandVal) | ||
238 | - }, | ||
239 | - cancelCommand() { | ||
240 | - this.hideMpModal(); | ||
241 | - this.hideAppModal(); | ||
242 | - this.commandTypeStr = 'OneWay' | ||
243 | - this.inputCommandContent = '' | ||
244 | - this.$nextTick(() => { | ||
245 | - this.$refs.mpCommandIssuanceRef.reset() | ||
246 | - }) | ||
247 | - }, | ||
248 | - handleAppCommand() { | ||
249 | - this.handleCommand(this.commandTypeStr, this.inputCommandContent) | ||
250 | - }, | ||
251 | - async handleCommand(commandType, inputCommandVal) { | ||
252 | - if (!inputCommandVal) return uni.$u.toast('请输入下发内容~'); | 259 | + } |
260 | + | ||
261 | + this.commandValue.persistent = true; | ||
262 | + this.commandValue.additionalInfo = { | ||
263 | + cmdType: commandType == 0 ? 0 : 1 | ||
264 | + }; | ||
265 | + this.commandValue.method = 'methodThingskit'; | ||
266 | + this.commandValue.params = values; | ||
267 | + if (commandType == 0) {//下发类型是自定义时 | ||
253 | if (this.isShowTCP) { | 268 | if (this.isShowTCP) { |
254 | //TCP的格式只能是字符串 | 269 | //TCP的格式只能是字符串 |
255 | const zg = /^[0-9a-zA-Z]*$/; | 270 | const zg = /^[0-9a-zA-Z]*$/; |
256 | - if (!zg.test(inputCommandVal)) { | 271 | + if (!zg.test(values)) { |
257 | uni.$u.toast('输入的内容只能是字母和数字的组合'); | 272 | uni.$u.toast('输入的内容只能是字母和数字的组合'); |
258 | return; | 273 | return; |
259 | } | 274 | } |
260 | - this.commandValue.params = inputCommandVal; | 275 | + this.commandValue.params = values; |
261 | } else { | 276 | } else { |
262 | - this.commandValue.params = JSON.parse(inputCommandVal); | 277 | + this.commandValue.params = JSON.parse(values); |
263 | } | 278 | } |
264 | - this.commandValue.persistent = true; | ||
265 | - this.commandValue.additionalInfo = { | ||
266 | - cmdType: 'API' | ||
267 | - }; | ||
268 | - this.commandValue.method = 'methodThingskit'; | ||
269 | - this.commandValue.params = inputCommandVal; | ||
270 | - await api.deviceApi.issueCommand(commandType, this.deviceDetail.tbDeviceId, this.commandValue); | ||
271 | - this.cancelCommand(); | ||
272 | - uni.$u.toast('下发成功~'); | ||
273 | - | ||
274 | - }, | 279 | + } |
280 | + | ||
281 | + await api.deviceApi.issueCommand(callType, this.deviceDetail.tbDeviceId, this.commandValue); | ||
282 | + this.cancelCommand(); | ||
283 | + uni.$u.toast('下发成功~'); | ||
284 | + }, | ||
275 | 285 | ||
276 | - } | ||
277 | - }; | 286 | + } |
287 | +}; | ||
278 | </script> | 288 | </script> |
279 | 289 | ||
280 | <style lang="scss" scoped> | 290 | <style lang="scss" scoped> |
281 | - @import url('../static/modal.css'); | 291 | +@import url('../static/modal.css'); |
282 | 292 | ||
283 | - .app-command-content { | ||
284 | - .app-command-text { | ||
285 | - display: flex; | ||
286 | - justify-content: center; | ||
287 | - align-items: center; | 293 | +.app-command-content { |
294 | + .app-command-text { | ||
295 | + display: flex; | ||
296 | + justify-content: center; | ||
297 | + align-items: center; | ||
288 | 298 | ||
289 | - text { | ||
290 | - font-weight: 700; | ||
291 | - } | 299 | + text { |
300 | + font-weight: 700; | ||
292 | } | 301 | } |
302 | + } | ||
293 | 303 | ||
294 | - .app-command-type { | ||
295 | - display: flex; | ||
296 | - margin-top: 20rpx; | ||
297 | - margin-left: 20rpx; | 304 | + .app-command-type { |
305 | + display: flex; | ||
306 | + margin-top: 20rpx; | ||
307 | + margin-left: 20rpx; | ||
298 | 308 | ||
299 | - text { | ||
300 | - font-weight: 700; | ||
301 | - } | 309 | + text { |
310 | + font-weight: 700; | ||
302 | } | 311 | } |
312 | + } | ||
303 | 313 | ||
304 | - .app-command-body { | ||
305 | - display: flex; | ||
306 | - align-items: center; | ||
307 | - justify-content: space-between; | ||
308 | - margin-top: 20rpx; | ||
309 | - margin-left: 20rpx; | 314 | + .app-command-body { |
315 | + display: flex; | ||
316 | + align-items: center; | ||
317 | + justify-content: space-between; | ||
318 | + margin-top: 20rpx; | ||
319 | + margin-left: 20rpx; | ||
310 | 320 | ||
311 | - .app-command-textarea { | ||
312 | - width: 625rpx; | ||
313 | - height: 400rpx; | ||
314 | - background: #FFFFFF; | ||
315 | - box-shadow: 2px 2px 4px 0px rgba(0, 0, 0, 0.03); | ||
316 | - border-radius: 10px; | ||
317 | - } | 321 | + .app-command-textarea { |
322 | + width: 625rpx; | ||
323 | + height: 400rpx; | ||
324 | + background: #FFFFFF; | ||
325 | + box-shadow: 2px 2px 4px 0px rgba(0, 0, 0, 0.03); | ||
326 | + border-radius: 10px; | ||
318 | } | 327 | } |
328 | + } | ||
319 | 329 | ||
320 | - .app-command-buttons { | ||
321 | - display: flex; | ||
322 | - align-items: center; | ||
323 | - justify-content: space-evenly; | ||
324 | - height: 85rpx; | ||
325 | - margin-top: 20rpx; | ||
326 | - margin-bottom: 20rpx; | 330 | + .app-command-buttons { |
331 | + display: flex; | ||
332 | + align-items: center; | ||
333 | + justify-content: space-evenly; | ||
334 | + height: 85rpx; | ||
335 | + margin-top: 20rpx; | ||
336 | + margin-bottom: 20rpx; | ||
327 | 337 | ||
328 | - .cancel-button { | ||
329 | - width: 300rpx; | ||
330 | - background: #e3e3e5; | ||
331 | - height: 85rpx; | ||
332 | - border-radius: 38rpx; | ||
333 | - line-height: 85rpx; | 338 | + .cancel-button { |
339 | + width: 300rpx; | ||
340 | + background: #e3e3e5; | ||
341 | + height: 85rpx; | ||
342 | + border-radius: 38rpx; | ||
343 | + line-height: 85rpx; | ||
334 | 344 | ||
335 | - .cancel-text { | ||
336 | - color: #333333 | ||
337 | - } | 345 | + .cancel-text { |
346 | + color: #333333 | ||
338 | } | 347 | } |
348 | + } | ||
339 | 349 | ||
340 | - .confrim-button { | ||
341 | - width: 300rpx; | ||
342 | - background: #3388ff; | ||
343 | - border-radius: 38rpx; | ||
344 | - height: 85rpx; | ||
345 | - line-height: 85rpx; | 350 | + .confrim-button { |
351 | + width: 300rpx; | ||
352 | + background: #3388ff; | ||
353 | + border-radius: 38rpx; | ||
354 | + height: 85rpx; | ||
355 | + line-height: 85rpx; | ||
346 | 356 | ||
347 | - .confrim-text { | ||
348 | - color: white | ||
349 | - } | 357 | + .confrim-text { |
358 | + color: white | ||
350 | } | 359 | } |
351 | } | 360 | } |
352 | } | 361 | } |
362 | +} | ||
353 | 363 | ||
354 | - .basic-page { | ||
355 | - padding: 0 30rpx; | 364 | +.basic-page { |
365 | + padding: 0 30rpx; | ||
356 | 366 | ||
357 | - .basic-header { | ||
358 | - display: flex; | ||
359 | - justify-content: space-between; | ||
360 | - align-items: center; | ||
361 | - height: 140rpx; | ||
362 | - background-color: #fff; | ||
363 | - border-radius: 20rpx; | 367 | + .basic-header { |
368 | + display: flex; | ||
369 | + justify-content: space-between; | ||
370 | + align-items: center; | ||
371 | + height: 140rpx; | ||
372 | + background-color: #fff; | ||
373 | + border-radius: 20rpx; | ||
364 | 374 | ||
365 | - .basic-text { | ||
366 | - width: 370rpx; | ||
367 | - } | 375 | + .basic-text { |
376 | + width: 370rpx; | ||
377 | + } | ||
368 | 378 | ||
369 | - .cu-item { | ||
370 | - background: #3388ff; | ||
371 | - border-radius: 12px; | ||
372 | - width: 120rpx; | ||
373 | - height: 48rpx; | ||
374 | - text-align: center; | ||
375 | - line-height: 40rpx; | 379 | + .cu-item { |
380 | + background: #3388ff; | ||
381 | + border-radius: 12px; | ||
382 | + width: 120rpx; | ||
383 | + height: 48rpx; | ||
384 | + text-align: center; | ||
385 | + line-height: 40rpx; | ||
376 | 386 | ||
377 | - text { | ||
378 | - font-size: 12px; | ||
379 | - font-family: PingFangSC-Regular, PingFang SC; | ||
380 | - font-weight: 400; | ||
381 | - color: #ffffff; | ||
382 | - } | 387 | + text { |
388 | + font-size: 12px; | ||
389 | + font-family: PingFangSC-Regular, PingFang SC; | ||
390 | + font-weight: 400; | ||
391 | + color: #ffffff; | ||
383 | } | 392 | } |
393 | + } | ||
384 | 394 | ||
385 | - .basic-text-status { | ||
386 | - font-size: 14px; | ||
387 | - } | 395 | + .basic-text-status { |
396 | + font-size: 14px; | ||
388 | } | 397 | } |
398 | + } | ||
389 | 399 | ||
390 | - .detail { | ||
391 | - background-color: #fff; | ||
392 | - margin-top: 30rpx; | ||
393 | - border-radius: 20rpx; | ||
394 | - width: 690rpx; | 400 | + .detail { |
401 | + background-color: #fff; | ||
402 | + margin-top: 30rpx; | ||
403 | + border-radius: 20rpx; | ||
404 | + width: 690rpx; | ||
395 | 405 | ||
396 | - .detail-item { | ||
397 | - padding: 30rpx; | ||
398 | - display: flex; | ||
399 | - align-items: center; | 406 | + .detail-item { |
407 | + padding: 30rpx; | ||
408 | + display: flex; | ||
409 | + align-items: center; | ||
400 | 410 | ||
401 | - .detail-label { | ||
402 | - color: #333; | ||
403 | - font-size: 15px; | ||
404 | - } | 411 | + .detail-label { |
412 | + color: #333; | ||
413 | + font-size: 15px; | ||
414 | + } | ||
405 | 415 | ||
406 | - .detail-value { | ||
407 | - color: #666; | ||
408 | - font-size: 14px; | ||
409 | - margin-left: 30rpx; | ||
410 | - } | 416 | + .detail-value { |
417 | + color: #666; | ||
418 | + font-size: 14px; | ||
419 | + margin-left: 30rpx; | ||
411 | } | 420 | } |
412 | } | 421 | } |
413 | } | 422 | } |
423 | +} | ||
414 | 424 | ||
415 | - /deep/ .u-modal__content { | ||
416 | - padding: 30rpx 0 !important; | ||
417 | - } | 425 | +/deep/ .u-modal__content { |
426 | + padding: 30rpx 0 !important; | ||
427 | +} | ||
418 | </style> | 428 | </style> |
1 | <template> | 1 | <template> |
2 | - <view class="mp-u-modal"> | ||
3 | - <u-modal :mask-close-able="true" :show="showModal" closeOnClickOverlay :showConfirmButton="false" | ||
4 | - @close="$emit('hideModal')" @touchmove.stop.prevent="disabledScroll" z-index="99999"> | ||
5 | - <view class="w-100 modal-content"> | ||
6 | - <view class="header-title">命令下发</view> | ||
7 | - <view class="u-flex"> | ||
8 | - <text class="type-text">下发类型:</text> | ||
9 | - <u-radio-group v-model="commandType" placement="row"> | ||
10 | - <u-radio activeColor="#3388FF" label="单向" name="OneWay"></u-radio> | ||
11 | - <view style="margin: 0 20rpx;"></view> | ||
12 | - <u-radio activeColor="#3388FF" label="双向" name="TwoWay"></u-radio> | ||
13 | - </u-radio-group> | ||
14 | - </view> | ||
15 | - <view class="content-body"> | ||
16 | - <div class="u-flex u-row-between"> | ||
17 | - <u--textarea :placeholder="`请输入下发内容${isShowTCP?'(字符串格式)':'(json格式)'}`" | ||
18 | - v-model="inputCommandVal" /> | ||
19 | - <u-icon v-if="!isShowTCP" @click="handleCopy(copyTextValue)" name="question-circle" | ||
20 | - color="#2979ff" size="28" class="ml-10"> | ||
21 | - </u-icon> | ||
22 | - </div> | ||
23 | - </view> | ||
24 | - <view class="button-group"> | ||
25 | - <view> | ||
26 | - <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="取消" | ||
27 | - @click="cancelCommand"></u-button> | ||
28 | - </view> | ||
29 | - <view> | ||
30 | - <u-button color="#3388ff" shape="circle" text="确认" @click="confirmCommand"></u-button> | ||
31 | - </view> | ||
32 | - </view> | ||
33 | - </view> | ||
34 | - </u-modal> | ||
35 | - </view> | 2 | + <view class="mp-u-modal"> |
3 | + <u-modal :mask-close-able="true" :show="showModal" closeOnClickOverlay :showConfirmButton="false" @close="$emit('hideModal')" z-index="99999"> | ||
4 | + <view class="w-100 modal-content"> | ||
5 | + <view style="max-height:560rpx;overflow-y: scroll;"> | ||
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" @change="handleCommand"> | ||
10 | + <u-radio :customStyle="{marginRight: '20rpx'}" v-for="item in commandTypeList" activeColor="#3388FF" :label="item.label" :name="item.value" :key="item.value"></u-radio> | ||
11 | + </u-radio-group> | ||
12 | + </view> | ||
13 | + <view class="u-flex" style="margin-top: 28rpx" v-if="commandType == 0"> | ||
14 | + <text class="type-text">单向/双向:</text> | ||
15 | + <u-radio-group v-model="callType" placement="row"> | ||
16 | + <u-radio activeColor="#3388FF" label="单向" name="OneWay"></u-radio> | ||
17 | + <view style="margin: 0 20rpx"></view> | ||
18 | + <u-radio activeColor="#3388FF" label="双向" name="TwoWay"></u-radio> | ||
19 | + </u-radio-group> | ||
20 | + </view> | ||
21 | + <view class="u-flex" style="margin-top: 28rpx" v-else> | ||
22 | + <text class="type-text">服务:</text> | ||
23 | + <view @click="openService"> | ||
24 | + <u-input shape="circle" v-model="serviceName" placeholder="请选择服务" disabled disabledColor="#fff" suffixIcon="arrow-down" /> | ||
25 | + </view> | ||
26 | + </view> | ||
27 | + <view class="u-flex" style="margin-top: 28rpx; flex-direction: column; align-items: flex-start" v-if="isShowServiceFunctionName && commandType == 1"> | ||
28 | + <text class="type-text">输入参数:</text> | ||
29 | + <seriesForm ref="seriesFormRef" :seriesInputData="seriesInputData" :isTCPTransport="isTCPTransport"></seriesForm> | ||
30 | + </view> | ||
31 | + <view class="content-body" v-if="commandType == 0"> | ||
32 | + <div class="u-flex u-row-between"> | ||
33 | + <u--textarea :placeholder="`请输入下发内容${isShowTCP ? '(字符串格式)' : '(json格式)'}`" v-model="inputCommandVal" /> | ||
34 | + <u-icon v-if="!isShowTCP" @click="handleCopy(copyTextValue)" name="question-circle" color="#2979ff" size="28" class="ml-10"> </u-icon> | ||
35 | + </div> | ||
36 | + </view> | ||
37 | + </view> | ||
38 | + <view class="button-group"> | ||
39 | + <view> | ||
40 | + <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="取消" @click="cancelCommand"></u-button> | ||
41 | + </view> | ||
42 | + <view> | ||
43 | + <u-button color="#3388ff" shape="circle" text="确认" @click="confirmCommand"></u-button> | ||
44 | + </view> | ||
45 | + </view> | ||
46 | + </view> | ||
47 | + <u-picker | ||
48 | + :show="isShowService" | ||
49 | + :columns="[seriesList.map((item) => ({ label: item.functionName, value: item.identifier,callType:item.callType }))]" | ||
50 | + keyName="label" | ||
51 | + closeOnClickOverlay | ||
52 | + @cancel="cancelTypeGap" | ||
53 | + @close="cancelTypeGap" | ||
54 | + @confirm="handleSelect" | ||
55 | + ></u-picker> | ||
56 | + </u-modal> | ||
57 | + </view> | ||
36 | </template> | 58 | </template> |
37 | 59 | ||
38 | <script> | 60 | <script> |
39 | - import { | ||
40 | - useShowModal | ||
41 | - } from '@/plugins/utils.js' | 61 | +import { useShowModal } from '@/plugins/utils.js' |
62 | +import seriesForm from './seriesForm.vue' | ||
42 | 63 | ||
43 | - export default { | ||
44 | - props: { | ||
45 | - showModal: Boolean, | ||
46 | - isShowTCP: Boolean | ||
47 | - }, | ||
48 | - data() { | ||
49 | - return { | ||
50 | - current: 0, | ||
51 | - commandType: 'OneWay', | ||
52 | - inputCommandVal: '', | ||
53 | - copyTextValue: { | ||
54 | - "method": "methodThingskit", | ||
55 | - "params": { | ||
56 | - "pin": 7, | ||
57 | - "value": 1 | ||
58 | - } | 64 | +import api from '@/api/index.js' |
65 | + | ||
66 | +export default { | ||
67 | + components: { | ||
68 | + seriesForm, | ||
69 | + }, | ||
70 | + props: { | ||
71 | + showModal: Boolean, | ||
72 | + isShowTCP: Boolean, | ||
73 | + deviceDetail: Object, | ||
74 | + }, | ||
75 | + data() { | ||
76 | + return { | ||
77 | + current: 0, | ||
78 | + commandType: 0, //下发类型 | ||
79 | + callType: 'OneWay', //单双向 | ||
80 | + serviceName: '', //服务 | ||
81 | + service: null, //服务 | ||
82 | + inputCommandVal: '',//自定义命令 | ||
83 | + isShowService: false, //服务下拉框 | ||
84 | + isShowServiceFunctionName: false, //选择服务过后的输入参数 | ||
85 | + copyTextValue: { | ||
86 | + method: 'methodThingskit', | ||
87 | + params: { | ||
88 | + pin: 7, | ||
89 | + value: 1, | ||
90 | + }, | ||
91 | + }, | ||
92 | + commandTypeList: [{ label: '自定义', value: 0 }], | ||
93 | + seriesList: [], //服务下拉框的数据 | ||
94 | + boolList: [], | ||
95 | + enumList: [], | ||
96 | + seriesInputData: [], | ||
97 | + isTCPTransport:false | ||
98 | + } | ||
99 | + }, | ||
100 | + mounted() { | ||
101 | + this.getFormInfo() | ||
102 | + }, | ||
103 | + watch: { | ||
104 | + showModal: { | ||
105 | + deep: true, | ||
106 | + handler() { | ||
107 | + this.commandType = 0 | ||
108 | + this.serviceName = '' | ||
109 | + this.isShowServiceFunctionName = false | ||
110 | + }, | ||
111 | + }, | ||
112 | + }, | ||
113 | + methods: { | ||
114 | + cancelCommand() { | ||
115 | + this.commandType = 0 | ||
116 | + this.$emit('cancelCommand') | ||
117 | + }, | ||
118 | + async confirmCommand() { | ||
119 | + if(this.commandType==0){ | ||
120 | + this.$emit('confirmCommand',this.commandType, this.callType, this.inputCommandVal) | ||
121 | + }else{ | ||
122 | + const result = this.$refs.seriesFormRef.handleValidate() | ||
123 | + if(!result){ | ||
124 | + return | ||
125 | + } | ||
126 | + const value = this.$refs.seriesFormRef.getFormField() | ||
127 | + const values =this.isTCPTransport?value.serviceCommand: { | ||
128 | + [this.service]:value | ||
59 | } | 129 | } |
130 | + this.$emit('confirmCommand',this.commandType, this.callType, values) | ||
60 | } | 131 | } |
61 | - }, | ||
62 | - methods: { | ||
63 | - cancelCommand() { | ||
64 | - this.$emit('cancelCommand') | ||
65 | - }, | ||
66 | - confirmCommand() { | ||
67 | - this.$emit('confirmCommand', this.commandType, this.inputCommandVal) | ||
68 | - }, | ||
69 | - handleCopy(value) { | ||
70 | - useShowModal(JSON.stringify(value), '命令下发', '复制内容').then(res => { | ||
71 | - uni.setClipboardData({ | ||
72 | - data: JSON.stringify(value), | ||
73 | - success: () => { | ||
74 | - uni.showToast({ | ||
75 | - title: '复制成功' | ||
76 | - }) | ||
77 | - } | ||
78 | - }); | ||
79 | - }) | ||
80 | - }, | ||
81 | - reset() { | ||
82 | - this.commandType = 'OneWay' | ||
83 | - this.inputCommandVal = '' | 132 | + }, |
133 | + handleCopy(value) { | ||
134 | + useShowModal(JSON.stringify(value), '命令下发', '复制内容').then((res) => { | ||
135 | + uni.setClipboardData({ | ||
136 | + data: JSON.stringify(value), | ||
137 | + success: () => { | ||
138 | + uni.showToast({ | ||
139 | + title: '复制成功', | ||
140 | + }) | ||
141 | + }, | ||
142 | + }) | ||
143 | + }) | ||
144 | + }, | ||
145 | + | ||
146 | + async getFormInfo() { | ||
147 | + const { transportType, deviceType, deviceProfile } = this.deviceDetail || {} | ||
148 | + const { | ||
149 | + profileData: { | ||
150 | + transportConfiguration: { protocol }, | ||
151 | + }, | ||
152 | + } = deviceProfile || {} | ||
153 | + this.isTCPTransport = transportType === 'TCP' | ||
154 | + const isTCPModbus = this.isTCPTransport && protocol === 'MODBUS_RTU' | ||
155 | + if (isTCPModbus || (this.isTCPTransport && deviceType === 'SENSOR')) { | ||
156 | + this.commandTypeList = this.commandTypeList.length==2?this.commandTypeList.pop():this.commandTypeList | ||
157 | + } else { | ||
158 | + this.commandTypeList.push({ label: '服务', value: 1 }) | ||
159 | + this.seriesList = await api.deviceApi.getModelServices(this.deviceDetail) | ||
160 | + } | ||
161 | + }, | ||
162 | + | ||
163 | + openService() { | ||
164 | + this.isShowService = true | ||
165 | + }, | ||
166 | + handleSelect(e) { | ||
167 | + this.isShowService = false | ||
168 | + const { value } = e || {} | ||
169 | + this.serviceName = value[0].label | ||
170 | + this.service = value[0].value | ||
171 | + if (this.service) { | ||
172 | + this.isShowServiceFunctionName = true | ||
173 | + const { functionJson } = this.seriesList.filter((item) => item.identifier === this.service)[0] || {} | ||
174 | + const { inputData } = functionJson || {} | ||
175 | + this.seriesInputData = inputData || [] | ||
176 | + this.callType = value[0].callType==='ASYNC'?'OneWay':'TwoWay' | ||
177 | + } | ||
178 | + }, | ||
179 | + | ||
180 | + cancelModel() { | ||
181 | + this.isShowService = false | ||
182 | + this.seriesInputData = [] | ||
183 | + this.seriesList = [] | ||
184 | + }, | ||
185 | + | ||
186 | + handleCommand(name){ | ||
187 | + this.seriesInputData = [] | ||
188 | + this.serviceName = '' | ||
189 | + this.isShowServiceFunctionName = false | ||
190 | + if(this.commandType==0){ | ||
191 | + this.callType = 'OneWay' | ||
84 | } | 192 | } |
85 | - } | ||
86 | - } | 193 | + }, |
194 | + | ||
195 | + cancelTypeGap() { | ||
196 | + this.isShowService = false | ||
197 | + }, | ||
198 | + | ||
199 | + reset() { | ||
200 | + this.callType = 'OneWay' | ||
201 | + this.inputCommandVal = '' | ||
202 | + }, | ||
203 | + }, | ||
204 | +} | ||
87 | </script> | 205 | </script> |
88 | 206 | ||
89 | <style lang="scss" scoped> | 207 | <style lang="scss" scoped> |
90 | - .modal-content { | ||
91 | - width: 720rpx; | ||
92 | - padding: 0 30rpx; | ||
93 | - background-color: white; | 208 | +.modal-content { |
209 | + width: 720rpx; | ||
210 | + padding: 0 30rpx; | ||
211 | + background-color: white; | ||
94 | 212 | ||
95 | - .header-title { | ||
96 | - text-align: center; | ||
97 | - font-weight: 700; | ||
98 | - margin-bottom: 40rpx; | ||
99 | - } | 213 | + .header-title { |
214 | + text-align: center; | ||
215 | + font-weight: 700; | ||
216 | + margin-bottom: 40rpx; | ||
217 | + } | ||
100 | 218 | ||
101 | - .type-text { | ||
102 | - color: #333; | ||
103 | - font-size: 14px; | ||
104 | - font-weight: 700; | ||
105 | - margin-right: 30rpx; | ||
106 | - } | 219 | + .type-text { |
220 | + color: #333; | ||
221 | + font-size: 14px; | ||
222 | + font-weight: 700; | ||
223 | + margin-right: 30rpx; | ||
224 | + } | ||
107 | 225 | ||
108 | - .content-body { | ||
109 | - margin-top: 28rpx; | ||
110 | - width: 100%; | ||
111 | - } | 226 | + .content-body { |
227 | + margin-top: 28rpx; | ||
228 | + width: 100%; | ||
229 | + } | ||
112 | 230 | ||
113 | - .button-group { | ||
114 | - display: flex; | ||
115 | - margin-top: 40rpx; | ||
116 | - justify-content: space-between; | 231 | + .button-group { |
232 | + display: flex; | ||
233 | + margin-top: 40rpx; | ||
234 | + justify-content: space-between; | ||
117 | 235 | ||
118 | - view { | ||
119 | - width: 300rpx; | ||
120 | - } | ||
121 | - } | ||
122 | - } | ||
123 | -</style> | ||
236 | + view { | ||
237 | + width: 300rpx; | ||
238 | + } | ||
239 | + } | ||
240 | +} | ||
241 | +</style> |
1 | +<template> | ||
2 | + <view> | ||
3 | + <!-- :label-style="{'width':'120rpx','overflow':'hidden','text-overflow':'ellipsis','white-space':'nowrap',display:'block'}" label-width="120rpx" --> | ||
4 | + <u-form :model="seriesForm" ref="seriesRef"> | ||
5 | + <u-form-item style="display: flex" v-for="(item, index) in seriesFunctionList" label-position="left" :key="item.identifier" :prop="item.identifier"> | ||
6 | + <view v-if="!isTCPTransport"" :class="Array.isArray(item.dataType.specs)?'positionTop':'positionLeft'"> | ||
7 | + <view class="text">{{ item.functionName }}</view> | ||
8 | + <u-input | ||
9 | + v-if="item.dataType.type == 'INT' || item.dataType.type == 'DOUBLE'" | ||
10 | + shape="circle" | ||
11 | + type="number" | ||
12 | + v-model.number="seriesForm[item.identifier]" | ||
13 | + :placeholder="`请输入${item.functionName}`" | ||
14 | + @blur="(e)=>handleBlur(e,item.identifier)" | ||
15 | + /> | ||
16 | + <u-input v-if="item.dataType.type == 'TEXT'" shape="circle" type="text" v-model="seriesForm[item.identifier]" :placeholder="`请输入${item.functionName}`" /> | ||
17 | + <view v-if="item.dataType.type == 'BOOL'" @click="handleBool(item.functionName, item.dataType, index)"> | ||
18 | + <u-input | ||
19 | + shape="circle" | ||
20 | + v-model="seriesForm[item.identifier]" | ||
21 | + :placeholder="`请选择${item.functionName}`" | ||
22 | + disabled | ||
23 | + disabledColor="#fff" | ||
24 | + suffixIcon="arrow-down" | ||
25 | + /> | ||
26 | + <u-picker | ||
27 | + :show="item.isShowModel" | ||
28 | + :columns="[boolList]" | ||
29 | + keyName="label" | ||
30 | + @cancel="handleCancelBool(index)" | ||
31 | + @close="handleCancelBool(index)" | ||
32 | + @confirm="(e) => handleSelectBool(e, item.identifier, index)" | ||
33 | + ></u-picker> | ||
34 | + </view> | ||
35 | + <view v-if="item.dataType.type == 'ENUM'" @click="handleEnum(item.functionName, item.dataType, index)"> | ||
36 | + <u-input | ||
37 | + shape="circle" | ||
38 | + v-model="seriesForm[item.identifier]" | ||
39 | + :placeholder="`请选择${item.functionName}`" | ||
40 | + disabled | ||
41 | + disabledColor="#fff" | ||
42 | + suffixIcon="arrow-down" | ||
43 | + /> | ||
44 | + <u-picker | ||
45 | + :show="item.isShowModel" | ||
46 | + :columns="[enumList.map((item) => ({ label: item.name, value: item.value }))]" | ||
47 | + keyName="label" | ||
48 | + @cancel="handleCancelEnum(index)" | ||
49 | + @close="handleCancelEnum(index)" | ||
50 | + @confirm="(e) => handleSelectEnum(e, item.identifier, index)" | ||
51 | + ></u-picker> | ||
52 | + </view> | ||
53 | + <template v-if="Array.isArray(item.dataType.specs)"> | ||
54 | + <structuralForm class="seriesForm" :ref="item.identifier" :seriesInputData="item.dataType.specs || []"></structuralForm> | ||
55 | + </template> | ||
56 | + </view> | ||
57 | + <view v-else class="positionLeft" style=" border: 1px dashed #f0f0f0; padding: 20rpx;"> | ||
58 | + <view class="text">服务命令</view> | ||
59 | + <u-input v-model="seriesForm.serviceCommand" type="text" shape="circle" :disabled="true"></u-input> | ||
60 | + </view> | ||
61 | + </u-form-item> | ||
62 | + </u-form> | ||
63 | + </view> | ||
64 | +</template> | ||
65 | + | ||
66 | +<script> | ||
67 | +import structuralForm from './structuralForm.vue' | ||
68 | +export default { | ||
69 | + name: 'StructForm', | ||
70 | + props: { | ||
71 | + seriesInputData: { | ||
72 | + type: Array, | ||
73 | + default: () => [], | ||
74 | + }, | ||
75 | + isTCPTransport:{ | ||
76 | + type:Boolean, | ||
77 | + default:false | ||
78 | + } | ||
79 | + }, | ||
80 | + components: { | ||
81 | + structuralForm, | ||
82 | + }, | ||
83 | + data() { | ||
84 | + return { | ||
85 | + boolInfo: {}, | ||
86 | + enumInfo: {}, | ||
87 | + seriesForm: {}, | ||
88 | + seriesRules: {}, | ||
89 | + seriesFunctionList: [], | ||
90 | + boolList: [], | ||
91 | + enumList: [], | ||
92 | + } | ||
93 | + }, | ||
94 | + async mounted() { | ||
95 | + await this.$nextTick(() => { | ||
96 | + this.createInit() | ||
97 | + }) | ||
98 | + }, | ||
99 | + watch: { | ||
100 | + seriesInputData: { | ||
101 | + deep: true, | ||
102 | + handler(newVal, oldVal) { | ||
103 | + this.createInit() | ||
104 | + }, | ||
105 | + }, | ||
106 | + }, | ||
107 | + | ||
108 | + methods: { | ||
109 | + createInit() { | ||
110 | + this.seriesForm = {} | ||
111 | + this.boolInfo = {} | ||
112 | + this.seriesFunctionList = this.seriesInputData | ||
113 | + for(const item of this.seriesInputData){ | ||
114 | + this.$set(item, 'isShowModel', false)//动态添加picker的控制变量 | ||
115 | + const { | ||
116 | + dataType, | ||
117 | + identifier, | ||
118 | + functionName, | ||
119 | + serviceCommand | ||
120 | + } = item || {} | ||
121 | + const {specs,type} = dataType || {} | ||
122 | + if(this.isTCPTransport){ | ||
123 | + this.$set(this.seriesForm,'serviceCommand',serviceCommand) | ||
124 | + break; | ||
125 | + } | ||
126 | + if (Array.isArray(specs)) { | ||
127 | + specs.forEach((itemSpecs) => { | ||
128 | + this.$set(this.seriesForm, identifier, { | ||
129 | + [itemSpecs.identifier]: '', | ||
130 | + }) | ||
131 | + }) | ||
132 | + } else { | ||
133 | + this.$set(this.seriesForm, identifier, '') | ||
134 | + } | ||
135 | + | ||
136 | + //设置验证规则 | ||
137 | + const { valueRange, length = 10240 } = specs || {} | ||
138 | + const { max = 2147483647, min = -2147483647 } = valueRange || {} | ||
139 | + if (type !== 'STRUCT') { | ||
140 | + this.seriesRules[identifier] = [ | ||
141 | + { required: true, message: type == 'BOOL' || type == 'ENUM' ? '请选择' : '请输入' + functionName }, | ||
142 | + type == 'INT' || type == 'DOUBLE' | ||
143 | + ? { | ||
144 | + type: 'number', | ||
145 | + trigger: 'change', | ||
146 | + validator: (_rule, value) => { | ||
147 | + const reg = /^[0-9]*$/ | ||
148 | + if (!reg.test(value)) return Promise.reject(new Error(`${functionName}不是一个有效的数字`)) | ||
149 | + if (value < min || value > max) return Promise.reject(new Error(`${functionName}取值范围在${min}~${max}之间`)) | ||
150 | + | ||
151 | + return Promise.resolve(value) | ||
152 | + }, | ||
153 | + } | ||
154 | + : type == 'TEXT' | ||
155 | + ? { | ||
156 | + type: 'string', | ||
157 | + trigger: 'change', | ||
158 | + validator: (_rule, value) => { | ||
159 | + if ((value?.length || 0) > length) return Promise.reject(new Error(`${functionName}数据长度应该小于${length}`)) | ||
160 | + | ||
161 | + return Promise.resolve(value) | ||
162 | + }, | ||
163 | + } | ||
164 | + : {}, | ||
165 | + ] | ||
166 | + } | ||
167 | + } | ||
168 | + //设置验证规则 | ||
169 | + this.$nextTick(() => { | ||
170 | + !this.isTCPTransport && this.$refs.seriesRef.setRules(this.seriesRules) | ||
171 | + }) | ||
172 | + }, | ||
173 | + isEmptyObject(obj) { | ||
174 | + return Object.keys(obj).length === 0 && obj.constructor === Object | ||
175 | + }, | ||
176 | + | ||
177 | + //打开Bool的picker | ||
178 | + handleBool(name, dataType, num) { | ||
179 | + this.seriesFunctionList.forEach((item, index) => { | ||
180 | + if (index == num) { | ||
181 | + item.isShowModel = true | ||
182 | + } | ||
183 | + }) | ||
184 | + const { specs } = dataType || {} | ||
185 | + const { boolClose, boolOpen } = specs || {} | ||
186 | + console.log(boolClose, boolOpen,'boolClose, boolOpen') | ||
187 | + if(!boolClose&&!boolOpen){ | ||
188 | + uni.$u.toast(`暂无可选的${name}`) | ||
189 | + this.boolList = [] | ||
190 | + return | ||
191 | + } | ||
192 | + this.boolList = [ | ||
193 | + { label: boolClose + '-0', value: 0 }, | ||
194 | + { label: boolOpen + '-1', value: 1 }, | ||
195 | + ] | ||
196 | + }, | ||
197 | + handleSelectBool(e, name, num) { | ||
198 | + const { value } = e || {} | ||
199 | + this.boolInfo[name] = value[0].value | ||
200 | + this.$set(this.seriesForm, name, value[0].label) | ||
201 | + this.seriesFunctionList.forEach((item, index) => { | ||
202 | + if (index == num) { | ||
203 | + item.isShowModel = false | ||
204 | + } | ||
205 | + }) | ||
206 | + }, | ||
207 | + handleBlur(value,name){ | ||
208 | + if(!value) return | ||
209 | + this.$set(this.seriesForm,name,Number(value)) | ||
210 | + }, | ||
211 | + | ||
212 | + //打开Enum的picker | ||
213 | + handleEnum(name, dataType, num) { | ||
214 | + const { specsList } = dataType || {} | ||
215 | + this.enumList = specsList || [] | ||
216 | + console.log(this.enumList,'enumInfo') | ||
217 | + if(!this.enumList.length){ | ||
218 | + return uni.$u.toast(`暂无可选的${name}`) | ||
219 | + } | ||
220 | + this.seriesFunctionList.forEach((item, index) => { | ||
221 | + if (index == num) { | ||
222 | + item.isShowModel = true | ||
223 | + } | ||
224 | + }) | ||
225 | + }, | ||
226 | + //确定选中 | ||
227 | + handleSelectEnum(e, name, num) { | ||
228 | + const { value } = e || {} | ||
229 | + this.enumInfo[name] = value[0].value | ||
230 | + this.$set(this.seriesForm, name, value[0].label) | ||
231 | + this.seriesFunctionList.forEach((item, index) => { | ||
232 | + if (index == num) { | ||
233 | + item.isShowModel = false | ||
234 | + } | ||
235 | + }) | ||
236 | + }, | ||
237 | + | ||
238 | + | ||
239 | + //关闭Enum和Bool的picker弹框 | ||
240 | + handleCancelEnum(num){ | ||
241 | + this.seriesFunctionList.forEach((item, index) => { | ||
242 | + if (index == num) { | ||
243 | + item.isShowModel = false | ||
244 | + } | ||
245 | + }) | ||
246 | + }, | ||
247 | + handleCancelBool(num){ | ||
248 | + this.seriesFunctionList.forEach((item, index) => { | ||
249 | + if (index == num) { | ||
250 | + item.isShowModel = false | ||
251 | + } | ||
252 | + }) | ||
253 | + }, | ||
254 | + | ||
255 | + // 获取表单数据 | ||
256 | + getFormField() { | ||
257 | + const keys = Object.keys(this.seriesForm) | ||
258 | + for (let i = 0; i < keys.length; i++) { | ||
259 | + const key = keys[i] | ||
260 | + if (Array.isArray(this.$refs[key])) { | ||
261 | + const values = this.$refs[key][0]?.getFormField() | ||
262 | + if (!this.isEmptyObject(values)) { | ||
263 | + this.seriesForm[key] = values | ||
264 | + } | ||
265 | + } | ||
266 | + } | ||
267 | + return { | ||
268 | + ...this.seriesForm, | ||
269 | + ...this.boolInfo, | ||
270 | + ...this.enumInfo, | ||
271 | + } | ||
272 | + }, | ||
273 | + | ||
274 | + | ||
275 | + handleValidate() { | ||
276 | + const keys = Object.keys(this.seriesForm) | ||
277 | + for (let i = 0; i < keys.length; i++) { | ||
278 | + const key = keys[i] | ||
279 | + if (Array.isArray(this.$refs[key])) { | ||
280 | + this.$refs[key][0]?.handleValidate(valid=>{ | ||
281 | + if(!valid) return false | ||
282 | + }) | ||
283 | + } else { | ||
284 | + this.$refs.seriesRef.validate(valid=>{ | ||
285 | + if(!valid) return false | ||
286 | + }) | ||
287 | + } | ||
288 | + return true | ||
289 | + } | ||
290 | + }, | ||
291 | + }, | ||
292 | +} | ||
293 | +</script> | ||
294 | + | ||
295 | +<style lang="scss" scoped> | ||
296 | +.positionLeft { | ||
297 | + display: flex; | ||
298 | + align-items: center; | ||
299 | + .text{ | ||
300 | + | ||
301 | + max-width: 190rpx; | ||
302 | + overflow: hidden; | ||
303 | + text-overflow: ellipsis; | ||
304 | + white-space: nowrap; | ||
305 | + } | ||
306 | +} | ||
307 | + | ||
308 | +.positionTop { | ||
309 | + display: flex; | ||
310 | + flex-direction: column; | ||
311 | + border: 1px dashed #f0f0f0; | ||
312 | + padding: 20rpx; | ||
313 | + .seriesForm{ | ||
314 | + margin-left:30rpx | ||
315 | + } | ||
316 | +} | ||
317 | +</style> |
1 | +<template> | ||
2 | + <view> | ||
3 | + <u-form :model="seriesForm" ref="seriesRef" :label-style="{ width: '160rpx', overflow: 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap', display: 'block' }" label-width="160rpx"> | ||
4 | + <u-form-item v-for="(item, index) in seriesFunctionList" :key="item.identifier" :label="item.functionName" :prop="item.identifier"> | ||
5 | + <u-input v-if="item.dataType.type == 'INT' || item.dataType.type == 'DOUBLE'" shape="circle" type="number" v-model="seriesForm[item.identifier]" :placeholder="`请输入${item.functionName}`" /> | ||
6 | + <u-input v-if="item.dataType.type == 'TEXT'" shape="circle" type="text" v-model="seriesForm[item.identifier]" :placeholder="`请输入${item.functionName}`" /> | ||
7 | + <view @click="handleBool(item.identifier, item.dataType, index)"> | ||
8 | + <u-input | ||
9 | + v-if="item.dataType.type == 'BOOL'" | ||
10 | + shape="circle" | ||
11 | + v-model="seriesForm[item.identifier]" | ||
12 | + :placeholder="`请选择${item.functionName}`" | ||
13 | + disabled | ||
14 | + disabledColor="#fff" | ||
15 | + suffixIcon="arrow-down" | ||
16 | + /> | ||
17 | + <u-picker | ||
18 | + :show="item.isShowModel" | ||
19 | + :columns="[boolList]" | ||
20 | + keyName="label" | ||
21 | + closeOnClickOverlay | ||
22 | + @cancel="handleCancel(index)" | ||
23 | + @close="handleCancel(index)" | ||
24 | + @confirm="(e) => handleSelectBool(e, item.identifier, index)" | ||
25 | + ></u-picker> | ||
26 | + </view> | ||
27 | + | ||
28 | + <view @click="handleEnum(item.identifier, item.dataType, index)"> | ||
29 | + <u-input | ||
30 | + v-if="item.dataType.type == 'ENUM'" | ||
31 | + shape="circle" | ||
32 | + v-model="seriesForm[item.identifier]" | ||
33 | + :placeholder="`请选择${item.functionName}`" | ||
34 | + disabled | ||
35 | + disabledColor="#fff" | ||
36 | + suffixIcon="arrow-down" | ||
37 | + /> | ||
38 | + <u-picker | ||
39 | + :show="item.isShowModel" | ||
40 | + :columns="[enumList.map((item) => ({ label: item.name, value: item.value }))]" | ||
41 | + keyName="label" | ||
42 | + closeOnClickOverlay | ||
43 | + @cancel="handleCancel(index)" | ||
44 | + @close="handleCancel(index)" | ||
45 | + @confirm="(e) => handleSelectEnum(e, item.identifier, index)" | ||
46 | + ></u-picker> | ||
47 | + </view> | ||
48 | + </u-form-item> | ||
49 | + </u-form> | ||
50 | + </view> | ||
51 | +</template> | ||
52 | + | ||
53 | +<script> | ||
54 | +export default { | ||
55 | + name: 'StructForm', | ||
56 | + props: { | ||
57 | + seriesInputData: { | ||
58 | + type: Array, | ||
59 | + default: () => [], | ||
60 | + }, | ||
61 | + }, | ||
62 | + data() { | ||
63 | + return { | ||
64 | + boolInfo: {}, | ||
65 | + enumInfo: {}, | ||
66 | + seriesForm: {}, | ||
67 | + seriesRules: {}, | ||
68 | + seriesFunctionList: [], | ||
69 | + boolList: [], | ||
70 | + enumList: [], | ||
71 | + } | ||
72 | + }, | ||
73 | + async mounted() { | ||
74 | + await this.$nextTick(() => { | ||
75 | + this.createInit() | ||
76 | + }) | ||
77 | + }, | ||
78 | + watch: { | ||
79 | + seriesInputData: { | ||
80 | + deep: true, | ||
81 | + handler(newVal, oldVal) { | ||
82 | + this.createInit() | ||
83 | + }, | ||
84 | + }, | ||
85 | + }, | ||
86 | + | ||
87 | + methods: { | ||
88 | + createInit() { | ||
89 | + this.seriesFunctionList = this.seriesInputData | ||
90 | + this.seriesInputData.forEach((item) => { | ||
91 | + this.$set(item, 'isShowModel', false) | ||
92 | + const { | ||
93 | + dataType: { specs, type }, | ||
94 | + identifier, | ||
95 | + functionName, | ||
96 | + } = item || {} | ||
97 | + | ||
98 | + this.$set(this.seriesForm, identifier, '') | ||
99 | + const { valueRange, length = 10240 } = specs || {} | ||
100 | + | ||
101 | + const { max = 2147483647, min = -2147483647 } = valueRange || {} | ||
102 | + | ||
103 | + this.seriesRules[identifier] = [ | ||
104 | + { required: true, message: type == 'BOOL' || type == 'ENUM' ? '请选择' : '请输入' + functionName }, | ||
105 | + type == 'INT' || type == 'DOUBLE' | ||
106 | + ? { | ||
107 | + type: 'number', | ||
108 | + trigger: 'change', | ||
109 | + validator: (_rule, value) => { | ||
110 | + const reg = /^[0-9]*$/ | ||
111 | + if (!reg.test(value)) return Promise.reject(new Error(`${functionName}不是一个有效的数字`)) | ||
112 | + if (value < min || value > max) return Promise.reject(new Error(`${functionName}取值范围在${min}~${max}之间`)) | ||
113 | + | ||
114 | + return Promise.resolve(value) | ||
115 | + }, | ||
116 | + } | ||
117 | + : type == 'TEXT' | ||
118 | + ? { | ||
119 | + type: 'string', | ||
120 | + trigger: 'change', | ||
121 | + validator: (_rule, value) => { | ||
122 | + if ((value?.length || 0) > length) return Promise.reject(new Error(`${functionName}数据长度应该小于${length}`)) | ||
123 | + | ||
124 | + return Promise.resolve(value) | ||
125 | + }, | ||
126 | + } | ||
127 | + : {}, | ||
128 | + ] | ||
129 | + }) | ||
130 | + this.$nextTick(() => { | ||
131 | + this.$refs.seriesRef.setRules(this.seriesRules) | ||
132 | + }) | ||
133 | + }, | ||
134 | + | ||
135 | + handleBool(name, dataType, num) { | ||
136 | + this.seriesFunctionList.forEach((item, index) => { | ||
137 | + if (index == num) { | ||
138 | + item.isShowModel = true | ||
139 | + } | ||
140 | + }) | ||
141 | + const { specs } = dataType || {} | ||
142 | + const { boolClose, boolOpen } = specs || {} | ||
143 | + this.boolList = [ | ||
144 | + { label: boolClose + '0', value: 0 }, | ||
145 | + { label: boolOpen + '0', value: 1 }, | ||
146 | + ] | ||
147 | + this.isShowBool = true | ||
148 | + }, | ||
149 | + handleSelectBool(e, name, num) { | ||
150 | + this.isShowBool = false | ||
151 | + const { value } = e || {} | ||
152 | + this.boolInfo[name] = value[0].value | ||
153 | + this.$set(this.seriesForm, name, value[0].label) | ||
154 | + this.seriesFunctionList.forEach((item, index) => { | ||
155 | + if (index == num) { | ||
156 | + item.isShowModel = false | ||
157 | + } | ||
158 | + }) | ||
159 | + }, | ||
160 | + | ||
161 | + getFormField() { | ||
162 | + return { | ||
163 | + ...this.seriesForm, | ||
164 | + ...this.boolInfo, | ||
165 | + ...this.enumInfo, | ||
166 | + } | ||
167 | + }, | ||
168 | + | ||
169 | + handleEnum(name, dataType, num) { | ||
170 | + const { specsList } = dataType || {} | ||
171 | + this.isShowEnum = true | ||
172 | + this.enumList = specsList || [] | ||
173 | + if(!this.enumList.length){ | ||
174 | + return uni.$u.toast(`暂无可选的${name}`) | ||
175 | + } | ||
176 | + this.seriesFunctionList.forEach((item, index) => { | ||
177 | + if (index == num) { | ||
178 | + item.isShowModel = true | ||
179 | + } | ||
180 | + }) | ||
181 | + }, | ||
182 | + handleSelectEnum(e, name, num) { | ||
183 | + const { value } = e || {} | ||
184 | + this.enumInfo[name] = value[0].value | ||
185 | + this.$set(this.seriesForm, name, value[0].label) | ||
186 | + this.seriesFunctionList.forEach((item, index) => { | ||
187 | + if (index == num) { | ||
188 | + item.isShowModel = false | ||
189 | + } | ||
190 | + }) | ||
191 | + }, | ||
192 | + handleCancel(num){ | ||
193 | + this.seriesFunctionList.forEach((item, index) => { | ||
194 | + if (index == num) { | ||
195 | + item.isShowModel = false | ||
196 | + } | ||
197 | + }) | ||
198 | + }, | ||
199 | + | ||
200 | + handleValidate() { | ||
201 | + return this.$refs.seriesRef?.validate((valid) => { | ||
202 | + if (!valid) return | ||
203 | + }) | ||
204 | + }, | ||
205 | + }, | ||
206 | +} | ||
207 | +</script> | ||
208 | + | ||
209 | +<style lang="scss" scoped></style> |