Commit 911695fda38a22b8d2ff8cdac6faff5c959a8e0f

Authored by xp.Huang
2 parents fe27c91c 1c2f2152

Merge branch 'feat/add-device-list-command-issuance' into 'main_dev'

feat: 设备命令下发添加服务调用

See merge request yunteng/thingskit-app!154
... ... @@ -4,37 +4,47 @@
4 4 * data ((deviceProfileIds))
5 5 */
6 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 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 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 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 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 42 export default {
35   - getDeviceApi,
36   - getDeviceDetail,
37   - getAttribute,
38   - issueCommand,
39   - getRpcRecord
40   -}
\ No newline at end of file
  43 + getDeviceApi,
  44 + getDeviceDetail,
  45 + getAttribute,
  46 + issueCommand,
  47 + getRpcRecord,
  48 + getModelServices,
  49 + getDeviceActiveTime,
  50 +}
... ...
... ... @@ -6,8 +6,7 @@
6 6 <view class="basic-header">
7 7 <view class="u-flex">
8 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 10 </view>
12 11 <view class="basic-text text-clip ml-2">
13 12 {{ deviceDetail.alias ? deviceDetail.alias : deviceDetail.name }}
... ... @@ -17,13 +16,13 @@
17 16 </view>
18 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 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 22 <!-- #endif -->
24 23 <!-- #ifdef APP-PLUS -->
25 24 <view class="cu-item" @tap="handleAppShowModal" data-target="Modal">
26   - <text>下发命令</text>
  25 + <text>命令下发</text>
27 26 </view>
28 27 <!-- #endif -->
29 28 </view>
... ... @@ -79,7 +78,7 @@
79 78 <radio :value="item.value" :checked="index === current" />
80 79 </view>
81 80 <view style="width:10rpx"></view>
82   - <view class="ml-1">{{item.name}}</view>
  81 + <view class="ml-1">{{ item.name }}</view>
83 82 </view>
84 83 </label>
85 84 </radio-group>
... ... @@ -87,9 +86,9 @@
87 86 </view>
88 87 <view class="app-command-body">
89 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 92 </u-icon>
94 93 </view>
95 94 <view class="app-command-buttons">
... ... @@ -104,315 +103,326 @@
104 103 <!-- #ifdef MP -->
105 104 <!-- u-modal在app端弹窗层级无法覆盖背景色 -->
106 105 <mp-command-issuance ref="mpCommandIssuanceRef" :isShowTCP="isShowTCP" :showModal="mpShowModal"
107   - @hideModal="hideMpModal" @cancelCommand="cancelCommand"
  106 + :deviceDetail="deviceDetail" @hideModal="hideMpModal" @cancelCommand="cancelCommand"
108 107 @confirmCommand="confirmCommand"></mp-command-issuance>
109 108 <!-- #endif -->
110 109 </view>
111 110 </template>
112 111
113 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 268 if (this.isShowTCP) {
254 269 //TCP的格式只能是字符串
255 270 const zg = /^[0-9a-zA-Z]*$/;
256   - if (!zg.test(inputCommandVal)) {
  271 + if (!zg.test(values)) {
257 272 uni.$u.toast('输入的内容只能是字母和数字的组合');
258 273 return;
259 274 }
260   - this.commandValue.params = inputCommandVal;
  275 + this.commandValue.params = values;
261 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 288 </script>
279 289
280 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 428 </style>
... ...
1 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 58 </template>
37 59
38 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 205 </script>
88 206
89 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>
\ No newline at end of file
  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>
... ...