Commit 71af254dea10c884a49872a9cfedee78378073b5

Authored by xp.Huang
2 parents c885d453 7a1b5404

Merge branch 'feat/app-command' into 'main_dev'

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

See merge request yunteng/thingskit-app!157
... ... @@ -6,7 +6,8 @@
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" name="map-fill"></u-icon>
  9 + <u-icon v-if="deviceDetail.deviceInfo.longitude !== ''" @click="handleClick"
  10 + name="map-fill"></u-icon>
10 11 </view>
11 12 <view class="basic-text text-clip ml-2">
12 13 {{ deviceDetail.alias ? deviceDetail.alias : deviceDetail.name }}
... ... @@ -16,15 +17,11 @@
16 17 </view>
17 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 22 <view class="cu-item" @tap="handleAppShowModal" data-target="Modal">
25 23 <text>命令下发</text>
26 24 </view>
27   - <!-- #endif -->
28 25 </view>
29 26 </view>
30 27 <!-- 设备详情 -->
... ... @@ -60,369 +57,339 @@
60 57 </view>
61 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 66 </view>
110 67 </template>
111 68
112 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 255 </script>
289 256
290 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 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>
\ No newline at end of file
... ...
  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>
\ No newline at end of file
... ...
1 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 8 </template>
59 9
60 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 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>
\ No newline at end of file
... ...
... ... @@ -24,7 +24,7 @@
24 24 suffixIcon="arrow-down"
25 25 />
26 26 <u-picker
27   - :show="item.isShowModel"
  27 + :show="showPickerView"
28 28 :columns="[boolList]"
29 29 keyName="label"
30 30 @cancel="handleCancelBool(index)"
... ... @@ -42,7 +42,7 @@
42 42 suffixIcon="arrow-down"
43 43 />
44 44 <u-picker
45   - :show="item.isShowModel"
  45 + :show="showPickerView"
46 46 :columns="[enumList.map((item) => ({ label: item.name, value: item.value }))]"
47 47 keyName="label"
48 48 @cancel="handleCancelEnum(index)"
... ... @@ -82,6 +82,7 @@ export default {
82 82 },
83 83 data() {
84 84 return {
  85 + showPickerView:false,
85 86 boolInfo: {},
86 87 enumInfo: {},
87 88 seriesForm: {},
... ... @@ -178,12 +179,12 @@ export default {
178 179 handleBool(name, dataType, num) {
179 180 this.seriesFunctionList.forEach((item, index) => {
180 181 if (index == num) {
181   - item.isShowModel = true
  182 + this.showPickerView = true
  183 + item.isShowModel = true
182 184 }
183 185 })
184 186 const { specs } = dataType || {}
185 187 const { boolClose, boolOpen } = specs || {}
186   - console.log(boolClose, boolOpen,'boolClose, boolOpen')
187 188 if(!boolClose&&!boolOpen){
188 189 uni.$u.toast(`暂无可选的${name}`)
189 190 this.boolList = []
... ... @@ -201,6 +202,7 @@ export default {
201 202 this.seriesFunctionList.forEach((item, index) => {
202 203 if (index == num) {
203 204 item.isShowModel = false
  205 + this.showPickerView = false
204 206 }
205 207 })
206 208 },
... ... @@ -220,6 +222,7 @@ export default {
220 222 this.seriesFunctionList.forEach((item, index) => {
221 223 if (index == num) {
222 224 item.isShowModel = true
  225 + this.showPickerView = true
223 226 }
224 227 })
225 228 },
... ... @@ -231,6 +234,7 @@ export default {
231 234 this.seriesFunctionList.forEach((item, index) => {
232 235 if (index == num) {
233 236 item.isShowModel = false
  237 + this.showPickerView = false
234 238 }
235 239 })
236 240 },
... ... @@ -241,6 +245,7 @@ export default {
241 245 this.seriesFunctionList.forEach((item, index) => {
242 246 if (index == num) {
243 247 item.isShowModel = false
  248 + this.showPickerView = false
244 249 }
245 250 })
246 251 },
... ... @@ -248,6 +253,7 @@ export default {
248 253 this.seriesFunctionList.forEach((item, index) => {
249 254 if (index == num) {
250 255 item.isShowModel = false
  256 + this.showPickerView = false
251 257 }
252 258 })
253 259 },
... ...
... ... @@ -15,7 +15,7 @@
15 15 suffixIcon="arrow-down"
16 16 />
17 17 <u-picker
18   - :show="item.isShowModel"
  18 + :show="showPickerView"
19 19 :columns="[boolList]"
20 20 keyName="label"
21 21 closeOnClickOverlay
... ... @@ -36,7 +36,7 @@
36 36 suffixIcon="arrow-down"
37 37 />
38 38 <u-picker
39   - :show="item.isShowModel"
  39 + :show="showPickerView"
40 40 :columns="[enumList.map((item) => ({ label: item.name, value: item.value }))]"
41 41 keyName="label"
42 42 closeOnClickOverlay
... ... @@ -61,6 +61,7 @@ export default {
61 61 },
62 62 data() {
63 63 return {
  64 + showPickerView: false,
64 65 boolInfo: {},
65 66 enumInfo: {},
66 67 seriesForm: {},
... ... @@ -89,6 +90,7 @@ export default {
89 90 this.seriesFunctionList = this.seriesInputData
90 91 this.seriesInputData.forEach((item) => {
91 92 this.$set(item, 'isShowModel', false)
  93 + this.showPickerView=false
92 94 const {
93 95 dataType: { specs, type },
94 96 identifier,
... ... @@ -136,6 +138,7 @@ export default {
136 138 this.seriesFunctionList.forEach((item, index) => {
137 139 if (index == num) {
138 140 item.isShowModel = true
  141 + this.showPickerView=true
139 142 }
140 143 })
141 144 const { specs } = dataType || {}
... ... @@ -154,6 +157,7 @@ export default {
154 157 this.seriesFunctionList.forEach((item, index) => {
155 158 if (index == num) {
156 159 item.isShowModel = false
  160 + this.showPickerView=false
157 161 }
158 162 })
159 163 },
... ... @@ -176,6 +180,7 @@ export default {
176 180 this.seriesFunctionList.forEach((item, index) => {
177 181 if (index == num) {
178 182 item.isShowModel = true
  183 + this.showPickerView=true
179 184 }
180 185 })
181 186 },
... ... @@ -186,6 +191,7 @@ export default {
186 191 this.seriesFunctionList.forEach((item, index) => {
187 192 if (index == num) {
188 193 item.isShowModel = false
  194 + this.showPickerView=false
189 195 }
190 196 })
191 197 },
... ... @@ -193,6 +199,7 @@ export default {
193 199 this.seriesFunctionList.forEach((item, index) => {
194 200 if (index == num) {
195 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 +}
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
  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)
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
  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 +
... ...