Commit 7a1b540403a8697eb0eb098289be39c5d729f180

Authored by fengtao
Committed by xp.Huang
1 parent c885d453

feat: app端下发命令新增下发类型

@@ -6,7 +6,8 @@ @@ -6,7 +6,8 @@
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" name="map-fill"></u-icon> 9 + <u-icon v-if="deviceDetail.deviceInfo.longitude !== ''" @click="handleClick"
  10 + name="map-fill"></u-icon>
10 </view> 11 </view>
11 <view class="basic-text text-clip ml-2"> 12 <view class="basic-text text-clip ml-2">
12 {{ deviceDetail.alias ? deviceDetail.alias : deviceDetail.name }} 13 {{ deviceDetail.alias ? deviceDetail.alias : deviceDetail.name }}
@@ -16,15 +17,11 @@ @@ -16,15 +17,11 @@
16 </view> 17 </view>
17 </view> 18 </view>
18 <!-- 命令下发 设备在线并且不是网关子设备 --> 19 <!-- 命令下发 设备在线并且不是网关子设备 -->
19 - <view class="mr-2" v-if="deviceDetail.deviceState === 'ONLINE' && deviceDetail.transportType !== deviceTypeNum.GBT">  
20 - <!-- #ifdef MP -->  
21 - <u-button type="primary" shape="circle" size="mini" text="命令下发" @click="handleMpShowModal" />  
22 - <!-- #endif -->  
23 - <!-- #ifdef APP-PLUS --> 20 + <view class="mr-2"
  21 + v-if="deviceDetail.deviceState === 'ONLINE' && deviceDetail.transportType !== deviceTypeNum.GBT">
24 <view class="cu-item" @tap="handleAppShowModal" data-target="Modal"> 22 <view class="cu-item" @tap="handleAppShowModal" data-target="Modal">
25 <text>命令下发</text> 23 <text>命令下发</text>
26 </view> 24 </view>
27 - <!-- #endif -->  
28 </view> 25 </view>
29 </view> 26 </view>
30 <!-- 设备详情 --> 27 <!-- 设备详情 -->
@@ -60,369 +57,339 @@ @@ -60,369 +57,339 @@
60 </view> 57 </view>
61 </view> 58 </view>
62 <!-- 命令下发 --> 59 <!-- 命令下发 -->
63 - <!-- #ifdef APP-PLUS -->  
64 - <!-- 原生弹窗 封装成子组件无效 -->  
65 - <view v-show="showNativeModal" class="cu-modal" :class="modalName == 'Modal' ? 'show' : ''">  
66 - <view class="cu-dialog">  
67 - <view class="app-command-content">  
68 - <view class="app-command-text">  
69 - <text>命令下发</text>  
70 - </view>  
71 - <view class="app-command-type">  
72 - <text>下发类型</text>  
73 - <view class="mr-2">  
74 - <radio-group @change="radioChange" class="flex mr-1">  
75 - <label v-for="(item, index) in commandTypeList" :key="item.value">  
76 - <view class="flex">  
77 - <view class="ml-1">  
78 - <radio :value="item.value" :checked="index === current" />  
79 - </view>  
80 - <view style="width:10rpx"></view>  
81 - <view class="ml-1">{{ item.name }}</view>  
82 - </view>  
83 - </label>  
84 - </radio-group>  
85 - </view>  
86 - </view>  
87 - <view class="app-command-body">  
88 - <textarea class="app-command-textarea" v-model="inputCommandContent"  
89 - :placeholder="`请输入下发内容${isShowTCP ? '(字符串格式)' : '(json格式)'}`" />  
90 - <u-icon @click="handleCopy(copyTextValue)" v-if="!isShowTCP" name="question-circle" color="#2979ff"  
91 - size="28" class="ml-10">  
92 - </u-icon>  
93 - </view>  
94 - <view class="app-command-buttons">  
95 - <view class="cancel-button" @click="cancelCommand"><text class="cancel-text">取消</text></view>  
96 - <view @click="handleAppCommand" class="confrim-button"><text class="confrim-text">确认</text>  
97 - </view>  
98 - </view>  
99 - </view>  
100 - </view>  
101 - </view>  
102 - <!-- #endif -->  
103 - <!-- #ifdef MP -->  
104 - <!-- u-modal在app端弹窗层级无法覆盖背景色 -->  
105 - <mp-command-issuance ref="mpCommandIssuanceRef" :isShowTCP="isShowTCP" :showModal="mpShowModal"  
106 - :deviceDetail="deviceDetail" @hideModal="hideMpModal" @cancelCommand="cancelCommand"  
107 - @confirmCommand="confirmCommand"></mp-command-issuance>  
108 - <!-- #endif --> 60 + <!-- 引入三方库,进行mp端和app端同步弹窗 -->
  61 + <tn-popup v-model="showNativeModal">
  62 + <commandIssuanceVue ref="mpCommandIssuanceRef" :isShowTCP="isShowTCP"
  63 + :deviceDetail="deviceDetail" @hideModal="hideMpModal" @cancelCommand="cancelCommand"
  64 + @confirmCommand="confirmCommand" />
  65 + </tn-popup>
109 </view> 66 </view>
110 </template> 67 </template>
111 68
112 <script> 69 <script>
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'; 70 + import {
  71 + formatToDate
  72 + } from '@/plugins/utils.js';
  73 + import api from '@/api/index.js';
  74 + import mpCommandIssuance from './mp-command-issuance.vue';
  75 + import {
  76 + commandTypeList
  77 + } from '../config/data.js'
  78 + import {
  79 + useShowModal
  80 + } from '@/plugins/utils.js'
  81 + import {
  82 + deviceTypeNum
  83 + } from '../config/data'
  84 + import deviceDetail from '../device-detail.vue';
  85 + import commandIssuanceVue from './command-issuance.vue';
120 86
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 - }  
149 - }  
150 - };  
151 - },  
152 - computed: {  
153 - deviceStatus() {  
154 - return this.deviceDetail.deviceState === 'INACTIVE' ? '待激活' : this.deviceDetail.deviceState === 'ONLINE' ?  
155 - '在线' : '离线'; 87 + export default {
  88 + components: {
  89 + mpCommandIssuance,
  90 + commandIssuanceVue
156 }, 91 },
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' ? '否' : '是'; 92 + props: {
  93 + deviceDetail: {
  94 + type: Object,
  95 + default: () => ({})
  96 + }
168 }, 97 },
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 - }) 98 + data() {
  99 + return {
  100 + showNativeModal: false,
  101 + current: 0,
  102 + commandTypeList,
  103 + deviceTypeNum: deviceTypeNum,
  104 + commandTypeStr: 'OneWay',
  105 + inputCommandContent: '',
  106 + mpShowModal: false,
  107 + commandValue: {},
  108 + isShowTCP: false, //用于下发命令时判断是否是TCP/UDP
  109 + modalName: null,
  110 + copyTextValue: {
  111 + "method": "methodThingskit",
  112 + "params": {
  113 + "pin": 7,
  114 + "value": 1
190 } 115 }
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 } 116 }
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  
210 }; 117 };
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 }, 118 },
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) 119 + computed: {
  120 + deviceStatus() {
  121 + return this.deviceDetail.deviceState === 'INACTIVE' ? '待激活' : this.deviceDetail.deviceState === 'ONLINE' ?
  122 + '在线' : '离线';
  123 + },
  124 + deviceType() {
  125 + return this.deviceDetail.deviceType === 'DIRECT_CONNECTION' ?
  126 + '直连设备' :
  127 + this.deviceDetail.deviceType === 'GATEWAY' ?
  128 + '网关设备' :
  129 + this.deviceDetail.deviceType === 'SENSOR' ?
  130 + '网关子设备' :
  131 + '';
  132 + },
  133 + alarmStatus() {
  134 + return this.deviceDetail.alarmStatus === '0' ? '否' : '是';
  135 + },
  136 + formatLastOnlineTime() {
  137 + return formatToDate(Number(this.deviceDetail.lastOnlineTime), 'YYYY-MM-DD HH:mm:ss');
  138 + }
238 }, 139 },
239 - cancelCommand() {  
240 - this.hideMpModal();  
241 - this.hideAppModal();  
242 - this.commandTypeStr = 'OneWay'  
243 - this.inputCommandContent = ''  
244 - this.$nextTick(() => {  
245 - this.$refs.mpCommandIssuanceRef.reset()  
246 - }) 140 + beforeCreate() {
  141 + this.modalName = null
247 }, 142 },
248 - handleAppCommand() {  
249 - this.handleCommand(this.commandTypeStr, this.inputCommandContent) 143 + onLoad() {
  144 + // 隐藏原生的tabbar
  145 + uni.hideTabBar();
  146 + this.modalName = null
250 }, 147 },
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('当前设备不在线~') 148 + methods: {
  149 + handleCopy(value) {
  150 + useShowModal(JSON.stringify(value), '命令下发', '复制内容').then(res => {
  151 + uni.setClipboardData({
  152 + data: JSON.stringify(value),
  153 + success: () => {
  154 + uni.showToast({
  155 + title: '复制成功'
  156 + })
  157 + }
  158 + });
  159 + })
  160 + },
  161 + radioChange: function(evt) {
  162 + for (let i = 0; i < this.commandTypeList.length; i++) {
  163 + if (this.items[i].value === evt.detail.value) {
  164 + this.current = i;
  165 + break;
  166 + }
258 } 167 }
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) {//下发类型是自定义时  
268 - if (this.isShowTCP) {  
269 - //TCP的格式只能是字符串  
270 - const zg = /^[0-9a-zA-Z]*$/;  
271 - if (!zg.test(values)) {  
272 - uni.$u.toast('输入的内容只能是字母和数字的组合');  
273 - return; 168 + this.commandTypeStr = evt.detail.value
  169 + },
  170 + formatTextStatus(deviceState) {
  171 + return deviceState === 'INACTIVE' ? '#666' : deviceState === 'ONLINE' ? '#377DFF' : '#DE4437';
  172 + },
  173 + handleClick() {
  174 + const data = {
  175 + longitude: this.deviceDetail.deviceInfo.longitude || 0,
  176 + latitude: this.deviceDetail.deviceInfo.latitude || 0
  177 + };
  178 + uni.navigateTo({
  179 + url: '/device-subpackage/device-detail/device-position?data=' + JSON.stringify(data)
  180 + });
  181 + },
  182 + disabledScroll() {
  183 + return;
  184 + },
  185 + handleAppShowModal(e) {
  186 + this.modalName = e.currentTarget.dataset.target;
  187 + this.showNativeModal = true
  188 + },
  189 + handleMpShowModal() {
  190 + const {
  191 + transportType
  192 + } = this.deviceDetail.deviceProfile;
  193 + this.isShowTCP = transportType == 'TCP' ? true : false;
  194 + this.mpShowModal = true;
  195 + },
  196 + hideMpModal() {
  197 + this.mpShowModal = false;
  198 + },
  199 + hideAppModal() {
  200 + this.modalName = null;
  201 + this.showNativeModal = false
  202 + },
  203 + confirmCommand(commandType, callType, values) {
  204 + this.handleCommand(commandType, callType, values)
  205 + },
  206 + cancelCommand() {
  207 + this.hideMpModal();
  208 + this.hideAppModal();
  209 + this.commandTypeStr = 'OneWay'
  210 + this.inputCommandContent = ''
  211 + this.$nextTick(() => {
  212 + this.$refs.mpCommandIssuanceRef.reset()
  213 + })
  214 + },
  215 + handleAppCommand() {
  216 + this.handleCommand(this.commandTypeStr, this.commandTypeStr, this.inputCommandContent)
  217 + },
  218 + async handleCommand(commandType, callType, values) {
  219 + if (!values) return uni.$u.toast('请输入下发内容~');
  220 + if (callType == 'TwoWay') {
  221 + const result = await api.deviceApi.getDeviceActiveTime(this.deviceDetail.tbDeviceId)
  222 + const [firsetItem] = result || []
  223 + if (!firsetItem.value) {
  224 + return uni.$u.toast('当前设备不在线~')
274 } 225 }
275 - this.commandValue.params = values;  
276 - } else {  
277 - this.commandValue.params = JSON.parse(values);  
278 } 226 }
279 - }  
280 -  
281 - await api.deviceApi.issueCommand(callType, this.deviceDetail.tbDeviceId, this.commandValue);  
282 - this.cancelCommand();  
283 - uni.$u.toast('下发成功~');  
284 - },  
285 227
286 - }  
287 -}; 228 + this.commandValue.persistent = true;
  229 + this.commandValue.additionalInfo = {
  230 + cmdType: commandType == 0 ? 0 : 1
  231 + };
  232 + this.commandValue.method = 'methodThingskit';
  233 + this.commandValue.params = values;
  234 + if (commandType == 0) { //下发类型是自定义时
  235 + if (this.isShowTCP) {
  236 + //TCP的格式只能是字符串
  237 + const zg = /^[0-9a-zA-Z]*$/;
  238 + if (!zg.test(values)) {
  239 + uni.$u.toast('输入的内容只能是字母和数字的组合');
  240 + return;
  241 + }
  242 + this.commandValue.params = values;
  243 + } else {
  244 + this.commandValue.params = JSON.parse(values);
  245 + }
  246 + }
  247 +
  248 + await api.deviceApi.issueCommand(callType, this.deviceDetail.tbDeviceId, this.commandValue);
  249 + this.cancelCommand();
  250 + uni.$u.toast('下发成功~');
  251 + },
  252 +
  253 + }
  254 + };
288 </script> 255 </script>
289 256
290 <style lang="scss" scoped> 257 <style lang="scss" scoped>
291 -@import url('../static/modal.css'); 258 + @import url('../static/modal.css');
292 259
293 -.app-command-content {  
294 - .app-command-text {  
295 - display: flex;  
296 - justify-content: center;  
297 - align-items: center; 260 + .app-command-content {
  261 + .app-command-text {
  262 + display: flex;
  263 + justify-content: center;
  264 + align-items: center;
298 265
299 - text {  
300 - font-weight: 700; 266 + text {
  267 + font-weight: 700;
  268 + }
301 } 269 }
302 - }  
303 270
304 - .app-command-type {  
305 - display: flex;  
306 - margin-top: 20rpx;  
307 - margin-left: 20rpx; 271 + .app-command-type {
  272 + display: flex;
  273 + margin-top: 20rpx;
  274 + margin-left: 20rpx;
308 275
309 - text {  
310 - font-weight: 700; 276 + text {
  277 + font-weight: 700;
  278 + }
311 } 279 }
312 - }  
313 280
314 - .app-command-body {  
315 - display: flex;  
316 - align-items: center;  
317 - justify-content: space-between;  
318 - margin-top: 20rpx;  
319 - margin-left: 20rpx; 281 + .app-command-body {
  282 + display: flex;
  283 + align-items: center;
  284 + justify-content: space-between;
  285 + margin-top: 20rpx;
  286 + margin-left: 20rpx;
320 287
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; 288 + .app-command-textarea {
  289 + width: 625rpx;
  290 + height: 400rpx;
  291 + background: #FFFFFF;
  292 + box-shadow: 2px 2px 4px 0px rgba(0, 0, 0, 0.03);
  293 + border-radius: 10px;
  294 + }
327 } 295 }
328 - }  
329 -  
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;  
337 296
338 - .cancel-button {  
339 - width: 300rpx;  
340 - background: #e3e3e5; 297 + .app-command-buttons {
  298 + display: flex;
  299 + align-items: center;
  300 + justify-content: space-evenly;
341 height: 85rpx; 301 height: 85rpx;
342 - border-radius: 38rpx;  
343 - line-height: 85rpx; 302 + margin-top: 20rpx;
  303 + margin-bottom: 20rpx;
344 304
345 - .cancel-text {  
346 - color: #333333 305 + .cancel-button {
  306 + width: 300rpx;
  307 + background: #e3e3e5;
  308 + height: 85rpx;
  309 + border-radius: 38rpx;
  310 + line-height: 85rpx;
  311 +
  312 + .cancel-text {
  313 + color: #333333
  314 + }
347 } 315 }
348 - }  
349 316
350 - .confrim-button {  
351 - width: 300rpx;  
352 - background: #3388ff;  
353 - border-radius: 38rpx;  
354 - height: 85rpx;  
355 - line-height: 85rpx; 317 + .confrim-button {
  318 + width: 300rpx;
  319 + background: #3388ff;
  320 + border-radius: 38rpx;
  321 + height: 85rpx;
  322 + line-height: 85rpx;
356 323
357 - .confrim-text {  
358 - color: white 324 + .confrim-text {
  325 + color: white
  326 + }
359 } 327 }
360 } 328 }
361 } 329 }
362 -}  
363 330
364 -.basic-page {  
365 - padding: 0 30rpx; 331 + .basic-page {
  332 + padding: 0 30rpx;
366 333
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; 334 + .basic-header {
  335 + display: flex;
  336 + justify-content: space-between;
  337 + align-items: center;
  338 + height: 140rpx;
  339 + background-color: #fff;
  340 + border-radius: 20rpx;
374 341
375 - .basic-text {  
376 - width: 370rpx;  
377 - } 342 + .basic-text {
  343 + width: 370rpx;
  344 + }
378 345
379 - .cu-item {  
380 - background: #3388ff;  
381 - border-radius: 12px;  
382 - width: 120rpx;  
383 - height: 48rpx;  
384 - text-align: center;  
385 - line-height: 40rpx; 346 + .cu-item {
  347 + background: #3388ff;
  348 + border-radius: 12px;
  349 + width: 120rpx;
  350 + height: 48rpx;
  351 + text-align: center;
  352 + line-height: 40rpx;
386 353
387 - text {  
388 - font-size: 12px;  
389 - font-family: PingFangSC-Regular, PingFang SC;  
390 - font-weight: 400;  
391 - color: #ffffff; 354 + text {
  355 + font-size: 12px;
  356 + font-family: PingFangSC-Regular, PingFang SC;
  357 + font-weight: 400;
  358 + color: #ffffff;
  359 + }
392 } 360 }
393 - }  
394 361
395 - .basic-text-status {  
396 - font-size: 14px; 362 + .basic-text-status {
  363 + font-size: 14px;
  364 + }
397 } 365 }
398 - }  
399 366
400 - .detail {  
401 - background-color: #fff;  
402 - margin-top: 30rpx;  
403 - border-radius: 20rpx;  
404 - width: 690rpx; 367 + .detail {
  368 + background-color: #fff;
  369 + margin-top: 30rpx;
  370 + border-radius: 20rpx;
  371 + width: 690rpx;
405 372
406 - .detail-item {  
407 - padding: 30rpx;  
408 - display: flex;  
409 - align-items: center; 373 + .detail-item {
  374 + padding: 30rpx;
  375 + display: flex;
  376 + align-items: center;
410 377
411 - .detail-label {  
412 - color: #333;  
413 - font-size: 15px;  
414 - } 378 + .detail-label {
  379 + color: #333;
  380 + font-size: 15px;
  381 + }
415 382
416 - .detail-value {  
417 - color: #666;  
418 - font-size: 14px;  
419 - margin-left: 30rpx; 383 + .detail-value {
  384 + color: #666;
  385 + font-size: 14px;
  386 + margin-left: 30rpx;
  387 + }
420 } 388 }
421 } 389 }
422 } 390 }
423 -}  
424 391
425 -/deep/ .u-modal__content {  
426 - padding: 30rpx 0 !important;  
427 -}  
428 -</style> 392 + /deep/ .u-modal__content {
  393 + padding: 30rpx 0 !important;
  394 + }
  395 +</style>
  1 +<template>
  2 + <view class="w-100 modal-content">
  3 + <view>
  4 + <view style="max-height: 560rpx; overflow-y: scroll">
  5 + <view class="header-title">命令下发</view>
  6 + <view class="u-flex">
  7 + <text class="type-text">下发类型:</text>
  8 + <u-radio-group v-model="commandType" placement="row" @change="handleCommand">
  9 + <u-radio :customStyle="{ marginRight: '20rpx' }" v-for="item in commandTypeList"
  10 + 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"
  25 + suffixIcon="arrow-down" />
  26 + </view>
  27 + </view>
  28 + <view class="u-flex" style="
  29 + margin-top: 28rpx;
  30 + flex-direction: column;
  31 + align-items: flex-start;
  32 + " v-if="isShowServiceFunctionName && commandType == 1">
  33 + <text class="type-text">输入参数:</text>
  34 + <seriesForm ref="seriesFormRef" :seriesInputData="seriesInputData" :isTCPTransport="isTCPTransport">
  35 + </seriesForm>
  36 + </view>
  37 + <view class="content-body" v-if="commandType == 0">
  38 + <div class="u-flex u-row-between">
  39 + <u--textarea :placeholder="`请输入下发内容${
  40 + isShowTCP ? '(字符串格式)' : '(json格式)'
  41 + }`" v-model="inputCommandVal" />
  42 + <u-icon v-if="!isShowTCP" @click="handleCopy(copyTextValue)" name="question-circle"
  43 + color="#2979ff" size="28" class="ml-10">
  44 + </u-icon>
  45 + </div>
  46 + </view>
  47 + </view>
  48 + <view class="button-group">
  49 + <view>
  50 + <u-button :customStyle="{ color: '#333' }" color="#e3e3e5" shape="circle" text="取消"
  51 + @click="cancelCommand"></u-button>
  52 + </view>
  53 + <view>
  54 + <u-button color="#3388ff" shape="circle" text="确认" @click="confirmCommand"></u-button>
  55 + </view>
  56 + </view>
  57 + </view>
  58 + <u-picker :show="isShowService" :columns="[
  59 + seriesList.map((item) => ({
  60 + label: item.functionName,
  61 + value: item.identifier,
  62 + callType: item.callType,
  63 + })),
  64 + ]" keyName="label" closeOnClickOverlay @cancel="cancelTypeGap" @close="cancelTypeGap"
  65 + @confirm="handleSelect"></u-picker>
  66 + </view>
  67 +</template>
  68 +
  69 +<script>
  70 + import {
  71 + useShowModal
  72 + } from "@/plugins/utils.js";
  73 + import seriesForm from "./seriesForm.vue";
  74 + import api from "@/api/index.js";
  75 +
  76 + export default {
  77 + components: {
  78 + seriesForm,
  79 + },
  80 + props: {
  81 + showModal: Boolean,
  82 + isShowTCP: Boolean,
  83 + deviceDetail: Object,
  84 + },
  85 + data() {
  86 + return {
  87 + current: 0,
  88 + commandType: 0, //下发类型
  89 + callType: "OneWay", //单双向
  90 + serviceName: "", //服务
  91 + service: null, //服务
  92 + inputCommandVal: "", //自定义命令
  93 + isShowService: false, //服务下拉框
  94 + isShowServiceFunctionName: false, //选择服务过后的输入参数
  95 + copyTextValue: {
  96 + method: "methodThingskit",
  97 + params: {
  98 + pin: 7,
  99 + value: 1,
  100 + },
  101 + },
  102 + commandTypeList: [{
  103 + label: "自定义",
  104 + value: 0
  105 + }],
  106 + seriesList: [], //服务下拉框的数据
  107 + boolList: [],
  108 + enumList: [],
  109 + seriesInputData: [],
  110 + isTCPTransport: false,
  111 + };
  112 + },
  113 + mounted() {
  114 + this.getFormInfo();
  115 + },
  116 + watch: {
  117 + showModal: {
  118 + deep: true,
  119 + handler() {
  120 + this.commandType = 0;
  121 + this.serviceName = "";
  122 + this.isShowServiceFunctionName = false;
  123 + },
  124 + },
  125 + },
  126 + methods: {
  127 + cancelCommand() {
  128 + this.commandType = 0;
  129 + this.$emit("cancelCommand");
  130 + },
  131 + async confirmCommand() {
  132 + if (this.commandType == 0) {
  133 + this.$emit(
  134 + "confirmCommand",
  135 + this.commandType,
  136 + this.callType,
  137 + this.inputCommandVal
  138 + );
  139 + } else {
  140 + const result = this.$refs.seriesFormRef.handleValidate();
  141 + if (!result) {
  142 + return;
  143 + }
  144 + const value = this.$refs.seriesFormRef.getFormField();
  145 + const values = this.isTCPTransport ?
  146 + value.serviceCommand : {
  147 + [this.service]: value,
  148 + };
  149 + this.$emit("confirmCommand", this.commandType, this.callType, values);
  150 + }
  151 + },
  152 + handleCopy(value) {
  153 + useShowModal(JSON.stringify(value), "命令下发", "复制内容").then(
  154 + (res) => {
  155 + uni.setClipboardData({
  156 + data: JSON.stringify(value),
  157 + success: () => {
  158 + uni.showToast({
  159 + title: "复制成功",
  160 + });
  161 + },
  162 + });
  163 + }
  164 + );
  165 + },
  166 +
  167 + async getFormInfo() {
  168 + const {
  169 + transportType,
  170 + deviceType,
  171 + deviceProfile
  172 + } =
  173 + this.deviceDetail || {};
  174 + const {
  175 + profileData: {
  176 + transportConfiguration: {
  177 + protocol
  178 + },
  179 + },
  180 + } = deviceProfile || {};
  181 + this.isTCPTransport = transportType === "TCP";
  182 + const isTCPModbus = this.isTCPTransport && protocol === "MODBUS_RTU";
  183 + if (isTCPModbus || (this.isTCPTransport && deviceType === "SENSOR")) {
  184 + this.commandTypeList =
  185 + this.commandTypeList.length == 2 ?
  186 + this.commandTypeList.pop() :
  187 + this.commandTypeList;
  188 + } else {
  189 + this.commandTypeList.push({
  190 + label: "服务",
  191 + value: 1
  192 + });
  193 + this.seriesList = await api.deviceApi.getModelServices(
  194 + this.deviceDetail
  195 + );
  196 + }
  197 + },
  198 +
  199 + openService() {
  200 + this.isShowService = true;
  201 + },
  202 + handleSelect(e) {
  203 + this.isShowService = false;
  204 + const {
  205 + value
  206 + } = e || {};
  207 + this.serviceName = value[0].label;
  208 + this.service = value[0].value;
  209 + if (this.service) {
  210 + this.isShowServiceFunctionName = true;
  211 + const {
  212 + functionJson
  213 + } =
  214 + this.seriesList.filter(
  215 + (item) => item.identifier === this.service
  216 + )[0] || {};
  217 + const {
  218 + inputData
  219 + } = functionJson || {};
  220 + this.seriesInputData = inputData || [];
  221 + this.callType = value[0].callType === "ASYNC" ? "OneWay" : "TwoWay";
  222 + }
  223 + },
  224 +
  225 + cancelModel() {
  226 + this.isShowService = false;
  227 + this.seriesInputData = [];
  228 + this.seriesList = [];
  229 + },
  230 +
  231 + handleCommand(name) {
  232 + this.seriesInputData = [];
  233 + this.serviceName = "";
  234 + this.isShowServiceFunctionName = false;
  235 + if (this.commandType == 0) {
  236 + this.callType = "OneWay";
  237 + }
  238 + },
  239 +
  240 + cancelTypeGap() {
  241 + this.isShowService = false;
  242 + },
  243 +
  244 + reset() {
  245 + this.callType = "OneWay";
  246 + this.inputCommandVal = "";
  247 + },
  248 + },
  249 + };
  250 +</script>
  251 +
  252 +<style lang="scss" scoped>
  253 + .modal-content {
  254 + width: 720rpx;
  255 + padding: 0 30rpx;
  256 + background-color: white;
  257 +
  258 + .header-title {
  259 + text-align: center;
  260 + font-weight: 700;
  261 + margin-bottom: 40rpx;
  262 + }
  263 +
  264 + .type-text {
  265 + color: #333;
  266 + font-size: 14px;
  267 + font-weight: 700;
  268 + margin-right: 30rpx;
  269 + }
  270 +
  271 + .content-body {
  272 + margin-top: 28rpx;
  273 + width: 100%;
  274 + }
  275 +
  276 + .button-group {
  277 + display: flex;
  278 + margin-top: 40rpx;
  279 + justify-content: space-between;
  280 +
  281 + view {
  282 + width: 300rpx;
  283 + }
  284 + }
  285 + }
  286 +</style>
1 <template> 1 <template>
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> 2 + <view class="mp-u-modal">
  3 + <u-modal :mask-close-able="true" :show="showModal" closeOnClickOverlay :showConfirmButton="false"
  4 + @close="$emit('hideModal')" z-index="99999">
  5 + <commandIssuance :isShowTCP="isShowTCP" :deviceDetail="deviceDetail" />
  6 + </u-modal>
  7 + </view>
58 </template> 8 </template>
59 9
60 <script> 10 <script>
61 -import { useShowModal } from '@/plugins/utils.js'  
62 -import seriesForm from './seriesForm.vue'  
63 -  
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  
129 - }  
130 - this.$emit('confirmCommand',this.commandType, this.callType, values)  
131 - }  
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'  
192 - }  
193 - },  
194 -  
195 - cancelTypeGap() {  
196 - this.isShowService = false  
197 - },  
198 -  
199 - reset() {  
200 - this.callType = 'OneWay'  
201 - this.inputCommandVal = ''  
202 - },  
203 - },  
204 -} 11 + import commandIssuance from "./command-issuance.vue";
  12 +
  13 + export default {
  14 + components: {
  15 + commandIssuance,
  16 + },
  17 + props: {
  18 + showModal: Boolean,
  19 + isShowTCP: Boolean,
  20 + deviceDetail: Object,
  21 + },
  22 + };
205 </script> 23 </script>
206 24
207 -<style lang="scss" scoped>  
208 -.modal-content {  
209 - width: 720rpx;  
210 - padding: 0 30rpx;  
211 - background-color: white;  
212 -  
213 - .header-title {  
214 - text-align: center;  
215 - font-weight: 700;  
216 - margin-bottom: 40rpx;  
217 - }  
218 -  
219 - .type-text {  
220 - color: #333;  
221 - font-size: 14px;  
222 - font-weight: 700;  
223 - margin-right: 30rpx;  
224 - }  
225 -  
226 - .content-body {  
227 - margin-top: 28rpx;  
228 - width: 100%;  
229 - }  
230 -  
231 - .button-group {  
232 - display: flex;  
233 - margin-top: 40rpx;  
234 - justify-content: space-between;  
235 -  
236 - view {  
237 - width: 300rpx;  
238 - }  
239 - }  
240 -}  
241 -</style> 25 +<style lang="scss" scoped></style>
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 suffixIcon="arrow-down" 24 suffixIcon="arrow-down"
25 /> 25 />
26 <u-picker 26 <u-picker
27 - :show="item.isShowModel" 27 + :show="showPickerView"
28 :columns="[boolList]" 28 :columns="[boolList]"
29 keyName="label" 29 keyName="label"
30 @cancel="handleCancelBool(index)" 30 @cancel="handleCancelBool(index)"
@@ -42,7 +42,7 @@ @@ -42,7 +42,7 @@
42 suffixIcon="arrow-down" 42 suffixIcon="arrow-down"
43 /> 43 />
44 <u-picker 44 <u-picker
45 - :show="item.isShowModel" 45 + :show="showPickerView"
46 :columns="[enumList.map((item) => ({ label: item.name, value: item.value }))]" 46 :columns="[enumList.map((item) => ({ label: item.name, value: item.value }))]"
47 keyName="label" 47 keyName="label"
48 @cancel="handleCancelEnum(index)" 48 @cancel="handleCancelEnum(index)"
@@ -82,6 +82,7 @@ export default { @@ -82,6 +82,7 @@ export default {
82 }, 82 },
83 data() { 83 data() {
84 return { 84 return {
  85 + showPickerView:false,
85 boolInfo: {}, 86 boolInfo: {},
86 enumInfo: {}, 87 enumInfo: {},
87 seriesForm: {}, 88 seriesForm: {},
@@ -178,12 +179,12 @@ export default { @@ -178,12 +179,12 @@ export default {
178 handleBool(name, dataType, num) { 179 handleBool(name, dataType, num) {
179 this.seriesFunctionList.forEach((item, index) => { 180 this.seriesFunctionList.forEach((item, index) => {
180 if (index == num) { 181 if (index == num) {
181 - item.isShowModel = true 182 + this.showPickerView = true
  183 + item.isShowModel = true
182 } 184 }
183 }) 185 })
184 const { specs } = dataType || {} 186 const { specs } = dataType || {}
185 const { boolClose, boolOpen } = specs || {} 187 const { boolClose, boolOpen } = specs || {}
186 - console.log(boolClose, boolOpen,'boolClose, boolOpen')  
187 if(!boolClose&&!boolOpen){ 188 if(!boolClose&&!boolOpen){
188 uni.$u.toast(`暂无可选的${name}`) 189 uni.$u.toast(`暂无可选的${name}`)
189 this.boolList = [] 190 this.boolList = []
@@ -201,6 +202,7 @@ export default { @@ -201,6 +202,7 @@ export default {
201 this.seriesFunctionList.forEach((item, index) => { 202 this.seriesFunctionList.forEach((item, index) => {
202 if (index == num) { 203 if (index == num) {
203 item.isShowModel = false 204 item.isShowModel = false
  205 + this.showPickerView = false
204 } 206 }
205 }) 207 })
206 }, 208 },
@@ -220,6 +222,7 @@ export default { @@ -220,6 +222,7 @@ export default {
220 this.seriesFunctionList.forEach((item, index) => { 222 this.seriesFunctionList.forEach((item, index) => {
221 if (index == num) { 223 if (index == num) {
222 item.isShowModel = true 224 item.isShowModel = true
  225 + this.showPickerView = true
223 } 226 }
224 }) 227 })
225 }, 228 },
@@ -231,6 +234,7 @@ export default { @@ -231,6 +234,7 @@ export default {
231 this.seriesFunctionList.forEach((item, index) => { 234 this.seriesFunctionList.forEach((item, index) => {
232 if (index == num) { 235 if (index == num) {
233 item.isShowModel = false 236 item.isShowModel = false
  237 + this.showPickerView = false
234 } 238 }
235 }) 239 })
236 }, 240 },
@@ -241,6 +245,7 @@ export default { @@ -241,6 +245,7 @@ export default {
241 this.seriesFunctionList.forEach((item, index) => { 245 this.seriesFunctionList.forEach((item, index) => {
242 if (index == num) { 246 if (index == num) {
243 item.isShowModel = false 247 item.isShowModel = false
  248 + this.showPickerView = false
244 } 249 }
245 }) 250 })
246 }, 251 },
@@ -248,6 +253,7 @@ export default { @@ -248,6 +253,7 @@ export default {
248 this.seriesFunctionList.forEach((item, index) => { 253 this.seriesFunctionList.forEach((item, index) => {
249 if (index == num) { 254 if (index == num) {
250 item.isShowModel = false 255 item.isShowModel = false
  256 + this.showPickerView = false
251 } 257 }
252 }) 258 })
253 }, 259 },
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 suffixIcon="arrow-down" 15 suffixIcon="arrow-down"
16 /> 16 />
17 <u-picker 17 <u-picker
18 - :show="item.isShowModel" 18 + :show="showPickerView"
19 :columns="[boolList]" 19 :columns="[boolList]"
20 keyName="label" 20 keyName="label"
21 closeOnClickOverlay 21 closeOnClickOverlay
@@ -36,7 +36,7 @@ @@ -36,7 +36,7 @@
36 suffixIcon="arrow-down" 36 suffixIcon="arrow-down"
37 /> 37 />
38 <u-picker 38 <u-picker
39 - :show="item.isShowModel" 39 + :show="showPickerView"
40 :columns="[enumList.map((item) => ({ label: item.name, value: item.value }))]" 40 :columns="[enumList.map((item) => ({ label: item.name, value: item.value }))]"
41 keyName="label" 41 keyName="label"
42 closeOnClickOverlay 42 closeOnClickOverlay
@@ -61,6 +61,7 @@ export default { @@ -61,6 +61,7 @@ export default {
61 }, 61 },
62 data() { 62 data() {
63 return { 63 return {
  64 + showPickerView: false,
64 boolInfo: {}, 65 boolInfo: {},
65 enumInfo: {}, 66 enumInfo: {},
66 seriesForm: {}, 67 seriesForm: {},
@@ -89,6 +90,7 @@ export default { @@ -89,6 +90,7 @@ export default {
89 this.seriesFunctionList = this.seriesInputData 90 this.seriesFunctionList = this.seriesInputData
90 this.seriesInputData.forEach((item) => { 91 this.seriesInputData.forEach((item) => {
91 this.$set(item, 'isShowModel', false) 92 this.$set(item, 'isShowModel', false)
  93 + this.showPickerView=false
92 const { 94 const {
93 dataType: { specs, type }, 95 dataType: { specs, type },
94 identifier, 96 identifier,
@@ -136,6 +138,7 @@ export default { @@ -136,6 +138,7 @@ export default {
136 this.seriesFunctionList.forEach((item, index) => { 138 this.seriesFunctionList.forEach((item, index) => {
137 if (index == num) { 139 if (index == num) {
138 item.isShowModel = true 140 item.isShowModel = true
  141 + this.showPickerView=true
139 } 142 }
140 }) 143 })
141 const { specs } = dataType || {} 144 const { specs } = dataType || {}
@@ -154,6 +157,7 @@ export default { @@ -154,6 +157,7 @@ export default {
154 this.seriesFunctionList.forEach((item, index) => { 157 this.seriesFunctionList.forEach((item, index) => {
155 if (index == num) { 158 if (index == num) {
156 item.isShowModel = false 159 item.isShowModel = false
  160 + this.showPickerView=false
157 } 161 }
158 }) 162 })
159 }, 163 },
@@ -176,6 +180,7 @@ export default { @@ -176,6 +180,7 @@ export default {
176 this.seriesFunctionList.forEach((item, index) => { 180 this.seriesFunctionList.forEach((item, index) => {
177 if (index == num) { 181 if (index == num) {
178 item.isShowModel = true 182 item.isShowModel = true
  183 + this.showPickerView=true
179 } 184 }
180 }) 185 })
181 }, 186 },
@@ -186,6 +191,7 @@ export default { @@ -186,6 +191,7 @@ export default {
186 this.seriesFunctionList.forEach((item, index) => { 191 this.seriesFunctionList.forEach((item, index) => {
187 if (index == num) { 192 if (index == num) {
188 item.isShowModel = false 193 item.isShowModel = false
  194 + this.showPickerView=false
189 } 195 }
190 }) 196 })
191 }, 197 },
@@ -193,6 +199,7 @@ export default { @@ -193,6 +199,7 @@ export default {
193 this.seriesFunctionList.forEach((item, index) => { 199 this.seriesFunctionList.forEach((item, index) => {
194 if (index == num) { 200 if (index == num) {
195 item.isShowModel = false 201 item.isShowModel = false
  202 + this.showPickerView=false
196 } 203 }
197 }) 204 })
198 }, 205 },
  1 +## 1.0.0(2024-02-17)
  2 +1. 剥离图鸟框架,可通过 uni_modules 进行导入 节省开发成本
  1 +let color = [
  2 + 'red',
  3 + 'purplered',
  4 + 'purple',
  5 + 'bluepurple',
  6 + 'aquablue',
  7 + 'blue',
  8 + 'indigo',
  9 + 'cyan',
  10 + 'teal',
  11 + 'green',
  12 + 'yellowgreen',
  13 + 'lime',
  14 + 'yellow',
  15 + 'orangeyellow',
  16 + 'orange',
  17 + 'orangered',
  18 + 'brown',
  19 + 'grey',
  20 + 'gray'
  21 +]
  22 +
  23 +// 酷炫颜色的数量
  24 +const COOL_BG_COLOR_COUNT = 16
  25 +
  26 +/**
  27 + * 获取图鸟配色颜色列表
  28 + */
  29 +function getTuniaoColorList() {
  30 + return color
  31 +}
  32 +
  33 +/**
  34 + * 获取指定类型的随机颜色对应的类
  35 + * @param {String} type 颜色类型
  36 + */
  37 +function getRandomColorClass(type = 'bg') {
  38 + const index = Math.floor(Math.random() * color.length)
  39 + const colorValue = color[index]
  40 +
  41 + return 'tn-' + type + '-' + colorValue
  42 +}
  43 +
  44 +/**
  45 + * 随机获取酷炫背景对应的类
  46 + */
  47 +function getRandomCoolBgClass() {
  48 + const index = (Math.random() * COOL_BG_COLOR_COUNT) + 1
  49 + return 'tn-cool-bg-color-' + Math.floor(index)
  50 +}
  51 +
  52 +/**
  53 + * 根据传入的值获取内部背景颜色类
  54 + *
  55 + * @param {String} backgroundColor 背景颜色信息
  56 + */
  57 +function getBackgroundColorInternalClass(backgroundColor = '') {
  58 + if (!backgroundColor) return ''
  59 +
  60 + if (['tn-bg', 'tn-dynamic-bg', 'tn-main-gradient', 'tn-cool-bg'].some(item => {
  61 + return backgroundColor.includes(item)
  62 + })) {
  63 + return backgroundColor
  64 + }
  65 + return ''
  66 +}
  67 +
  68 +/**
  69 + * 根据传入的值获取背景颜色样式
  70 + *
  71 + * @param {String} backgroundColor 背景颜色信息
  72 + */
  73 +function getBackgroundColorStyle(backgroundColor = '') {
  74 + if (!backgroundColor) return ''
  75 +
  76 + if (!backgroundColor.startsWith('tn-') || ['#', 'rgb', 'rgba'].some(item => {
  77 + return backgroundColor.includes(item)
  78 + })) {
  79 + return backgroundColor
  80 + }
  81 + return ''
  82 +}
  83 +
  84 +/**
  85 + * 根据传入的值获取内部字体颜色类
  86 + *
  87 + * @param {String} fontColor 背景颜色信息
  88 + */
  89 +function getFontColorInternalClass(fontColor = '') {
  90 + if (!fontColor) return ''
  91 +
  92 + if (['tn-color'].some(item => {
  93 + return fontColor.includes(item)
  94 + })) {
  95 + return fontColor
  96 + }
  97 + return ''
  98 +}
  99 +
  100 +/**
  101 + * 根据传入的值获取字体颜色样式
  102 + *
  103 + * @param {String} fontColor 背景颜色信息
  104 + */
  105 +function getFontColorStyle(fontColor = '') {
  106 + if (!fontColor) return ''
  107 +
  108 + if (!fontColor.startsWith('tn-') || ['#', 'rgb', 'rgba'].some(item => {
  109 + return fontColor.includes(item)
  110 + })) {
  111 + return fontColor
  112 + }
  113 + return ''
  114 +}
  115 +
  116 +/**
  117 + * 求两个颜色之间的渐变值
  118 + *
  119 + * @param {String} startColor 开始颜色
  120 + * @param {String} endColor 结束颜色
  121 + * @param {Number} step 颜色等分的份额
  122 + */
  123 +function colorGradient(startColor = 'rgb(0, 0, 0)', endColor='rgb(255, 255, 255)', step = 10) {
  124 + let startRGB = hexToRGB(startColor, false)
  125 + let startR = startRGB[0]
  126 + let startG = startRGB[1]
  127 + let startB = startRGB[2]
  128 +
  129 + let endRGB = hexToRGB(endColor, false)
  130 + let endR = endRGB[0]
  131 + let endG = endRGB[1]
  132 + let endB = endRGB[2]
  133 +
  134 + // 求差值
  135 + let R = (endR - startR) / step
  136 + let G = (endG - startG) / step
  137 + let B = (endB - startB) / step
  138 +
  139 + let colorArr = []
  140 + for (let i = 0; i < step; i++) {
  141 + // 计算每一步的hex值
  142 + let hex = rgbToHex(`rgb(${Math.round(R * i + startR)}, ${Math.round(G * i + startG)}, ${Math.round(B * i + startB)})`)
  143 + colorArr.push(hex)
  144 + }
  145 + return colorArr
  146 +}
  147 +
  148 +/**
  149 + * 将hex的颜色表示方式转换为rgb表示方式
  150 + *
  151 + * @param {String} color 颜色
  152 + * @param {Boolean} str 是否返回字符串
  153 + * @return {Array|String} rgb的值
  154 + */
  155 +function hexToRGB(color, str = true) {
  156 + let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
  157 +
  158 + color = color.toLowerCase()
  159 + if (color && reg.test(color)) {
  160 + // #000 => #000000
  161 + if (color.length === 4) {
  162 + let colorNew = '#'
  163 + for (let i = 1; i < 4; i++) {
  164 + colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
  165 + }
  166 + color = colorNew
  167 + }
  168 + // 处理六位的颜色值
  169 + let colorChange = []
  170 + for (let i = 1; i < 7; i += 2) {
  171 + colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
  172 + }
  173 + if (!str) {
  174 + return colorChange
  175 + } else {
  176 + return `rgb(${colorChange[0]}, ${colorChange[1]}, ${colorChange[2]})`
  177 + }
  178 + } else if (/^(rgb|RGB)/.test(color)) {
  179 + let arr = color.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(',')
  180 + return arr.map(item => Number(item))
  181 + } else {
  182 + return color
  183 + }
  184 +}
  185 +
  186 +/**
  187 + * 将rgb的颜色表示方式转换成hex表示方式
  188 + *
  189 + * @param {Object} rgb rgb颜色值
  190 + */
  191 +function rgbToHex(rgb) {
  192 + let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
  193 + if (/^(rgb|RGB)/.test(rgb)) {
  194 + let color = rgb.replace(/(?:\(|\)|rgb|GRB)*/g, "").split(',')
  195 + let strHex = '#'
  196 + for (let i = 0; i < color.length; i++) {
  197 + let hex = Number(color[i]).toString(16)
  198 + // 保证每个值否是两位数
  199 + hex = String(hex).length === 1 ? 0 + '' + hex: hex
  200 + if (hex === '0') {
  201 + hex += hex
  202 + }
  203 + strHex += hex
  204 + }
  205 + if (strHex.length !== 7) {
  206 + strHex = rgb
  207 + }
  208 + return strHex
  209 + } else if (reg.test(rgb)) {
  210 + let num = rgb.replace(/#/, '').split('')
  211 + if (num.length === 6) {
  212 + return rgb
  213 + } else if (num.length === 3) {
  214 + let numHex = '#'
  215 + for (let i = 0; i < num.length; i++) {
  216 + numHex += (num[i] + num[i])
  217 + }
  218 + return numHex
  219 + }
  220 + } else {
  221 + return rgb
  222 + }
  223 +}
  224 +
  225 +/**
  226 + * 将传入的颜色值转换为rgba字符串
  227 + *
  228 + * @param {String} color 颜色
  229 + * @param {Number} alpha 透明度
  230 + */
  231 +function colorToRGBA(color, alpha = 0.3) {
  232 + color = rgbToHex(color)
  233 + // 十六进制颜色值的正则表达式
  234 + let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
  235 +
  236 + color = color.toLowerCase()
  237 + if (color && reg.test(color)) {
  238 + // #000 => #000000
  239 + if (color.length === 4) {
  240 + let colorNew = '#'
  241 + for (let i = 1; i < 4; i++) {
  242 + colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
  243 + }
  244 + color = colorNew
  245 + }
  246 + // 处理六位的颜色值
  247 + let colorChange = []
  248 + for (let i = 1; i < 7; i += 2) {
  249 + colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
  250 + }
  251 + return `rgba(${colorChange[0]}, ${colorChange[1]}, ${colorChange[2]}, ${alpha})`
  252 + } else {
  253 + return color
  254 + }
  255 +}
  256 +
  257 +export default {
  258 + COOL_BG_COLOR_COUNT: COOL_BG_COLOR_COUNT,
  259 + getTuniaoColorList,
  260 + getRandomColorClass,
  261 + getRandomCoolBgClass,
  262 + getBackgroundColorInternalClass,
  263 + getBackgroundColorStyle,
  264 + getFontColorInternalClass,
  265 + getFontColorStyle,
  266 + colorGradient,
  267 + hexToRGB,
  268 + rgbToHex,
  269 + colorToRGBA
  270 +}
  1 +<template>
  2 + <view
  3 + v-if="visibleSync"
  4 + class="tn-popup-class tn-popup"
  5 + :style="[customStyle, popupStyle, { zIndex: elZIndex - 1}]"
  6 + hover-stop-propagation
  7 + >
  8 + <!-- mask -->
  9 + <view
  10 + class="tn-popup__mask"
  11 + :class="[{'tn-popup__mask--show': showPopup && mask}]"
  12 + :style="{zIndex: elZIndex - 2}"
  13 + @tap="maskClick"
  14 + @touchmove.stop.prevent = "() => {}"
  15 + hover-stop-propagation
  16 + ></view>
  17 + <!-- 弹框内容 -->
  18 + <view
  19 + class="tn-popup__content"
  20 + :class="[
  21 + mode !== 'center' ? backgroundColorClass : '',
  22 + safeAreaInsetBottom ? 'tn-safe-area-inset-bottom' : '',
  23 + 'tn-popup--' + mode,
  24 + showPopup ? 'tn-popup__content--visible' : '',
  25 + zoom && mode === 'center' ? 'tn-popup__content__center--animation-zoom' : ''
  26 + ]"
  27 + :style="[contentStyle]"
  28 + @tap="modeCenterClose"
  29 + @touchmove.stop.prevent
  30 + @tap.stop.prevent
  31 + >
  32 + <!-- 居中时候的内容 -->
  33 + <view
  34 + v-if="mode === 'center'"
  35 + class="tn-popup__content__center_box"
  36 + :class="[backgroundColorClass]"
  37 + :style="[centerStyle]"
  38 + @touchmove.stop.prevent
  39 + @tap.stop.prevent
  40 + >
  41 + <!-- 关闭按钮 -->
  42 + <view
  43 + v-if="closeBtn"
  44 + class="tn-popup__close"
  45 + :class="[`tn-icon-${closeBtnIcon}`, `tn-popup__close--${closeBtnPosition}`]"
  46 + :style="[closeBtnStyle, {zIndex: elZIndex}]"
  47 + @tap="close"
  48 + ></view>
  49 + <scroll-view scroll-y class="tn-popup__content__scroll-view">
  50 + <slot></slot>
  51 + </scroll-view>
  52 + </view>
  53 +
  54 + <!-- 除居中外的其他情况 -->
  55 + <scroll-view scroll-y v-else class="tn-popup__content__scroll-view">
  56 + <slot></slot>
  57 + </scroll-view>
  58 + <!-- 关闭按钮 -->
  59 + <view
  60 + v-if="mode !== 'center' && closeBtn"
  61 + class="tn-popup__close"
  62 + :class="[`tn-popup__close--${closeBtnPosition}`]"
  63 + :style="{zIndex: elZIndex}"
  64 + @tap="close"
  65 + >
  66 + <view :class="[`tn-icon-${closeBtnIcon}`]" :style="[closeBtnStyle]"></view>
  67 + </view>
  68 + </view>
  69 + </view>
  70 +</template>
  71 +
  72 +<script>
  73 + import componentsColorMixin from '../../components_color.js'
  74 + // 格式化字符串工具
  75 + import string from '../../string.js'
  76 + export default {
  77 + mixins: [componentsColorMixin],
  78 + name: 'tn-popup',
  79 + props: {
  80 + value: {
  81 + type: Boolean,
  82 + default: false
  83 + },
  84 + // 弹出方向
  85 + // left/right/top/bottom/center
  86 + mode: {
  87 + type: String,
  88 + default: 'left'
  89 + },
  90 + // 是否显示遮罩
  91 + mask: {
  92 + type: Boolean,
  93 + default: true
  94 + },
  95 + // 抽屉的宽度(mode=left/right),高度(mode=top/bottom)
  96 + length: {
  97 + type: [Number, String],
  98 + default: 'auto'
  99 + },
  100 + // 宽度,只对左,右,中部弹出时起作用,单位rpx,或者"auto"
  101 + // 或者百分比"50%",表示由内容撑开高度或者宽度,优先级高于length参数
  102 + width: {
  103 + type: String,
  104 + default: ''
  105 + },
  106 + // 高度,只对上,下,中部弹出时起作用,单位rpx,或者"auto"
  107 + // 或者百分比"50%",表示由内容撑开高度或者宽度,优先级高于length参数
  108 + height: {
  109 + type: String,
  110 + default: ''
  111 + },
  112 + // 是否开启动画,只在mode=center有效
  113 + zoom: {
  114 + type: Boolean,
  115 + default: true
  116 + },
  117 + // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
  118 + safeAreaInsetBottom: {
  119 + type: Boolean,
  120 + default: false
  121 + },
  122 + // 是否可以通过点击遮罩进行关闭
  123 + maskCloseable: {
  124 + type: Boolean,
  125 + default: true
  126 + },
  127 + // 用户自定义样式
  128 + customStyle: {
  129 + type: Object,
  130 + default() {
  131 + return {}
  132 + }
  133 + },
  134 + // 显示圆角的大小
  135 + borderRadius: {
  136 + type: Number,
  137 + default: 0
  138 + },
  139 + // zIndex
  140 + zIndex: {
  141 + type: Number,
  142 + default: 0
  143 + },
  144 + // 是否显示关闭按钮
  145 + closeBtn: {
  146 + type: Boolean,
  147 + default: false
  148 + },
  149 + // 关闭按钮的图标
  150 + closeBtnIcon: {
  151 + type: String,
  152 + default: 'close'
  153 + },
  154 + // 关闭按钮显示的位置
  155 + // top-left/top-right/bottom-left/bottom-right
  156 + closeBtnPosition: {
  157 + type: String,
  158 + default: 'top-right'
  159 + },
  160 + // 关闭按钮图标颜色
  161 + closeIconColor: {
  162 + type: String,
  163 + default: '#AAAAAA'
  164 + },
  165 + // 关闭按钮图标的大小
  166 + closeIconSize: {
  167 + type: Number,
  168 + default: 30
  169 + },
  170 + // 给一个负的margin-top,往上偏移,避免和键盘重合的情况,仅在mode=center时有效
  171 + negativeTop: {
  172 + type: Number,
  173 + default: 0
  174 + },
  175 + // marginTop,在mode = top,left,right时生效,避免用户使用了自定义导航栏,组件把导航栏遮挡了
  176 + marginTop: {
  177 + type: Number,
  178 + default: 0
  179 + },
  180 + // 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件
  181 + // 对v-model双向绑定多层调用造成报错不能修改props值的问题
  182 + popup: {
  183 + type: Boolean,
  184 + default: true
  185 + },
  186 + },
  187 + computed: {
  188 + // 处理使用了自定义导航栏时被遮挡的问题
  189 + popupStyle() {
  190 + let style = {}
  191 + if ((this.mode === 'top' || this.mode === 'left' || this.mode === 'right') && this.marginTop) {
  192 + style.marginTop = string.getLengthUnitValue(this.marginTop, 'px')
  193 + }
  194 +
  195 + return style
  196 + },
  197 + // 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom)
  198 + contentStyle() {
  199 + let style = {}
  200 + // 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏
  201 + if (this.mode === 'left' || this.mode === 'right') {
  202 + style = {
  203 + width: this.width ? string.getLengthUnitValue(this.width) : string.getLengthUnitValue(this.length),
  204 + height: '100%',
  205 + transform: `translate3D(${this.mode === 'left' ? '-100%' : '100%'}, 0px, 0px)`
  206 + }
  207 + } else if (this.mode === 'top' || this.mode === 'bottom') {
  208 + style = {
  209 + width: '100%',
  210 + height: this.height ? string.getLengthUnitValue(this.height) : string.getLengthUnitValue(this.length),
  211 + transform: `translate3D(0px, ${this.mode === 'top' ? '-100%': '100%'}, 0px)`
  212 + }
  213 + }
  214 + style.zIndex = this.elZIndex
  215 + // 如果设置了圆角的值,添加弹窗的圆角
  216 + if (this.borderRadius) {
  217 + switch(this.mode) {
  218 + case 'left':
  219 + style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`
  220 + break
  221 + case 'top':
  222 + style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`
  223 + break
  224 + case 'right':
  225 + style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`
  226 + break
  227 + case 'bottom':
  228 + style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`
  229 + break
  230 + }
  231 + style.overflow = 'hidden'
  232 + }
  233 +
  234 + if (this.backgroundColorStyle && this.mode !== 'center') {
  235 + style.backgroundColor = this.backgroundColorStyle
  236 + }
  237 +
  238 + return style
  239 + },
  240 + // 中部弹窗的样式
  241 + centerStyle() {
  242 + let style = {}
  243 + style.width = this.width ? string.getLengthUnitValue(this.width) : string.getLengthUnitValue(this.length)
  244 + // 中部弹出的模式,如果没有设置高度,就用auto值,由内容撑开
  245 + style.height = this.height ? string.getLengthUnitValue(this.height) : 'auto'
  246 + style.zIndex = this.elZIndex
  247 + if (this.negativeTop) {
  248 + style.marginTop = `-${string.getLengthUnitValue(this.negativeTop)}`
  249 + }
  250 + if (this.borderRadius) {
  251 + style.borderRadius = `${this.borderRadius}rpx`
  252 + style.overflow='hidden'
  253 + }
  254 + if (this.backgroundColorStyle) {
  255 + style.backgroundColor = this.backgroundColorStyle
  256 + }
  257 + return style
  258 + },
  259 + // 关闭按钮样式
  260 + closeBtnStyle() {
  261 + let style = {}
  262 + if (this.closeIconColor) {
  263 + style.color = this.closeIconColor
  264 + }
  265 + if (this.closeIconSize) {
  266 + style.fontSize = this.closeIconSize + 'rpx'
  267 + }
  268 +
  269 + return style
  270 + },
  271 + elZIndex() {
  272 + return this.zIndex ? this.zIndex : 20075
  273 + }
  274 + },
  275 + data() {
  276 + return {
  277 + timer: null,
  278 + visibleSync: false,
  279 + showPopup: false,
  280 + closeFromInner: false
  281 + }
  282 + },
  283 + watch: {
  284 + value(val) {
  285 + if (val) {
  286 + // console.log(this.visibleSync);
  287 + if (this.visibleSync) {
  288 + this.visibleSync = false
  289 + return
  290 + }
  291 + this.open()
  292 + } else if (!this.closeFromInner) {
  293 + this.close()
  294 + }
  295 + this.closeFromInner = false
  296 + }
  297 + },
  298 + mounted() {
  299 + // 组件渲染完成时,检查value是否为true,如果是,弹出popup
  300 + this.value && this.open()
  301 + },
  302 + methods: {
  303 + // 点击遮罩
  304 + maskClick() {
  305 + if (!this.maskCloseable) return
  306 + this.close()
  307 + },
  308 + open() {
  309 + this.change('visibleSync', 'showPopup', true)
  310 + },
  311 + // 关闭弹框
  312 + close() {
  313 + // 标记关闭是内部发生的,否则修改了value值,导致watch中对value检测,导致再执行一遍close
  314 + // 造成@close事件触发两次
  315 + this.closeFromInner = true
  316 + this.change('showPopup', 'visibleSync', false)
  317 + },
  318 + // 中部弹出时,需要.tn-drawer-content将内容居中,此元素会铺满屏幕,点击需要关闭弹窗
  319 + // 让其只在mode=center时起作用
  320 + modeCenterClose() {
  321 + if (this.mode != 'center' || !this.maskCloseable) return
  322 + this.close()
  323 + },
  324 + // 关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
  325 + // 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
  326 + change(param1, param2, status) {
  327 + // 如果this.popup为false,意味着为picker,actionsheet等组件调用了popup组件
  328 + if (this.popup === true) {
  329 + this.$emit('input', status)
  330 + }
  331 + this[param1] = status
  332 + if (status) {
  333 + // #ifdef H5 || MP
  334 + this.timer = setTimeout(() => {
  335 + this[param2] = status
  336 + this.$emit(status ? 'open' : 'close')
  337 + clearTimeout(this.timer)
  338 + }, 10)
  339 + // #endif
  340 + // #ifndef H5 || MP
  341 + this.$nextTick(() => {
  342 + this[param2] = status
  343 + this.$emit(status ? 'open' : 'close')
  344 + })
  345 + // #endif
  346 + } else {
  347 + this.timer = setTimeout(() => {
  348 + this[param2] = status
  349 + this.$emit(status ? 'open' : 'close')
  350 + clearTimeout(this.timer)
  351 + }, 250)
  352 + }
  353 + }
  354 + }
  355 + }
  356 +</script>
  357 +
  358 +<style lang="scss" scoped>
  359 + @import '../../theme.scss';
  360 + .tn-popup {
  361 + /* #ifndef APP-NVUE */
  362 + display: block;
  363 + /* #endif */
  364 + position: fixed;
  365 + top: 0;
  366 + left: 0;
  367 + right: 0;
  368 + bottom: 0;
  369 + overflow: hidden;
  370 + z-index: 29091 !important;
  371 +
  372 + &__content {
  373 + /* #ifndef APP-NVUE */
  374 + display: block;
  375 + /* #endif */
  376 + position: absolute;
  377 + transition: all 0.25s linear;
  378 +
  379 + &--visible {
  380 + transform: translate3D(0px, 0px, 0px) !important;
  381 + &.tn-popup--center {
  382 + transform: scale(1);
  383 + opacity: 1;
  384 + }
  385 + }
  386 +
  387 + &__center_box {
  388 + min-width: 100rpx;
  389 + min-height: 100rpx;
  390 + /* #ifndef APP-NVUE */
  391 + display: block;
  392 + /* #endif */
  393 + position: relative;
  394 + background-color: #FFFFFF;
  395 + }
  396 +
  397 + &__scroll-view {
  398 + width: 100%;
  399 + height: 100%;
  400 + }
  401 +
  402 + &__center--animation-zoom {
  403 + transform: scale(1.15);
  404 + }
  405 + }
  406 +
  407 + &__scroll_view {
  408 + width: 100%;
  409 + height: 100%;
  410 + }
  411 +
  412 + &--left {
  413 + top: 0;
  414 + bottom: 0;
  415 + left: 0;
  416 + background-color: #FFFFFF;
  417 + }
  418 +
  419 + &--right {
  420 + top: 0;
  421 + bottom: 0;
  422 + right: 0;
  423 + background-color: #FFFFFF;
  424 + }
  425 +
  426 + &--top {
  427 + left: 0;
  428 + right: 0;
  429 + top: 0;
  430 + background-color: #FFFFFF;
  431 + }
  432 +
  433 + &--bottom {
  434 + left: 0;
  435 + right: 0;
  436 + bottom: 0;
  437 + background-color: #FFFFFF;
  438 + }
  439 +
  440 + &--center {
  441 + display: flex;
  442 + flex-direction: column;
  443 + bottom: 0;
  444 + top: 0;
  445 + left: 0;
  446 + right: 0;
  447 + justify-content: center;
  448 + align-items: center;
  449 + opacity: 0;
  450 + }
  451 +
  452 + &__close {
  453 + position: absolute;
  454 +
  455 + &--top-left {
  456 + top: 30rpx;
  457 + left: 30rpx;
  458 + }
  459 +
  460 + &--top-right {
  461 + top: 30rpx;
  462 + right: 30rpx;
  463 + }
  464 +
  465 + &--bottom-left {
  466 + bottom: 30rpx;
  467 + left: 30rpx;
  468 + }
  469 +
  470 + &--bottom-right {
  471 + bottom: 30rpx;
  472 + right: 30rpx;
  473 + }
  474 + }
  475 +
  476 + &__mask {
  477 + width: 100%;
  478 + height: 100%;
  479 + position: fixed;
  480 + top: 0;
  481 + left: 0;
  482 + right: 0;
  483 + border: 0;
  484 + background-color: $tn-mask-bg-color;
  485 + transition: 0.25s linear;
  486 + transition-property: opacity;
  487 + opacity: 0;
  488 +
  489 + &--show {
  490 + opacity: 1;
  491 + }
  492 + }
  493 + }
  494 +</style>
  1 +// 格式化字符串工具
  2 +import string from './string.js'
  3 +// 获取颜色工具
  4 +import color from './color.js'
  5 +module.exports = {
  6 + data() {
  7 +
  8 + },
  9 + props: {
  10 + // 背景颜色
  11 + backgroundColor: {
  12 + type: String,
  13 + default: ''
  14 + },
  15 + // 字体颜色
  16 + fontColor: {
  17 + type: String,
  18 + default: ''
  19 + },
  20 + // 字体大小
  21 + fontSize: {
  22 + type: Number,
  23 + default: 0
  24 + },
  25 + // 字体大小单位
  26 + fontUnit: {
  27 + type: String,
  28 + default: 'rpx'
  29 + }
  30 + },
  31 + computed: {
  32 + backgroundColorStyle() {
  33 + return color.getBackgroundColorStyle(this.backgroundColor)
  34 + },
  35 + backgroundColorClass() {
  36 + return color.getBackgroundColorInternalClass(this.backgroundColor)
  37 + },
  38 + fontColorStyle() {
  39 + return color.getFontColorStyle(this.fontColor)
  40 + },
  41 + fontColorClass() {
  42 + return color.getFontColorInternalClass(this.fontColor)
  43 + },
  44 + fontSizeStyle() {
  45 + string.getLengthUnitValue(this.fontSize, this.fontUnit)
  46 + }
  47 + },
  48 + methods: {
  49 +
  50 + }
  51 +}
  1 +{
  2 + "id": "tn-popup",
  3 + "displayName": "tn-popup 弹出层 全面兼容vue2、app、h5、wx小程序",
  4 + "version": "1.0.0",
  5 + "description": "弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义",
  6 + "keywords": [
  7 + "tuniaoui",
  8 + "tuniao",
  9 + "tn-popup",
  10 + "popup",
  11 + "弹出层"
  12 +],
  13 + "repository": "",
  14 + "engines": {
  15 + "HBuilderX": "^3.1.0"
  16 + },
  17 + "dcloudext": {
  18 + "type": "component-vue",
  19 + "sale": {
  20 + "regular": {
  21 + "price": "0.00"
  22 + },
  23 + "sourcecode": {
  24 + "price": "0.00"
  25 + }
  26 + },
  27 + "contact": {
  28 + "qq": ""
  29 + },
  30 + "declaration": {
  31 + "ads": "无",
  32 + "data": "无",
  33 + "permissions": "无"
  34 + },
  35 + "npmurl": ""
  36 + },
  37 + "uni_modules": {
  38 + "dependencies": [],
  39 + "encrypt": [],
  40 + "platforms": {
  41 + "cloud": {
  42 + "tcb": "y",
  43 + "aliyun": "y",
  44 + "alipay": "y"
  45 + },
  46 + "client": {
  47 + "Vue": {
  48 + "vue2": "y",
  49 + "vue3": "u"
  50 + },
  51 + "App": {
  52 + "app-vue": "y",
  53 + "app-nvue": "u",
  54 + "app-uvue": "u"
  55 + },
  56 + "H5-mobile": {
  57 + "Safari": "y",
  58 + "Android Browser": "y",
  59 + "微信浏览器(Android)": "y",
  60 + "QQ浏览器(Android)": "y"
  61 + },
  62 + "H5-pc": {
  63 + "Chrome": "y",
  64 + "IE": "y",
  65 + "Edge": "y",
  66 + "Firefox": "y",
  67 + "Safari": "y"
  68 + },
  69 + "小程序": {
  70 + "微信": "y",
  71 + "阿里": "u",
  72 + "百度": "u",
  73 + "字节跳动": "u",
  74 + "QQ": "u",
  75 + "钉钉": "u",
  76 + "快手": "u",
  77 + "飞书": "u",
  78 + "京东": "u"
  79 + },
  80 + "快应用": {
  81 + "华为": "u",
  82 + "联盟": "u"
  83 + }
  84 + }
  85 + }
  86 + }
  87 +}
  1 +# Popup 弹出层
  2 +> 组件名: tn-popup
  3 +> 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。
  4 +# 提示
  5 +> popup弹出层中对应自定义内容的容器中已经内置了scroll-view元素,内如内容超出容器的高度,将会自动获得垂直滚动的特性,如果您因为在slot内容做了滚动的处理,而造成了 冲突的话,请移除自定义关于滚动部分的逻辑。
  6 +# [查看文档](https://vue2.tuniaokj.com/components/popup.html)
  7 +## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=tuniao-tuniaoui)
  8 +## [问题反馈/提出建议](https://vue2.tuniaokj.com/cooperation/about.html)
  9 +![](https://vue2.tuniaokj.com/index/index.jpg)
  1 +/**
  2 + * 去掉字符串中空格
  3 + *
  4 + * @param {String} str 待处理的字符串
  5 + * @param {String} type 处理类型
  6 + */
  7 +function trim(str, type = 'both') {
  8 + if (type === 'both') {
  9 + return str.replace(/^\s+|\s+$/g, "")
  10 + } else if (type === 'left') {
  11 + return str.replace(/^\s*/g, "")
  12 + } else if (type === 'right') {
  13 + return str.replace(/(\s*$)/g, "")
  14 + } else if (type === 'all') {
  15 + return str.replace(/\s+/g, "")
  16 + } else {
  17 + return str
  18 + }
  19 +}
  20 +
  21 +/**
  22 + * 获取带单位的长度值
  23 + *
  24 + * @param {String} value 待处理的值
  25 + * @param {String} unit 单位
  26 + */
  27 +function getLengthUnitValue(value, unit = 'rpx') {
  28 + if (!value) {
  29 + return ''
  30 + }
  31 + if (/(%|px|rpx|auto)$/.test(value)) return value
  32 + else return value + unit
  33 +}
  34 +
  35 +/**
  36 + * 将驼峰命名的字符串转换为指定连接符来进行连接
  37 + *
  38 + * @param {Object} string 待转换的字符串
  39 + * @param {Object} replace 进行连接的字符
  40 + */
  41 +function humpConvertChar(string, replace = '_') {
  42 + if (!string || !replace) {
  43 + return ''
  44 + }
  45 + return string.replace(/([A-Z])/g, `${replace}$1`).toLowerCase()
  46 +}
  47 +
  48 +/**
  49 + * 将用指定连接符来进行连接的字符串转为驼峰命名的字符串
  50 + *
  51 + * @param {Object} string 待转换的字符串
  52 + * @param {Object} replace 进行连接的字符
  53 + */
  54 +function charConvertHump(string, replace = '_') {
  55 + if (!string || !replace) {
  56 + return ''
  57 + }
  58 + let reg = RegExp(replace + "(\\w)", "g")
  59 + return string.replace(reg, function(all, letter) {
  60 + return letter.toUpperCase()
  61 + })
  62 +}
  63 +
  64 +export default {
  65 + trim,
  66 + getLengthUnitValue,
  67 + humpConvertChar,
  68 + charConvertHump
  69 +}
  1 +// 此文件为TuniaoUI的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于
  2 +// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
  3 +// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
  4 +
  5 +// 组件配置
  6 +$tn-form-item-height: 70rpx;
  7 +
  8 +
  9 +// 主颜色
  10 +$tn-main-color: #01BEFF;
  11 +$tn-main-orange: #FBBD12;
  12 +$tn-embellished-green: #00FFC6;
  13 +$tn-embellished-yellow: #FFF00D;
  14 +$tn-auxiliary-powder: #FF71D2;
  15 +$tn-auxiliary-blue: #82B2FF;
  16 +$tn-bg-color: #FFFFFF;
  17 +$tn-bg-gray-color: #F4F4F4;
  18 +$tn-space-color: #F8F7F8;
  19 +
  20 +// 边框颜色
  21 +$tn-border-solid-color: rgba(0, 0, 0, 0.1);
  22 +$tn-border-solids-color: #eee;
  23 +$tn-border-dashed-color: #ddd;
  24 +
  25 +// 阴影颜色
  26 +$tn-shadow-color: rgba(0, 0, 0, 0.1);
  27 +$tn-box-shadow-color: rgba(0, 0, 0, 0.2);
  28 +
  29 +// 字体颜色
  30 +$tn-font-color: #080808;
  31 +$tn-font-sub-color: #AAAAAA;
  32 +$tn-content-color: #838383;
  33 +$tn-font-holder-color: #E6E6E6;
  34 +
  35 +$tn-mask-bg-color: rgba(0, 0, 0, 0.4);
  36 +
  37 +$tn-progress-bg-color: #f0f0f0;
  38 +
  39 +
  40 +$tn-color-red: #E83A30;
  41 +$tn-color-red-dark: #BA2E26;
  42 +$tn-color-red-disabled: #F39C97;
  43 +$tn-color-red-light: #FAD8D6;
  44 +
  45 +$tn-color-purplered: #E72F8C;
  46 +$tn-color-purplered-dark: #B9266F;
  47 +$tn-color-purplered-disabled: #F397C5;
  48 +$tn-color-purplered-light: #FAD5E8;
  49 +
  50 +$tn-color-purple: #892FE8;
  51 +$tn-color-purple-dark: #6E26BA;
  52 +$tn-color-purple-disabled: #C497F3;
  53 +$tn-color-purple-light: #E7D5FA;
  54 +
  55 +$tn-color-bluepurple: #5F4FD9;
  56 +$tn-color-bluepurple-dark: #4C3FAE;
  57 +$tn-color-bluepurple-disabled: #AFA7EC;
  58 +$tn-color-bluepurple-light: #DFDCF7;
  59 +
  60 +$tn-color-aquablue: #3646FF;
  61 +$tn-color-aquablue-dark: #2B38CC;
  62 +$tn-color-aquablue-disabled: #9AA2FF;
  63 +$tn-color-aquablue-light: #D7DAFF;
  64 +
  65 +$tn-color-blue: #3D7EFF;
  66 +$tn-color-blue-dark: #3165CC;
  67 +$tn-color-blue-disabled: #9EBEFF;
  68 +$tn-color-blue-light: #D8E5FF;
  69 +
  70 +$tn-color-indigo: #31C9E8;
  71 +$tn-color-indigo-dark: #27A1BA;
  72 +$tn-color-indigo-disabled: #98E4F3;
  73 +$tn-color-indigo-light: #D6F4FA;
  74 +
  75 +$tn-color-cyan: #2DE8BD;
  76 +$tn-color-cyan-dark: #24BA97;
  77 +$tn-color-cyan-disabled: #96F3DE;
  78 +$tn-color-cyan-light: #D5FAF2;
  79 +
  80 +$tn-color-teal: #24F083;
  81 +$tn-color-teal-dark: #1DC069;
  82 +$tn-color-teal-disabled: #91F7C1;
  83 +$tn-color-teal-light: #D3FCE6;
  84 +
  85 +$tn-color-green: #31E749;
  86 +$tn-color-green-dark: #27B93A;
  87 +$tn-color-green-disabled: #98F3A4;
  88 +$tn-color-green-light: #D6FADB;
  89 +
  90 +$tn-color-yellowgreen: #A4E82F;
  91 +$tn-color-yellowgreen-dark: #82BA26;
  92 +$tn-color-yellowgreen-disabled: #D1F397;
  93 +$tn-color-yellowgreen-light: #EDFAD5;
  94 +
  95 +$tn-color-lime: #D5EB00;
  96 +$tn-color-lime-dark: #AABC00;
  97 +$tn-color-lime-disabled: #E9F57F;
  98 +$tn-color-lime-light: #F7FBCC;
  99 +
  100 +$tn-color-yellow: #FFF420;
  101 +$tn-color-yellow-dark: #CCC21A;
  102 +$tn-color-yellow-disabled: #FFF88F;
  103 +$tn-color-yellow-light: #FFFDD2;
  104 +
  105 +$tn-color-orangeyellow: #FFCA28;
  106 +$tn-color-orangeyellow-dark: #CCA220;
  107 +$tn-color-orangeyellow-disabled: #FFE493;
  108 +$tn-color-orangeyellow-light: #FFF4D4;
  109 +
  110 +$tn-color-orange: #FFA726;
  111 +$tn-color-orange-dark: #CC851E;
  112 +$tn-color-orange-disabled: #FFD392;
  113 +$tn-color-orange-light: #FFEDD4 ;
  114 +
  115 +$tn-color-orangered: #FF7043;
  116 +$tn-color-orangered-dark: #CC5A36;
  117 +$tn-color-orangered-disabled: #FFB7A1;
  118 +$tn-color-orangered-light: #FFE2D9;
  119 +
  120 +$tn-color-brown: #914F2C;
  121 +$tn-color-brown-dark: #743F23;
  122 +$tn-color-brown-disabled: #C8A795;
  123 +$tn-color-brown-light: #E9DCD5;
  124 +
  125 +$tn-color-grey: #78909C;
  126 +$tn-color-grey-dark: #5F7E8B;
  127 +$tn-color-grey-disabled: #C6D1D8;
  128 +$tn-color-grey-light: #E4E9EC;
  129 +
  130 +$tn-color-gray: #AAAAAA;
  131 +$tn-color-gray-dark: #838383;
  132 +$tn-color-gray-disabled: #E6E6E6;
  133 +$tn-color-gray-light: #F8F7F8;
  134 +
  135 +$tn-cool-bg-color-1-start: #F5317F;
  136 +$tn-cool-bg-color-1-end: #FF7C6E;
  137 +
  138 +$tn-cool-bg-color-2-start: #CA26FF;
  139 +$tn-cool-bg-color-2-end: #F360A7;
  140 +
  141 +$tn-cool-bg-color-3-start: #A26FFC;
  142 +$tn-cool-bg-color-3-end: #9D12FF;
  143 +
  144 +$tn-cool-bg-color-4-start: #AA77F0;
  145 +$tn-cool-bg-color-4-end: #E871E5;
  146 +
  147 +$tn-cool-bg-color-5-start: #40A0F7;
  148 +$tn-cool-bg-color-5-end: #4866E6;
  149 +
  150 +$tn-cool-bg-color-6-start: #209CFF;
  151 +$tn-cool-bg-color-6-end: #68E0CF;
  152 +
  153 +$tn-cool-bg-color-7-start: #00C3FF;
  154 +$tn-cool-bg-color-7-end: #58FFF5;
  155 +
  156 +$tn-cool-bg-color-8-start: #00d1FF;
  157 +$tn-cool-bg-color-8-end: #69FF97;
  158 +
  159 +$tn-cool-bg-color-9-start: #0FD893;
  160 +$tn-cool-bg-color-9-end: #29ECBF;
  161 +
  162 +$tn-cool-bg-color-10-start: #0FD850;
  163 +$tn-cool-bg-color-10-end: #F9F047;
  164 +
  165 +$tn-cool-bg-color-11-start: #24FE41;
  166 +$tn-cool-bg-color-11-end: #F7FD47;
  167 +
  168 +$tn-cool-bg-color-12-start: #D6FF7F;
  169 +$tn-cool-bg-color-12-end: #00F657;
  170 +
  171 +$tn-cool-bg-color-13-start: #FA709A;
  172 +$tn-cool-bg-color-13-end: #FEE140;
  173 +
  174 +$tn-cool-bg-color-14-start: #FE5E9C;
  175 +$tn-cool-bg-color-14-end: #F1AA76;
  176 +
  177 +$tn-cool-bg-color-15-start: #FF3181;
  178 +$tn-cool-bg-color-15-end: #FF8331;
  179 +
  180 +$tn-cool-bg-color-16-start: #ED1C24;
  181 +$tn-cool-bg-color-16-end: #FECE12;
  182 +
  183 +