From d7afd94e3858b85899c32c8f2b22376b15c3eab2 Mon Sep 17 00:00:00 2001
From: lefengyang <lefengyang@tencent.com>
Date: Sun, 09 Aug 2026 01:36:01 +0800
Subject: [PATCH] uni_module
---
ledApp/uni_modules/uv-tooltip/changelog.md | 13
ledApp/uni_modules/uv-ui-tools/package.json | 81
ledApp/uni_modules/uv-waterfall/package.json | 89
ledApp/uni_modules/uv-loading-page/package.json | 89
ledApp/uni_modules/uv-ui-tools/libs/css/color.scss | 32
ledApp/uni_modules/uv-tabbar/components/uv-tabbar-item/props.js | 40
ledApp/uni_modules/uv-ui-tools/libs/mixin/mpShare.js | 13
ledApp/uni_modules/uv-ui-tools/libs/function/throttle.js | 30
ledApp/uni_modules/uv-calendars/package.json | 89
ledApp/uni_modules/uv-text/package.json | 89
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/canvas.js | 74
ledApp/uni_modules/uv-code/package.json | 87
ledApp/uni_modules/uv-loading-icon/package.json | 87
ledApp/uni_modules/uv-empty/components/uv-empty/props.js | 60
ledApp/uni_modules/uv-navbar/package.json | 89
ledApp/uni_modules/uv-list/components/uv-list/uv-list.vue | 147
ledApp/uni_modules/uv-popup/readme.md | 21
ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/defaults.js | 33
ledApp/uni_modules/uv-slider/components/uv-slider/props.js | 58
ledApp/uni_modules/uv-calendar/components/uv-calendar/calendar.js | 546
ledApp/uni_modules/uv-sticky/components/uv-sticky/props.js | 41
ledApp/uni_modules/uv-grid/components/uv-grid-item/uv-grid-item.vue | 226
ledApp/uni_modules/uv-collapse/changelog.md | 5
ledApp/uni_modules/uv-row/components/uv-col/props.js | 30
ledApp/uni_modules/uv-switch/components/uv-switch/props.js | 58
ledApp/uni_modules/uv-datetime-picker/package.json | 89
ledApp/uni_modules/uv-skeleton/readme.md | 11
ledApp/uni_modules/uv-toast/package.json | 90
ledApp/uni_modules/uv-radio/changelog.md | 31
ledApp/uni_modules/uv-toolbar/package.json | 87
ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/index.js | 8
ledApp/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js | 8
ledApp/uni_modules/uv-tags/package.json | 89
ledApp/uni_modules/uv-upload/components/uv-preview-video/uv-preview-video.vue | 52
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/index.js | 39
ledApp/uni_modules/uv-grid/package.json | 88
ledApp/uni_modules/uv-ui-tools/libs/mixin/button.js | 13
ledApp/uni_modules/uv-keyboard/components/uv-keyboard/props.js | 95
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStylePattern.js | 8
ledApp/uni_modules/uv-count-down/readme.md | 11
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ActiveInfo.js | 11
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index.wxs | 225
ledApp/uni_modules/uv-cell/components/uv-cell-group/uv-cell-group.vue | 63
ledApp/uni_modules/uv-upload/readme.md | 11
ledApp/uni_modules/uv-code-input/package.json | 87
ledApp/uni_modules/x-color-picker/changelog.md | 2
ledApp/uni_modules/uv-checkbox/components/uv-checkbox/props.js | 70
ledApp/uni_modules/uv-form/components/uv-form/valid.js | 1343 +
ledApp/uni_modules/uv-text/components/uv-text/value.js | 87
ledApp/uni_modules/uv-ui-tools/libs/function/digit.js | 167
ledApp/uni_modules/uv-line-progress/readme.md | 11
ledApp/uni_modules/uv-parse/package.json | 87
ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/js/handler.js | 224
ledApp/uni_modules/uv-popup/package.json | 92
ledApp/uni_modules/uv-load-more/readme.md | 11
ledApp/uni_modules/uv-waterfall/components/uv-waterfall/uv-waterfall.vue | 265
ledApp/uni_modules/uv-swiper/readme.md | 19
ledApp/uni_modules/uv-grid/changelog.md | 21
ledApp/uni_modules/uv-transition/changelog.md | 19
ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/en.json | 12
ledApp/uni_modules/uv-pick-color/package.json | 89
ledApp/uni_modules/uv-image/package.json | 89
ledApp/uni_modules/uv-scroll-list/package.json | 87
ledApp/uni_modules/uv-number-box/readme.md | 11
ledApp/uni_modules/uv-keyboard/package.json | 89
ledApp/uni_modules/uv-qrcode/readme.md | 21
ledApp/uni_modules/uv-popup/changelog.md | 18
ledApp/uni_modules/uv-rate/components/uv-rate/props.js | 73
ledApp/uni_modules/uv-read-more/readme.md | 11
ledApp/uni_modules/uv-notice-bar/components/uv-column-notice/props.js | 61
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLmethod.js | 142
ledApp/uni_modules/uv-status-bar/components/uv-status-bar/props.js | 8
ledApp/uni_modules/uv-back-top/components/uv-back-top/props.js | 58
ledApp/uni_modules/uv-empty/components/uv-empty/uv-empty.vue | 126
ledApp/uni_modules/uv-swipe-action/package.json | 88
ledApp/uni_modules/uv-list/package.json | 87
ledApp/uni_modules/uv-ui-tools/libs/function/test.js | 287
ledApp/uni_modules/uv-code-input/components/uv-code-input/uv-code-input.vue | 272
ledApp/uni_modules/uv-ui-tools/libs/config/config.js | 34
ledApp/uni_modules/uv-count-to/components/uv-count-to/uv-count-to.vue | 187
ledApp/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue | 370
ledApp/uni_modules/uv-number-box/components/uv-number-box/uv-number-box.vue | 395
ledApp/uni_modules/uv-parse/components/uv-parse/parser.js | 1335 +
ledApp/uni_modules/uv-safe-bottom/package.json | 87
ledApp/uni_modules/uv-vtabs/package.json | 87
ledApp/uni_modules/uv-datetime-picker/changelog.md | 34
ledApp/uni_modules/uv-avatar/package.json | 89
ledApp/uni_modules/uv-form/package.json | 93
ledApp/uni_modules/uv-search/readme.md | 19
ledApp/uni_modules/uv-ui-tools/libs/function/index.js | 734
ledApp/uni_modules/uv-link/package.json | 87
ledApp/uni_modules/uv-list/readme.md | 27
ledApp/uni_modules/uv-code/readme.md | 11
ledApp/uni_modules/uv-list/changelog.md | 25
ledApp/uni_modules/uv-keyboard/components/uv-keyboard/uv-keyboard.vue | 180
ledApp/uni_modules/uv-scroll-list/changelog.md | 12
ledApp/uni_modules/uv-search/components/uv-search/props.js | 127
ledApp/uni_modules/uv-tags/components/uv-tags/props.js | 95
ledApp/uni_modules/uv-swipe-action/changelog.md | 11
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue - backup.js | 264
ledApp/uni_modules/uv-keyboard/changelog.md | 17
ledApp/uni_modules/uv-tabs/components/uv-tabs/props.js | 71
ledApp/uni_modules/uv-loading-page/components/uv-loading-page/props.js | 55
ledApp/uni_modules/uv-modal/changelog.md | 28
ledApp/uni_modules/uv-icon/components/uv-icon/props.js | 90
ledApp/uni_modules/uv-link/changelog.md | 7
ledApp/uni_modules/uv-swiper/components/uv-swiper-indicator/props.js | 30
ledApp/uni_modules/uv-back-top/components/uv-back-top/uv-back-top.vue | 116
ledApp/uni_modules/uv-swiper/components/uv-swiper-indicator/uv-swiper-indicator.vue | 112
ledApp/uni_modules/uv-popup/components/uv-popup/uv-popup.vue | 539
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Framebuffer.js | 21
ledApp/uni_modules/uv-input/readme.md | 19
ledApp/uni_modules/uv-steps/components/uv-steps/uv-steps.vue | 83
ledApp/uni_modules/uv-avatar/changelog.md | 13
ledApp/uni_modules/uv-avatar/components/uv-avatar-group/uv-avatar-group.vue | 106
ledApp/uni_modules/uv-ui-tools/components/uv-ui-tools/uv-ui-tools.vue | 6
ledApp/uni_modules/uv-checkbox/components/uv-checkbox-group/props.js | 84
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/uv-qrcode.vue | 1038 +
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/qrcode.js | 34
ledApp/uni_modules/uv-avatar/readme.md | 11
ledApp/uni_modules/uv-form/components/uv-form/props.js | 46
ledApp/uni_modules/uv-search/components/uv-search/uv-search.vue | 311
ledApp/uni_modules/uv-button/readme.md | 19
ledApp/uni_modules/uv-index-list/components/uv-index-anchor/uv-index-anchor.vue | 98
ledApp/uni_modules/uv-ui-tools/libs/mixin/openType.js | 47
ledApp/uni_modules/uv-input/package.json | 88
ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/mergeConfig.js | 126
ledApp/uni_modules/uv-row/changelog.md | 5
ledApp/uni_modules/uv-tabbar/components/uv-tabbar-item/uv-tabbar-item.vue | 146
ledApp/uni_modules/uv-line/readme.md | 11
ledApp/uni_modules/uv-load-more/components/uv-load-more/props.js | 95
ledApp/uni_modules/uv-vtabs/components/uv-vtabs/props.js | 79
ledApp/uni_modules/uv-collapse/package.json | 89
ledApp/uni_modules/uv-code/changelog.md | 9
ledApp/uni_modules/uv-action-sheet/components/uv-action-sheet/uv-action-sheet.vue | 280
ledApp/uni_modules/uv-line-progress/components/uv-line-progress/props.js | 29
ledApp/uni_modules/uv-count-down/components/uv-count-down/props.js | 25
ledApp/uni_modules/uv-code-input/changelog.md | 13
ledApp/uni_modules/uv-navbar/components/uv-navbar/props.js | 89
ledApp/uni_modules/uv-cell/readme.md | 11
ledApp/uni_modules/uv-toolbar/changelog.md | 2
ledApp/uni_modules/uv-text/changelog.md | 11
ledApp/uni_modules/uv-overlay/readme.md | 11
ledApp/uni_modules/x-color-picker/package.json | 87
ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/js/uni.webview.min.js | 19
ledApp/uni_modules/uv-index-list/components/uv-index-anchor/props.js | 30
ledApp/uni_modules/uv-notice-bar/components/uv-row-notice/props.js | 40
ledApp/uni_modules/uv-switch/changelog.md | 14
ledApp/uni_modules/uv-ui-tools/index.js | 79
ledApp/uni_modules/uv-read-more/changelog.md | 7
ledApp/uni_modules/uv-checkbox/readme.md | 19
ledApp/uni_modules/uv-gap/changelog.md | 5
ledApp/uni_modules/uv-sticky/package.json | 87
ledApp/uni_modules/uv-row/components/uv-col/uv-col.vue | 165
ledApp/uni_modules/uv-loading-page/components/uv-loading-page/uv-loading-page.vue | 96
ledApp/uni_modules/uv-notice-bar/package.json | 88
ledApp/uni_modules/uv-row/components/uv-row/uv-row.vue | 95
ledApp/uni_modules/uv-calendars/changelog.md | 40
ledApp/uni_modules/uv-gap/readme.md | 12
ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar-item.vue | 248
ledApp/uni_modules/uv-image/components/uv-image/props.js | 95
ledApp/uni_modules/uv-subsection/readme.md | 11
ledApp/uni_modules/uv-calendars/components/uv-calendars/uv-calendars.vue | 452
ledApp/uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue | 54
ledApp/uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js | 264
ledApp/uni_modules/uv-album/changelog.md | 10
ledApp/uni_modules/uv-line-progress/components/uv-line-progress/uv-line-progress.vue | 146
ledApp/uni_modules/uv-waterfall/readme.md | 19
ledApp/uni_modules/uv-index-list/changelog.md | 18
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLtype.js | 23
ledApp/uni_modules/uv-status-bar/package.json | 87
ledApp/uni_modules/uv-code/components/uv-code/uv-code.vue | 136
ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/isAbsoluteURL.js | 14
ledApp/uni_modules/uv-keyboard/components/uv-keyboard-number/props.js | 19
ledApp/uni_modules/uv-ui-tools/changelog.md | 76
ledApp/uni_modules/uv-notify/components/uv-notify/uv-notify.vue | 220
ledApp/uni_modules/uv-pick-color/changelog.md | 18
ledApp/uni_modules/uv-ui-tools/libs/util/dayjs.js | 216
ledApp/uni_modules/uv-drop-down/readme.md | 23
ledApp/uni_modules/uv-ui-tools/libs/luch-request/index.d.ts | 197
ledApp/uni_modules/uv-number-box/components/uv-number-box/props.js | 113
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Renderbuffer.js | 21
ledApp/uni_modules/uv-loading-icon/changelog.md | 9
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/cache.js | 1
ledApp/uni_modules/uv-vtabs/readme.md | 19
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ShaderPrecisionFormat.js | 11
ledApp/uni_modules/uv-ui-tools/index.scss | 7
ledApp/uni_modules/uv-upload/changelog.md | 17
ledApp/uni_modules/uv-subsection/components/uv-subsection/props.js | 54
ledApp/uni_modules/uv-picker/components/uv-picker/props.js | 95
ledApp/uni_modules/uv-toast/changelog.md | 10
ledApp/uni_modules/uv-alert/components/uv-alert/uv-alert.vue | 246
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/classUtils.js | 3
ledApp/uni_modules/uv-textarea/readme.md | 19
ledApp/uni_modules/uv-calendar/components/uv-calendar/month.vue | 616
ledApp/uni_modules/uv-drop-down/components/uv-drop-down/uv-drop-down.vue | 135
ledApp/uni_modules/uv-skeleton/components/uv-skeleton/props.js | 65
ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/InterceptorManager.js | 51
ledApp/uni_modules/uv-avatar/components/uv-avatar/props.js | 80
ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar.js | 546
ledApp/uni_modules/uv-sticky/readme.md | 11
ledApp/uni_modules/uv-subsection/components/uv-subsection/uv-subsection.vue | 269
ledApp/uni_modules/uv-action-sheet/components/uv-action-sheet/props.js | 50
ledApp/uni_modules/uv-upload/components/uv-upload/utils.js | 151
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleRadialGradient.js | 17
ledApp/uni_modules/uv-navbar/readme.md | 19
ledApp/uni_modules/uv-qrcode/changelog.md | 13
ledApp/uni_modules/uv-alert/package.json | 88
ledApp/uni_modules/uv-ui-tools/libs/css/common.scss | 100
ledApp/uni_modules/uv-overlay/package.json | 88
ledApp/uni_modules/uv-read-more/components/uv-read-more/uv-read-more.vue | 162
ledApp/uni_modules/uv-divider/package.json | 88
ledApp/uni_modules/uv-link/components/uv-link/uv-link.vue | 83
ledApp/uni_modules/uv-collapse/components/uv-collapse/props.js | 20
ledApp/uni_modules/uv-datetime-picker/components/uv-datetime-picker/props.js | 130
ledApp/uni_modules/uv-toolbar/components/uv-toolbar/props.js | 40
ledApp/uni_modules/uv-badge/package.json | 87
ledApp/uni_modules/uv-vtabs/components/uv-vtabs-item/uv-vtabs-item.vue | 68
ledApp/uni_modules/uv-transition/readme.md | 15
ledApp/uni_modules/uv-notify/changelog.md | 9
ledApp/uni_modules/uv-switch/components/uv-switch/uv-switch.vue | 192
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action/props.js | 10
ledApp/uni_modules/uv-count-down/components/uv-count-down/utils.js | 62
ledApp/uni_modules/uv-cell/package.json | 89
ledApp/uni_modules/uv-icon/components/uv-icon/icons.js | 160
ledApp/uni_modules/uv-ui-tools/libs/css/variable.scss | 111
ledApp/uni_modules/uv-load-more/components/uv-load-more/uv-load-more.vue | 152
ledApp/uni_modules/uv-line/changelog.md | 5
ledApp/uni_modules/uv-icon/components/uv-icon/uv-icon.vue | 226
ledApp/uni_modules/uv-popup/components/uv-popup/keypress.js | 45
ledApp/uni_modules/uv-drop-down/components/uv-drop-down-item/uv-drop-down-item.vue | 169
ledApp/uni_modules/uv-slider/readme.md | 15
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue.js | 182
ledApp/uni_modules/uv-textarea/components/uv-textarea/props.js | 138
ledApp/uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue | 347
ledApp/uni_modules/uv-no-network/components/uv-no-network/uv-no-network.vue | 222
ledApp/uni_modules/uv-toolbar/components/uv-toolbar/uv-toolbar.vue | 109
ledApp/uni_modules/uv-upload/components/uv-upload/uv-upload.vue | 488
ledApp/uni_modules/uv-ui/package.json | 162
ledApp/uni_modules/uv-rate/readme.md | 19
ledApp/uni_modules/uv-loading-page/changelog.md | 11
ledApp/uni_modules/uv-swiper/components/uv-swiper/props.js | 136
ledApp/uni_modules/uv-back-top/changelog.md | 8
ledApp/uni_modules/uv-ui-tools/libs/luch-request/adapters/index.js | 132
ledApp/uni_modules/uv-button/changelog.md | 33
ledApp/uni_modules/uv-no-network/package.json | 90
ledApp/uni_modules/uv-count-down/changelog.md | 9
ledApp/uni_modules/uv-count-to/package.json | 87
ledApp/uni_modules/uv-input/components/uv-input/props.js | 175
ledApp/uni_modules/x-color-picker/components/x-color-picker/x-color-picker.vue | 1242 +
ledApp/uni_modules/uv-line/components/uv-line/uv-line.vue | 60
ledApp/uni_modules/uv-divider/readme.md | 11
ledApp/uni_modules/uv-no-network/readme.md | 11
ledApp/uni_modules/uv-badge/components/uv-badge/uv-badge.vue | 176
ledApp/uni_modules/uv-steps/changelog.md | 10
ledApp/uni_modules/uv-loading-page/readme.md | 11
ledApp/uni_modules/uv-tags/readme.md | 11
ledApp/uni_modules/uv-picker/changelog.md | 33
ledApp/uni_modules/uv-tabbar/package.json | 90
ledApp/uni_modules/uv-index-list/components/uv-index-list/props.js | 30
ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/buildFullPath.js | 20
ledApp/uni_modules/uv-form/components/uv-form/uv-form.vue | 209
ledApp/uni_modules/uv-upload/components/uv-upload/props.js | 130
ledApp/uni_modules/uv-empty/package.json | 88
ledApp/uni_modules/uv-keyboard/readme.md | 19
ledApp/uni_modules/uv-transition/components/uv-transition/uv-transition.vue | 320
ledApp/uni_modules/uv-ui-tools/libs/luch-request/utils.js | 135
ledApp/uni_modules/uv-form/readme.md | 19
ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/buildURL.js | 64
ledApp/uni_modules/uv-gap/package.json | 87
ledApp/uni_modules/uv-tooltip/components/uv-tooltip/props.js | 60
ledApp/uni_modules/uv-sticky/changelog.md | 11
ledApp/uni_modules/uv-skeletons/package.json | 88
ledApp/uni_modules/uv-button/components/uv-button/props.js | 163
ledApp/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue | 223
ledApp/uni_modules/uv-notice-bar/changelog.md | 19
ledApp/uni_modules/uv-status-bar/changelog.md | 7
ledApp/uni_modules/uv-icon/components/uv-icon/uvicons.ttf | 0
ledApp/uni_modules/uv-swiper/changelog.md | 16
ledApp/uni_modules/uv-load-more/package.json | 89
ledApp/uni_modules/uv-skeleton/changelog.md | 9
ledApp/uni_modules/uv-slider/package.json | 87
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/props.js | 40
ledApp/uni_modules/uv-modal/components/uv-modal/props.js | 90
ledApp/uni_modules/uv-slider/components/uv-slider/uv-slider.vue | 58
ledApp/uni_modules/uv-badge/readme.md | 11
ledApp/uni_modules/uv-parse/components/uv-parse/uv-parse.vue | 498
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index - backup.wxs | 256
ledApp/uni_modules/uv-avatar/components/uv-avatar/uv-avatar.vue | 175
ledApp/uni_modules/uv-ui-tools/theme.scss | 43
ledApp/uni_modules/uv-line/package.json | 87
ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/nvue.js | 29
ledApp/uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue | 146
ledApp/uni_modules/uv-collapse/readme.md | 11
ledApp/uni_modules/uv-loading-icon/components/uv-loading-icon/props.js | 67
ledApp/uni_modules/uv-collapse/components/uv-collapse-item/props.js | 60
ledApp/uni_modules/uv-number-box/package.json | 88
ledApp/uni_modules/uv-cell/components/uv-cell/props.js | 116
ledApp/uni_modules/uv-keyboard/components/uv-keyboard-car/props.js | 24
ledApp/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue | 115
ledApp/uni_modules/uv-upload/package.json | 90
ledApp/uni_modules/uv-textarea/changelog.md | 28
ledApp/uni_modules/uv-ui-tools/libs/function/debounce.js | 29
ledApp/uni_modules/uv-tabbar/readme.md | 21
ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/Request.js | 201
ledApp/uni_modules/uv-modal/readme.md | 23
ledApp/uni_modules/uv-radio/readme.md | 19
ledApp/uni_modules/uv-drop-down/changelog.md | 13
ledApp/uni_modules/uv-toolbar/readme.md | 31
ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/settle.js | 16
ledApp/uni_modules/uv-line-progress/package.json | 87
ledApp/uni_modules/uv-index-list/package.json | 88
ledApp/uni_modules/uv-link/readme.md | 11
ledApp/uni_modules/uv-grid/readme.md | 17
ledApp/uni_modules/uv-radio/components/uv-radio/uv-radio.vue | 348
ledApp/uni_modules/uv-action-sheet/package.json | 92
ledApp/uni_modules/uv-parse/components/uv-parse/node/node.vue | 576
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLenum.js | 298
ledApp/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue | 119
ledApp/uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue | 67
ledApp/uni_modules/uv-pick-color/readme.md | 19
ledApp/uni_modules/uv-navbar/components/uv-navbar/uv-navbar.vue | 245
ledApp/uni_modules/uv-calendar/components/uv-calendar/props.js | 145
ledApp/uni_modules/uv-album/package.json | 88
ledApp/uni_modules/uv-toast/readme.md | 11
ledApp/uni_modules/uv-upload/components/uv-upload/mixin.js | 22
ledApp/uni_modules/uv-radio/components/uv-radio/props.js | 65
ledApp/uni_modules/uv-tabbar/components/uv-tabbar/props.js | 50
ledApp/uni_modules/uv-transition/components/uv-transition/props.js | 31
ledApp/uni_modules/uv-alert/components/uv-alert/props.js | 45
ledApp/uni_modules/uv-calendars/readme.md | 23
ledApp/uni_modules/uv-cell/components/uv-cell-group/props.js | 15
ledApp/uni_modules/uv-tags/components/uv-tags/uv-tags.vue | 379
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Program.js | 21
ledApp/uni_modules/uv-safe-bottom/changelog.md | 11
ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/local.html | 1
ledApp/uni_modules/uv-divider/components/uv-divider/props.js | 45
ledApp/uni_modules/uv-gap/components/uv-gap/uv-gap.vue | 36
ledApp/uni_modules/uv-safe-bottom/readme.md | 11
ledApp/uni_modules/uv-waterfall/components/uv-waterfall/props.js | 69
ledApp/uni_modules/uv-button/components/uv-button/uv-button.vue | 528
ledApp/uni_modules/uv-switch/readme.md | 19
ledApp/uni_modules/uv-form/components/uv-form-item/props.js | 49
ledApp/uni_modules/uv-steps/components/uv-steps-item/props.js | 25
ledApp/uni_modules/uv-calendar/changelog.md | 16
ledApp/uni_modules/uv-icon/package.json | 83
ledApp/uni_modules/uv-picker/readme.md | 21
ledApp/uni_modules/uv-no-network/components/uv-no-network/props.js | 20
ledApp/uni_modules/uv-search/changelog.md | 25
ledApp/uni_modules/uv-form/components/uv-form-item/uv-form-item.vue | 226
ledApp/uni_modules/uv-line-progress/changelog.md | 7
ledApp/uni_modules/uv-notice-bar/components/uv-row-notice/uv-row-notice.vue | 341
ledApp/uni_modules/uv-transition/components/uv-transition/createAnimation.js | 131
ledApp/uni_modules/uv-grid/components/uv-grid/uv-grid.vue | 100
ledApp/uni_modules/uv-index-list/components/uv-index-list/uv-index-list.vue | 461
ledApp/uni_modules/uv-pick-color/components/uv-pick-color/colors.js | 217
ledApp/uni_modules/uv-notice-bar/components/uv-notice-bar/props.js | 76
ledApp/uni_modules/uv-tabs/changelog.md | 22
ledApp/uni_modules/uv-calendar/components/uv-calendar/uv-calendar.vue | 390
ledApp/uni_modules/uv-number-box/changelog.md | 7
ledApp/uni_modules/uv-row/components/uv-row/props.js | 20
ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hans.json | 12
ledApp/uni_modules/uv-icon/changelog.md | 31
ledApp/uni_modules/uv-divider/components/uv-divider/uv-divider.vue | 113
ledApp/uni_modules/uv-read-more/components/uv-read-more/props.js | 62
ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hant.json | 12
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/queue.js | 41
ledApp/uni_modules/uv-skeletons/changelog.md | 4
ledApp/uni_modules/uv-vtabs/changelog.md | 20
ledApp/uni_modules/uv-image/readme.md | 15
ledApp/uni_modules/uv-rate/changelog.md | 17
ledApp/uni_modules/uv-datetime-picker/readme.md | 19
ledApp/uni_modules/uv-count-to/components/uv-count-to/props.js | 60
ledApp/uni_modules/uv-subsection/changelog.md | 10
ledApp/uni_modules/uv-tabs/components/uv-tabs/uv-tabs.vue | 390
ledApp/uni_modules/uv-checkbox/package.json | 88
ledApp/uni_modules/uv-tabs/package.json | 90
ledApp/uni_modules/uv-avatar/components/uv-avatar-group/props.js | 53
ledApp/uni_modules/uv-keyboard/components/uv-keyboard-number/uv-keyboard-number.vue | 201
ledApp/uni_modules/uv-row/readme.md | 11
ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/scrollWxs.wxs | 51
ledApp/uni_modules/uv-code/components/uv-code/props.js | 35
ledApp/uni_modules/uv-loading-icon/readme.md | 19
ledApp/uni_modules/uv-alert/readme.md | 15
ledApp/uni_modules/uv-badge/components/uv-badge/props.js | 73
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleLinearGradient.js | 18
ledApp/uni_modules/uv-form/changelog.md | 23
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Texture.js | 22
ledApp/uni_modules/uv-modal/package.json | 90
ledApp/uni_modules/uv-scroll-list/readme.md | 19
ledApp/uni_modules/uv-steps/readme.md | 11
ledApp/uni_modules/uv-ui-tools/libs/mixin/mixin.js | 172
ledApp/uni_modules/uv-notify/readme.md | 11
ledApp/uni_modules/uv-subsection/package.json | 87
ledApp/uni_modules/uv-ui-tools/libs/function/platform.js | 75
ledApp/uni_modules/uv-image/changelog.md | 36
ledApp/uni_modules/uv-checkbox/changelog.md | 34
ledApp/uni_modules/uv-tooltip/components/uv-tooltip/uv-tooltip.vue | 372
ledApp/uni_modules/uv-modal/components/uv-modal/uv-modal.vue | 245
ledApp/uni_modules/uv-input/changelog.md | 29
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/RenderingContext.js | 1191 +
ledApp/uni_modules/uv-row/package.json | 87
ledApp/uni_modules/uv-search/package.json | 89
ledApp/uni_modules/uv-empty/changelog.md | 13
ledApp/uni_modules/uv-rate/package.json | 88
ledApp/uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue | 85
ledApp/uni_modules/uv-swiper/package.json | 88
ledApp/uni_modules/uv-album/components/uv-album/uv-album.vue | 312
ledApp/uni_modules/uv-transition/package.json | 87
ledApp/uni_modules/uv-pick-color/components/uv-pick-color/props.js | 47
ledApp/uni_modules/uv-textarea/components/uv-textarea/uv-textarea.vue | 238
ledApp/uni_modules/uv-gap/components/uv-gap/props.js | 25
ledApp/uni_modules/uv-index-list/components/uv-index-item/uv-index-item.vue | 87
ledApp/uni_modules/uv-link/components/uv-link/props.js | 40
ledApp/uni_modules/uv-ui-tools/libs/css/vue.scss | 40
ledApp/uni_modules/uv-pick-color/components/uv-pick-color/uv-pick-color.vue | 551
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/bridge/bridge-weex.js | 241
ledApp/uni_modules/uv-drop-down/components/uv-drop-down-popup/uv-drop-down-popup.vue | 242
ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar-body.vue | 376
ledApp/uni_modules/uv-code-input/readme.md | 19
ledApp/uni_modules/uv-ui-tools/libs/luch-request/index.js | 2
ledApp/uni_modules/uv-navbar/changelog.md | 17
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/tool.js | 24
ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/combineURLs.js | 14
ledApp/uni_modules/uv-datetime-picker/components/uv-datetime-picker/uv-datetime-picker.vue | 360
ledApp/uni_modules/uv-rate/components/uv-rate/uv-rate.vue | 274
ledApp/uni_modules/uv-empty/readme.md | 19
ledApp/uni_modules/uv-notice-bar/components/uv-notice-bar/uv-notice-bar.vue | 110
ledApp/uni_modules/uv-waterfall/changelog.md | 24
ledApp/uni_modules/uv-button/package.json | 89
ledApp/uni_modules/uv-ui-tools/libs/css/components.scss | 23
ledApp/uni_modules/uv-back-top/readme.md | 11
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Shader.js | 22
ledApp/uni_modules/uv-radio/components/uv-radio-group/props.js | 89
ledApp/uni_modules/uv-ui-tools/readme.md | 23
ledApp/uni_modules/uv-steps/components/uv-steps/props.js | 40
ledApp/uni_modules/uv-tabs/readme.md | 19
ledApp/uni_modules/uv-image/components/uv-image/uv-image.vue | 287
ledApp/uni_modules/uv-picker/components/uv-picker/uv-picker.vue | 330
ledApp/uni_modules/uv-notice-bar/readme.md | 19
ledApp/uni_modules/uv-index-list/readme.md | 19
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Buffer.js | 21
ledApp/uni_modules/uv-parse/changelog.md | 13
ledApp/uni_modules/uv-calendar/components/uv-calendar/header.vue | 104
ledApp/uni_modules/uv-toast/components/uv-toast/uv-toast.vue | 333
ledApp/uni_modules/uv-code-input/components/uv-code-input/props.js | 83
ledApp/uni_modules/uv-line/components/uv-line/props.js | 34
ledApp/uni_modules/uv-action-sheet/changelog.md | 7
ledApp/uni_modules/uv-input/components/uv-input/uv-input.vue | 348
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/props.js | 85
ledApp/uni_modules/uv-ui-tools/libs/mixin/touch.js | 59
ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/props.js | 35
ledApp/uni_modules/uv-notice-bar/components/uv-column-notice/uv-column-notice.vue | 176
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/image.js | 96
ledApp/uni_modules/uv-swipe-action/readme.md | 11
ledApp/uni_modules/uv-text/components/uv-text/uv-text.vue | 218
ledApp/uni_modules/uv-drop-down/package.json | 91
ledApp/uni_modules/uv-vtabs/components/uv-vtabs/uv-vtabs.vue | 438
ledApp/uni_modules/uv-album/readme.md | 21
ledApp/uni_modules/uv-radio/package.json | 88
ledApp/uni_modules/uv-collapse/components/uv-collapse/uv-collapse.vue | 86
ledApp/uni_modules/uv-load-more/changelog.md | 7
ledApp/uni_modules/uv-skeleton/components/uv-skeleton/uv-skeleton.vue | 250
ledApp/uni_modules/uv-calendar/package.json | 89
ledApp/uni_modules/uv-skeletons/components/uv-skeletons/uv-skeletons.vue | 247
ledApp/uni_modules/uv-cell/changelog.md | 13
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/RenderingContext.js | 666
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action/uv-swipe-action.vue | 65
ledApp/uni_modules/uv-textarea/package.json | 87
ledApp/uni_modules/uv-count-to/readme.md | 11
ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/UniformLocation.js | 22
ledApp/uni_modules/uv-collapse/components/uv-collapse-item/uv-collapse-item.vue | 229
ledApp/uni_modules/uv-action-sheet/readme.md | 13
ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/dispatchRequest.js | 6
ledApp/uni_modules/uv-ui/readme.md | 164
ledApp/uni_modules/uv-badge/changelog.md | 7
ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/uv-scroll-list.vue | 218
ledApp/uni_modules/uv-list/components/uv-list-item/uv-list-item.vue | 535
ledApp/uni_modules/uv-read-more/package.json | 89
ledApp/uni_modules/uv-skeletons/readme.md | 23
ledApp/uni_modules/uv-overlay/components/uv-overlay/props.js | 25
ledApp/uni_modules/uv-overlay/changelog.md | 9
ledApp/uni_modules/uv-switch/package.json | 88
ledApp/uni_modules/uv-button/components/uv-button/vue.scss | 93
ledApp/uni_modules/uv-grid/components/uv-grid/props.js | 20
ledApp/uni_modules/uv-skeleton/package.json | 87
ledApp/uni_modules/uv-slider/changelog.md | 9
ledApp/uni_modules/uv-tags/changelog.md | 10
ledApp/uni_modules/uv-parse/readme.md | 21
ledApp/uni_modules/uv-alert/changelog.md | 7
ledApp/uni_modules/uv-tabbar/changelog.md | 14
ledApp/uni_modules/uv-text/readme.md | 17
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/wxs.js | 15
ledApp/uni_modules/uv-picker/package.json | 90
ledApp/uni_modules/uv-calendar/readme.md | 21
ledApp/uni_modules/uv-calendars/components/uv-calendars/util.js | 435
ledApp/uni_modules/uv-cell/components/uv-cell/uv-cell.vue | 209
ledApp/uni_modules/uv-notify/package.json | 90
ledApp/uni_modules/uv-qrcode/package.json | 87
ledApp/uni_modules/uv-tooltip/package.json | 90
ledApp/uni_modules/uv-count-down/package.json | 87
ledApp/uni_modules/uv-ui/components/uv-ui/uv-ui.vue | 7
ledApp/uni_modules/uv-count-down/components/uv-count-down/uv-count-down.vue | 169
ledApp/uni_modules/uv-steps/components/uv-steps-item/uv-steps-item.vue | 347
ledApp/uni_modules/uv-ui-tools/libs/util/route.js | 126
ledApp/uni_modules/uv-tooltip/readme.md | 11
ledApp/uni_modules/uv-keyboard/components/uv-keyboard-car/uv-keyboard-car.vue | 347
ledApp/uni_modules/uv-back-top/package.json | 89
ledApp/uni_modules/uv-status-bar/readme.md | 10
ledApp/uni_modules/x-color-picker/readme.md | 225
ledApp/uni_modules/uv-text/components/uv-text/props.js | 113
ledApp/uni_modules/uv-ui-tools/libs/function/colorGradient.js | 134
ledApp/uni_modules/uv-no-network/changelog.md | 5
ledApp/uni_modules/uv-grid/components/uv-grid-item/props.js | 15
ledApp/uni_modules/uv-divider/changelog.md | 11
ledApp/uni_modules/uv-notify/components/uv-notify/props.js | 45
ledApp/uni_modules/uv-count-to/changelog.md | 13
ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/uv-swipe-action-item.vue | 200
ledApp/uni_modules/uv-icon/readme.md | 15
ledApp/uni_modules/uv-button/components/uv-button/nvue.scss | 46
ledApp/uni_modules/uv-swiper/components/uv-swiper/uv-swiper.vue | 263
ledApp/uni_modules/uv-ui/changelog.md | 353
ledApp/uni_modules/uv-steps/package.json | 89
523 files changed, 55,596 insertions(+), 0 deletions(-)
diff --git a/ledApp/uni_modules/uv-action-sheet/changelog.md b/ledApp/uni_modules/uv-action-sheet/changelog.md
new file mode 100644
index 0000000..ab3545e
--- /dev/null
+++ b/ledApp/uni_modules/uv-action-sheet/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-07-02)
+uv-action-sheet 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/actionSheet.html
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-action-sheet 底部操作菜单
diff --git a/ledApp/uni_modules/uv-action-sheet/components/uv-action-sheet/props.js b/ledApp/uni_modules/uv-action-sheet/components/uv-action-sheet/props.js
new file mode 100644
index 0000000..8adffee
--- /dev/null
+++ b/ledApp/uni_modules/uv-action-sheet/components/uv-action-sheet/props.js
@@ -0,0 +1,50 @@
+export default {
+ props: {
+ // 标题,有值则显示,同时会显示关闭按钮
+ title: {
+ type: String,
+ default: ''
+ },
+ // 选项上方的描述信息
+ description: {
+ type: String,
+ default: ''
+ },
+ // 数据
+ actions: {
+ type: Array,
+ default: () => []
+ },
+ // 取消按钮的文字,不为空时显示按钮
+ cancelText: {
+ type: String,
+ default: ''
+ },
+ // 点击某个菜单项时是否关闭弹窗
+ closeOnClickAction: {
+ type: Boolean,
+ default: true
+ },
+ // 处理底部安全区(默认true)
+ safeAreaInsetBottom: {
+ type: Boolean,
+ default: true
+ },
+ // 小程序的打开方式
+ openType: {
+ type: String,
+ default: ''
+ },
+ // 点击遮罩是否允许关闭 (默认true)
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: true
+ },
+ // 圆角值
+ round: {
+ type: [Boolean, String, Number],
+ default: 0
+ },
+ ...uni.$uv?.props?.actionSheet
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-action-sheet/components/uv-action-sheet/uv-action-sheet.vue b/ledApp/uni_modules/uv-action-sheet/components/uv-action-sheet/uv-action-sheet.vue
new file mode 100644
index 0000000..edca089
--- /dev/null
+++ b/ledApp/uni_modules/uv-action-sheet/components/uv-action-sheet/uv-action-sheet.vue
@@ -0,0 +1,280 @@
+
+<template>
+ <uv-popup
+ ref="popup"
+ mode="bottom"
+ :safeAreaInsetBottom="safeAreaInsetBottom"
+ :round="round"
+ :close-on-click-overlay="closeOnClickOverlay"
+ @change="popupChange"
+ >
+ <view class="uv-action-sheet">
+ <view
+ class="uv-action-sheet__header"
+ v-if="title"
+ >
+ <text class="uv-action-sheet__header__title uv-line-1">{{title}}</text>
+ <view
+ class="uv-action-sheet__header__icon-wrap"
+ @tap.stop="cancel"
+ >
+ <uv-icon
+ name="close"
+ size="17"
+ color="#c8c9cc"
+ bold
+ ></uv-icon>
+ </view>
+ </view>
+ <text
+ class="uv-action-sheet__description"
+ :style="[{
+ marginTop: `${title && description ? 0 : '18px'}`
+ }]"
+ v-if="description"
+ >{{description}}</text>
+ <slot>
+ <uv-line v-if="description"></uv-line>
+ <view class="uv-action-sheet__item-wrap">
+ <view v-for="(item, index) in actions" :key="index">
+ <!-- #ifdef MP -->
+ <button
+ class="uv-reset-button"
+ :openType="item.openType"
+ @getuserinfo="onGetUserInfo"
+ @contact="onContact"
+ @getphonenumber="onGetPhoneNumber"
+ @error="onError"
+ @launchapp="onLaunchApp"
+ @opensetting="onOpenSetting"
+ :lang="lang"
+ :session-from="sessionFrom"
+ :send-message-title="sendMessageTitle"
+ :send-message-path="sendMessagePath"
+ :send-message-img="sendMessageImg"
+ :show-message-card="showMessageCard"
+ :app-parameter="appParameter"
+ @tap="selectHandler(index)"
+ :hover-class="!item.disabled && !item.loading ? 'uv-action-sheet--hover' : ''"
+ >
+ <!-- #endif -->
+ <view
+ class="uv-action-sheet__item-wrap__item"
+ @tap.stop="selectHandler(index)"
+ :hover-class="!item.disabled && !item.loading ? 'uv-action-sheet--hover' : ''"
+ :hover-stay-time="150"
+ >
+ <template v-if="!item.loading">
+ <text
+ class="uv-action-sheet__item-wrap__item__name"
+ :style="[itemStyle(index)]"
+ >{{ item.name }}</text>
+ <text
+ v-if="item.subname"
+ class="uv-action-sheet__item-wrap__item__subname"
+ >{{ item.subname }}</text>
+ </template>
+ <uv-loading-icon
+ v-else
+ custom-class="van-action-sheet__loading"
+ size="18"
+ mode="circle"
+ />
+ </view>
+ <!-- #ifdef MP -->
+ </button>
+ <!-- #endif -->
+ <uv-line v-if="index !== actions.length - 1"></uv-line>
+ </view>
+ </view>
+ </slot>
+ <uv-gap
+ bgColor="#eaeaec"
+ height="6"
+ v-if="cancelText"
+ ></uv-gap>
+ <view hover-class="uv-action-sheet--hover">
+ <text
+ @touchmove.stop.prevent
+ :hover-stay-time="150"
+ v-if="cancelText"
+ class="uv-action-sheet__cancel-text"
+ @tap="cancel"
+ >{{cancelText}}</text>
+ </view>
+ </view>
+ </uv-popup>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import button from '@/uni_modules/uv-ui-tools/libs/mixin/button.js'
+ import openType from '@/uni_modules/uv-ui-tools/libs/mixin/openType.js'
+ import props from './props.js';
+ /**
+ * ActionSheet 操作菜单
+ * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
+ * @tutorial https://www.uvui.cn/components/actionSheet.html
+ * @property {Boolean} show 操作菜单是否展示 (默认 false )
+ * @property {String} title 操作菜单标题
+ * @property {String} description 选项上方的描述信息
+ * @property {Array<Object>} actions 按钮的文字数组,见官方文档示例
+ * @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
+ * @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true )
+ * @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true )
+ * @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
+ * @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
+ * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
+ * @property {String} sessionFrom 会话来源,openType="contact"时有效
+ * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
+ * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+ * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
+ * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
+ * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
+ *
+ * @event {Function} select 点击ActionSheet列表项时触发
+ * @event {Function} close 点击取消按钮时触发
+ * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
+ * @event {Function} contact 客服消息回调,openType="contact"时有效
+ * @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效
+ * @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效
+ * @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效
+ * @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效
+ * @example <uv-action-sheet ref="actionSheet" :actions="list" :title="title" ></uv-action-sheet>
+ */
+ export default {
+ name: "uv-action-sheet",
+ mixins: [openType, button, mpMixin , mixin, props],
+ emits: ['close', 'select'],
+ computed: {
+ // 操作项目的样式
+ itemStyle() {
+ return (index) => {
+ let style = {};
+ if (this.actions[index].color) style.color = this.actions[index].color
+ if (this.actions[index].fontSize) style.fontSize = this.$uv.addUnit(this.actions[index].fontSize)
+ // 选项被禁用的样式
+ if (this.actions[index].disabled) style.color = '#c0c4cc'
+ return style;
+ }
+ },
+ },
+ methods: {
+ open() {
+ this.$refs.popup.open();
+ },
+ close() {
+ this.$refs.popup.close();
+ },
+ popupChange(e) {
+ if(!e.show) this.$emit('close');
+ },
+ // 点击取消按钮
+ cancel() {
+ this.close();
+ },
+ selectHandler(index) {
+ const item = this.actions[index]
+ if (item && !item.disabled && !item.loading) {
+ this.$emit('select', item)
+ if (this.closeOnClickAction) {
+ this.close();
+ }
+ }
+ },
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-lines: 1;
+ $show-reset-button: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-action-sheet-reset-button-width:100% !default;
+ $uv-action-sheet-title-font-size: 16px !default;
+ $uv-action-sheet-title-padding: 12px 30px !default;
+ $uv-action-sheet-title-color: $uv-main-color !default;
+ $uv-action-sheet-header-icon-wrap-right:15px !default;
+ $uv-action-sheet-header-icon-wrap-top:15px !default;
+ $uv-action-sheet-description-font-size:13px !default;
+ $uv-action-sheet-description-color:14px !default;
+ $uv-action-sheet-description-margin: 18px 15px !default;
+ $uv-action-sheet-item-wrap-item-padding:15px !default;
+ $uv-action-sheet-item-wrap-name-font-size:16px !default;
+ $uv-action-sheet-item-wrap-subname-font-size:13px !default;
+ $uv-action-sheet-item-wrap-subname-color: #c0c4cc !default;
+ $uv-action-sheet-item-wrap-subname-margin-top:10px !default;
+ $uv-action-sheet-cancel-text-font-size:16px !default;
+ $uv-action-sheet-cancel-text-color:$uv-content-color !default;
+ $uv-action-sheet-cancel-text-font-size:15px !default;
+ $uv-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
+
+ .uv-reset-button {
+ width: $uv-action-sheet-reset-button-width;
+ }
+
+ .uv-action-sheet {
+ text-align: center;
+ &__header {
+ position: relative;
+ padding: $uv-action-sheet-title-padding;
+ &__title {
+ font-size: $uv-action-sheet-title-font-size;
+ color: $uv-action-sheet-title-color;
+ font-weight: bold;
+ text-align: center;
+ }
+
+ &__icon-wrap {
+ position: absolute;
+ right: $uv-action-sheet-header-icon-wrap-right;
+ top: $uv-action-sheet-header-icon-wrap-top;
+ }
+ }
+
+ &__description {
+ font-size: $uv-action-sheet-description-font-size;
+ color: $uv-tips-color;
+ margin: $uv-action-sheet-description-margin;
+ text-align: center;
+ }
+
+ &__item-wrap {
+
+ &__item {
+ padding: $uv-action-sheet-item-wrap-item-padding;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+
+ &__name {
+ font-size: $uv-action-sheet-item-wrap-name-font-size;
+ color: $uv-main-color;
+ text-align: center;
+ }
+
+ &__subname {
+ font-size: $uv-action-sheet-item-wrap-subname-font-size;
+ color: $uv-action-sheet-item-wrap-subname-color;
+ margin-top: $uv-action-sheet-item-wrap-subname-margin-top;
+ text-align: center;
+ }
+ }
+ }
+
+ &__cancel-text {
+ font-size: $uv-action-sheet-cancel-text-font-size;
+ color: $uv-action-sheet-cancel-text-color;
+ text-align: center;
+ padding: $uv-action-sheet-cancel-text-font-size;
+ }
+
+ &--hover {
+ background-color: $uv-action-sheet-cancel-text-hover-background-color;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-action-sheet/package.json b/ledApp/uni_modules/uv-action-sheet/package.json
new file mode 100644
index 0000000..e7b6173
--- /dev/null
+++ b/ledApp/uni_modules/uv-action-sheet/package.json
@@ -0,0 +1,92 @@
+{
+ "id": "uv-action-sheet",
+ "displayName": "uv-action-sheet 底部操作菜单 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "该组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheet API,配置更加灵活,所有平台都表现一致。",
+ "keywords": [
+ "action-sheet",
+ "uvui",
+ "uv-ui",
+ "操作菜单",
+ "菜单选择"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-popup",
+ "uv-icon",
+ "uv-line",
+ "uv-loading-icon",
+ "uv-gap"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-action-sheet/readme.md b/ledApp/uni_modules/uv-action-sheet/readme.md
new file mode 100644
index 0000000..9ea5487
--- /dev/null
+++ b/ledApp/uni_modules/uv-action-sheet/readme.md
@@ -0,0 +1,13 @@
+## ActionSheet 操作菜单
+
+> **组件名:uv-action-sheet**
+
+本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。
+
+本组件功能类似于uni的uni.showActionSheet API,配置更加灵活,所有平台都表现一致。
+
+### <a href="https://www.uvui.cn/components/actionSheet.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-album/changelog.md b/ledApp/uni_modules/uv-album/changelog.md
new file mode 100644
index 0000000..42ab5e2
--- /dev/null
+++ b/ledApp/uni_modules/uv-album/changelog.md
@@ -0,0 +1,10 @@
+## 1.0.4(2023-12-06)
+1. 阻止事件冒泡处理
+## 1.0.3(2023-10-23)
+1. 修复报错的BUG
+## 1.0.2(2023-10-23)
+1. 修复设置singleSize、multipleSize、space等值带单位,存在不显示的BUG
+## 1.0.1(2023-09-13)
+1. 添加依赖
+## 1.0.0(2023-08-30)
+1. 新增uv-album相册组件
diff --git a/ledApp/uni_modules/uv-album/components/uv-album/uv-album.vue b/ledApp/uni_modules/uv-album/components/uv-album/uv-album.vue
new file mode 100644
index 0000000..835d792
--- /dev/null
+++ b/ledApp/uni_modules/uv-album/components/uv-album/uv-album.vue
@@ -0,0 +1,312 @@
+<template>
+ <view class="uv-album">
+ <view
+ class="uv-album__row"
+ ref="uv-album__row"
+ v-for="(arr, index) in showUrls"
+ :forComputedUse="albumWidth"
+ :key="index"
+ >
+ <view
+ class="uv-album__row__wrapper"
+ v-for="(item, index1) in arr"
+ :key="index1"
+ :style="[imageStyle(index + 1, index1 + 1)]"
+ @tap.stop="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
+ >
+ <image
+ :src="getSrc(item)"
+ :mode="
+ urls.length === 1
+ ? imageHeight > 0
+ ? singleMode
+ : 'widthFix'
+ : multipleMode
+ "
+ :style="[
+ {
+ width: imageWidth,
+ height: imageHeight
+ }
+ ]"
+ ></image>
+ <view
+ v-if="
+ showMore &&
+ urls.length > rowCount * showUrls.length &&
+ index === showUrls.length - 1 &&
+ index1 === showUrls[showUrls.length - 1].length - 1
+ "
+ class="uv-album__row__wrapper__text"
+ >
+ <uv-text
+ :text="`+${urls.length - maxCount}`"
+ color="#fff"
+ :size="$uv.getPx(multipleSize) * 0.3"
+ align="center"
+ customStyle="justify-content: center"
+ ></uv-text>
+ </view>
+ </view>
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef APP-NVUE
+ // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+
+ /**
+ * Album 相册
+ * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
+ * @tutorial https://www.uvui.cn/components/album.html
+ * @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
+ * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
+ * @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
+ * @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
+ * @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
+ * @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
+ * @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
+ * @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
+ * @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
+ * @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
+ * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
+ *
+ * @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
+ * @example <uv-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></uv-album>
+ */
+ export default {
+ name: 'uv-album',
+ mixins: [mpMixin, mixin],
+ emits: ['albumWidth'],
+ props: {
+ // 图片地址,Array<String>|Array<Object>形式
+ urls: {
+ type: Array,
+ default: () => []
+ },
+ // 指定从数组的对象元素中读取哪个属性作为图片地址
+ keyName: {
+ type: String,
+ default: ''
+ },
+ // 单图时,图片长边的长度
+ singleSize: {
+ type: [String, Number],
+ default: 180
+ },
+ // 多图时,图片边长
+ multipleSize: {
+ type: [String, Number],
+ default: 70
+ },
+ // 多图时,图片水平和垂直之间的间隔
+ space: {
+ type: [String, Number],
+ default: 6
+ },
+ // 单图时,图片缩放裁剪的模式
+ singleMode: {
+ type: String,
+ default: 'scaleToFill'
+ },
+ // 多图时,图片缩放裁剪的模式
+ multipleMode: {
+ type: String,
+ default: 'aspectFill'
+ },
+ // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
+ maxCount: {
+ type: [String, Number],
+ default: 9
+ },
+ // 是否可以预览图片
+ previewFullImage: {
+ type: Boolean,
+ default: true
+ },
+ // 每行展示图片数量,如设置,singleSize和multipleSize将会无效
+ rowCount: {
+ type: [String, Number],
+ default: 3
+ },
+ // 超出maxCount时是否显示查看更多的提示
+ showMore: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.album
+ },
+ data() {
+ return {
+ // 单图的宽度
+ singleWidth: 0,
+ // 单图的高度
+ singleHeight: 0,
+ // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
+ singlePercent: 0.6
+ }
+ },
+ watch: {
+ urls: {
+ immediate: true,
+ handler(newVal) {
+ if (newVal.length === 1) {
+ this.getImageRect()
+ }
+ }
+ }
+ },
+ computed: {
+ imageStyle() {
+ return (index1, index2) => {
+ const { space, rowCount, multipleSize, urls } = this;
+ const rowLen = this.showUrls.length;
+ const allLen = this.urls.length;
+ const style = {
+ marginRight: this.$uv.addUnit(space),
+ marginBottom: this.$uv.addUnit(space)
+ }
+ // 如果为最后一行,则每个图片都无需下边框
+ if (index1 === rowLen) style.marginBottom = 0
+ // 每行的最右边一张和总长度的最后一张无需右边框
+ if (
+ index2 === rowCount ||
+ (index1 === rowLen &&
+ index2 === this.showUrls[index1 - 1].length)
+ )
+ style.marginRight = 0
+ return style
+ }
+ },
+ // 将数组划分为二维数组
+ showUrls() {
+ const arr = []
+ this.urls.map((item, index) => {
+ // 限制最大展示数量
+ if (index + 1 <= this.maxCount) {
+ // 计算该元素为第几个素组内
+ const itemIndex = Math.floor(index / this.rowCount)
+ // 判断对应的索引是否存在
+ if (!arr[itemIndex]) {
+ arr[itemIndex] = []
+ }
+ arr[itemIndex].push(item)
+ }
+ })
+ return arr
+ },
+ imageWidth() {
+ return this.$uv.addUnit(
+ this.urls.length === 1 ? this.singleWidth : this.multipleSize
+ )
+ },
+ imageHeight() {
+ return this.$uv.addUnit(
+ this.urls.length === 1 ? this.singleHeight : this.multipleSize
+ )
+ },
+ // 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
+ // 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
+ albumWidth() {
+ let width = 0
+ if (this.urls.length === 1) {
+ width = this.singleWidth
+ } else {
+ width =
+ this.showUrls[0].length * this.$uv.getPx(this.multipleSize) +
+ this.$uv.getPx(this.space) * (this.showUrls[0].length - 1)
+ }
+ this.$emit('albumWidth', width)
+ return width
+ }
+ },
+ methods: {
+ // 预览图片
+ onPreviewTap(url) {
+ const urls = this.urls.map((item) => {
+ return this.getSrc(item)
+ })
+ uni.previewImage({
+ current: url,
+ urls
+ })
+ },
+ // 获取图片的路径
+ getSrc(item) {
+ return this.$uv.test.object(item) ?
+ (this.keyName && item[this.keyName]) || item.src :
+ item
+ },
+ // 单图时,获取图片的尺寸
+ // 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
+ // 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
+ getImageRect() {
+ const src = this.getSrc(this.urls[0])
+ uni.getImageInfo({
+ src,
+ success: (res) => {
+ // 判断图片横向还是竖向展示方式
+ const isHorizotal = res.width >= res.height
+ this.singleWidth = isHorizotal ?
+ this.singleSize :
+ (res.width / res.height) * this.$uv.getPx(this.singleSize)
+ this.singleHeight = !isHorizotal ?
+ this.singleSize :
+ (res.height / res.width) * this.singleWidth
+ },
+ fail: () => {
+ this.getComponentWidth()
+ }
+ })
+ },
+ // 获取组件的宽度
+ async getComponentWidth() {
+ // 延时一定时间,以获取dom尺寸
+ await this.$uv.sleep(30)
+ // #ifndef APP-NVUE
+ this.$uGetRect('.uv-album__row').then((size) => {
+ this.singleWidth = size.width * this.singlePercent
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ // 这里ref="uv-album__row"所在的标签为通过for循环出来,导致this.$refs['uv-album__row']是一个数组
+ const ref = this.$refs['uv-album__row'][0]
+ ref &&
+ dom.getComponentRect(ref, (res) => {
+ this.singleWidth = res.size.width * this.singlePercent
+ })
+ // #endif
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-album {
+ @include flex(column);
+ &__row {
+ @include flex(row);
+ flex-wrap: wrap;
+ &__wrapper {
+ position: relative;
+ &__text {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.3);
+ @include flex(row);
+ justify-content: center;
+ align-items: center;
+ }
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-album/package.json b/ledApp/uni_modules/uv-album/package.json
new file mode 100644
index 0000000..700602f
--- /dev/null
+++ b/ledApp/uni_modules/uv-album/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-album",
+ "displayName": "uv-album 相册 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.4",
+ "description": "本组件提供一个类似相册的功能,让开发者开发起来更加得心应手,功能齐全,灵活配置可以,开箱即用。减少重复的模板代码",
+ "keywords": [
+ "album",
+ "uv-ui",
+ "uvui",
+ "相册",
+ "图片"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-text"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-album/readme.md b/ledApp/uni_modules/uv-album/readme.md
new file mode 100644
index 0000000..edd2f0b
--- /dev/null
+++ b/ledApp/uni_modules/uv-album/readme.md
@@ -0,0 +1,21 @@
+# Album 相册
+
+> **组件名:uv-album**
+
+本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。
+
+功能齐全,灵活配置可以,开箱即用。减少重复的模板代码。
+
+# <a href="https://www.uvui.cn/components/album.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-alert/changelog.md b/ledApp/uni_modules/uv-alert/changelog.md
new file mode 100644
index 0000000..71cca03
--- /dev/null
+++ b/ledApp/uni_modules/uv-alert/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-01)
+1. 修复点击触发两次实践的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-alert 警告提示组件
diff --git a/ledApp/uni_modules/uv-alert/components/uv-alert/props.js b/ledApp/uni_modules/uv-alert/components/uv-alert/props.js
new file mode 100644
index 0000000..cc8edc1
--- /dev/null
+++ b/ledApp/uni_modules/uv-alert/components/uv-alert/props.js
@@ -0,0 +1,45 @@
+export default {
+ props: {
+ // 显示文字
+ title: {
+ type: String,
+ default: ''
+ },
+ // 主题,success/warning/info/error
+ type: {
+ type: String,
+ default: 'warning'
+ },
+ // 辅助性文字
+ description: {
+ type: String,
+ default: ''
+ },
+ // 是否可关闭
+ closable: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示图标
+ showIcon: {
+ type: Boolean,
+ default: false
+ },
+ // 浅或深色调,light-浅色,dark-深色
+ effect: {
+ type: String,
+ default: 'light'
+ },
+ // 文字是否居中
+ center: {
+ type: Boolean,
+ default: false
+ },
+ // 字体大小
+ fontSize: {
+ type: [String, Number],
+ default: 14
+ },
+ ...uni.$uv?.props?.alert
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-alert/components/uv-alert/uv-alert.vue b/ledApp/uni_modules/uv-alert/components/uv-alert/uv-alert.vue
new file mode 100644
index 0000000..ba3965e
--- /dev/null
+++ b/ledApp/uni_modules/uv-alert/components/uv-alert/uv-alert.vue
@@ -0,0 +1,246 @@
+<template>
+ <uv-transition
+ mode="fade"
+ :show="show"
+ >
+ <view
+ class="uv-alert"
+ :class="[`uv-alert--${type}--${effect}`]"
+ @tap.stop="clickHandler"
+ :style="[$uv.addStyle(customStyle)]"
+ >
+ <view
+ class="uv-alert__icon"
+ v-if="showIcon"
+ >
+ <uv-icon
+ :name="iconName"
+ size="18"
+ :color="iconColor"
+ ></uv-icon>
+ </view>
+ <view
+ class="uv-alert__content"
+ :style="[{
+ paddingRight: closable ? '20px' : 0
+ }]"
+ >
+ <text
+ class="uv-alert__content__title"
+ v-if="title"
+ :style="[{
+ fontSize: $uv.addUnit(fontSize),
+ textAlign: center ? 'center' : 'left'
+ }]"
+ :class="[effect === 'dark' ? 'uv-alert__text--dark' : `uv-alert__text--${type}--light`]"
+ >{{ title }}</text>
+ <text
+ class="uv-alert__content__desc"
+ v-if="description"
+ :style="[{
+ fontSize: $uv.addUnit(fontSize),
+ textAlign: center ? 'center' : 'left'
+ }]"
+ :class="[effect === 'dark' ? 'uv-alert__text--dark' : `uv-alert__text--${type}--light`]"
+ >{{ description }}</text>
+ </view>
+ <view
+ class="uv-alert__close"
+ v-if="closable"
+ @tap.stop="closeHandler"
+ >
+ <uv-icon
+ name="close"
+ :color="iconColor"
+ size="15"
+ ></uv-icon>
+ </view>
+ </view>
+ </uv-transition>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Alert 警告提示
+ * @description 警告提示,展现需要关注的信息。
+ * @tutorial https://www.uvui.cn/components/alertTips.html
+ *
+ * @property {String} title 显示的文字
+ * @property {String} type 使用预设的颜色 (默认 'warning' )
+ * @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
+ * @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false )
+ * @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false )
+ * @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' )
+ * @property {Boolean} center 文字是否居中 (默认 false )
+ * @property {String | Number} fontSize 字体大小 (默认 14 )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @event {Function} click 点击组件时触发
+ * @example <uv-alert :title="title" type = "warning" :closable="closable" :description = "description"></uv-alert>
+ */
+ export default {
+ name: 'uv-alert',
+ mixins: [mpMixin, mixin, props],
+ emits: ['click'],
+ data() {
+ return {
+ show: true
+ }
+ },
+ computed: {
+ iconColor() {
+ return this.effect === 'light' ? this.type : '#fff'
+ },
+ // 不同主题对应不同的图标
+ iconName() {
+ switch (this.type) {
+ case 'success':
+ return 'checkmark-circle-fill';
+ break;
+ case 'error':
+ return 'close-circle-fill';
+ break;
+ case 'warning':
+ return 'error-circle-fill';
+ break;
+ case 'info':
+ return 'info-circle-fill';
+ break;
+ case 'primary':
+ return 'more-circle-fill';
+ break;
+ default:
+ return 'error-circle-fill';
+ }
+ }
+ },
+ methods: {
+ // 点击内容
+ clickHandler() {
+ this.$emit('click')
+ },
+ // 点击关闭按钮
+ closeHandler() {
+ this.show = false
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-alert {
+ position: relative;
+ background-color: $uv-primary;
+ padding: 8px 10px;
+ @include flex(row);
+ align-items: center;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+
+ &--primary--dark {
+ background-color: $uv-primary;
+ }
+
+ &--primary--light {
+ background-color: #ecf5ff;
+ }
+
+ &--error--dark {
+ background-color: $uv-error;
+ }
+
+ &--error--light {
+ background-color: #FEF0F0;
+ }
+
+ &--success--dark {
+ background-color: $uv-success;
+ }
+
+ &--success--light {
+ background-color: #f5fff0;
+ }
+
+ &--warning--dark {
+ background-color: $uv-warning;
+ }
+
+ &--warning--light {
+ background-color: #FDF6EC;
+ }
+
+ &--info--dark {
+ background-color: $uv-info;
+ }
+
+ &--info--light {
+ background-color: #f4f4f5;
+ }
+
+ &__icon {
+ margin-right: 5px;
+ }
+
+ &__content {
+ @include flex(column);
+ flex: 1;
+
+ &__title {
+ color: $uv-main-color;
+ font-size: 14px;
+ font-weight: bold;
+ color: #fff;
+ margin-bottom: 2px;
+ }
+
+ &__desc {
+ color: $uv-main-color;
+ font-size: 14px;
+ flex-wrap: wrap;
+ color: #fff;
+ }
+ }
+
+ &__title--dark,
+ &__desc--dark {
+ color: #FFFFFF;
+ }
+
+ &__text--primary--light,
+ &__text--primary--light {
+ color: $uv-primary;
+ }
+
+ &__text--success--light,
+ &__text--success--light {
+ color: $uv-success;
+ }
+
+ &__text--warning--light,
+ &__text--warning--light {
+ color: $uv-warning;
+ }
+
+ &__text--error--light,
+ &__text--error--light {
+ color: $uv-error;
+ }
+
+ &__text--info--light,
+ &__text--info--light {
+ color: $uv-info;
+ }
+
+ &__close {
+ position: absolute;
+ top: 11px;
+ right: 10px;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-alert/package.json b/ledApp/uni_modules/uv-alert/package.json
new file mode 100644
index 0000000..34bec20
--- /dev/null
+++ b/ledApp/uni_modules/uv-alert/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-alert",
+ "displayName": "uv-alert 警告提示 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "uv-alert 警告提示,展现需要关注的信息。灵活配置,功能齐全,兼容全端",
+ "keywords": [
+ "alert",
+ "uvui",
+ "uv-ui",
+ "警告提示"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-transition",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-alert/readme.md b/ledApp/uni_modules/uv-alert/readme.md
new file mode 100644
index 0000000..63dda76
--- /dev/null
+++ b/ledApp/uni_modules/uv-alert/readme.md
@@ -0,0 +1,15 @@
+## Alert 警告提示
+
+> **组件名:uv-alert**
+
+警告提示,展现需要关注的信息。
+
+当某个页面需要向用户显示警告的信息时。
+
+非浮层的静态展现形式,始终展现,不会自动消失,用户可以点击关闭。
+
+### <a href="https://www.uvui.cn/components/alert.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-avatar/changelog.md b/ledApp/uni_modules/uv-avatar/changelog.md
new file mode 100644
index 0000000..8631c86
--- /dev/null
+++ b/ledApp/uni_modules/uv-avatar/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.5(2023-12-06)
+1. 优化
+## 1.0.4(2023-12-06)
+1. 优化
+## 1.0.3(2023-12-06)
+1. 阻止事件冒泡处理,单个头像模式
+## 1.0.2(2023-12-06)
+1. 阻止事件冒泡处理
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-avatar 头像组件
diff --git a/ledApp/uni_modules/uv-avatar/components/uv-avatar-group/props.js b/ledApp/uni_modules/uv-avatar/components/uv-avatar-group/props.js
new file mode 100644
index 0000000..a610ab4
--- /dev/null
+++ b/ledApp/uni_modules/uv-avatar/components/uv-avatar-group/props.js
@@ -0,0 +1,53 @@
+export default {
+ props: {
+ // 头像图片组
+ urls: {
+ type: Array,
+ default: () => []
+ },
+ // 最多展示的头像数量
+ maxCount: {
+ type: [String, Number],
+ default: 5
+ },
+ // 头像形状
+ shape: {
+ type: String,
+ default: 'circle'
+ },
+ // 图片裁剪模式
+ mode: {
+ type: String,
+ default: 'scaleToFill'
+ },
+ // 超出maxCount时是否显示查看更多的提示
+ showMore: {
+ type: Boolean,
+ default: true
+ },
+ // 头像大小
+ size: {
+ type: [String, Number],
+ default: 40
+ },
+ // 指定从数组的对象元素中读取哪个属性作为图片地址
+ keyName: {
+ type: String,
+ default: ''
+ },
+ // 头像之间的遮挡比例
+ gap: {
+ type: [String, Number],
+ validator(value) {
+ return value >= 0 && value <= 1
+ },
+ default: 0.5
+ },
+ // 需额外显示的值
+ extraValue: {
+ type: [Number, String],
+ default: 0
+ },
+ ...uni.$uv?.props?.avatarGroup
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-avatar/components/uv-avatar-group/uv-avatar-group.vue b/ledApp/uni_modules/uv-avatar/components/uv-avatar-group/uv-avatar-group.vue
new file mode 100644
index 0000000..59481f6
--- /dev/null
+++ b/ledApp/uni_modules/uv-avatar/components/uv-avatar-group/uv-avatar-group.vue
@@ -0,0 +1,106 @@
+<template>
+ <view class="uv-avatar-group">
+ <view
+ class="uv-avatar-group__item"
+ v-for="(item, index) in showUrl"
+ :key="index"
+ :style="{
+ marginLeft: index === 0 ? 0 : $uv.addUnit(-size * gap)
+ }"
+ >
+ <uv-avatar
+ :size="size"
+ :shape="shape"
+ :mode="mode"
+ :src="$uv.test.object(item) ? keyName && item[keyName] || item.url : item"
+ ></uv-avatar>
+ <view
+ class="uv-avatar-group__item__show-more"
+ v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
+ @tap="clickHandler"
+ >
+ <uv-text
+ color="#ffffff"
+ :size="size * 0.4"
+ :text="`+${extraValue || urls.length - showUrl.length}`"
+ align="center"
+ customStyle="justify-content: center"
+ ></uv-text>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * AvatarGroup 头像组
+ * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+ * @tutorial https://www.uvui.cn/components/avatar.html
+ *
+ * @property {Array} urls 头像图片组 (默认 [] )
+ * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
+ * @property {String} shape 头像形状( 'circle' (默认) | 'square' )
+ * @property {String} mode 图片裁剪模式(默认 'scaleToFill' )
+ * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
+ * @property {String | Number} size 头像大小 (默认 40 )
+ * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
+ * @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 )
+ * @property {String | Number} extraValue 需额外显示的值
+ * @event {Function} showMore 头像组更多点击
+ * @example <uv-avatar-group:urls="urls" size="35" gap="0.4" ></uv-avatar-group:urls=>
+ */
+ export default {
+ name: 'uv-avatar-group',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+
+ }
+ },
+ computed: {
+ showUrl() {
+ return this.urls.slice(0, this.maxCount)
+ }
+ },
+ methods: {
+ clickHandler() {
+ this.$emit('showMore')
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+
+ .uv-avatar-group {
+ @include flex;
+
+ &__item {
+ margin-left: -10px;
+ position: relative;
+
+ &--no-indent {
+ // 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
+ margin-left: 0;
+ }
+
+ &__show-more {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: rgba(0, 0, 0, 0.3);
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 100px;
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-avatar/components/uv-avatar/props.js b/ledApp/uni_modules/uv-avatar/components/uv-avatar/props.js
new file mode 100644
index 0000000..6c8d725
--- /dev/null
+++ b/ledApp/uni_modules/uv-avatar/components/uv-avatar/props.js
@@ -0,0 +1,80 @@
+import { range } from '@/uni_modules/uv-ui-tools/libs/function/test.js'
+export default {
+ props: {
+ // 头像图片路径(不能为相对路径)
+ src: {
+ type: String,
+ default: ''
+ },
+ // 头像形状,circle-圆形,square-方形
+ shape: {
+ type: String,
+ default: 'circle'
+ },
+ // 头像尺寸
+ size: {
+ type: [String, Number],
+ default: 40
+ },
+ // 裁剪模式
+ mode: {
+ type: String,
+ default: 'scaleToFill'
+ },
+ // 显示的文字
+ text: {
+ type: String,
+ default: ''
+ },
+ // 背景色
+ bgColor: {
+ type: String,
+ default: '#c0c4cc'
+ },
+ // 文字颜色
+ color: {
+ type: String,
+ default: '#fff'
+ },
+ // 文字大小
+ fontSize: {
+ type: [String, Number],
+ default: 18
+ },
+ // 显示的图标
+ icon: {
+ type: String,
+ default: ''
+ },
+ // 显示小程序头像,只对百度,微信,QQ小程序有效
+ mpAvatar: {
+ type: Boolean,
+ default: false
+ },
+ // 是否使用随机背景色
+ randomBgColor: {
+ type: Boolean,
+ default: false
+ },
+ // 加载失败的默认头像(组件有内置默认图片)
+ defaultUrl: {
+ type: String,
+ default: ''
+ },
+ // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
+ colorIndex: {
+ type: [String, Number],
+ // 校验参数规则,索引在0-19之间
+ validator(n) {
+ return range(n, [0, 19]) || n === ''
+ },
+ default: ''
+ },
+ // 组件标识符
+ name: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.avatar
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-avatar/components/uv-avatar/uv-avatar.vue b/ledApp/uni_modules/uv-avatar/components/uv-avatar/uv-avatar.vue
new file mode 100644
index 0000000..0959d6a
--- /dev/null
+++ b/ledApp/uni_modules/uv-avatar/components/uv-avatar/uv-avatar.vue
@@ -0,0 +1,175 @@
+<template>
+ <view
+ class="uv-avatar"
+ :class="[`uv-avatar--${shape}`]"
+ :style="[{
+ backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $uv.random(0, 19)] : bgColor) : 'transparent',
+ width: $uv.addUnit(size),
+ height: $uv.addUnit(size),
+ }, $uv.addStyle(customStyle)]"
+ @tap="clickHandler"
+ >
+ <slot>
+ <!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU -->
+ <open-data
+ v-if="mpAvatar && allowMp"
+ type="userAvatarUrl"
+ :style="[{
+ width: $uv.addUnit(size),
+ height: $uv.addUnit(size)
+ }]"
+ />
+ <!-- #endif -->
+ <!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
+ <template v-if="mpAvatar && allowMp"></template>
+ <!-- #endif -->
+ <uv-icon
+ v-else-if="icon"
+ :name="icon"
+ :size="fontSize"
+ :color="color"
+ ></uv-icon>
+ <uv-text
+ v-else-if="text"
+ :text="text"
+ :size="fontSize"
+ :color="color"
+ align="center"
+ customStyle="justify-content: center"
+ ></uv-text>
+ <image
+ class="uv-avatar__image"
+ v-else
+ :class="[`uv-avatar__image--${shape}`]"
+ :src="avatarUrl || defaultUrl"
+ :mode="mode"
+ @error="errorHandler"
+ :style="[{
+ width: $uv.addUnit(size),
+ height: $uv.addUnit(size)
+ }]"
+ ></image>
+ </slot>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ const base64Avatar =
+ "data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z";
+ /**
+ * Avatar 头像
+ * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+ * @tutorial https://www.uvui.cn/components/avatar.html
+ *
+ * @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径)
+ * @property {String} shape 头像形状 ( circle (默认) | square)
+ * @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 )
+ * @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' )
+ * @property {String} text 用文字替代图片,级别优先于src
+ * @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' )
+ * @property {String} color 文字颜色 (默认 '#ffffff' )
+ * @property {String | Number} fontSize 文字大小 (默认 18 )
+ * @property {String} icon 显示的图标
+ * @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false )
+ * @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false )
+ * @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片)
+ * @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
+ * @property {String} name 组件标识符 (默认 'level' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} click 点击组件时触发 index: 用户传递的标识符
+ * @example <uv-avatar :src="src" mode="square"></uv-avatar>
+ */
+ export default {
+ name: 'uv-avatar',
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色
+ colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',
+ '#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',
+ '#73d1f1',
+ '#80a7dc'
+ ],
+ avatarUrl: this.src,
+ allowMp: false
+ }
+ },
+ watch: {
+ // 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值
+ // 而组件内部不能直接修改props的值,所以需要一个中间变量
+ src: {
+ immediate: true,
+ handler(newVal) {
+ this.avatarUrl = newVal
+ // 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示
+ if(!newVal) {
+ this.errorHandler()
+ }
+ }
+ }
+ },
+ computed: {
+ imageStyle() {
+ const style = {}
+ return style
+ }
+ },
+ created() {
+ this.init()
+ },
+ methods: {
+ init() {
+ // 目前只有这几个小程序平台具有open-data标签
+ // 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑
+ // 故目前自动获取小程序头像只支持这几个平台
+ // #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
+ this.allowMp = true
+ // #endif
+ },
+ // 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
+ isImg() {
+ return this.src.indexOf('/') !== -1
+ },
+ // 图片加载时失败时触发
+ errorHandler() {
+ this.avatarUrl = this.defaultUrl || base64Avatar
+ },
+ clickHandler() {
+ this.$emit('click', this.name)
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-avatar {
+ @include flex;
+ align-items: center;
+ justify-content: center;
+
+ &--circle {
+ border-radius: 100px;
+ }
+
+ &--square {
+ border-radius: 4px;
+ }
+
+ &__image {
+ &--circle {
+ border-radius: 100px;
+ }
+
+ &--square {
+ border-radius: 4px;
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-avatar/package.json b/ledApp/uni_modules/uv-avatar/package.json
new file mode 100644
index 0000000..e77ab68
--- /dev/null
+++ b/ledApp/uni_modules/uv-avatar/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-avatar",
+ "displayName": "uv-avatar 头像 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.5",
+ "description": "uv-avatar 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。",
+ "keywords": [
+ "uv-avatar",
+ "uvui",
+ "uv-ui",
+ "avatar",
+ "头像"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-text"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-avatar/readme.md b/ledApp/uni_modules/uv-avatar/readme.md
new file mode 100644
index 0000000..1f068eb
--- /dev/null
+++ b/ledApp/uni_modules/uv-avatar/readme.md
@@ -0,0 +1,11 @@
+## Avatar 头像
+
+> **组件名:uv-avatar**
+
+本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+
+### <a href="https://www.uvui.cn/components/avatar.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-back-top/changelog.md b/ledApp/uni_modules/uv-back-top/changelog.md
new file mode 100644
index 0000000..9772262
--- /dev/null
+++ b/ledApp/uni_modules/uv-back-top/changelog.md
@@ -0,0 +1,8 @@
+## 1.0.2(2023-07-03)
+1. 优化插槽自定义内容部分
+2. 增加backToTop方法说明
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-back-top 返回顶部
diff --git a/ledApp/uni_modules/uv-back-top/components/uv-back-top/props.js b/ledApp/uni_modules/uv-back-top/components/uv-back-top/props.js
new file mode 100644
index 0000000..74daff4
--- /dev/null
+++ b/ledApp/uni_modules/uv-back-top/components/uv-back-top/props.js
@@ -0,0 +1,58 @@
+export default {
+ props: {
+ // 返回顶部的形状,circle-圆形,square-方形
+ mode: {
+ type: String,
+ default: 'circle'
+ },
+ // 自定义图标
+ icon: {
+ type: String,
+ default: 'arrow-upward'
+ },
+ // 提示文字
+ text: {
+ type: String,
+ default: ''
+ },
+ // 返回顶部滚动时间
+ duration: {
+ type: [String, Number],
+ default: 100
+ },
+ // 滚动距离
+ scrollTop: {
+ type: [String, Number],
+ default: 0
+ },
+ // 距离顶部多少距离显示,单位px
+ top: {
+ type: [String, Number],
+ default: 400
+ },
+ // 返回顶部按钮到底部的距离,单位px
+ bottom: {
+ type: [String, Number],
+ default: 100
+ },
+ // 返回顶部按钮到右边的距离,单位px
+ right: {
+ type: [String, Number],
+ default: 20
+ },
+ // 层级
+ zIndex: {
+ type: [String, Number],
+ default: 9
+ },
+ // 图标的样式,对象形式
+ iconStyle: {
+ type: Object,
+ default: () => ({
+ color: '#909399',
+ fontSize: '19px'
+ })
+ },
+ ...uni.$uv?.props?.backtop
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-back-top/components/uv-back-top/uv-back-top.vue b/ledApp/uni_modules/uv-back-top/components/uv-back-top/uv-back-top.vue
new file mode 100644
index 0000000..fad73d4
--- /dev/null
+++ b/ledApp/uni_modules/uv-back-top/components/uv-back-top/uv-back-top.vue
@@ -0,0 +1,116 @@
+<template>
+ <uv-transition mode="fade" :customStyle="backTopStyle" :show="show">
+ <slot>
+ <view class="uv-back-top" :style="[contentStyle]" @click="backToTop">
+ <uv-icon :name="icon" :custom-style="iconStyle"></uv-icon>
+ <text v-if="text" class="uv-back-top__text">{{text}}</text>
+ </view>
+ </slot>
+ </uv-transition>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const dom = weex.requireModule('dom')
+ // #endif
+ /**
+ * backTop 返回顶部
+ * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
+ * @tutorial https://www.uvui.cn/components/backTop.html
+ * @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
+ * @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例
+ * @property {String} text 提示文字
+ * @property {String | Number} duration 返回顶部滚动时间 (默认 100)
+ * @property {String | Number} scrollTop 滚动距离 (默认 0 )
+ * @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 )
+ * @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 )
+ * @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 )
+ * @property {String | Number} zIndex 层级 (默认 9 )
+ * @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'})
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @example <uv-back-top :scrollTop="scrollTop"></uv-back-top>
+ */
+ export default {
+ name: 'uv-back-top',
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ backTopStyle() {
+ // 动画组件样式
+ const style = {
+ bottom: this.$uv.addUnit(this.bottom),
+ right: this.$uv.addUnit(this.right),
+ width: '40px',
+ height: '40px',
+ position: 'fixed',
+ zIndex: 10,
+ }
+ return style
+ },
+ show() {
+ return this.$uv.getPx(this.scrollTop) > this.$uv.getPx(this.top)
+ },
+ contentStyle() {
+ const style = {}
+ let radius = 0
+ // 是否圆形
+ if (this.mode === 'circle') {
+ radius = '100px'
+ } else {
+ radius = '4px'
+ }
+ // 为了兼容安卓nvue,只能这么分开写
+ style.borderTopLeftRadius = radius
+ style.borderTopRightRadius = radius
+ style.borderBottomLeftRadius = radius
+ style.borderBottomRightRadius = radius
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ methods: {
+ backToTop() {
+ // #ifdef APP-NVUE
+ if (!this.$parent.$refs['uv-back-top']) {
+ this.$uv.error(`nvue页面需要给页面最外层元素设置"ref='uv-back-top'`)
+ }
+ dom.scrollToElement(this.$parent.$refs['uv-back-top'], {
+ offset: 0
+ })
+ // #endif
+
+ // #ifndef APP-NVUE
+ uni.pageScrollTo({
+ scrollTop: 0,
+ duration: this.duration
+ });
+ // #endif
+ this.$emit('click')
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ $uv-back-top-flex: 1 !default;
+ $uv-back-top-height: 100% !default;
+ $uv-back-top-background-color: #E1E1E1 !default;
+ $uv-back-top-tips-font-size: 12px !default;
+ .uv-back-top {
+ @include flex;
+ flex-direction: column;
+ align-items: center;
+ flex: $uv-back-top-flex;
+ height: $uv-back-top-height;
+ justify-content: center;
+ background-color: $uv-back-top-background-color;
+ &__tips {
+ font-size: $uv-back-top-tips-font-size;
+ transform: scale(0.8);
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-back-top/package.json b/ledApp/uni_modules/uv-back-top/package.json
new file mode 100644
index 0000000..a2035a5
--- /dev/null
+++ b/ledApp/uni_modules/uv-back-top/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-back-top",
+ "displayName": "uv-back-top 返回顶部 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "返回顶部 组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。",
+ "keywords": [
+ "uv-back-top",
+ "uvui",
+ "uv-ui",
+ "avatar",
+ "返回顶部"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-transition"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-back-top/readme.md b/ledApp/uni_modules/uv-back-top/readme.md
new file mode 100644
index 0000000..d6a2aec
--- /dev/null
+++ b/ledApp/uni_modules/uv-back-top/readme.md
@@ -0,0 +1,11 @@
+## BackTop 返回顶部
+
+> **组件名:uv-back-top**
+
+该组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
+
+### <a href="https://www.uvui.cn/components/backTop.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-badge/changelog.md b/ledApp/uni_modules/uv-badge/changelog.md
new file mode 100644
index 0000000..b0ba104
--- /dev/null
+++ b/ledApp/uni_modules/uv-badge/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-04)
+1. 修复type等属性为null的时候不显示徽标的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-badge 徽标数,数字角标
diff --git a/ledApp/uni_modules/uv-badge/components/uv-badge/props.js b/ledApp/uni_modules/uv-badge/components/uv-badge/props.js
new file mode 100644
index 0000000..c6369c3
--- /dev/null
+++ b/ledApp/uni_modules/uv-badge/components/uv-badge/props.js
@@ -0,0 +1,73 @@
+export default {
+ props: {
+ // 是否显示圆点
+ isDot: {
+ type: Boolean,
+ default: false
+ },
+ // 显示的内容
+ value: {
+ type: [Number, String],
+ default: ''
+ },
+ // 是否显示
+ show: {
+ type: Boolean,
+ default: true
+ },
+ // 最大值,超过最大值会显示 '{max}+'
+ max: {
+ type: [Number, String],
+ default: 999
+ },
+ // 主题类型,error|warning|success|primary
+ type: {
+ type: [String,undefined,null],
+ default: 'error'
+ },
+ // 当数值为 0 时,是否展示 Badge
+ showZero: {
+ type: Boolean,
+ default: false
+ },
+ // 背景颜色,优先级比type高,如设置,type参数会失效
+ bgColor: {
+ type: [String, null],
+ default: null
+ },
+ // 字体颜色
+ color: {
+ type: [String, null],
+ default: null
+ },
+ // 徽标形状,circle-四角均为圆角,horn-左下角为直角
+ shape: {
+ type: [String,undefined,null],
+ default: 'circle'
+ },
+ // 设置数字的显示方式,overflow|ellipsis|limit
+ // overflow会根据max字段判断,超出显示`${max}+`
+ // ellipsis会根据max判断,超出显示`${max}...`
+ // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
+ numberType: {
+ type: [String,undefined,null],
+ default: 'overflow'
+ },
+ // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+ offset: {
+ type: Array,
+ default: () => []
+ },
+ // 是否反转背景和字体颜色
+ inverted: {
+ type: Boolean,
+ default: false
+ },
+ // 是否绝对定位
+ absolute: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.badge
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-badge/components/uv-badge/uv-badge.vue b/ledApp/uni_modules/uv-badge/components/uv-badge/uv-badge.vue
new file mode 100644
index 0000000..f7d3911
--- /dev/null
+++ b/ledApp/uni_modules/uv-badge/components/uv-badge/uv-badge.vue
@@ -0,0 +1,176 @@
+<template>
+ <text
+ v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
+ :class="[isDot ? 'uv-badge--dot' : 'uv-badge--not-dot', inverted && 'uv-badge--inverted', shape === 'horn' && 'uv-badge--horn', `uv-badge--${propsType}${inverted ? '--inverted' : ''}`]"
+ :style="[$uv.addStyle(customStyle), badgeStyle]"
+ class="uv-badge"
+ >{{ isDot ? '' :showValue }}</text>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * badge 徽标数
+ * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+ * @tutorial https://www.uvui.cn/components/badge.html
+ *
+ * @property {Boolean} isDot 是否显示圆点 (默认 false )
+ * @property {String | Number} value 显示的内容
+ * @property {Boolean} show 是否显示 (默认 true )
+ * @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999)
+ * @property {String} type 主题类型,error|warning|success|primary (默认 'error' )
+ * @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false )
+ * @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
+ * @property {String} color 字体颜色 (默认 '#ffffff' )
+ * @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
+ * @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' )
+ * @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+ * @property {Boolean} inverted 是否反转背景和字体颜色(默认 false )
+ * @property {Boolean} absolute 是否绝对定位(默认 false )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @example <uv-badge :type="type" :count="count"></uv-badge>
+ */
+ export default {
+ name: 'uv-badge',
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ // 是否将badge中心与父组件右上角重合
+ boxStyle() {
+ let style = {};
+ return style;
+ },
+ // 整个组件的样式
+ badgeStyle() {
+ const style = {}
+ if(this.color) {
+ style.color = this.color
+ }
+ if (this.bgColor && !this.inverted) {
+ style.backgroundColor = this.bgColor
+ }
+ if (this.absolute) {
+ style.position = 'absolute'
+ // 如果有设置offset参数
+ if(this.offset.length) {
+ // top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
+ const top = this.offset[0]
+ const right = this.offset[1] || top
+ style.top = this.$uv.addUnit(top)
+ style.right = this.$uv.addUnit(right)
+ }
+ }
+ return style
+ },
+ showValue() {
+ switch (this.numberType) {
+ case "overflow":
+ return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
+ break;
+ case "ellipsis":
+ return Number(this.value) > Number(this.max) ? "..." : this.value
+ break;
+ case "limit":
+ return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
+ Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
+ 1e3 * 100) / 100 + "k" : this.value
+ break;
+ default:
+ return Number(this.value)
+ }
+ },
+ propsType(){
+ return this.type || 'error'
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-badge-primary: $uv-primary !default;
+ $uv-badge-error: $uv-error !default;
+ $uv-badge-success: $uv-success !default;
+ $uv-badge-info: $uv-info !default;
+ $uv-badge-warning: $uv-warning !default;
+ $uv-badge-dot-radius: 100px !default;
+ $uv-badge-dot-size: 8px !default;
+ $uv-badge-dot-right: 4px !default;
+ $uv-badge-dot-top: 0 !default;
+ $uv-badge-text-font-size: 11px !default;
+ $uv-badge-text-right: 10px !default;
+ $uv-badge-text-padding: 2px 5px !default;
+ $uv-badge-text-align: center !default;
+ $uv-badge-text-color: #FFFFFF !default;
+
+ .uv-badge {
+ border-top-right-radius: $uv-badge-dot-radius;
+ border-top-left-radius: $uv-badge-dot-radius;
+ border-bottom-left-radius: $uv-badge-dot-radius;
+ border-bottom-right-radius: $uv-badge-dot-radius;
+ @include flex;
+ line-height: $uv-badge-text-font-size;
+ text-align: $uv-badge-text-align;
+ font-size: $uv-badge-text-font-size;
+ color: $uv-badge-text-color;
+
+ &--dot {
+ height: $uv-badge-dot-size;
+ width: $uv-badge-dot-size;
+ }
+
+ &--inverted {
+ font-size: 13px;
+ }
+
+ &--not-dot {
+ padding: $uv-badge-text-padding;
+ }
+
+ &--horn {
+ border-bottom-left-radius: 0;
+ }
+
+ &--primary {
+ background-color: $uv-badge-primary;
+ }
+
+ &--primary--inverted {
+ color: $uv-badge-primary;
+ }
+
+ &--error {
+ background-color: $uv-badge-error;
+ }
+
+ &--error--inverted {
+ color: $uv-badge-error;
+ }
+
+ &--success {
+ background-color: $uv-badge-success;
+ }
+
+ &--success--inverted {
+ color: $uv-badge-success;
+ }
+
+ &--info {
+ background-color: $uv-badge-info;
+ }
+
+ &--info--inverted {
+ color: $uv-badge-info;
+ }
+
+ &--warning {
+ background-color: $uv-badge-warning;
+ }
+
+ &--warning--inverted {
+ color: $uv-badge-warning;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-badge/package.json b/ledApp/uni_modules/uv-badge/package.json
new file mode 100644
index 0000000..5a81386
--- /dev/null
+++ b/ledApp/uni_modules/uv-badge/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-badge",
+ "displayName": "uv-badge 徽标数,数字角标 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "徽标数一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。",
+ "keywords": [
+ "uv-badge",
+ "uvui",
+ "uv-ui",
+ "徽标数",
+ "数字角标"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-badge/readme.md b/ledApp/uni_modules/uv-badge/readme.md
new file mode 100644
index 0000000..cf597eb
--- /dev/null
+++ b/ledApp/uni_modules/uv-badge/readme.md
@@ -0,0 +1,11 @@
+## Badge 徽标数
+
+> **组件名:uv-badge**
+
+该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+
+### <a href="https://www.uvui.cn/components/badge.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-button/changelog.md b/ledApp/uni_modules/uv-button/changelog.md
new file mode 100644
index 0000000..11e954e
--- /dev/null
+++ b/ledApp/uni_modules/uv-button/changelog.md
@@ -0,0 +1,33 @@
+## 1.0.15(2023-12-20)
+1. 优化
+## 1.0.14(2023-12-06)
+1. 优化
+## 1.0.13(2023-12-06)
+1. 阻止事件冒泡处理
+## 1.0.12(2023-10-19)
+1. 增加后置插槽
+## 1.0.11(2023-09-21)
+1. 修复通过customStyle修改按钮宽度,组件中最外层节点不改变的问题
+## 1.0.10(2023-09-15)
+1. 按钮支持open-type="agreePrivacyAuthorization"
+## 1.0.9(2023-09-11)
+1. 增加参数iconSize,用于控制图标的大小
+## 1.0.8(2023-09-10)
+1. 修复多个按钮在一行宽度不正常的BUG
+## 1.0.7(2023-09-07)
+1. 修复warning颜色对应错误的BUG
+## 1.0.6(2023-07-25)
+1. 增加customTextStyle属性,方便自定义文字样式
+## 1.0.5(2023-07-20)
+1. 解决微信小程序动态设置hover-class点击态不消失的BUG
+## 1.0.4(2023-06-29)
+1. 修改上次更新出现nvue报错异常
+## 1.0.3(2023-06-28)
+ 修复:设置open-type="chooseAvatar"等值不生效的BUG
+## 1.0.2(2023-06-01)
+1. 修复按钮点击触发两次的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-button 按钮
diff --git a/ledApp/uni_modules/uv-button/components/uv-button/nvue.scss b/ledApp/uni_modules/uv-button/components/uv-button/nvue.scss
new file mode 100644
index 0000000..be6c16e
--- /dev/null
+++ b/ledApp/uni_modules/uv-button/components/uv-button/nvue.scss
@@ -0,0 +1,46 @@
+$uv-button-active-opacity:0.75 !default;
+$uv-button-loading-text-margin-left:4px !default;
+$uv-button-text-color: #FFFFFF !default;
+$uv-button-text-plain-error-color:$uv-error !default;
+$uv-button-text-plain-warning-color:$uv-warning !default;
+$uv-button-text-plain-success-color:$uv-success !default;
+$uv-button-text-plain-info-color:$uv-info !default;
+$uv-button-text-plain-primary-color:$uv-primary !default;
+.uv-button {
+ &--active {
+ opacity: $uv-button-active-opacity;
+ }
+
+ &--active--plain {
+ background-color: rgb(217, 217, 217);
+ }
+
+ &__loading-text {
+ margin-left:$uv-button-loading-text-margin-left;
+ }
+
+ &__text,
+ &__loading-text {
+ color:$uv-button-text-color;
+ }
+
+ &__text--plain--error {
+ color:$uv-button-text-plain-error-color;
+ }
+
+ &__text--plain--warning {
+ color:$uv-button-text-plain-warning-color;
+ }
+
+ &__text--plain--success{
+ color:$uv-button-text-plain-success-color;
+ }
+
+ &__text--plain--info {
+ color:$uv-button-text-plain-info-color;
+ }
+
+ &__text--plain--primary {
+ color:$uv-button-text-plain-primary-color;
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-button/components/uv-button/props.js b/ledApp/uni_modules/uv-button/components/uv-button/props.js
new file mode 100644
index 0000000..6275ad5
--- /dev/null
+++ b/ledApp/uni_modules/uv-button/components/uv-button/props.js
@@ -0,0 +1,163 @@
+export default {
+ props: {
+ // 是否细边框
+ hairline: {
+ type: Boolean,
+ default: true
+ },
+ // 按钮的预置样式,info,primary,error,warning,success
+ type: {
+ type: String,
+ default: 'info'
+ },
+ // 按钮尺寸,large,normal,small,mini
+ size: {
+ type: String,
+ default: 'normal'
+ },
+ // 按钮形状,circle(两边为半圆),square(带圆角)
+ shape: {
+ type: String,
+ default: 'square'
+ },
+ // 按钮是否镂空
+ plain: {
+ type: Boolean,
+ default: false
+ },
+ // 是否禁止状态
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否加载中
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ // 加载中提示文字
+ loadingText: {
+ type: [String, Number],
+ default: ''
+ },
+ // 加载状态图标类型
+ loadingMode: {
+ type: String,
+ default: 'spinner'
+ },
+ // 加载图标大小
+ loadingSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 开放能力,具体请看uniapp稳定关于button组件部分说明
+ // https://uniapp.dcloud.io/component/button
+ openType: {
+ type: String,
+ default: ''
+ },
+ // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ // 取值为submit(提交表单),reset(重置表单)
+ formType: {
+ type: String,
+ default: ''
+ },
+ // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
+ // 只微信小程序、QQ小程序有效
+ appParameter: {
+ type: String,
+ default: ''
+ },
+ // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
+ hoverStopPropagation: {
+ type: Boolean,
+ default: true
+ },
+ // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
+ lang: {
+ type: String,
+ default: 'en'
+ },
+ // 会话来源,open-type="contact"时有效。只微信小程序有效
+ sessionFrom: {
+ type: String,
+ default: ''
+ },
+ // 会话内消息卡片标题,open-type="contact"时有效
+ // 默认当前标题,只微信小程序有效
+ sendMessageTitle: {
+ type: String,
+ default: ''
+ },
+ // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
+ // 默认当前分享路径,只微信小程序有效
+ sendMessagePath: {
+ type: String,
+ default: ''
+ },
+ // 会话内消息卡片图片,open-type="contact"时有效
+ // 默认当前页面截图,只微信小程序有效
+ sendMessageImg: {
+ type: String,
+ default: ''
+ },
+ // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
+ // 用户点击后可以快速发送小程序消息,open-type="contact"时有效
+ showMessageCard: {
+ type: Boolean,
+ default: true
+ },
+ // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ dataName: {
+ type: String,
+ default: ''
+ },
+ // 节流,一定时间内只能触发一次
+ throttleTime: {
+ type: [String, Number],
+ default: 0
+ },
+ // 按住后多久出现点击态,单位毫秒
+ hoverStartTime: {
+ type: [String, Number],
+ default: 0
+ },
+ // 手指松开后点击态保留时间,单位毫秒
+ hoverStayTime: {
+ type: [String, Number],
+ default: 200
+ },
+ // 按钮文字,之所以通过props传入,是因为slot传入的话
+ // nvue中无法控制文字的样式
+ text: {
+ type: [String, Number],
+ default: ''
+ },
+ // 按钮图标
+ icon: {
+ type: String,
+ default: ''
+ },
+ // 按钮图标大小
+ iconSize: {
+ type: [String, Number],
+ default: ''
+ },
+ // 按钮图标颜色
+ iconColor: {
+ type: String,
+ default: '#000000'
+ },
+ // 按钮颜色,支持传入linear-gradient渐变色
+ color: {
+ type: String,
+ default: ''
+ },
+ // 自定义按钮文本样式
+ customTextStyle: {
+ type: [Object,String],
+ default: ''
+ },
+ ...uni.$uv?.props?.button
+ }
+}
diff --git a/ledApp/uni_modules/uv-button/components/uv-button/uv-button.vue b/ledApp/uni_modules/uv-button/components/uv-button/uv-button.vue
new file mode 100644
index 0000000..3a2470a
--- /dev/null
+++ b/ledApp/uni_modules/uv-button/components/uv-button/uv-button.vue
@@ -0,0 +1,528 @@
+<template>
+ <view
+ class="uv-button-wrapper"
+ :style="[btnWrapperStyle]"
+ >
+ <!-- #ifndef APP-NVUE -->
+ <!-- #ifdef MP -->
+ <!-- 为了解决微信小程序动态设置hover-class点击态不消失的BUG -->
+ <view class="uv-button-wrapper--dis" v-if="disabled || loading"></view>
+ <button
+ :hover-start-time="Number(hoverStartTime)"
+ :hover-stay-time="Number(hoverStayTime)"
+ :form-type="formType"
+ :open-type="openType"
+ :app-parameter="appParameter"
+ :hover-stop-propagation="hoverStopPropagation"
+ :send-message-title="sendMessageTitle"
+ :send-message-path="sendMessagePath"
+ :lang="lang"
+ :data-name="dataName"
+ :session-from="sessionFrom"
+ :send-message-img="sendMessageImg"
+ :show-message-card="showMessageCard"
+ @getphonenumber="onGetPhoneNumber"
+ @getuserinfo="onGetUserInfo"
+ @error="onError"
+ @opensetting="onOpenSetting"
+ @launchapp="onLaunchApp"
+ @contact="onContact"
+ @chooseavatar="onChooseavatar"
+ @agreeprivacyauthorization="onAgreeprivacyauthorization"
+ @addgroupapp="onAddgroupapp"
+ @chooseaddress="onChooseaddress"
+ @subscribe="onSubscribe"
+ @login="onLogin"
+ @im="onIm"
+ hover-class="uv-button--active"
+ class="uv-button uv-reset-button"
+ :style="[baseColor, $uv.addStyle(customStyle)]"
+ @tap="clickHandler"
+ :class="bemClass"
+ >
+ <!-- #endif -->
+ <!-- #ifndef MP -->
+ <button
+ :hover-start-time="Number(hoverStartTime)"
+ :hover-stay-time="Number(hoverStayTime)"
+ :form-type="formType"
+ :open-type="openType"
+ :app-parameter="appParameter"
+ :hover-stop-propagation="hoverStopPropagation"
+ :send-message-title="sendMessageTitle"
+ :send-message-path="sendMessagePath"
+ :lang="lang"
+ :data-name="dataName"
+ :session-from="sessionFrom"
+ :send-message-img="sendMessageImg"
+ :show-message-card="showMessageCard"
+ :hover-class="!disabled && !loading ? 'uv-button--active' : ''"
+ class="uv-button uv-reset-button"
+ :style="[baseColor, $uv.addStyle(customStyle)]"
+ @tap="clickHandler"
+ :class="bemClass"
+ >
+ <!-- #endif -->
+ <template v-if="loading">
+ <uv-loading-icon
+ :mode="loadingMode"
+ :size="loadingSize * 1.15"
+ :color="loadingColor"
+ ></uv-loading-icon>
+ <text
+ class="uv-button__loading-text"
+ :style="[
+ { fontSize: textSize + 'px' },
+ $uv.addStyle(customTextStyle)
+ ]"
+ >{{ loadingText || text }}</text>
+ </template>
+ <template v-else>
+ <uv-icon
+ v-if="icon"
+ :name="icon"
+ :color="iconColorCom"
+ :size="getIconSize"
+ :customStyle="{ marginRight: '2px' }"
+ ></uv-icon>
+ <slot>
+ <text
+ class="uv-button__text"
+ :style="[
+ { fontSize: textSize + 'px' },
+ $uv.addStyle(customTextStyle)
+ ]"
+ >{{ text }}</text>
+ </slot>
+ <slot name="suffix"></slot>
+ </template>
+ </button>
+ <!-- #endif -->
+ <!-- #ifdef APP-NVUE -->
+ <view
+ :hover-start-time="Number(hoverStartTime)"
+ :hover-stay-time="Number(hoverStayTime)"
+ class="uv-button"
+ :hover-class="
+ !disabled && !loading && !color && (plain || type === 'info')
+ ? 'uv-button--active--plain'
+ : !disabled && !loading && !plain
+ ? 'uv-button--active'
+ : ''
+ "
+ @tap="clickHandler"
+ :class="bemClass"
+ :style="[baseColor, $uv.addStyle(customStyle)]"
+ >
+ <template v-if="loading">
+ <uv-loading-icon
+ :mode="loadingMode"
+ :size="loadingSize * 1.15"
+ :color="loadingColor"
+ ></uv-loading-icon>
+ <text
+ class="uv-button__loading-text"
+ :style="[nvueTextStyle,$uv.addStyle(customTextStyle)]"
+ :class="[plain && `uv-button__text--plain--${type}`]"
+ >{{ loadingText || text }}</text>
+ </template>
+ <template v-else>
+ <uv-icon
+ v-if="icon"
+ :name="icon"
+ :color="iconColorCom"
+ :size="getIconSize"
+ ></uv-icon>
+ <text
+ class="uv-button__text"
+ :style="[
+ {
+ marginLeft: icon ? '2px' : 0,
+ },
+ nvueTextStyle,
+ $uv.addStyle(customTextStyle)
+ ]"
+ :class="[plain && `uv-button__text--plain--${type}`]"
+ >{{ text }}</text>
+ <slot name="suffix"></slot>
+ </template>
+ </view>
+ <!-- #endif -->
+ </view>
+</template>
+
+<script>
+import throttle from '@/uni_modules/uv-ui-tools/libs/function/throttle.js';
+import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+import button from '@/uni_modules/uv-ui-tools/libs/mixin/button.js'
+import openType from '@/uni_modules/uv-ui-tools/libs/mixin/openType.js'
+import props from "./props.js";
+/**
+ * button 按钮
+ * @description Button 按钮
+ * @tutorial https://www.uvui.cn/components/button.html
+ * @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
+ * @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' )
+ * @property {String} size 按钮尺寸,large,normal,mini (默认 normal)
+ * @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
+ * @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false)
+ * @property {Boolean} disabled 是否禁用 (默认 false)
+ * @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
+ * @property {String | Number} loadingText 加载中提示文字
+ * @property {String} loadingMode 加载状态图标类型 (默认 'spinner' )
+ * @property {String | Number} loadingSize 加载图标大小 (默认 15 )
+ * @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明
+ * @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ * @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
+ * @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
+ * @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
+ * @property {String} sessionFrom 会话来源,openType="contact"时有效
+ * @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
+ * @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+ * @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
+ * @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
+ * @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ * @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 )
+ * @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 )
+ * @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 )
+ * @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
+ * @property {String} icon 按钮图标
+ * @property {String} iconColor 按钮图标颜色
+ * @property {String} color 按钮颜色,支持传入linear-gradient渐变色
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @event {Function} click 非禁止并且非加载中,才能点击
+ * @event {Function} getphonenumber open-type="getPhoneNumber"时有效
+ * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
+ * @event {Function} error 当使用开放能力时,发生错误的回调
+ * @event {Function} opensetting 在打开授权设置页并关闭后回调
+ * @event {Function} launchapp 打开 APP 成功的回调
+ * @example <uv-button>月落</uv-button>
+ */
+export default {
+ name: "uv-button",
+ // #ifdef MP
+ mixins: [mpMixin, mixin, button, openType, props],
+ // #endif
+ // #ifndef MP
+ mixins: [mpMixin, mixin, props],
+ // #endif
+ emits: ['click'],
+ data() {
+ return {};
+ },
+ computed: {
+ // 生成bem风格的类名
+ bemClass() {
+ // this.bem为一个computed变量,在mixin中
+ if (!this.color) {
+ return this.bem("button",
+ ["type", "shape", "size"],
+ ["disabled", "plain", "hairline"]);
+ } else {
+ // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
+ return this.bem("button",
+ ["shape", "size"],
+ ["disabled", "plain", "hairline"]);
+ }
+ },
+ loadingColor() {
+ if (this.plain) {
+ // 如果有设置color值,则用color值,否则使用type主题颜色
+ return this.color ? this.color : '#3c9cff';
+ }
+ if (this.type === "info") {
+ return "#c9c9c9";
+ }
+ return "rgb(200, 200, 200)";
+ },
+ iconColorCom() {
+ // 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
+ // uv-icon的color能接受一个主题颜色的值
+ if (this.iconColor) return this.iconColor;
+ if (this.plain) {
+ return this.color ? this.color : this.type;
+ } else {
+ return this.type === "info" ? "#000000" : "#ffffff";
+ }
+ },
+ baseColor() {
+ let style = {};
+ if (this.color) {
+ // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+ style.color = this.plain ? this.color : "white";
+ if (!this.plain) {
+ // 非镂空,背景色使用自定义的颜色
+ style["background-color"] = this.color;
+ }
+ if (this.color.indexOf("gradient") !== -1) {
+ // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
+ // weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
+ // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
+ style.borderTopWidth = 0;
+ style.borderRightWidth = 0;
+ style.borderBottomWidth = 0;
+ style.borderLeftWidth = 0;
+ if (!this.plain) {
+ style.backgroundImage = this.color;
+ }
+ } else {
+ // 非渐变色,则设置边框相关的属性
+ style.borderColor = this.color;
+ style.borderWidth = "1px";
+ style.borderStyle = "solid";
+ }
+ }
+ return style;
+ },
+ // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
+ nvueTextStyle() {
+ let style = {};
+ // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+ if (this.type === "info") {
+ style.color = "#323233";
+ }
+ if (this.color) {
+ style.color = this.plain ? this.color : "white";
+ }
+ style.fontSize = this.textSize + "px";
+ return style;
+ },
+ // 字体大小
+ textSize() {
+ let fontSize = 14,
+ { size } = this;
+ if (size === "large") fontSize = 16;
+ if (size === "normal") fontSize = 14;
+ if (size === "small") fontSize = 12;
+ if (size === "mini") fontSize = 10;
+ return fontSize;
+ },
+ // 设置图标大小
+ getIconSize() {
+ const size = this.iconSize ? this.iconSize : this.textSize * 1.35;
+ return this.$uv.addUnit(size);
+ },
+ // 设置外层盒子的宽度,其他样式不需要
+ btnWrapperStyle() {
+ const style = {};
+ const customStyle = this.$uv.addStyle(this.customStyle);
+ if(customStyle.width) style.width = customStyle.width;
+ return style;
+ }
+ },
+ methods: {
+ clickHandler() {
+ // 非禁止并且非加载中,才能点击
+ if (!this.disabled && !this.loading) {
+ // 进行节流控制,每this.throttle毫秒内,只在开始处执行
+ throttle(() => {
+ this.$emit("click");
+ }, this.throttleTime);
+ }
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+$show-reset-button: 1;
+@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+
+/* #ifndef APP-NVUE */
+@import "./vue.scss";
+/* #endif */
+
+/* #ifdef APP-NVUE */
+@import "./nvue.scss";
+/* #endif */
+
+$uv-button-uv-button-height: 40px !default;
+$uv-button-text-font-size: 15px !default;
+$uv-button-loading-text-font-size: 15px !default;
+$uv-button-loading-text-margin-left: 4px !default;
+$uv-button-large-width: 100% !default;
+$uv-button-large-height: 50px !default;
+$uv-button-normal-padding: 0 12px !default;
+$uv-button-large-padding: 0 15px !default;
+$uv-button-normal-font-size: 14px !default;
+$uv-button-small-min-width: 60px !default;
+$uv-button-small-height: 30px !default;
+$uv-button-small-padding: 0px 8px !default;
+$uv-button-mini-padding: 0px 8px !default;
+$uv-button-small-font-size: 12px !default;
+$uv-button-mini-height: 22px !default;
+$uv-button-mini-font-size: 10px !default;
+$uv-button-mini-min-width: 50px !default;
+$uv-button-disabled-opacity: 0.5 !default;
+$uv-button-info-color: #323233 !default;
+$uv-button-info-background-color: #fff !default;
+$uv-button-info-border-color: #ebedf0 !default;
+$uv-button-info-border-width: 1px !default;
+$uv-button-info-border-style: solid !default;
+$uv-button-success-color: #fff !default;
+$uv-button-success-background-color: $uv-success !default;
+$uv-button-success-border-color: $uv-button-success-background-color !default;
+$uv-button-success-border-width: 1px !default;
+$uv-button-success-border-style: solid !default;
+$uv-button-primary-color: #fff !default;
+$uv-button-primary-background-color: $uv-primary !default;
+$uv-button-primary-border-color: $uv-button-primary-background-color !default;
+$uv-button-primary-border-width: 1px !default;
+$uv-button-primary-border-style: solid !default;
+$uv-button-error-color: #fff !default;
+$uv-button-error-background-color: $uv-error !default;
+$uv-button-error-border-color: $uv-button-error-background-color !default;
+$uv-button-error-border-width: 1px !default;
+$uv-button-error-border-style: solid !default;
+$uv-button-warning-color: #fff !default;
+$uv-button-warning-background-color: $uv-warning !default;
+$uv-button-warning-border-color: $uv-button-warning-background-color !default;
+$uv-button-warning-border-width: 1px !default;
+$uv-button-warning-border-style: solid !default;
+$uv-button-block-width: 100% !default;
+$uv-button-circle-border-top-right-radius: 100px !default;
+$uv-button-circle-border-top-left-radius: 100px !default;
+$uv-button-circle-border-bottom-left-radius: 100px !default;
+$uv-button-circle-border-bottom-right-radius: 100px !default;
+$uv-button-square-border-top-right-radius: 3px !default;
+$uv-button-square-border-top-left-radius: 3px !default;
+$uv-button-square-border-bottom-left-radius: 3px !default;
+$uv-button-square-border-bottom-right-radius: 3px !default;
+$uv-button-icon-min-width: 1em !default;
+$uv-button-plain-background-color: #fff !default;
+$uv-button-hairline-border-width: 0.5px !default;
+
+.uv-button {
+ height: $uv-button-uv-button-height;
+ position: relative;
+ align-items: center;
+ justify-content: center;
+ @include flex;
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ /* #endif */
+ flex-direction: row;
+
+ &__text {
+ font-size: $uv-button-text-font-size;
+ }
+
+ &__loading-text {
+ font-size: $uv-button-loading-text-font-size;
+ margin-left: $uv-button-loading-text-margin-left;
+ }
+
+ &--large {
+ /* #ifndef APP-NVUE */
+ width: $uv-button-large-width;
+ /* #endif */
+ height: $uv-button-large-height;
+ padding: $uv-button-large-padding;
+ }
+
+ &--normal {
+ padding: $uv-button-normal-padding;
+ font-size: $uv-button-normal-font-size;
+ }
+
+ &--small {
+ /* #ifndef APP-NVUE */
+ min-width: $uv-button-small-min-width;
+ /* #endif */
+ height: $uv-button-small-height;
+ padding: $uv-button-small-padding;
+ font-size: $uv-button-small-font-size;
+ }
+
+ &--mini {
+ height: $uv-button-mini-height;
+ font-size: $uv-button-mini-font-size;
+ /* #ifndef APP-NVUE */
+ min-width: $uv-button-mini-min-width;
+ /* #endif */
+ padding: $uv-button-mini-padding;
+ }
+
+ &--disabled {
+ opacity: $uv-button-disabled-opacity;
+ }
+
+ &--info {
+ color: $uv-button-info-color;
+ background-color: $uv-button-info-background-color;
+ border-color: $uv-button-info-border-color;
+ border-width: $uv-button-info-border-width;
+ border-style: $uv-button-info-border-style;
+ }
+
+ &--success {
+ color: $uv-button-success-color;
+ background-color: $uv-button-success-background-color;
+ border-color: $uv-button-success-border-color;
+ border-width: $uv-button-success-border-width;
+ border-style: $uv-button-success-border-style;
+ }
+
+ &--primary {
+ color: $uv-button-primary-color;
+ background-color: $uv-button-primary-background-color;
+ border-color: $uv-button-primary-border-color;
+ border-width: $uv-button-primary-border-width;
+ border-style: $uv-button-primary-border-style;
+ }
+
+ &--error {
+ color: $uv-button-error-color;
+ background-color: $uv-button-error-background-color;
+ border-color: $uv-button-error-border-color;
+ border-width: $uv-button-error-border-width;
+ border-style: $uv-button-error-border-style;
+ }
+
+ &--warning {
+ color: $uv-button-warning-color;
+ background-color: $uv-button-warning-background-color;
+ border-color: $uv-button-warning-border-color;
+ border-width: $uv-button-warning-border-width;
+ border-style: $uv-button-warning-border-style;
+ }
+
+ &--block {
+ @include flex;
+ width: $uv-button-block-width;
+ }
+
+ &--circle {
+ border-top-right-radius: $uv-button-circle-border-top-right-radius;
+ border-top-left-radius: $uv-button-circle-border-top-left-radius;
+ border-bottom-left-radius: $uv-button-circle-border-bottom-left-radius;
+ border-bottom-right-radius: $uv-button-circle-border-bottom-right-radius;
+ }
+
+ &--square {
+ border-bottom-left-radius: $uv-button-square-border-top-right-radius;
+ border-bottom-right-radius: $uv-button-square-border-top-left-radius;
+ border-top-left-radius: $uv-button-square-border-bottom-left-radius;
+ border-top-right-radius: $uv-button-square-border-bottom-right-radius;
+ }
+
+ &__icon {
+ /* #ifndef APP-NVUE */
+ min-width: $uv-button-icon-min-width;
+ line-height: inherit !important;
+ vertical-align: top;
+ /* #endif */
+ }
+
+ &--plain {
+ background-color: $uv-button-plain-background-color;
+ }
+
+ &--hairline {
+ border-width: $uv-button-hairline-border-width !important;
+ }
+}
+</style>
diff --git a/ledApp/uni_modules/uv-button/components/uv-button/vue.scss b/ledApp/uni_modules/uv-button/components/uv-button/vue.scss
new file mode 100644
index 0000000..7a089be
--- /dev/null
+++ b/ledApp/uni_modules/uv-button/components/uv-button/vue.scss
@@ -0,0 +1,93 @@
+@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+// nvue下hover-class无效
+$uv-button-before-top:50% !default;
+$uv-button-before-left:50% !default;
+$uv-button-before-width:100% !default;
+$uv-button-before-height:100% !default;
+$uv-button-before-transform:translate(-50%, -50%) !default;
+$uv-button-before-opacity:0 !default;
+$uv-button-before-background-color:#000 !default;
+$uv-button-before-border-color:#000 !default;
+$uv-button-active-before-opacity:.15 !default;
+$uv-button-icon-margin-left:4px !default;
+$uv-button-plain-uv-button-info-color:$uv-info;
+$uv-button-plain-uv-button-success-color:$uv-success;
+$uv-button-plain-uv-button-error-color:$uv-error;
+$uv-button-plain-uv-button-warning-color:$uv-warning;
+
+.uv-button-wrapper {
+ position: relative;
+ &--dis {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 9;
+ }
+}
+
+.uv-button {
+ width: 100%;
+
+ &__text {
+ white-space: nowrap;
+ line-height: 1;
+ }
+
+ &:before {
+ position: absolute;
+ top:$uv-button-before-top;
+ left:$uv-button-before-left;
+ width:$uv-button-before-width;
+ height:$uv-button-before-height;
+ border: inherit;
+ border-radius: inherit;
+ transform:$uv-button-before-transform;
+ opacity:$uv-button-before-opacity;
+ content: " ";
+ background-color:$uv-button-before-background-color;
+ border-color:$uv-button-before-border-color;
+ }
+
+ &--active {
+ &:before {
+ opacity: .15
+ }
+ }
+
+ &__icon+&__text:not(:empty),
+ &__loading-text {
+ margin-left:$uv-button-icon-margin-left;
+ }
+
+ &--plain {
+ &.uv-button--primary {
+ color: $uv-primary;
+ }
+ }
+
+ &--plain {
+ &.uv-button--info {
+ color:$uv-button-plain-uv-button-info-color;
+ }
+ }
+
+ &--plain {
+ &.uv-button--success {
+ color:$uv-button-plain-uv-button-success-color;
+ }
+ }
+
+ &--plain {
+ &.uv-button--error {
+ color:$uv-button-plain-uv-button-error-color;
+ }
+ }
+
+ &--plain {
+ &.uv-button--warning {
+ color:$uv-button-plain-uv-button-warning-color;
+ }
+ }
+}
diff --git a/ledApp/uni_modules/uv-button/package.json b/ledApp/uni_modules/uv-button/package.json
new file mode 100644
index 0000000..c2a4a17
--- /dev/null
+++ b/ledApp/uni_modules/uv-button/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-button",
+ "displayName": "uv-button 按钮 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.15",
+ "description": "按钮组件内部实现以uni-app的button组件为基础,进行二次封装,灵活配置,功能齐全,兼容全端。",
+ "keywords": [
+ "uv-button",
+ "uvui",
+ "uv-ui",
+ "button",
+ "按钮"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-loading-icon",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-button/readme.md b/ledApp/uni_modules/uv-button/readme.md
new file mode 100644
index 0000000..124a7f4
--- /dev/null
+++ b/ledApp/uni_modules/uv-button/readme.md
@@ -0,0 +1,19 @@
+## Button 按钮
+
+> **组件名:uv-button**
+
+该组件内部实现以`uni-app`的`button`组件为基础,进行二次封装,灵活配置,功能齐全,兼容全端。灵活配置,内置状态设置,开箱即用。
+
+# <a href="https://www.uvui.cn/components/button.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendar/changelog.md b/ledApp/uni_modules/uv-calendar/changelog.md
new file mode 100644
index 0000000..581562a
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendar/changelog.md
@@ -0,0 +1,16 @@
+## 1.0.6(2023-11-03)
+1. **该版本不再维护**,推荐使用新版本[https://ext.dcloud.net.cn/plugin?name=uv-calendars](https://ext.dcloud.net.cn/plugin?name=uv-calendars)
+## 1.0.5(2023-07-02)
+uv-calendar 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/calendar.html
+## 1.0.4(2023-06-15)
+1. formatter格式化中增加topInfo参数
+## 1.0.3(2023-06-08)
+1. 增加点击日期change回调
+2. 优化
+## 1.0.2(2023-06-05)
+1. 修改多个时间选择的时候存在反选的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-calendar 日历
diff --git a/ledApp/uni_modules/uv-calendar/components/uv-calendar/calendar.js b/ledApp/uni_modules/uv-calendar/components/uv-calendar/calendar.js
new file mode 100644
index 0000000..e006dea
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendar/components/uv-calendar/calendar.js
@@ -0,0 +1,546 @@
+/**
+* @1900-2100区间内的公历、农历互转
+* @charset UTF-8
+* @github https://github.com/jjonline/calendar.js
+* @Author Jea杨(JJonline@JJonline.Cn)
+* @Time 2014-7-21
+* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
+* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
+* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
+* @Version 1.0.3
+* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
+* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
+*/
+/* eslint-disable */
+var calendar = {
+
+ /**
+ * 农历1900-2100的润大小信息表
+ * @Array Of Property
+ * @return Hex
+ */
+ lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
+ 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
+ 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
+ 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
+ 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
+ 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
+ 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
+ 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
+ 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
+ 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
+ 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
+ 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
+ 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
+ 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
+ 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
+ /** Add By JJonline@JJonline.Cn**/
+ 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
+ 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
+ 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
+ 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
+ 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
+ 0x0d520], // 2100
+
+ /**
+ * 公历每个月份的天数普通表
+ * @Array Of Property
+ * @return Number
+ */
+ solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+
+ /**
+ * 天干地支之天干速查表
+ * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
+ * @return Cn string
+ */
+ Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
+
+ /**
+ * 天干地支之地支速查表
+ * @Array Of Property
+ * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
+ * @return Cn string
+ */
+ Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
+
+ /**
+ * 天干地支之地支速查表<=>生肖
+ * @Array Of Property
+ * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
+ * @return Cn string
+ */
+ Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
+
+ /**
+ * 24节气速查表
+ * @Array Of Property
+ * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
+ * @return Cn string
+ */
+ solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
+
+ /**
+ * 1900-2100各年的24节气日期速查表
+ * @Array Of Property
+ * @return 0x string For splice
+ */
+ sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
+ '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
+ 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
+ '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
+ '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
+ '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+ '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
+ '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+ '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
+ '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+ '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
+ '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
+ '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
+ '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
+ '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
+ '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
+ '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
+ '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
+ '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
+ '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
+
+ /**
+ * 数字转中文速查表
+ * @Array Of Property
+ * @trans ['日','一','二','三','四','五','六','七','八','九','十']
+ * @return Cn string
+ */
+ nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
+
+ /**
+ * 日期转农历称呼速查表
+ * @Array Of Property
+ * @trans ['初','十','廿','卅']
+ * @return Cn string
+ */
+ nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
+
+ /**
+ * 月份转农历称呼速查表
+ * @Array Of Property
+ * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
+ * @return Cn string
+ */
+ nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
+
+ /**
+ * 返回农历y年一整年的总天数
+ * @param lunar Year
+ * @return Number
+ * @eg:var count = calendar.lYearDays(1987) ;//count=387
+ */
+ lYearDays: function (y) {
+ var i; var sum = 348
+ for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
+ return (sum + this.leapDays(y))
+ },
+
+ /**
+ * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
+ * @param lunar Year
+ * @return Number (0-12)
+ * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
+ */
+ leapMonth: function (y) { // 闰字编码 \u95f0
+ return (this.lunarInfo[y - 1900] & 0xf)
+ },
+
+ /**
+ * 返回农历y年闰月的天数 若该年没有闰月则返回0
+ * @param lunar Year
+ * @return Number (0、29、30)
+ * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
+ */
+ leapDays: function (y) {
+ if (this.leapMonth(y)) {
+ return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
+ }
+ return (0)
+ },
+
+ /**
+ * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
+ * @param lunar Year
+ * @return Number (-1、29、30)
+ * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
+ */
+ monthDays: function (y, m) {
+ if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
+ return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
+ },
+
+ /**
+ * 返回公历(!)y年m月的天数
+ * @param solar Year
+ * @return Number (-1、28、29、30、31)
+ * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
+ */
+ solarDays: function (y, m) {
+ if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+ var ms = m - 1
+ if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
+ return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
+ } else {
+ return (this.solarMonth[ms])
+ }
+ },
+
+ /**
+ * 农历年份转换为干支纪年
+ * @param lYear 农历年的年份数
+ * @return Cn string
+ */
+ toGanZhiYear: function (lYear) {
+ var ganKey = (lYear - 3) % 10
+ var zhiKey = (lYear - 3) % 12
+ if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
+ if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
+ return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
+ },
+
+ /**
+ * 公历月、日判断所属星座
+ * @param cMonth [description]
+ * @param cDay [description]
+ * @return Cn string
+ */
+ toAstro: function (cMonth, cDay) {
+ var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
+ var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
+ return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
+ },
+
+ /**
+ * 传入offset偏移量返回干支
+ * @param offset 相对甲子的偏移量
+ * @return Cn string
+ */
+ toGanZhi: function (offset) {
+ return this.Gan[offset % 10] + this.Zhi[offset % 12]
+ },
+
+ /**
+ * 传入公历(!)y年获得该年第n个节气的公历日期
+ * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
+ * @return day Number
+ * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
+ */
+ getTerm: function (y, n) {
+ if (y < 1900 || y > 2100) { return -1 }
+ if (n < 1 || n > 24) { return -1 }
+ var _table = this.sTermInfo[y - 1900]
+ var _info = [
+ parseInt('0x' + _table.substr(0, 5)).toString(),
+ parseInt('0x' + _table.substr(5, 5)).toString(),
+ parseInt('0x' + _table.substr(10, 5)).toString(),
+ parseInt('0x' + _table.substr(15, 5)).toString(),
+ parseInt('0x' + _table.substr(20, 5)).toString(),
+ parseInt('0x' + _table.substr(25, 5)).toString()
+ ]
+ var _calday = [
+ _info[0].substr(0, 1),
+ _info[0].substr(1, 2),
+ _info[0].substr(3, 1),
+ _info[0].substr(4, 2),
+
+ _info[1].substr(0, 1),
+ _info[1].substr(1, 2),
+ _info[1].substr(3, 1),
+ _info[1].substr(4, 2),
+
+ _info[2].substr(0, 1),
+ _info[2].substr(1, 2),
+ _info[2].substr(3, 1),
+ _info[2].substr(4, 2),
+
+ _info[3].substr(0, 1),
+ _info[3].substr(1, 2),
+ _info[3].substr(3, 1),
+ _info[3].substr(4, 2),
+
+ _info[4].substr(0, 1),
+ _info[4].substr(1, 2),
+ _info[4].substr(3, 1),
+ _info[4].substr(4, 2),
+
+ _info[5].substr(0, 1),
+ _info[5].substr(1, 2),
+ _info[5].substr(3, 1),
+ _info[5].substr(4, 2)
+ ]
+ return parseInt(_calday[n - 1])
+ },
+
+ /**
+ * 传入农历数字月份返回汉语通俗表示法
+ * @param lunar month
+ * @return Cn string
+ * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
+ */
+ toChinaMonth: function (m) { // 月 => \u6708
+ if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+ var s = this.nStr3[m - 1]
+ s += '\u6708'// 加上月字
+ return s
+ },
+
+ /**
+ * 传入农历日期数字返回汉字表示法
+ * @param lunar day
+ * @return Cn string
+ * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
+ */
+ toChinaDay: function (d) { // 日 => \u65e5
+ var s
+ switch (d) {
+ case 10:
+ s = '\u521d\u5341'; break
+ case 20:
+ s = '\u4e8c\u5341'; break
+ break
+ case 30:
+ s = '\u4e09\u5341'; break
+ break
+ default:
+ s = this.nStr2[Math.floor(d / 10)]
+ s += this.nStr1[d % 10]
+ }
+ return (s)
+ },
+
+ /**
+ * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
+ * @param y year
+ * @return Cn string
+ * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
+ */
+ getAnimal: function (y) {
+ return this.Animals[(y - 4) % 12]
+ },
+
+ /**
+ * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
+ * @param y solar year
+ * @param m solar month
+ * @param d solar day
+ * @return JSON object
+ * @eg:console.log(calendar.solar2lunar(1987,11,01));
+ */
+ solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
+ // 年份限定、上限
+ if (y < 1900 || y > 2100) {
+ return -1// undefined转换为数字变为NaN
+ }
+ // 公历传参最下限
+ if (y == 1900 && m == 1 && d < 31) {
+ return -1
+ }
+ // 未传参 获得当天
+ if (!y) {
+ var objDate = new Date()
+ } else {
+ var objDate = new Date(y, parseInt(m) - 1, d)
+ }
+ var i; var leap = 0; var temp = 0
+ // 修正ymd参数
+ var y = objDate.getFullYear()
+ var m = objDate.getMonth() + 1
+ var d = objDate.getDate()
+ var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
+ for (i = 1900; i < 2101 && offset > 0; i++) {
+ temp = this.lYearDays(i)
+ offset -= temp
+ }
+ if (offset < 0) {
+ offset += temp; i--
+ }
+
+ // 是否今天
+ var isTodayObj = new Date()
+ var isToday = false
+ if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
+ isToday = true
+ }
+ // 星期几
+ var nWeek = objDate.getDay()
+ var cWeek = this.nStr1[nWeek]
+ // 数字表示周几顺应天朝周一开始的惯例
+ if (nWeek == 0) {
+ nWeek = 7
+ }
+ // 农历年
+ var year = i
+ var leap = this.leapMonth(i) // 闰哪个月
+ var isLeap = false
+
+ // 效验闰月
+ for (i = 1; i < 13 && offset > 0; i++) {
+ // 闰月
+ if (leap > 0 && i == (leap + 1) && isLeap == false) {
+ --i
+ isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
+ } else {
+ temp = this.monthDays(year, i)// 计算农历普通月天数
+ }
+ // 解除闰月
+ if (isLeap == true && i == (leap + 1)) { isLeap = false }
+ offset -= temp
+ }
+ // 闰月导致数组下标重叠取反
+ if (offset == 0 && leap > 0 && i == leap + 1) {
+ if (isLeap) {
+ isLeap = false
+ } else {
+ isLeap = true; --i
+ }
+ }
+ if (offset < 0) {
+ offset += temp; --i
+ }
+ // 农历月
+ var month = i
+ // 农历日
+ var day = offset + 1
+ // 天干地支处理
+ var sm = m - 1
+ var gzY = this.toGanZhiYear(year)
+
+ // 当月的两个节气
+ // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
+ var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
+ var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
+
+ // 依据12节气修正干支月
+ var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
+ if (d >= firstNode) {
+ gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
+ }
+
+ // 传入的日期的节气与否
+ var isTerm = false
+ var Term = null
+ if (firstNode == d) {
+ isTerm = true
+ Term = this.solarTerm[m * 2 - 2]
+ }
+ if (secondNode == d) {
+ isTerm = true
+ Term = this.solarTerm[m * 2 - 1]
+ }
+ // 日柱 当月一日与 1900/1/1 相差天数
+ var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
+ var gzD = this.toGanZhi(dayCyclical + d - 1)
+ // 该日期所属的星座
+ var astro = this.toAstro(m, d)
+
+ return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
+ },
+
+ /**
+ * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
+ * @param y lunar year
+ * @param m lunar month
+ * @param d lunar day
+ * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
+ * @return JSON object
+ * @eg:console.log(calendar.lunar2solar(1987,9,10));
+ */
+ lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
+ var isLeapMonth = !!isLeapMonth
+ var leapOffset = 0
+ var leapMonth = this.leapMonth(y)
+ var leapDay = this.leapDays(y)
+ if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
+ if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
+ var day = this.monthDays(y, m)
+ var _day = day
+ // bugFix 2016-9-25
+ // if month is leap, _day use leapDays method
+ if (isLeapMonth) {
+ _day = this.leapDays(y, m)
+ }
+ if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
+
+ // 计算农历的时间差
+ var offset = 0
+ for (var i = 1900; i < y; i++) {
+ offset += this.lYearDays(i)
+ }
+ var leap = 0; var isAdd = false
+ for (var i = 1; i < m; i++) {
+ leap = this.leapMonth(y)
+ if (!isAdd) { // 处理闰月
+ if (leap <= i && leap > 0) {
+ offset += this.leapDays(y); isAdd = true
+ }
+ }
+ offset += this.monthDays(y, i)
+ }
+ // 转换闰月农历 需补充该年闰月的前一个月的时差
+ if (isLeapMonth) { offset += day }
+ // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
+ var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
+ var calObj = new Date((offset + d - 31) * 86400000 + stmap)
+ var cY = calObj.getUTCFullYear()
+ var cM = calObj.getUTCMonth() + 1
+ var cD = calObj.getUTCDate()
+
+ return this.solar2lunar(cY, cM, cD)
+ }
+}
+
+export default calendar
diff --git a/ledApp/uni_modules/uv-calendar/components/uv-calendar/header.vue b/ledApp/uni_modules/uv-calendar/components/uv-calendar/header.vue
new file mode 100644
index 0000000..d7daa7b
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendar/components/uv-calendar/header.vue
@@ -0,0 +1,104 @@
+<template>
+ <view class="uv-calendar-header uv-border-bottom">
+ <text
+ class="uv-calendar-header__title"
+ v-if="showTitle"
+ >{{ title }}</text>
+ <text
+ class="uv-calendar-header__subtitle"
+ v-if="showSubtitle"
+ >{{ subtitle }}</text>
+ <view class="uv-calendar-header__weekdays">
+ <text class="uv-calendar-header__weekdays__weekday">一</text>
+ <text class="uv-calendar-header__weekdays__weekday">二</text>
+ <text class="uv-calendar-header__weekdays__weekday">三</text>
+ <text class="uv-calendar-header__weekdays__weekday">四</text>
+ <text class="uv-calendar-header__weekdays__weekday">五</text>
+ <text class="uv-calendar-header__weekdays__weekday">六</text>
+ <text class="uv-calendar-header__weekdays__weekday">日</text>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ export default {
+ name: 'uv-calendar-header',
+ mixins: [mpMixin, mixin],
+ props: {
+ // 标题
+ title: {
+ type: String,
+ default: ''
+ },
+ // 副标题
+ subtitle: {
+ type: [String,null],
+ default: ''
+ },
+ // 是否显示标题
+ showTitle: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示副标题
+ showSubtitle: {
+ type: Boolean,
+ default: true
+ },
+ },
+ data() {
+ return {
+
+ }
+ },
+ methods: {
+ name() {
+
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-border: 1;
+ $show-border-bottom: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-calendar-header {
+ padding-bottom: 4px;
+
+ &__title {
+ font-size: 16px;
+ color: $uv-main-color;
+ text-align: center;
+ height: 42px;
+ line-height: 42px;
+ font-weight: bold;
+ }
+
+ &__subtitle {
+ font-size: 14px;
+ color: $uv-main-color;
+ height: 40px;
+ text-align: center;
+ line-height: 40px;
+ font-weight: bold;
+ }
+
+ &__weekdays {
+ @include flex;
+ justify-content: space-between;
+
+ &__weekday {
+ font-size: 13px;
+ color: $uv-main-color;
+ line-height: 30px;
+ flex: 1;
+ text-align: center;
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-calendar/components/uv-calendar/month.vue b/ledApp/uni_modules/uv-calendar/components/uv-calendar/month.vue
new file mode 100644
index 0000000..1a649bc
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendar/components/uv-calendar/month.vue
@@ -0,0 +1,616 @@
+<template>
+ <view class="uv-calendar-month-wrapper" ref="uv-calendar-month-wrapper">
+ <view v-for="(item, index) in months" :key="index" :class="[`uv-calendar-month-${index}`]"
+ :ref="`uv-calendar-month-${index}`" :id="`month-${index}`">
+ <text v-if="index !== 0" class="uv-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
+ <view class="uv-calendar-month__days">
+ <view v-if="showMark" class="uv-calendar-month__days__month-mark-wrapper">
+ <text class="uv-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
+ </view>
+ <view class="uv-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
+ :style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
+ :class="[item1.selected && 'uv-calendar-month__days__day__select--selected']">
+ <view class="uv-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
+ <text v-if="getTopInfo(index, index1, item1)"
+ class="uv-calendar-month__days__day__select__top-info"
+ :class="[item1.disabled && 'uv-calendar-month__days__day__select__top-info--disabled']"
+ :style="[textStyle(item1)]"
+ >{{ getTopInfo(index, index1, item1) }}</text>
+ <text class="uv-calendar-month__days__day__select__info"
+ :class="[item1.disabled && 'uv-calendar-month__days__day__select__info--disabled']"
+ :style="[textStyle(item1)]">{{ item1.day }}</text>
+ <text v-if="getBottomInfo(index, index1, item1)"
+ class="uv-calendar-month__days__day__select__buttom-info"
+ :class="[item1.disabled && 'uv-calendar-month__days__day__select__buttom-info--disabled']"
+ :style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
+ <text v-if="item1.dot" class="uv-calendar-month__days__day__select__dot"></text>
+ </view>
+ </view>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+ // #ifdef APP-NVUE
+ // 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ import { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js';
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'
+ export default {
+ name: 'uv-calendar-month',
+ emits:['monthSelected','updateMonthTop','change'],
+ mixins: [mpMixin, mixin],
+ props: {
+ // 是否显示月份背景色
+ showMark: {
+ type: Boolean,
+ default: true
+ },
+ // 主题色,对底部按钮和选中日期有效
+ color: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 月份数据
+ months: {
+ type: Array,
+ default: () => []
+ },
+ // 日期选择类型
+ mode: {
+ type: String,
+ default: 'single'
+ },
+ // 日期行高
+ rowHeight: {
+ type: [String, Number],
+ default: 58
+ },
+ // mode=multiple时,最多可选多少个日期
+ maxCount: {
+ type: [String, Number],
+ default: Infinity
+ },
+ // mode=range时,第一个日期底部的提示文字
+ startText: {
+ type: String,
+ default: '开始'
+ },
+ // mode=range时,最后一个日期底部的提示文字
+ endText: {
+ type: String,
+ default: '结束'
+ },
+ // 默认选中的日期,mode为multiple或range是必须为数组格式
+ defaultDate: {
+ type: [Array, String, Date],
+ default: null
+ },
+ // 最小的可选日期
+ minDate: {
+ type: [String, Number],
+ default: 0
+ },
+ // 最大可选日期
+ maxDate: {
+ type: [String, Number],
+ default: 0
+ },
+ // 如果没有设置maxDate,则往后推多少个月
+ maxMonth: {
+ type: [String, Number],
+ default: 2
+ },
+ // 是否为只读状态,只读状态下禁止选择日期
+ readonly: {
+ type: Boolean,
+ default: false
+ },
+ // 日期区间最多可选天数,默认无限制,mode = range时有效
+ maxRange: {
+ type: [Number, String],
+ default: Infinity
+ },
+ // 范围选择超过最多可选天数时的提示文案,mode = range时有效
+ rangePrompt: {
+ type: String,
+ default: ''
+ },
+ // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+ showRangePrompt: {
+ type: Boolean,
+ default: true
+ },
+ // 是否允许日期范围的起止时间为同一天,mode = range时有效
+ allowSameDay: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ // 每个日期的宽度
+ width: 0,
+ // 当前选中的日期item
+ item: {},
+ selected: []
+ }
+ },
+ watch: {
+ selectedChange: {
+ immediate: true,
+ handler(n) {
+ this.setDefaultDate()
+ }
+ }
+ },
+ computed: {
+ // 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+ selectedChange() {
+ return [this.minDate, this.maxDate, this.defaultDate]
+ },
+ dayStyle(index1, index2, item) {
+ return (index1, index2, item) => {
+ const style = {}
+ let week = item.week
+ // 不进行四舍五入的形式保留2位小数
+ const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
+ // 得出每个日期的宽度
+ // #ifdef APP-NVUE
+ style.width = this.$uv.addUnit(dayWidth)
+ // #endif
+ style.height = this.$uv.addUnit(this.rowHeight)
+ if (index2 === 0) {
+ // 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
+ week = (week === 0 ? 7 : week) - 1
+ style.marginLeft = this.$uv.addUnit(week * dayWidth)
+ }
+ if (this.mode === 'range') {
+ // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+ style.paddingLeft = 0
+ style.paddingRight = 0
+ style.paddingBottom = 0
+ style.paddingTop = 0
+ }
+ return style
+ }
+ },
+ daySelectStyle() {
+ return (index1, index2, item) => {
+ let date = dayjs(item.date).format("YYYY-MM-DD"),
+ style = {}
+ // 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
+ if (this.selected.some(item => this.dateSame(item, date))) {
+ style.backgroundColor = this.color
+ }
+ if (this.mode === 'single') {
+ if (date === this.selected[0]) {
+ // 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
+ style.borderTopLeftRadius = '3px'
+ style.borderBottomLeftRadius = '3px'
+ style.borderTopRightRadius = '3px'
+ style.borderBottomRightRadius = '3px'
+ }
+ } else if (this.mode === 'range') {
+ if (this.selected.length >= 2) {
+ const len = this.selected.length - 1
+ // 第一个日期设置左上角和左下角的圆角
+ if (this.dateSame(date, this.selected[0])) {
+ style.borderTopLeftRadius = '3px'
+ style.borderBottomLeftRadius = '3px'
+ }
+ // 最后一个日期设置右上角和右下角的圆角
+ if (this.dateSame(date, this.selected[len])) {
+ style.borderTopRightRadius = '3px'
+ style.borderBottomRightRadius = '3px'
+ }
+ // 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
+ if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+ .selected[len]))) {
+ style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]
+ // 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
+ style.opacity = 0.7
+ }
+ } else if (this.selected.length === 1) {
+ // 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+ // 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
+ style.borderTopLeftRadius = '3px'
+ style.borderBottomLeftRadius = '3px'
+ }
+ } else {
+ if (this.selected.some(item => this.dateSame(item, date))) {
+ style.borderTopLeftRadius = '3px'
+ style.borderBottomLeftRadius = '3px'
+ style.borderTopRightRadius = '3px'
+ style.borderBottomRightRadius = '3px'
+ }
+ }
+ return style
+ }
+ },
+ // 某个日期是否被选中
+ textStyle() {
+ return (item) => {
+ const date = dayjs(item.date).format("YYYY-MM-DD"),
+ style = {}
+ // 选中的日期,提示文字设置白色
+ if (this.selected.some(item => this.dateSame(item, date))) {
+ style.color = '#ffffff'
+ }
+ if (this.mode === 'range') {
+ const len = this.selected.length - 1
+ // 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
+ if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+ .selected[len]))) {
+ style.color = this.color
+ }
+ }
+ return style
+ }
+ },
+ // 获取顶部的提示文字
+ getTopInfo() {
+ return (index1, index2, item) => {
+ return item.topInfo;
+ }
+ },
+ // 获取底部的提示文字
+ getBottomInfo() {
+ return (index1, index2, item) => {
+ const date = dayjs(item.date).format("YYYY-MM-DD")
+ const bottomInfo = item.bottomInfo
+ // 当为日期范围模式时,且选择的日期个数大于0时
+ if (this.mode === 'range' && this.selected.length > 0) {
+ if (this.selected.length === 1) {
+ // 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
+ if (this.dateSame(date, this.selected[0])) return this.startText
+ else return bottomInfo
+ } else {
+ const len = this.selected.length - 1
+ // 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
+ if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
+ len === 1) {
+ // 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
+ return `${this.startText}/${this.endText}`
+ } else if (this.dateSame(date, this.selected[0])) {
+ return this.startText
+ } else if (this.dateSame(date, this.selected[len])) {
+ return this.endText
+ } else {
+ return bottomInfo
+ }
+ }
+ } else {
+ return bottomInfo
+ }
+ }
+ }
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ // 初始化默认选中
+ this.$emit('monthSelected', this.selected)
+ this.$nextTick(() => {
+ // 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
+ // 因为nvue下,$nextTick并不是100%可靠的
+ this.$uv.sleep(10).then(() => {
+ this.getWrapperWidth()
+ this.getMonthRect()
+ })
+ })
+ },
+ // 判断两个日期是否相等
+ dateSame(date1, date2) {
+ return dayjs(date1).isSame(dayjs(date2))
+ },
+ // 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
+ getWrapperWidth() {
+ // #ifdef APP-NVUE
+ dom.getComponentRect(this.$refs['uv-calendar-month-wrapper'], res => {
+ this.width = res.size.width
+ })
+ // #endif
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-calendar-month-wrapper').then(size => {
+ this.width = size.width
+ })
+ // #endif
+ },
+ getMonthRect() {
+ // 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
+ const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
+ `uv-calendar-month-${index}`))
+ // 一次性返回
+ Promise.all(promiseAllArr).then(
+ sizes => {
+ let height = 1
+ const topArr = []
+ for (let i = 0; i < this.months.length; i++) {
+ // 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
+ topArr[i] = height
+ height += sizes[i].height
+ }
+ // 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
+ this.$emit('updateMonthTop', topArr)
+ })
+ },
+ // 获取每个月份区域的尺寸
+ getMonthRectByPromise(el) {
+ // #ifndef APP-NVUE
+ // $uvGetRect为uvui自带的节点查询简化方法,详见文档介绍:https://www.uvui.cn/js/getRect.html
+ // 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+ return new Promise(resolve => {
+ this.$uvGetRect(`.${el}`).then(size => {
+ resolve(size)
+ })
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ // nvue下,使用dom模块查询元素高度
+ // 返回一个promise,让调用此方法的主体能使用then回调
+ return new Promise(resolve => {
+ dom.getComponentRect(this.$refs[el][0], res => {
+ resolve(res.size)
+ })
+ })
+ // #endif
+ },
+ // 点击某一个日期
+ clickHandler(index1, index2, item) {
+ if (this.readonly) {
+ return;
+ }
+ this.item = item
+ const date = dayjs(item.date).format("YYYY-MM-DD")
+ if (item.disabled) return
+ // 对上一次选择的日期数组进行深度克隆
+ let selected = this.$uv.deepClone(this.selected)
+ if (this.mode === 'single') {
+ // 单选情况下,让数组中的元素为当前点击的日期
+ selected = [date]
+ } else if (this.mode === 'multiple') {
+ if (selected.some(item => this.dateSame(item, date))) {
+ // 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
+ const itemIndex = selected.findIndex(item => dayjs(item).format("YYYY-MM-DD") === dayjs(date).format("YYYY-MM-DD"))
+ selected.splice(itemIndex, 1)
+ } else {
+ // 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
+ if (selected.length < this.maxCount) selected.push(date)
+ }
+ } else {
+ // 选择区间形式
+ if (selected.length === 0 || selected.length >= 2) {
+ // 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
+ selected = [date]
+ } else if (selected.length === 1) {
+ // 如果已经选择了开始日期
+ const existsDate = selected[0]
+ // 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
+ if (dayjs(date).isBefore(existsDate)) {
+ selected = [date]
+ } else if (dayjs(date).isAfter(existsDate)) {
+ // 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
+ if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
+ if(this.rangePrompt) {
+ this.$uv.toast(this.rangePrompt)
+ } else {
+ this.$uv.toast(`选择天数不能超过 ${this.maxRange} 天`)
+ }
+ return
+ }
+ // 如果当前日期大于已有日期,将当前的添加到数组尾部
+ selected.push(date)
+ const startDate = selected[0]
+ const endDate = selected[1]
+ const arr = []
+ let i = 0
+ do {
+ // 将开始和结束日期之间的日期添加到数组中
+ arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
+ i++
+ // 累加的日期小于结束日期时,继续下一次的循环
+ } while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
+ // 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
+ arr.push(endDate)
+ selected = arr
+ } else {
+ // 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
+ if (selected[0] === date && !this.allowSameDay) return
+ selected.push(date)
+ }
+ }
+ }
+ this.setSelected(selected)
+ this.$emit('change',{
+ day: date,
+ selected: selected
+ });
+ },
+ // 设置默认日期
+ setDefaultDate() {
+ if (!this.defaultDate) {
+ // 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+ const selected = [dayjs().format("YYYY-MM-DD")]
+ return this.setSelected(selected, false)
+ }
+ let defaultDate = []
+ const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
+ const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
+ if (this.mode === 'single') {
+ // 单选模式,可以是字符串或数组,Date对象等
+ if (!this.$uv.test.array(this.defaultDate)) {
+ defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
+ } else {
+ defaultDate = [this.defaultDate[0]]
+ }
+ } else {
+ // 如果为非数组,则不执行
+ if (!this.$uv.test.array(this.defaultDate)) return
+ defaultDate = this.defaultDate
+ }
+ // 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
+ defaultDate = defaultDate.filter(item => {
+ return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
+ maxDate).add(1, 'day'))
+ })
+ this.setSelected(defaultDate, false)
+ },
+ setSelected(selected, event = true) {
+ this.selected = selected
+ event && this.$emit('monthSelected', this.selected)
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-calendar-month-wrapper {
+ margin-top: 4px;
+ }
+
+ .uv-calendar-month {
+
+ &__title {
+ font-size: 14px;
+ line-height: 42px;
+ height: 42px;
+ color: $uv-main-color;
+ text-align: center;
+ font-weight: bold;
+ }
+
+ &__days {
+ position: relative;
+ @include flex;
+ flex-wrap: wrap;
+
+ &__month-mark-wrapper {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+
+ &__text {
+ font-size: 155px;
+ color: rgba(231, 232, 234, 0.83);
+ }
+ }
+
+ &__day {
+ @include flex;
+ padding: 2px;
+ /* #ifndef APP-NVUE */
+ // vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
+ width: calc(100% / 7);
+ box-sizing: border-box;
+ /* #endif */
+
+ &__select {
+ flex: 1;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+
+ &__dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 100px;
+ background-color: $uv-error;
+ position: absolute;
+ top: 12px;
+ right: 7px;
+ }
+
+ &__top-info {
+ color: $uv-content-color;
+ text-align: center;
+ position: absolute;
+ top: 2px;
+ font-size: 10px;
+ text-align: center;
+ left: 0;
+ right: 0;
+ &--selected {
+ color: #ffffff;
+ }
+
+ &--disabled {
+ color: #cacbcd;
+ }
+ }
+
+ &__buttom-info {
+ color: $uv-content-color;
+ text-align: center;
+ position: absolute;
+ bottom: 5px;
+ font-size: 10px;
+ text-align: center;
+ left: 0;
+ right: 0;
+
+ &--selected {
+ color: #ffffff;
+ }
+
+ &--disabled {
+ color: #cacbcd;
+ }
+ }
+
+ &__info {
+ text-align: center;
+ font-size: 16px;
+
+ &--selected {
+ color: #ffffff;
+ }
+
+ &--disabled {
+ color: #cacbcd;
+ }
+ }
+
+ &--selected {
+ background-color: $uv-primary;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ flex: 1;
+ border-radius: 3px;
+ }
+
+ &--range-selected {
+ opacity: 0.3;
+ border-radius: 0;
+ }
+
+ &--range-start-selected {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+
+ &--range-end-selected {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+ }
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-calendar/components/uv-calendar/props.js b/ledApp/uni_modules/uv-calendar/components/uv-calendar/props.js
new file mode 100644
index 0000000..c359293
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendar/components/uv-calendar/props.js
@@ -0,0 +1,145 @@
+export default {
+ props: {
+ // 日历顶部标题
+ title: {
+ type: String,
+ default: '日期选择'
+ },
+ // 是否显示标题
+ showTitle: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示副标题
+ showSubtitle: {
+ type: Boolean,
+ default: true
+ },
+ // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
+ mode: {
+ type: String,
+ default: 'single'
+ },
+ // mode=range时,第一个日期底部的提示文字
+ startText: {
+ type: String,
+ default: '开始'
+ },
+ // mode=range时,最后一个日期底部的提示文字
+ endText: {
+ type: String,
+ default: '结束'
+ },
+ // 自定义列表
+ customList: {
+ type: Array,
+ default: () => []
+ },
+ // 主题色,对底部按钮和选中日期有效
+ color: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 最小的可选日期
+ minDate: {
+ type: [String, Number],
+ default: 0
+ },
+ // 最大可选日期
+ maxDate: {
+ type: [String, Number],
+ default: 0
+ },
+ // 默认选中的日期,mode为multiple或range是必须为数组格式
+ defaultDate: {
+ type: [Array, String, Date, null],
+ default: null
+ },
+ // mode=multiple时,最多可选多少个日期
+ maxCount: {
+ type: [String, Number],
+ default: Number.MAX_SAFE_INTEGER
+ },
+ // 日期行高
+ rowHeight: {
+ type: [String, Number],
+ default: 56
+ },
+ // 日期格式化函数
+ formatter: {
+ type: [Function, null],
+ default: null
+ },
+ // 是否显示农历
+ showLunar: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示月份背景色
+ showMark: {
+ type: Boolean,
+ default: true
+ },
+ // 确定按钮的文字
+ confirmText: {
+ type: String,
+ default: '确定'
+ },
+ // 确认按钮处于禁用状态时的文字
+ confirmDisabledText: {
+ type: String,
+ default: '确定'
+ },
+ // 是否允许点击遮罩关闭日历
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: false
+ },
+ // 是否允许点击确认按钮关闭日历
+ closeOnClickConfirm: {
+ type: Boolean,
+ default: true
+ },
+ // 是否为只读状态,只读状态下禁止选择日期
+ readonly: {
+ type: Boolean,
+ default: false
+ },
+ // 是否展示确认按钮
+ showConfirm: {
+ type: Boolean,
+ default: true
+ },
+ // 日期区间最多可选天数,默认无限制,mode = range时有效 Infinity
+ maxRange: {
+ type: [Number, String],
+ default: Number.MAX_SAFE_INTEGER
+ },
+ // 范围选择超过最多可选天数时的提示文案,mode = range时有效
+ rangePrompt: {
+ type: String,
+ default: ''
+ },
+ // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+ showRangePrompt: {
+ type: Boolean,
+ default: true
+ },
+ // 是否允许日期范围的起止时间为同一天,mode = range时有效
+ allowSameDay: {
+ type: Boolean,
+ default: false
+ },
+ // 圆角值
+ round: {
+ type: [Boolean, String, Number],
+ default: 0
+ },
+ // 最多展示月份数量
+ monthNum: {
+ type: [Number, String],
+ default: 3
+ },
+ ...uni.$uv?.props?.calendar
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendar/components/uv-calendar/uv-calendar.vue b/ledApp/uni_modules/uv-calendar/components/uv-calendar/uv-calendar.vue
new file mode 100644
index 0000000..626e833
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendar/components/uv-calendar/uv-calendar.vue
@@ -0,0 +1,390 @@
+<template>
+ <uv-popup
+ ref="calendarPopup"
+ mode="bottom"
+ closeable
+ :round="round"
+ :closeOnClickOverlay="closeOnClickOverlay"
+ @change="popupChange"
+ >
+ <view class="uv-calendar">
+ <uvHeader
+ :title="title"
+ :subtitle="subtitle"
+ :showSubtitle="showSubtitle"
+ :showTitle="showTitle"
+ ></uvHeader>
+ <scroll-view
+ :style="{ height: $uv.addUnit(listHeight) }"
+ scroll-y
+ @scroll="onScroll"
+ :scroll-top="scrollTop"
+ :scrollIntoView="scrollIntoView"
+ >
+ <uvMonth
+ :color="color"
+ :rowHeight="rowHeight"
+ :showMark="showMark"
+ :months="months"
+ :mode="mode"
+ :maxCount="maxCount"
+ :startText="startText"
+ :endText="endText"
+ :defaultDate="defaultDate"
+ :minDate="innerMinDate"
+ :maxDate="innerMaxDate"
+ :maxMonth="monthNum"
+ :readonly="readonly"
+ :maxRange="maxRange"
+ :rangePrompt="rangePrompt"
+ :showRangePrompt="showRangePrompt"
+ :allowSameDay="allowSameDay"
+ ref="month"
+ @monthSelected="monthSelected"
+ @updateMonthTop="updateMonthTop"
+ @change="changeDay"
+ ></uvMonth>
+ </scroll-view>
+ <slot name="footer" v-if="showConfirm">
+ <view class="uv-calendar__confirm">
+ <uv-button
+ shape="circle"
+ :text="buttonDisabled ? confirmDisabledText : confirmText"
+ :color="color"
+ @click="confirm"
+ :disabled="buttonDisabled"
+ ></uv-button>
+ </view>
+ </slot>
+ </view>
+ </uv-popup>
+</template>
+
+<script>
+import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+import uvHeader from './header.vue'
+import uvMonth from './month.vue'
+import props from './props.js'
+import dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'
+import Calendar from './calendar.js'
+/**
+ * Calendar 日历
+ * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
+ * @tutorial https://www.uvui.cn/components/calendar.html
+ *
+ * @property {String} title 标题内容 (默认 日期选择 )
+ * @property {Boolean} showTitle 是否显示标题 (默认 true )
+ * @property {Boolean} showSubtitle 是否显示副标题 (默认 true )
+ * @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
+ * @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' )
+ * @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
+ * @property {Array} customList 自定义列表
+ * @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' )
+ * @property {String | Number} minDate 最小的可选日期 (默认 0 )
+ * @property {String | Number} maxDate 最大可选日期 (默认 0 )
+ * @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式
+ * @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER )
+ * @property {String | Number} rowHeight 日期行高 (默认 56 )
+ * @property {Function} formatter 日期格式化函数
+ * @property {Boolean} showLunar 是否显示农历 (默认 false )
+ * @property {Boolean} showMark 是否显示月份背景色 (默认 true )
+ * @property {String} confirmText 确定按钮的文字 (默认 '确定' )
+ * @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' )
+ * @property {Boolean} show 是否显示日历弹窗 (默认 false )
+ * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false )
+ * @property {Boolean} closeOnClickConfirm 是否允许点击确认按钮关闭日历,设置为false不影响confirm事件返回 (默认 true )
+ * @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false )
+ * @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效
+ * @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效
+ * @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
+ * @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
+ * @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
+ * @property {Number|String} monthNum 最多展示的月份数量 (默认 3 )
+ *
+ * @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数
+ * @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件
+ * @example <uv-calendar ref="calendar" :defaultDate="defaultDateMultiple" mode="multiple" @confirm="confirm">
+ </uv-calendar>
+ * */
+export default {
+ name: 'uv-calendar',
+ emits:['confirm','close','change'],
+ mixins: [mpMixin, mixin, props],
+ components: {
+ uvHeader,
+ uvMonth
+ },
+ data() {
+ return {
+ // 需要显示的月份的数组
+ months: [],
+ // 在月份滚动区域中,当前视图中月份的index索引
+ monthIndex: 0,
+ // 月份滚动区域的高度
+ listHeight: 0,
+ // month组件中选择的日期数组
+ selected: [],
+ scrollIntoView: '',
+ scrollTop:0,
+ // 过滤处理方法
+ innerFormatter: (value) => value
+ }
+ },
+ watch: {
+ selectedChange: {
+ immediate: true,
+ handler(n) {
+ this.setMonth()
+ }
+ }
+ },
+ computed: {
+ // 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理
+ innerMaxDate() {
+ return this.$uv.test.number(this.maxDate)
+ ? Number(this.maxDate)
+ : this.maxDate
+ },
+ innerMinDate() {
+ return this.$uv.test.number(this.minDate)
+ ? Number(this.minDate)
+ : this.minDate
+ },
+ // 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+ selectedChange() {
+ return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
+ },
+ subtitle() {
+ // 初始化时,this.months为空数组,所以需要特别判断处理
+ if (this.months.length) {
+ return `${this.months[this.monthIndex].year}年${
+ this.months[this.monthIndex].month
+ }月`
+ } else {
+ return ''
+ }
+ },
+ buttonDisabled() {
+ // 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
+ if (this.mode === 'range') {
+ if (this.selected.length <= 1) {
+ return true
+ } else {
+ return false
+ }
+ } else {
+ return false
+ }
+ }
+ },
+ mounted() {
+ this.start = Date.now()
+ this.init()
+ },
+ methods: {
+ // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+ setFormatter(e) {
+ this.innerFormatter = e
+ },
+ // 点击日期框触发
+ changeDay(e) {
+ this.$emit('change',e);
+ },
+ // month组件内部选择日期后,通过事件通知给父组件
+ monthSelected(e) {
+ this.selected = e
+ if (!this.showConfirm) {
+ // 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
+ if (
+ this.mode === 'multiple' ||
+ this.mode === 'single' ||
+ (this.mode === 'range' && this.selected.length >= 2)
+ ) {
+ this.$emit('confirm', this.selected)
+ }
+ }
+ },
+ init() {
+ // 校验maxDate,不能小于minDate
+ if (
+ this.innerMaxDate &&
+ this.innerMinDate &&
+ new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
+ ) {
+ return this.$uv.error('maxDate不能小于minDate')
+ }
+ // 滚动区域的高度
+ this.listHeight = this.rowHeight * 5 + 30
+ this.setMonth()
+ },
+ open() {
+ this.setMonth()
+ this.$refs.calendarPopup.open();
+ },
+ popupChange(e) {
+ if(!e.show) {
+ this.$emit('close');
+ }
+ },
+ // 点击确定按钮
+ confirm() {
+ if (!this.buttonDisabled) {
+ this.$emit('confirm', this.selected)
+ }
+ if (this.closeOnClickConfirm) {
+ this.$refs.calendarPopup.close();
+ }
+ },
+ // 获得两个日期之间的月份数
+ getMonths(minDate, maxDate) {
+ const minYear = dayjs(minDate).year()
+ const minMonth = dayjs(minDate).month() + 1
+ const maxYear = dayjs(maxDate).year()
+ const maxMonth = dayjs(maxDate).month() + 1
+ return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
+ },
+ // 设置月份数据
+ setMonth() {
+ // 最小日期的毫秒数
+ const minDate = this.innerMinDate || dayjs().valueOf()
+ // 如果没有指定最大日期,则往后推3个月
+ const maxDate =
+ this.innerMaxDate ||
+ dayjs(minDate)
+ .add(this.monthNum - 1, 'month')
+ .valueOf()
+ // 最大最小月份之间的共有多少个月份,
+ const months = this.$uv.range(
+ 1,
+ this.monthNum,
+ this.getMonths(minDate, maxDate)
+ )
+ // 先清空数组
+ this.months = []
+ for (let i = 0; i < months; i++) {
+ this.months.push({
+ date: new Array(
+ dayjs(minDate).add(i, 'month').daysInMonth()
+ )
+ .fill(1)
+ .map((item, index) => {
+ // 日期,取值1-31
+ let day = index + 1
+ // 星期,0-6,0为周日
+ const week = dayjs(minDate)
+ .add(i, 'month')
+ .date(day)
+ .day()
+ const date = dayjs(minDate)
+ .add(i, 'month')
+ .date(day)
+ .format('YYYY-MM-DD')
+ let topInfo = ''
+ let bottomInfo = ''
+ if (this.showLunar) {
+ // 将日期转为农历格式
+ const lunar = Calendar.solar2lunar(
+ dayjs(date).year(),
+ dayjs(date).month() + 1,
+ dayjs(date).date()
+ )
+ bottomInfo = lunar.IDayCn
+ }
+ let config = {
+ day,
+ week,
+ // 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
+ disabled:
+ dayjs(date).isBefore(
+ dayjs(minDate).format('YYYY-MM-DD')
+ ) ||
+ dayjs(date).isAfter(
+ dayjs(maxDate).format('YYYY-MM-DD')
+ ),
+ // 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
+ date: new Date(date),
+ topInfo,
+ bottomInfo,
+ dot: false,
+ month:
+ dayjs(minDate).add(i, 'month').month() + 1
+ }
+ const formatter =
+ this.formatter || this.innerFormatter
+ return formatter(config)
+ }),
+ // 当前所属的月份
+ month: dayjs(minDate).add(i, 'month').month() + 1,
+ // 当前年份
+ year: dayjs(minDate).add(i, 'month').year()
+ })
+ }
+
+ },
+ // 滚动到默认设置的月份
+ scrollIntoDefaultMonth(selected) {
+ // 查询默认日期在可选列表的下标
+ const _index = this.months.findIndex(({
+ year,
+ month
+ }) => {
+ month = this.$uv.padZero(month)
+ return `${year}-${month}` === selected
+ })
+ if (_index !== -1) {
+ // #ifndef MP-WEIXIN
+ this.$nextTick(() => {
+ this.scrollIntoView = `month-${_index}`
+ })
+ // #endif
+ // #ifdef MP-WEIXIN
+ this.scrollTop = this.months[_index].top || 0;
+ // #endif
+ }
+ },
+ // scroll-view滚动监听
+ onScroll(event) {
+ // 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
+ const scrollTop = Math.max(0, event.detail.scrollTop)
+ // 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
+ for (let i = 0; i < this.months.length; i++) {
+ if (scrollTop >= (this.months[i].top || this.listHeight)) {
+ this.monthIndex = i
+ }
+ }
+ },
+ // 更新月份的top值
+ updateMonthTop(topArr = []) {
+ // 设置对应月份的top值,用于onScroll方法更新月份
+ topArr.map((item, index) => {
+ this.months[index].top = item
+ })
+
+ // 获取默认日期的下标
+ if (!this.defaultDate) {
+ // 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+ const selected = dayjs().format("YYYY-MM")
+ this.scrollIntoDefaultMonth(selected)
+ return
+ }
+ let selected = dayjs().format("YYYY-MM");
+ // 单选模式,可以是字符串或数组,Date对象等
+ if (!this.$uv.test.array(this.defaultDate)) {
+ selected = dayjs(this.defaultDate).format("YYYY-MM")
+ } else {
+ selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
+ }
+ this.scrollIntoDefaultMonth(selected)
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.uv-calendar {
+ &__confirm {
+ padding: 7px 18px;
+ }
+}
+</style>
diff --git a/ledApp/uni_modules/uv-calendar/package.json b/ledApp/uni_modules/uv-calendar/package.json
new file mode 100644
index 0000000..03d8812
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendar/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-calendar",
+ "displayName": "uv-calendar 日历 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.6",
+ "description": "日历组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中,灵活配置,功能齐全,兼容全端。强烈推荐使用最新版日历组件,在下方跳入。",
+ "keywords": [
+ "uv-calendar",
+ "uvui",
+ "uv-ui",
+ "calendar",
+ "日历"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-button",
+ "uv-popup"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendar/readme.md b/ledApp/uni_modules/uv-calendar/readme.md
new file mode 100644
index 0000000..3c2de54
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendar/readme.md
@@ -0,0 +1,21 @@
+## Calendar 日历
+
+> **组件名:uv-calendar**
+
+此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。灵活配置,功能齐全,兼容全端。
+
+## 温馨提示:该组件不再更新,强烈推荐使用最新版日历组件:[https://www.uvui.cn/components/calendars.html](https://www.uvui.cn/components/calendars.html)。基于 `uv-ui` 插件市场首款多功能日历组件,不仅可以查看、选择日期,还可以选择任意范围内的日期、打点操作、自定义主题颜色、自定义文案、农历显示等。追求的就是完美。
+
+# <a href="https://www.uvui.cn/components/calendar.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendars/changelog.md b/ledApp/uni_modules/uv-calendars/changelog.md
new file mode 100644
index 0000000..b3b2e38
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/changelog.md
@@ -0,0 +1,40 @@
+## 1.0.15(2023-11-08)
+1. 增加readonly属性,是否为只读状态,只读状态下禁止选择日期
+## 1.0.14(2023-10-12)
+1. 修复selected没有设置了info或者info设置为空字符串后,文本则无法恢复BUG
+## 1.0.13(2023-09-19)
+1. 修复range模式下,selected设置了info后选中后,导致文本不恢复的问题
+2. 修复multiple模式下,selected自定义信息的颜色没变,依然是白色
+## 1.0.12(2023-09-14)
+1. 优化
+## 1.0.11(2023-09-14)
+1. 增加allowSameDay参数,是否允许日期范围的起止时间为同一天,mode = range时有效
+2. 修复在vue2+小程序渲染时闪烁的问题
+## 1.0.10(2023-09-07)
+1. 修复国际化失效的BUG
+## 1.0.9(2023-09-01)
+1. 修复在pages.json中设置easycom会报错的BUG
+## 1.0.8(2023-08-29)
+1. 修复mainjs中设置setConfig修改属性不生效的问题,出自评论区:https://ext.dcloud.net.cn/plugin?id=12287
+## 1.0.7(2023-08-26)
+1. 去除range参数,由mode="range"替换
+2. 新增mode参数,不传 / multiple / range,分别为单日期, 多个日期,选择日期范围
+3. 与uv-calendar选择日期的功能保持一致
+## 1.0.6(2023-08-25)
+1. 修复点击返回今天按钮时,monthSwitch方法回调参数返回月份不是当天对应月份:https://github.com/climblee/uv-ui/issues/7
+## 1.0.5(2023-08-13)
+1. 修复选择月份弹窗层级的问题
+## 1.0.4(2023-08-06)
+1. 优化
+## 1.0.3(2023-08-06)
+1. 修复高度不对的BUG
+2. 修复文案在小屏幕的BUG
+## 1.0.2(2023-08-05)
+1. 增加startText参数
+2. 增加endText参数
+3. 增加selected中的参数
+4. 优化日历范围选择
+## 1.0.1(2023-08-04)
+1. 修复 自定义主题时 颜色错误的BUG
+## 1.0.0(2023-08-03)
+1. 新增 uv-calendars 新版日历发布
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar-body.vue b/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar-body.vue
new file mode 100644
index 0000000..b675fbd
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar-body.vue
@@ -0,0 +1,376 @@
+<template>
+ <view class="uv-calendar-body">
+ <view class="uv-calendar__header">
+ <view class="uv-calendar__header-btn-box" @click.stop="pre">
+ <view class="uv-calendar__header-btn uv-calendar--left"></view>
+ </view>
+ <picker mode="date" :value="getDate" fields="month" @change="bindDateChange">
+ <text class="uv-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
+ </picker>
+ <view class="uv-calendar__header-btn-box" @click.stop="next">
+ <view class="uv-calendar__header-btn uv-calendar--right"></view>
+ </view>
+ <text class="uv-calendar__backtoday" @click="backToday">{{todayText}}</text>
+ </view>
+ <view class="uv-calendar__box">
+ <view v-if="showMonth" class="uv-calendar__box-bg">
+ <text class="uv-calendar__box-bg-text">{{nowDate.month}}</text>
+ </view>
+ <view class="uv-calendar__weeks uv-calendar__weeks-week">
+ <view class="uv-calendar__weeks-day">
+ <text class="uv-calendar__weeks-day-text">{{SUNText}}</text>
+ </view>
+ <view class="uv-calendar__weeks-day">
+ <text class="uv-calendar__weeks-day-text">{{monText}}</text>
+ </view>
+ <view class="uv-calendar__weeks-day">
+ <text class="uv-calendar__weeks-day-text">{{TUEText}}</text>
+ </view>
+ <view class="uv-calendar__weeks-day">
+ <text class="uv-calendar__weeks-day-text">{{WEDText}}</text>
+ </view>
+ <view class="uv-calendar__weeks-day">
+ <text class="uv-calendar__weeks-day-text">{{THUText}}</text>
+ </view>
+ <view class="uv-calendar__weeks-day">
+ <text class="uv-calendar__weeks-day-text">{{FRIText}}</text>
+ </view>
+ <view class="uv-calendar__weeks-day">
+ <text class="uv-calendar__weeks-day-text">{{SATText}}</text>
+ </view>
+ </view>
+ <view class="uv-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
+ <view class="uv-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
+ <calendar-item class="uv-calendar-item--hook" :weeks="weeks" :rangeInfoText="rangeInfoText(weeks)" :multiple="multiple" :range="range" :calendar="calendar" :selected="selected" :lunar="lunar" :color="color" @change="choiceDate"></calendar-item>
+ </view>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';
+
+ import CalendarItem from './calendar-item.vue';
+
+ import { initVueI18n } from '@dcloudio/uni-i18n';
+ import i18nMessages from './i18n/index.js';
+ const { t } = initVueI18n(i18nMessages);
+ export default {
+ mixins: [mpMixin, mixin],
+ components: {
+ CalendarItem
+ },
+ props: {
+ date: {
+ type: [String,Array],
+ default: ''
+ },
+ nowDate: {
+ type: [String, Object],
+ default: ''
+ },
+ weeks: {
+ type: [Array, Object],
+ default () {
+ return []
+ }
+ },
+ calendar: {
+ type: Object,
+ default () {
+ return {}
+ }
+ },
+ selected: {
+ type: Array,
+ default () {
+ return []
+ }
+ },
+ lunar: {
+ type: Boolean,
+ default: false
+ },
+ showMonth: {
+ type: Boolean,
+ default: true
+ },
+ color: {
+ type: String,
+ default: '#3c9cff'
+ },
+ startText: {
+ type: String,
+ default: '开始'
+ },
+ endText: {
+ type: String,
+ default: '结束'
+ },
+ range: {
+ type: Boolean,
+ default: false
+ },
+ multiple: {
+ type: Boolean,
+ default: false
+ },
+ // 是否允许日期范围的起止时间为同一天,mode = range时有效
+ allowSameDay: {
+ type: Boolean,
+ default: false
+ }
+ },
+ computed: {
+ getDate() {
+ return Array.isArray(this.date) ? this.date[0] : this.date;
+ },
+ /**
+ * for i18n
+ */
+ todayText() {
+ return t("uv-calender.today")
+ },
+ monText() {
+ return t("uv-calender.MON")
+ },
+ TUEText() {
+ return t("uv-calender.TUE")
+ },
+ WEDText() {
+ return t("uv-calender.WED")
+ },
+ THUText() {
+ return t("uv-calender.THU")
+ },
+ FRIText() {
+ return t("uv-calender.FRI")
+ },
+ SATText() {
+ return t("uv-calender.SAT")
+ },
+ SUNText() {
+ return t("uv-calender.SUN")
+ },
+ rangeInfoText(weeks) {
+ return weeks=> {
+ if(this.allowSameDay && weeks.beforeRange && weeks.afterRange && weeks.dateEqual) {
+ return this.setInfo(weeks,`${this.startText}/${this.endText}`);
+ }
+ if(weeks.beforeRange) {
+ return this.setInfo(weeks,this.startText);
+ }
+ if(weeks.afterRange) {
+ return this.setInfo(weeks,this.endText);
+ }
+ if(weeks.extraInfo?.info_old == ' ') {
+ weeks.extraInfo.info = null;
+ }else if(weeks.extraInfo?.info_old) {
+ weeks.extraInfo.info = weeks.extraInfo.info_old;
+ }
+ }
+ }
+ },
+ methods: {
+ setInfo(weeks,text) {
+ this.setInfoOld(weeks);
+ if(weeks.extraInfo) {
+ weeks.extraInfo.info = text;
+ }else {
+ weeks.extraInfo = {
+ info: text
+ }
+ }
+ },
+ setInfoOld(weeks) {
+ if(weeks.extraInfo) {
+ weeks.extraInfo.info_old = weeks.extraInfo.info ? weeks.extraInfo.info_old || weeks.extraInfo.info : ' ';
+ }
+ },
+ bindDateChange(e) {
+ this.$emit('bindDateChange', e);
+ },
+ backToday() {
+ this.$emit('backToday');
+ },
+ pre() {
+ this.$emit('pre');
+ },
+ next() {
+ this.$emit('next');
+ },
+ choiceDate(e) {
+ this.$emit('choiceDate', e);
+ }
+ }
+ }
+</script>
+
+<style scoped lang="scss">
+ @mixin flex($direction: row) {
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ flex-direction: $direction;
+ }
+ $uv-bg-color-mask: rgba($color: #000000, $alpha: 0.4);
+ $uv-border-color: #EDEDED !default;
+ $uv-text-color: #333;
+ $uv-bg-color-hover: #f1f1f1;
+ $uv-font-size-base: 14px;
+ $uv-text-color-placeholder: #808080;
+ $uv-color-subtitle: #555555;
+ $uv-text-color-grey: #999;
+ .uv-calendar {
+ @include flex(column);
+ }
+ .uv-calendar__mask {
+ position: fixed;
+ bottom: 0;
+ top: 0;
+ left: 0;
+ right: 0;
+ background-color: $uv-bg-color-mask;
+ transition-property: opacity;
+ transition-duration: 0.3s;
+ opacity: 0;
+ /* #ifndef APP-NVUE */
+ z-index: 99;
+ /* #endif */
+ }
+ .uv-calendar--mask-show {
+ opacity: 1
+ }
+ .uv-calendar--fixed {
+ position: fixed;
+ /* #ifdef APP-NVUE */
+ bottom: 0;
+ /* #endif */
+ left: 0;
+ right: 0;
+ transition-property: transform;
+ transition-duration: 0.3s;
+ transform: translateY(460px);
+ /* #ifndef APP-NVUE */
+ bottom: calc(var(--window-bottom));
+ z-index: 99;
+ /* #endif */
+ }
+ .uv-calendar--ani-show {
+ transform: translateY(0);
+ }
+ .uv-calendar__content {
+ background-color: #fff;
+ }
+ .uv-calendar__header {
+ position: relative;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ height: 50px;
+ border-bottom-color: $uv-border-color;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ }
+ .uv-calendar--fixed-top {
+ @include flex;
+ justify-content: space-between;
+ border-top-color: $uv-border-color;
+ border-top-style: solid;
+ border-top-width: 1px;
+ }
+ .uv-calendar--fixed-width {
+ width: 50px;
+ }
+ .uv-calendar__backtoday {
+ position: absolute;
+ right: 0;
+ top: 25rpx;
+ padding: 0 5px;
+ padding-left: 10px;
+ height: 25px;
+ line-height: 25px;
+ font-size: 12px;
+ border-top-left-radius: 25px;
+ border-bottom-left-radius: 25px;
+ color: $uv-text-color;
+ background-color: $uv-bg-color-hover;
+ }
+ .uv-calendar__header-text {
+ text-align: center;
+ width: 100px;
+ font-size: $uv-font-size-base;
+ color: $uv-text-color;
+ }
+ .uv-calendar__header-btn-box {
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ width: 50px;
+ height: 50px;
+ }
+ .uv-calendar__header-btn {
+ width: 10px;
+ height: 10px;
+ border-left-color: $uv-text-color-placeholder;
+ border-left-style: solid;
+ border-left-width: 2px;
+ border-top-color: $uv-color-subtitle;
+ border-top-style: solid;
+ border-top-width: 2px;
+ }
+ .uv-calendar--left {
+ transform: rotate(-45deg);
+ }
+ .uv-calendar--right {
+ transform: rotate(135deg);
+ }
+ .uv-calendar__weeks {
+ position: relative;
+ @include flex;
+ }
+ .uv-calendar__weeks-week {
+ padding: 0 0 2rpx;
+ }
+ .uv-calendar__weeks-item {
+ flex: 1;
+ }
+ .uv-calendar__weeks-day {
+ flex: 1;
+ @include flex(column);
+ justify-content: center;
+ align-items: center;
+ height: 45px;
+ border-bottom-color: #F5F5F5;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ }
+ .uv-calendar__weeks-day-text {
+ font-size: 14px;
+ }
+ .uv-calendar__box {
+ position: relative;
+ }
+ .uv-calendar__box-bg {
+ @include flex(column);
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+ .uv-calendar__box-bg-text {
+ font-size: 200px;
+ font-weight: bold;
+ color: $uv-text-color-grey;
+ opacity: 0.1;
+ text-align: center;
+ /* #ifndef APP-NVUE */
+ line-height: 1;
+ /* #endif */
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar-item.vue b/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar-item.vue
new file mode 100644
index 0000000..862d068
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar-item.vue
@@ -0,0 +1,248 @@
+<template>
+ <view class="uv-calendar-item__weeks-box" :class="{
+ 'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable),
+ 'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,
+ 'uv-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple) ,
+ 'uv-calendar-item--before-checked':weeks.beforeRange,
+ 'uv-calendar-item--range': weeks.range,
+ 'uv-calendar-item--after-checked':weeks.afterRange,
+ 'uv-calendar-item--multiple':weeks.multiple
+ }" :style="[itemBoxStyle]" @click="choiceDate(weeks)">
+ <view class="uv-calendar-item__weeks-box-item">
+ <text v-if="selected&&weeks.extraInfo&&weeks.extraInfo.badge" class="uv-calendar-item__weeks-box-circle"></text>
+ <text class="uv-calendar-item__weeks-top-text" v-if="weeks.extraInfo&&weeks.extraInfo.topinfo" :style="[infoStyle('top')]">{{weeks.extraInfo&&weeks.extraInfo.topinfo}}</text>
+ <text class="uv-calendar-item__weeks-box-text" :class="{
+ 'uv-calendar-item--isDay-text': weeks.isDay,
+ 'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,
+ 'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,
+ 'uv-calendar-item--before-checked':weeks.beforeRange,
+ 'uv-calendar-item--range': weeks.range,
+ 'uv-calendar-item--after-checked':weeks.afterRange,
+ 'uv-calendar-item--multiple':weeks.multiple,
+ 'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable)
+ }" :style="[itemBoxStyle]">{{weeks.date}}</text>
+ <text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uv-calendar-item__weeks-lunar-text" :class="{
+ 'uv-calendar-item--isDay-text':weeks.isDay,
+ 'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,
+ 'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,
+ 'uv-calendar-item--before-checked':weeks.beforeRange,
+ 'uv-calendar-item--range': weeks.range,
+ 'uv-calendar-item--after-checked':weeks.afterRange,
+ 'uv-calendar-item--multiple':weeks.multiple
+ }" :style="[itemBoxStyle]">{{todayText}}</text>
+ <text v-if="lunar&&!weeks.extraInfo" class="uv-calendar-item__weeks-lunar-text" :class="{
+ 'uv-calendar-item--isDay-text':weeks.isDay,
+ 'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,
+ 'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,
+ 'uv-calendar-item--before-checked':weeks.beforeRange,
+ 'uv-calendar-item--range': weeks.range,
+ 'uv-calendar-item--after-checked':weeks.afterRange,
+ 'uv-calendar-item--multiple':weeks.multiple,
+ 'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable)
+ }" :style="[itemBoxStyle]">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
+ <text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uv-calendar-item__weeks-lunar-text" :class="{
+ 'uv-calendar-item__weeks-lunar-text--equal': weeks.dateEqual
+ }" :style="[infoStyle('bottom')]">{{weeks.extraInfo.info}}</text>
+ </view>
+ </view>
+</template>
+<script>
+ import { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js';
+ import { initVueI18n } from '@dcloudio/uni-i18n'
+ import i18nMessages from './i18n/index.js'
+ const { t } = initVueI18n(i18nMessages)
+ export default {
+ emits: ['change'],
+ props: {
+ weeks: {
+ type: Object,
+ default () {
+ return {}
+ }
+ },
+ calendar: {
+ type: Object,
+ default: () => {
+ return {}
+ }
+ },
+ selected: {
+ type: Array,
+ default: () => {
+ return []
+ }
+ },
+ lunar: {
+ type: Boolean,
+ default: false
+ },
+ color: {
+ type: String,
+ default: '#3c9cff'
+ },
+ range: {
+ type: Boolean,
+ default: false
+ },
+ multiple: {
+ type: Boolean,
+ default: false
+ }
+ },
+ computed: {
+ todayText() {
+ return t("uv-calender.today")
+ },
+ itemBoxStyle() {
+ const style = {};
+ if (this.multiple) { // 多选状态
+ if (this.weeks.multiple) {
+ style.backgroundColor = this.color;
+ style.color = '#fff';
+ } else if (this.weeks.isDay) {
+ style.color = this.color;
+ }
+ } else if (this.range) { // 范围选择
+ if (this.weeks.beforeRange || this.weeks.afterRange) {
+ style.backgroundColor = this.color;
+ } else if (this.weeks.range) {
+ style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]
+ style.color = this.color;
+ style.opacity = 0.8;
+ style.borderRadius = 0;
+ }
+ } else {
+ if (this.weeks.isDay) {
+ style.color = this.color;
+ }
+ if (this.calendar.fullDate === this.weeks.fullDate) {
+ style.backgroundColor = this.color;
+ style.color = '#fff';
+ }
+ }
+ return style;
+ },
+ infoStyle(val) {
+ return val => {
+ const style = {};
+ if (!this.weeks.multiple) {
+ if (val == 'top') {
+ style.color = this.weeks.extraInfo.topinfoColor ? this.weeks.extraInfo.topinfoColor : '#606266';
+ } else if (val == 'bottom') {
+ style.color = this.weeks.extraInfo.infoColor ? this.weeks.extraInfo.infoColor : '#f56c6c';
+ }
+ if (this.weeks.range) {
+ style.color = this.color;
+ }
+ if (this.calendar.fullDate === this.weeks.fullDate || this.weeks.beforeRange || this.weeks.afterRange) {
+ style.color = this.multiple ? style.color : '#fff';
+ }
+ } else {
+ style.color = '#fff';
+ }
+ return style;
+ }
+ }
+ },
+ methods: {
+ choiceDate(weeks) {
+ if (this.weeks.extraInfo && this.weeks.extraInfo.disable) return;
+ this.$emit('change', weeks)
+ }
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ @mixin flex($direction: row) {
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ flex-direction: $direction;
+ }
+ $uv-font-size-base: 14px;
+ $uv-text-color: #333;
+ $uv-font-size-sm: 24rpx;
+ $uv-error: #f56c6c !default;
+ $uv-opacity-disabled: 0.3;
+ $uv-text-color-disable: #c0c0c0;
+ $uv-primary: #3c9cff !default;
+ $info-height: 32rpx;
+ .uv-calendar-item__weeks-box {
+ flex: 1;
+ @include flex(column);
+ justify-content: center;
+ align-items: center;
+ border-radius: 4px;
+ }
+ .uv-calendar-item__weeks-top-text {
+ height: $info-height;
+ line-height: $info-height;
+ font-size: $uv-font-size-sm;
+ }
+ .uv-calendar-item__weeks-box-text {
+ font-size: $uv-font-size-base;
+ color: $uv-text-color;
+ }
+ .uv-calendar-item__weeks-lunar-text {
+ height: $info-height;
+ line-height: $info-height;
+ font-size: $uv-font-size-sm;
+ color: $uv-text-color;
+ }
+ .uv-calendar-item__weeks-lunar-text--equal {
+ /* #ifdef H5 */
+ white-space: nowrap;
+ transform: scale(.8);
+ /* #endif */
+ /* #ifndef H5 */
+ font-size: 20rpx;
+ /* #endif */
+ }
+ .uv-calendar-item__weeks-box-item {
+ position: relative;
+ @include flex(column);
+ justify-content: center;
+ align-items: center;
+ width: 106rpx;
+ height: 56px;
+ }
+ .uv-calendar-item__weeks-box-circle {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ width: 8px;
+ height: 8px;
+ border-radius: 8px;
+ background-color: $uv-error;
+ }
+ .uv-calendar-item--disable {
+ background-color: rgba(249, 249, 249, $uv-opacity-disabled);
+ color: $uv-text-color-disable;
+ }
+ .uv-calendar-item--isDay-text {
+ color: $uv-primary;
+ }
+ .uv-calendar-item--isDay {
+ background-color: $uv-primary;
+ color: #fff;
+ }
+ .uv-calendar-item--checked {
+ background-color: $uv-primary;
+ color: #fff;
+ border-radius: 4px;
+ }
+ // .uv-calendar-item--range {
+ // background-color: $uv-primary;
+ // color: #fff;
+ // }
+ .uv-calendar-item--before-checked {
+ color: #fff;
+ }
+ .uv-calendar-item--after-checked {
+ color: #fff;
+ }
+ .uv-calendar-item--multiple {
+ background-color: $uv-primary;
+ color: #fff;
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar.js b/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar.js
new file mode 100644
index 0000000..b8d7d6f
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/calendar.js
@@ -0,0 +1,546 @@
+/**
+* @1900-2100区间内的公历、农历互转
+* @charset UTF-8
+* @github https://github.com/jjonline/calendar.js
+* @Author Jea杨(JJonline@JJonline.Cn)
+* @Time 2014-7-21
+* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
+* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
+* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
+* @Version 1.0.3
+* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
+* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
+*/
+/* eslint-disable */
+var calendar = {
+
+ /**
+ * 农历1900-2100的润大小信息表
+ * @Array Of Property
+ * @return Hex
+ */
+ lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
+ 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
+ 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
+ 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
+ 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
+ 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
+ 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
+ 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
+ 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
+ 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
+ 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
+ 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
+ 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
+ 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
+ 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
+ /** Add By JJonline@JJonline.Cn**/
+ 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
+ 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
+ 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
+ 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
+ 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
+ 0x0d520], // 2100
+
+ /**
+ * 公历每个月份的天数普通表
+ * @Array Of Property
+ * @return Number
+ */
+ solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+
+ /**
+ * 天干地支之天干速查表
+ * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
+ * @return Cn string
+ */
+ Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
+
+ /**
+ * 天干地支之地支速查表
+ * @Array Of Property
+ * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
+ * @return Cn string
+ */
+ Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
+
+ /**
+ * 天干地支之地支速查表<=>生肖
+ * @Array Of Property
+ * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
+ * @return Cn string
+ */
+ Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
+
+ /**
+ * 24节气速查表
+ * @Array Of Property
+ * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
+ * @return Cn string
+ */
+ solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
+
+ /**
+ * 1900-2100各年的24节气日期速查表
+ * @Array Of Property
+ * @return 0x string For splice
+ */
+ sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
+ '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
+ 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
+ '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
+ '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
+ '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+ '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
+ '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+ '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
+ '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+ '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+ '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+ '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+ '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
+ '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+ '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+ '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+ '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
+ '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
+ '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
+ '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+ '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
+ '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
+ '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
+ '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+ '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
+ '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
+ '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
+ '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+ '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+ '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
+ '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
+
+ /**
+ * 数字转中文速查表
+ * @Array Of Property
+ * @trans ['日','一','二','三','四','五','六','七','八','九','十']
+ * @return Cn string
+ */
+ nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
+
+ /**
+ * 日期转农历称呼速查表
+ * @Array Of Property
+ * @trans ['初','十','廿','卅']
+ * @return Cn string
+ */
+ nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
+
+ /**
+ * 月份转农历称呼速查表
+ * @Array Of Property
+ * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
+ * @return Cn string
+ */
+ nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
+
+ /**
+ * 返回农历y年一整年的总天数
+ * @param lunar Year
+ * @return Number
+ * @eg:var count = calendar.lYearDays(1987) ;//count=387
+ */
+ lYearDays: function (y) {
+ var i; var sum = 348
+ for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
+ return (sum + this.leapDays(y))
+ },
+
+ /**
+ * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
+ * @param lunar Year
+ * @return Number (0-12)
+ * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
+ */
+ leapMonth: function (y) { // 闰字编码 \u95f0
+ return (this.lunarInfo[y - 1900] & 0xf)
+ },
+
+ /**
+ * 返回农历y年闰月的天数 若该年没有闰月则返回0
+ * @param lunar Year
+ * @return Number (0、29、30)
+ * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
+ */
+ leapDays: function (y) {
+ if (this.leapMonth(y)) {
+ return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
+ }
+ return (0)
+ },
+
+ /**
+ * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
+ * @param lunar Year
+ * @return Number (-1、29、30)
+ * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
+ */
+ monthDays: function (y, m) {
+ if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
+ return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
+ },
+
+ /**
+ * 返回公历(!)y年m月的天数
+ * @param solar Year
+ * @return Number (-1、28、29、30、31)
+ * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
+ */
+ solarDays: function (y, m) {
+ if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+ var ms = m - 1
+ if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
+ return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
+ } else {
+ return (this.solarMonth[ms])
+ }
+ },
+
+ /**
+ * 农历年份转换为干支纪年
+ * @param lYear 农历年的年份数
+ * @return Cn string
+ */
+ toGanZhiYear: function (lYear) {
+ var ganKey = (lYear - 3) % 10
+ var zhiKey = (lYear - 3) % 12
+ if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
+ if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
+ return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
+ },
+
+ /**
+ * 公历月、日判断所属星座
+ * @param cMonth [description]
+ * @param cDay [description]
+ * @return Cn string
+ */
+ toAstro: function (cMonth, cDay) {
+ var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
+ var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
+ return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
+ },
+
+ /**
+ * 传入offset偏移量返回干支
+ * @param offset 相对甲子的偏移量
+ * @return Cn string
+ */
+ toGanZhi: function (offset) {
+ return this.Gan[offset % 10] + this.Zhi[offset % 12]
+ },
+
+ /**
+ * 传入公历(!)y年获得该年第n个节气的公历日期
+ * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
+ * @return day Number
+ * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
+ */
+ getTerm: function (y, n) {
+ if (y < 1900 || y > 2100) { return -1 }
+ if (n < 1 || n > 24) { return -1 }
+ var _table = this.sTermInfo[y - 1900]
+ var _info = [
+ parseInt('0x' + _table.substr(0, 5)).toString(),
+ parseInt('0x' + _table.substr(5, 5)).toString(),
+ parseInt('0x' + _table.substr(10, 5)).toString(),
+ parseInt('0x' + _table.substr(15, 5)).toString(),
+ parseInt('0x' + _table.substr(20, 5)).toString(),
+ parseInt('0x' + _table.substr(25, 5)).toString()
+ ]
+ var _calday = [
+ _info[0].substr(0, 1),
+ _info[0].substr(1, 2),
+ _info[0].substr(3, 1),
+ _info[0].substr(4, 2),
+
+ _info[1].substr(0, 1),
+ _info[1].substr(1, 2),
+ _info[1].substr(3, 1),
+ _info[1].substr(4, 2),
+
+ _info[2].substr(0, 1),
+ _info[2].substr(1, 2),
+ _info[2].substr(3, 1),
+ _info[2].substr(4, 2),
+
+ _info[3].substr(0, 1),
+ _info[3].substr(1, 2),
+ _info[3].substr(3, 1),
+ _info[3].substr(4, 2),
+
+ _info[4].substr(0, 1),
+ _info[4].substr(1, 2),
+ _info[4].substr(3, 1),
+ _info[4].substr(4, 2),
+
+ _info[5].substr(0, 1),
+ _info[5].substr(1, 2),
+ _info[5].substr(3, 1),
+ _info[5].substr(4, 2)
+ ]
+ return parseInt(_calday[n - 1])
+ },
+
+ /**
+ * 传入农历数字月份返回汉语通俗表示法
+ * @param lunar month
+ * @return Cn string
+ * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
+ */
+ toChinaMonth: function (m) { // 月 => \u6708
+ if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+ var s = this.nStr3[m - 1]
+ s += '\u6708'// 加上月字
+ return s
+ },
+
+ /**
+ * 传入农历日期数字返回汉字表示法
+ * @param lunar day
+ * @return Cn string
+ * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
+ */
+ toChinaDay: function (d) { // 日 => \u65e5
+ var s
+ switch (d) {
+ case 10:
+ s = '\u521d\u5341'; break
+ case 20:
+ s = '\u4e8c\u5341'; break
+ break
+ case 30:
+ s = '\u4e09\u5341'; break
+ break
+ default :
+ s = this.nStr2[Math.floor(d / 10)]
+ s += this.nStr1[d % 10]
+ }
+ return (s)
+ },
+
+ /**
+ * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
+ * @param y year
+ * @return Cn string
+ * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
+ */
+ getAnimal: function (y) {
+ return this.Animals[(y - 4) % 12]
+ },
+
+ /**
+ * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
+ * @param y solar year
+ * @param m solar month
+ * @param d solar day
+ * @return JSON object
+ * @eg:console.log(calendar.solar2lunar(1987,11,01));
+ */
+ solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
+ // 年份限定、上限
+ if (y < 1900 || y > 2100) {
+ return -1// undefined转换为数字变为NaN
+ }
+ // 公历传参最下限
+ if (y == 1900 && m == 1 && d < 31) {
+ return -1
+ }
+ // 未传参 获得当天
+ if (!y) {
+ var objDate = new Date()
+ } else {
+ var objDate = new Date(y, parseInt(m) - 1, d)
+ }
+ var i; var leap = 0; var temp = 0
+ // 修正ymd参数
+ var y = objDate.getFullYear()
+ var m = objDate.getMonth() + 1
+ var d = objDate.getDate()
+ var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
+ for (i = 1900; i < 2101 && offset > 0; i++) {
+ temp = this.lYearDays(i)
+ offset -= temp
+ }
+ if (offset < 0) {
+ offset += temp; i--
+ }
+
+ // 是否今天
+ var isTodayObj = new Date()
+ var isToday = false
+ if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
+ isToday = true
+ }
+ // 星期几
+ var nWeek = objDate.getDay()
+ var cWeek = this.nStr1[nWeek]
+ // 数字表示周几顺应天朝周一开始的惯例
+ if (nWeek == 0) {
+ nWeek = 7
+ }
+ // 农历年
+ var year = i
+ var leap = this.leapMonth(i) // 闰哪个月
+ var isLeap = false
+
+ // 效验闰月
+ for (i = 1; i < 13 && offset > 0; i++) {
+ // 闰月
+ if (leap > 0 && i == (leap + 1) && isLeap == false) {
+ --i
+ isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
+ } else {
+ temp = this.monthDays(year, i)// 计算农历普通月天数
+ }
+ // 解除闰月
+ if (isLeap == true && i == (leap + 1)) { isLeap = false }
+ offset -= temp
+ }
+ // 闰月导致数组下标重叠取反
+ if (offset == 0 && leap > 0 && i == leap + 1) {
+ if (isLeap) {
+ isLeap = false
+ } else {
+ isLeap = true; --i
+ }
+ }
+ if (offset < 0) {
+ offset += temp; --i
+ }
+ // 农历月
+ var month = i
+ // 农历日
+ var day = offset + 1
+ // 天干地支处理
+ var sm = m - 1
+ var gzY = this.toGanZhiYear(year)
+
+ // 当月的两个节气
+ // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
+ var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
+ var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
+
+ // 依据12节气修正干支月
+ var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
+ if (d >= firstNode) {
+ gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
+ }
+
+ // 传入的日期的节气与否
+ var isTerm = false
+ var Term = null
+ if (firstNode == d) {
+ isTerm = true
+ Term = this.solarTerm[m * 2 - 2]
+ }
+ if (secondNode == d) {
+ isTerm = true
+ Term = this.solarTerm[m * 2 - 1]
+ }
+ // 日柱 当月一日与 1900/1/1 相差天数
+ var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
+ var gzD = this.toGanZhi(dayCyclical + d - 1)
+ // 该日期所属的星座
+ var astro = this.toAstro(m, d)
+
+ return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
+ },
+
+ /**
+ * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
+ * @param y lunar year
+ * @param m lunar month
+ * @param d lunar day
+ * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
+ * @return JSON object
+ * @eg:console.log(calendar.lunar2solar(1987,9,10));
+ */
+ lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
+ var isLeapMonth = !!isLeapMonth
+ var leapOffset = 0
+ var leapMonth = this.leapMonth(y)
+ var leapDay = this.leapDays(y)
+ if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
+ if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
+ var day = this.monthDays(y, m)
+ var _day = day
+ // bugFix 2016-9-25
+ // if month is leap, _day use leapDays method
+ if (isLeapMonth) {
+ _day = this.leapDays(y, m)
+ }
+ if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
+
+ // 计算农历的时间差
+ var offset = 0
+ for (var i = 1900; i < y; i++) {
+ offset += this.lYearDays(i)
+ }
+ var leap = 0; var isAdd = false
+ for (var i = 1; i < m; i++) {
+ leap = this.leapMonth(y)
+ if (!isAdd) { // 处理闰月
+ if (leap <= i && leap > 0) {
+ offset += this.leapDays(y); isAdd = true
+ }
+ }
+ offset += this.monthDays(y, i)
+ }
+ // 转换闰月农历 需补充该年闰月的前一个月的时差
+ if (isLeapMonth) { offset += day }
+ // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
+ var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
+ var calObj = new Date((offset + d - 31) * 86400000 + stmap)
+ var cY = calObj.getUTCFullYear()
+ var cM = calObj.getUTCMonth() + 1
+ var cD = calObj.getUTCDate()
+
+ return this.solar2lunar(cY, cM, cD)
+ }
+}
+
+export default calendar
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/en.json b/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/en.json
new file mode 100644
index 0000000..c83e8b2
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/en.json
@@ -0,0 +1,12 @@
+{
+ "uv-calender.ok": "ok",
+ "uv-calender.cancel": "cancel",
+ "uv-calender.today": "today",
+ "uv-calender.MON": "MON",
+ "uv-calender.TUE": "TUE",
+ "uv-calender.WED": "WED",
+ "uv-calender.THU": "THU",
+ "uv-calender.FRI": "FRI",
+ "uv-calender.SAT": "SAT",
+ "uv-calender.SUN": "SUN"
+}
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/index.js b/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/index.js
new file mode 100644
index 0000000..de7509c
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/index.js
@@ -0,0 +1,8 @@
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+export default {
+ en,
+ 'zh-Hans': zhHans,
+ 'zh-Hant': zhHant
+}
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hans.json b/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hans.json
new file mode 100644
index 0000000..03c5486
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hans.json
@@ -0,0 +1,12 @@
+{
+ "uv-calender.ok": "确定",
+ "uv-calender.cancel": "取消",
+ "uv-calender.today": "今日",
+ "uv-calender.SUN": "日",
+ "uv-calender.MON": "一",
+ "uv-calender.TUE": "二",
+ "uv-calender.WED": "三",
+ "uv-calender.THU": "四",
+ "uv-calender.FRI": "五",
+ "uv-calender.SAT": "六"
+}
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hant.json b/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hant.json
new file mode 100644
index 0000000..95cf29e
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hant.json
@@ -0,0 +1,12 @@
+{
+ "uv-calender.ok": "確定",
+ "uv-calender.cancel": "取消",
+ "uv-calender.today": "今日",
+ "uv-calender.SUN": "日",
+ "uv-calender.MON": "一",
+ "uv-calender.TUE": "二",
+ "uv-calender.WED": "三",
+ "uv-calender.THU": "四",
+ "uv-calender.FRI": "五",
+ "uv-calender.SAT": "六"
+}
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/util.js b/ledApp/uni_modules/uv-calendars/components/uv-calendars/util.js
new file mode 100644
index 0000000..ae1c6d6
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/util.js
@@ -0,0 +1,435 @@
+import CALENDAR from './calendar.js'
+class Calendar {
+ constructor({
+ date,
+ selected,
+ startDate,
+ endDate,
+ range,
+ multiple,
+ allowSameDay
+ } = {}) {
+ // 当前日期
+ this.date = this.getDate(new Date()) // 当前初入日期
+ // 打点信息
+ this.selected = selected || [];
+ // 范围开始
+ this.startDate = startDate
+ // 范围结束
+ this.endDate = endDate
+ this.range = range
+ this.multiple = multiple
+ this.allowSameDay = allowSameDay
+ // 多选状态
+ this.cleanRangeStatus()
+ // 范围状态
+ this.cleanMultipleStatus()
+ // 每周日期
+ this.weeks = {}
+ // this._getWeek(this.date.fullDate)
+ }
+ /**
+ * 设置日期
+ * @param {Object} date
+ */
+ setDate(date, status) {
+ if (this.range && status == 'init') {
+ this.cleanRangeStatus();
+ if (Array.isArray(date)) {
+ this.rangeStatus.before = date[0];
+ this.rangeStatus.after = date.length > 1 ? date[date.length - 1] : '';
+ if (this.rangeStatus.after && this.dateCompare(this.rangeStatus.before, this.rangeStatus.after)) {
+ this.rangeStatus.data = this.geDateAll(this.rangeStatus.before, this.rangeStatus.after)
+ }
+ this.selectDate = this.getDate(date[0])
+ this._getWeek(this.selectDate.fullDate)
+ } else {
+ this.selectDate = this.getDate(date)
+ this.rangeStatus.before = this.selectDate.fullDate;
+ this._getWeek(this.selectDate.fullDate)
+ }
+ } else if (this.multiple && status == 'init') {
+ this.cleanMultipleStatus();
+ if (Array.isArray(date)) {
+ this.multipleStatus.data = date;
+ this.selectDate = this.getDate(date[0])
+ this._getWeek(this.selectDate.fullDate)
+ } else {
+ this.selectDate = this.getDate(date)
+ this.multipleStatus.data = [this.selectDate.fullDate];
+ this._getWeek(this.selectDate.fullDate)
+ }
+ } else {
+ if (Array.isArray(date)) {
+ this.selectDate = this.getDate(date[0])
+ this._getWeek(this.selectDate.fullDate)
+ } else {
+ this.selectDate = this.getDate(date)
+ this._getWeek(this.selectDate.fullDate)
+ }
+ }
+ }
+ /**
+ * 清理多选状态
+ */
+ cleanRangeStatus() {
+ this.rangeStatus = {
+ before: '',
+ after: '',
+ data: []
+ }
+ }
+ /**
+ * 清理多选状态
+ */
+ cleanMultipleStatus() {
+ this.multipleStatus = {
+ data: []
+ }
+ }
+ /**
+ * 重置开始日期
+ */
+ resetSatrtDate(startDate) {
+ // 范围开始
+ this.startDate = startDate
+ }
+ /**
+ * 重置结束日期
+ */
+ resetEndDate(endDate) {
+ // 范围结束
+ this.endDate = endDate
+ }
+ /**
+ * 获取任意时间
+ */
+ getDate(date, AddDayCount = 0, str = 'day') {
+ if (!date) {
+ date = new Date()
+ }
+ if (typeof date !== 'object') {
+ date = date.replace(/-/g, '/')
+ }
+ const dd = new Date(date)
+ switch (str) {
+ case 'day':
+ dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
+ break
+ case 'month':
+ if (dd.getDate() === 31 && AddDayCount > 0) {
+ dd.setDate(dd.getDate() + AddDayCount)
+ } else {
+ const preMonth = dd.getMonth()
+ dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
+ const nextMonth = dd.getMonth()
+ // 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
+ if (AddDayCount < 0 && preMonth !== 0 && nextMonth - preMonth > AddDayCount) {
+ dd.setMonth(nextMonth + (nextMonth - preMonth + AddDayCount))
+ }
+ // 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
+ if (AddDayCount > 0 && nextMonth - preMonth > AddDayCount) {
+ dd.setMonth(nextMonth - (nextMonth - preMonth - AddDayCount))
+ }
+ }
+ break
+ case 'year':
+ dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
+ break
+ }
+ const y = dd.getFullYear()
+ const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
+ const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
+ return {
+ fullDate: y + '-' + m + '-' + d,
+ year: y,
+ month: m,
+ date: d,
+ day: dd.getDay()
+ }
+ }
+ /**
+ * 获取上月剩余天数
+ */
+ _getLastMonthDays(firstDay, full) {
+ let dateArr = []
+ for (let i = firstDay; i > 0; i--) {
+ const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
+ dateArr.push({
+ date: beforeDate,
+ month: full.month - 1,
+ lunar: this.getlunar(full.year, full.month - 1, beforeDate),
+ disable: true
+ })
+ }
+ return dateArr
+ }
+ /**
+ * 获取本月天数
+ */
+ _currentMonthDys(dateData, full) {
+ let dateArr = []
+ let fullDate = this.date.fullDate
+ for (let i = 1; i <= dateData; i++) {
+ let nowDate = full.year + '-' + (full.month < 10 ? full.month : full.month) + '-' + (i < 10 ? '0' + i : i)
+ // 是否今天
+ let isDay = fullDate === nowDate
+ // 获取打点信息
+ let info = this.selected && this.selected.find((item) => {
+ if (this.dateEqual(nowDate, item.date)) {
+ return item
+ }
+ })
+ // 日期禁用
+ let disableBefore = true
+ let disableAfter = true
+ if (this.startDate) {
+ // let dateCompBefore = this.dateCompare(this.startDate, fullDate)
+ // disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
+ disableBefore = this.dateCompare(this.startDate, nowDate)
+ }
+ if (this.endDate) {
+ // let dateCompAfter = this.dateCompare(fullDate, this.endDate)
+ // disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
+ disableAfter = this.dateCompare(nowDate, this.endDate)
+ }
+ let ranges = this.rangeStatus.data
+ let checked = false
+ let rangesStatus = -1
+ if (this.range) {
+ if (ranges) {
+ rangesStatus = ranges.findIndex((item) => {
+ return this.dateEqual(item, nowDate)
+ })
+ }
+ if (rangesStatus !== -1) {
+ checked = true
+ }
+ }
+ let multiples = this.multipleStatus.data
+ let checked_multiple = false
+ let multiplesStatus = -1
+ if (this.multiple) {
+ if (multiples) {
+ multiplesStatus = multiples.findIndex((item) => {
+ return this.dateEqual(item, nowDate)
+ })
+ }
+ if (multiplesStatus !== -1) {
+ checked_multiple = true
+ }
+ }
+ let data = {
+ fullDate: nowDate,
+ year: full.year,
+ date: i,
+ range: this.range ? checked : false,
+ multiple: this.multiple ? checked_multiple : false,
+ beforeRange: this.dateEqual(this.rangeStatus.before, nowDate),
+ afterRange: this.dateEqual(this.rangeStatus.after, nowDate),
+ dateEqual: this.range && checked && this.dateEqual(this.rangeStatus.before, this.rangeStatus.after),
+ month: full.month,
+ lunar: this.getlunar(full.year, full.month, i),
+ disable: !(disableBefore && disableAfter),
+ isDay
+ }
+ if (info) {
+ data.extraInfo = info
+ }
+ dateArr.push(data)
+ }
+ return dateArr
+ }
+ /**
+ * 获取下月天数
+ */
+ _getNextMonthDays(surplus, full) {
+ let dateArr = []
+ for (let i = 1; i < surplus + 1; i++) {
+ dateArr.push({
+ date: i,
+ month: Number(full.month) + 1,
+ lunar: this.getlunar(full.year, Number(full.month) + 1, i),
+ disable: true
+ })
+ }
+ return dateArr
+ }
+ /**
+ * 获取当前日期详情
+ * @param {Object} date
+ */
+ getInfo(date) {
+ if (!date) {
+ date = new Date()
+ } else if (Array.isArray(date)) {
+ date = date[0]
+ }
+ const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
+ return dateInfo
+ }
+ /**
+ * 比较时间大小
+ */
+ dateCompare(startDate, endDate) {
+ // 计算截止时间
+ startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
+ // 计算详细项的截止时间
+ endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
+ if (startDate <= endDate) {
+ return true
+ } else {
+ return false
+ }
+ }
+ /**
+ * 比较时间是否相等
+ */
+ dateEqual(before, after) {
+ // 计算截止时间
+ before = new Date(before.replace('-', '/').replace('-', '/'))
+ // 计算详细项的截止时间
+ after = new Date(after.replace('-', '/').replace('-', '/'))
+ if (before.getTime() - after.getTime() === 0) {
+ return true
+ } else {
+ return false
+ }
+ }
+ /**
+ * 比较after时间是否大于before时间
+ */
+ dateAfterLgBefore(before, after) {
+ // 计算截止时间
+ before = new Date(before.replace('-', '/').replace('-', '/'))
+ // 计算详细项的截止时间
+ after = new Date(after.replace('-', '/').replace('-', '/'))
+ if (after.getTime() - before.getTime() > 0) {
+ return true
+ } else {
+ return false
+ }
+ }
+ /**
+ * 获取日期范围内所有日期
+ * @param {Object} begin
+ * @param {Object} end
+ */
+ geDateAll(begin, end) {
+ var arr = []
+ var ab = begin.split('-')
+ var ae = end.split('-')
+ var db = new Date()
+ db.setFullYear(ab[0], ab[1] - 1, ab[2])
+ var de = new Date()
+ de.setFullYear(ae[0], ae[1] - 1, ae[2])
+ var unixDb = db.getTime() - 24 * 60 * 60 * 1000
+ var unixDe = de.getTime() - 24 * 60 * 60 * 1000
+ for (var k = unixDb; k <= unixDe;) {
+ k = k + 24 * 60 * 60 * 1000
+ arr.push(this.getDate(new Date(parseInt(k))).fullDate)
+ }
+ return arr
+ }
+ /**
+ * 计算阴历日期显示
+ */
+ getlunar(year, month, date) {
+ return CALENDAR.solar2lunar(year, month, date)
+ }
+ /**
+ * 设置打点
+ */
+ setSelectInfo(data, value) {
+ this.selected = value
+ this._getWeek(data)
+ }
+ /**
+ * 获取多选状态
+ */
+ setMultiple(fullDate) {
+ if (!this.multiple) return
+ let multiples = this.multipleStatus.data;
+ const findIndex = multiples.findIndex(item => this.dateEqual(fullDate, item));
+ if (findIndex < 0) {
+ this.multipleStatus.data = this.multipleStatus.data.concat([fullDate]);
+ } else {
+ this.multipleStatus.data.splice(findIndex, 1);
+ }
+ this._getWeek(fullDate)
+ }
+ /**
+ * 获取范围状态
+ */
+ setRange(fullDate) {
+ let {
+ before,
+ after
+ } = this.rangeStatus
+ if (!this.range) return
+ if (before && after) {
+ this.cleanRangeStatus();
+ this.rangeStatus.before = fullDate
+ } else {
+ if (!before) {
+ this.rangeStatus.before = fullDate
+ } else {
+ if (this.allowSameDay && this.dateEqual(before, fullDate)) {
+ this.rangeStatus.after = fullDate
+ } else if (!this.dateAfterLgBefore(this.rangeStatus.before, fullDate)) {
+ this.cleanRangeStatus();
+ this.rangeStatus.before = fullDate
+ this._getWeek(fullDate)
+ return;
+ }
+ this.rangeStatus.after = fullDate
+ if (this.dateCompare(this.rangeStatus.before, this.rangeStatus.after)) {
+ this.rangeStatus.data = this.geDateAll(this.rangeStatus.before, this.rangeStatus.after);
+ } else {
+ this.rangeStatus.data = this.geDateAll(this.rangeStatus.after, this.rangeStatus.before);
+ }
+ }
+ }
+ this._getWeek(fullDate)
+ }
+ /**
+ * 获取每周数据
+ * @param {Object} dateData
+ */
+ _getWeek(dateData) {
+ const {
+ year,
+ month
+ } = this.getDate(dateData)
+ let firstDay = new Date(year, month - 1, 1).getDay()
+ let currentDay = new Date(year, month, 0).getDate()
+ let dates = {
+ lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
+ currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
+ nextMonthDays: [], // 下个月开始几天
+ weeks: []
+ }
+ let canlender = []
+ const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
+ dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
+ canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
+ let weeks = {}
+ // 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
+ for (let i = 0; i < canlender.length; i++) {
+ if (i % 7 === 0) {
+ weeks[parseInt(i / 7)] = new Array(7)
+ }
+ weeks[parseInt(i / 7)][i % 7] = canlender[i]
+ }
+ this.canlender = canlender
+ this.weeks = weeks
+ }
+ //静态方法
+ // static init(date) {
+ // if (!this.instance) {
+ // this.instance = new Calendar(date);
+ // }
+ // return this.instance;
+ // }
+}
+export default Calendar
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendars/components/uv-calendars/uv-calendars.vue b/ledApp/uni_modules/uv-calendars/components/uv-calendars/uv-calendars.vue
new file mode 100644
index 0000000..8d5b377
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/components/uv-calendars/uv-calendars.vue
@@ -0,0 +1,452 @@
+<template>
+ <view class="uv-calendar">
+ <view class="uv-calendar__content" v-if="insert">
+ <calendar-body
+ :date="date"
+ :nowDate="nowDate"
+ :weeks="weeks"
+ :calendar="calendar"
+ :selected="selected"
+ :lunar="lunar"
+ :showMonth="showMonth"
+ :color="color"
+ :startText="startText"
+ :endText="endText"
+ :range="range"
+ :multiple="multiple"
+ :allowSameDay="allowSameDay"
+ @bindDateChange="bindDateChange"
+ @pre="pre"
+ @next="next"
+ @backToday="backToday"
+ @choiceDate="choiceDate"
+ ></calendar-body>
+ </view>
+ <uv-popup ref="popup" mode="bottom" v-else :round="round" z-index="998" :close-on-click-overlay="closeOnClickOverlay" @maskClick="maskClick">
+ <view style="min-height: 100px;">
+ <uv-toolbar
+ :show="true"
+ :cancelColor="cancelColor"
+ :confirmColor="getConfirmColor"
+ :cancelText="cancelText"
+ :confirmText="confirmText"
+ :title="title"
+ @cancel="close"
+ @confirm="confirm"></uv-toolbar>
+ <view class="line"></view>
+ <calendar-body
+ :nowDate="nowDate"
+ :weeks="weeks"
+ :calendar="calendar"
+ :selected="selected"
+ :lunar="lunar"
+ :showMonth="showMonth"
+ :color="color"
+ :startText="startText"
+ :endText="endText"
+ :range="range"
+ :multiple="multiple"
+ :allowSameDay="allowSameDay"
+ @bindDateChange="bindDateChange"
+ @pre="pre"
+ @next="next"
+ @backToday="backToday"
+ @choiceDate="choiceDate"
+ ></calendar-body>
+ </view>
+ </uv-popup>
+ </view>
+</template>
+<script>
+ /**
+ * Calendar 日历
+ * @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
+ * @tutorial https://ext.dcloud.net.cn/plugin?name=uv-calendar
+ * @property {String} date 自定义当前时间,默认为今天
+ * @property {Boolean} lunar 显示农历
+ * @property {String} startDate 日期选择范围-开始日期
+ * @property {String} endDate 日期选择范围-结束日期
+ * @property {String} mode = [不传 | multiple | range ] 多个日期 | 选择日期范围 默认单日期
+ * @property {Boolean} insert = [true|false] 插入模式,默认为false
+ * @value true 弹窗模式
+ * @value false 插入模式
+ * @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
+ * @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
+ * @property {String} cancelColor 取消按钮颜色
+ * @property {String} confirmColor 确认按钮颜色,默认#3c9cff
+ * @property {String} title 头部工具条中间的标题文字
+ * @property {String} color 主题色,默认#3c9cff
+ * @property {Number} round :insert="false"时的圆角
+ * @property {Boolean} closeOnClickOverlay 点击遮罩是否关闭
+ * @property {String} startText range为true时,第一个日期底部的提示文字
+ * @property {String} endText range为true时,最后一个日期底部的提示文字
+ * @property {String} readonly 是否为只读状态,只读状态下禁止选择日期,默认false
+ *
+ * @event {Function} change 日期改变,`insert :ture` 时生效
+ * @event {Function} confirm 确认选择`insert :false` 时生效
+ * @event {Function} monthSwitch 切换月份时触发
+ *
+ * @example <uv-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
+ */
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';
+ import Calendar from './util.js';
+ import calendarBody from './calendar-body.vue';
+ import { initVueI18n } from '@dcloudio/uni-i18n';
+ import i18nMessages from './i18n/index.js';
+ const { t } = initVueI18n(i18nMessages);
+ export default {
+ components: {
+ calendarBody
+ },
+ mixins: [mpMixin, mixin],
+ emits: ['close', 'confirm', 'change', 'monthSwitch'],
+ props: {
+ // 取消按钮颜色
+ cancelColor: {
+ type: String,
+ default: ''
+ },
+ // 确认按钮颜色,range模式下未选全显示灰色
+ confirmColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 标题
+ title: {
+ type: String,
+ default: ''
+ },
+ // 主题色
+ color: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 默认显示日期
+ date: {
+ type: [String,Array],
+ default: ''
+ },
+ // 打点等设置
+ selected: {
+ type: Array,
+ default () {
+ return []
+ }
+ },
+ // 是否显示农历
+ lunar: {
+ type: Boolean,
+ default: false
+ },
+ // 可选择的起始日期
+ startDate: {
+ type: String,
+ default: ''
+ },
+ // 可选择的结束日期
+ endDate: {
+ type: String,
+ default: ''
+ },
+ // multiple - 选择多日期 range - 选择日期范围
+ mode: {
+ type: String,
+ default: ''
+ },
+ // 是否插入模式
+ insert: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示月份为背景
+ showMonth: {
+ type: Boolean,
+ default: true
+ },
+ // 弹窗模式是否清空上次选择内容
+ clearDate: {
+ type: Boolean,
+ default: true
+ },
+ // 弹窗圆角
+ round: {
+ type: [Number,String],
+ default: 8
+ },
+ // 点击遮罩是否关闭弹窗
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: true
+ },
+ // range为true时,第一个日期底部的提示文字
+ startText: {
+ type: String,
+ default: '开始'
+ },
+ // range为true时,最后一个日期底部的提示文字
+ endText: {
+ type: String,
+ default: '结束'
+ },
+ // 是否允许日期范围的起止时间为同一天,mode = range时有效
+ allowSameDay: {
+ type: Boolean,
+ default: false
+ },
+ // 是否禁用
+ readonly: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.calendars
+ },
+ data(){
+ return {
+ weeks: [],
+ calendar: {},
+ nowDate: '',
+ allowConfirm: false,
+ multiple: false,
+ range: false
+ }
+ },
+ computed:{
+ /**
+ * for i18n
+ */
+ confirmText() {
+ return t("uv-calender.ok")
+ },
+ cancelText() {
+ return t("uv-calender.cancel")
+ },
+ getConfirmColor() {
+ if(this.range || this.multiple || this.readonly) {
+ return this.allowConfirm? this.confirmColor: '#999'
+ }else {
+ return this.confirmColor;
+ }
+ }
+ },
+ watch: {
+ date(newVal) {
+ this.init(newVal)
+ },
+ startDate(val) {
+ this.cale.resetSatrtDate(val)
+ this.cale.setDate(this.nowDate.fullDate)
+ this.weeks = this.cale.weeks
+ },
+ endDate(val) {
+ this.cale.resetEndDate(val)
+ this.cale.setDate(this.nowDate.fullDate)
+ this.weeks = this.cale.weeks
+ },
+ selected(newVal) {
+ this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
+ this.weeks = this.cale.weeks
+ }
+ },
+ created() {
+ this.setMode();
+ this.cale = new Calendar({
+ selected: this.selected,
+ startDate: this.startDate,
+ endDate: this.endDate,
+ range: this.range,
+ multiple: this.multiple,
+ allowSameDay: this.allowSameDay
+ })
+ this.init(this.date)
+ },
+ methods: {
+ setMode() {
+ switch (this.mode){
+ case 'range':
+ this.range = true;
+ break;
+ case 'multiple':
+ this.multiple = true;
+ default:
+ break;
+ }
+ },
+ async open() {
+ if (this.clearDate && !this.insert) {
+ this.cale.cleanRangeStatus()
+ this.init(this.date)
+ }
+ if(!this.insert){
+ this.$refs.popup.open();
+ }
+ },
+ close() {
+ this.$refs.popup.close();
+ this.$emit('close');
+ },
+ confirm() {
+ if(this.readonly) {
+ return;
+ } else if(this.range && !this.cale.rangeStatus.after) {
+ return;
+ } else if(this.multiple && this.cale.multipleStatus.data.length == 0){
+ return;
+ }
+ this.setEmit('confirm');
+ this.close()
+ },
+ maskClick() {
+ if(this.closeOnClickOverlay) {
+ this.$emit('close');
+ }
+ },
+ bindDateChange(e) {
+ const value = e.detail.value + '-1'
+ this.setDate(value)
+
+ const { year, month } = this.cale.getDate(value)
+ this.$emit('monthSwitch', {
+ year,
+ month
+ })
+ },
+ /**
+ * 初始化日期显示
+ * @param {Object} date
+ */
+ init(date) {
+ if(this.range) {
+ // 重置范围选择状态
+ this.cale.cleanRangeStatus();
+ }else if(this.multiple){
+ // 重置多选状态
+ this.cale.cleanMultipleStatus();
+ }
+ this.cale.setDate(date,'init')
+ this.weeks = this.cale.weeks
+ this.nowDate = this.calendar = this.cale.getInfo(date)
+ this.changeConfirmStatus();
+ },
+ /**
+ * 变化触发
+ */
+ change() {
+ this.changeConfirmStatus();
+ if (!this.insert) return
+ this.setEmit('change')
+ },
+ changeConfirmStatus() {
+ if(this.readonly) {
+ this.allowConfirm = false;
+ } else if (this.range) {
+ this.allowConfirm = this.cale.rangeStatus.after ? true : false;
+ } else if(this.multiple) {
+ this.allowConfirm = this.cale.multipleStatus.data.length > 0 ? true : false;
+ }
+ },
+ /**
+ * 选择月份触发
+ */
+ monthSwitch() {
+ let {
+ year,
+ month
+ } = this.nowDate
+ this.$emit('monthSwitch', {
+ year,
+ month: Number(month)
+ })
+ },
+ /**
+ * 派发事件
+ * @param {Object} name
+ */
+ setEmit(name) {
+ let {
+ year,
+ month,
+ date,
+ fullDate,
+ lunar,
+ extraInfo
+ } = this.calendar
+ this.$emit(name, {
+ range: this.cale.rangeStatus,
+ multiple: this.cale.multipleStatus,
+ year,
+ month,
+ date,
+ fulldate: fullDate,
+ lunar,
+ extraInfo: extraInfo || {}
+ })
+ },
+ /**
+ * 选择天触发
+ * @param {Object} weeks
+ */
+ choiceDate(weeks) {
+ if (weeks.disable || this.readonly) return
+ this.calendar = weeks
+ // 设置范围选择
+ this.cale.setRange(this.calendar.fullDate)
+ // 设置多选
+ this.cale.setMultiple(this.calendar.fullDate);
+ this.weeks = this.cale.weeks
+ this.change()
+ },
+ /**
+ * 回到今天
+ */
+ backToday() {
+ const nowYearMonth = `${this.nowDate.year}-${this.nowDate.month}`
+ const date = this.cale.getDate(new Date())
+ const todayYearMonth = `${date.year}-${date.month}`
+ this.init(date.fullDate)
+ if (nowYearMonth !== todayYearMonth) {
+ this.monthSwitch()
+ }
+ this.change()
+ },
+ /**
+ * 上个月
+ */
+ pre() {
+ const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
+ this.setDate(preDate)
+ this.monthSwitch()
+ },
+ /**
+ * 下个月
+ */
+ next() {
+ const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
+ this.setDate(nextDate)
+ this.monthSwitch()
+ },
+ /**
+ * 设置日期
+ * @param {Object} date
+ */
+ setDate(date) {
+ this.cale.setDate(date)
+ this.weeks = this.cale.weeks
+ this.nowDate = this.cale.getInfo(date)
+ }
+ }
+ }
+</script>
+<style scoped lang="scss">
+ $uv-border-color: #EDEDED !default;
+ .uv-calendar__content {
+ background-color: #fff;
+ }
+ .line {
+ width: 750rpx;
+ height: 1px;
+ border-bottom-color: $uv-border-color;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendars/package.json b/ledApp/uni_modules/uv-calendars/package.json
new file mode 100644
index 0000000..7153510
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-calendars",
+ "displayName": "uv-calendars 最新日历 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.15",
+ "description": "新版本uv-calendars,不仅拥有老版本的所有功能,还增加了更加适用的插入页面等强大功能,且更加简洁。查看日期、选择单个或多个或任意范围日期,打点操作,自定义文案,自定义主题等强大功能。",
+ "keywords": [
+ "uv-ui",
+ "uvui",
+ "日历",
+ "打卡",
+ "日历选择"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration":{
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-popup",
+ "uv-toolbar"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-calendars/readme.md b/ledApp/uni_modules/uv-calendars/readme.md
new file mode 100644
index 0000000..d986eb9
--- /dev/null
+++ b/ledApp/uni_modules/uv-calendars/readme.md
@@ -0,0 +1,23 @@
+## Calendars 全新日历
+
+> **组件名:uv-calendars**
+
+为了解决老版本`uv-calendar`性能问题,特别是对日期选择范围有很大限制,体验不友好等缺点。于是有了新版日历组件。
+
+新版本`uv-calendars`,不仅拥有老版本的所有功能,还增加了更加适用的插入页面等强大功能,且更加简洁。查看日期、选择单个或多个或任意范围日期,打点操作,自定义文案,自定义主题等强大功能。
+
+常用场景:酒店日期预订、火车机票选择购买日期、上下班打卡等。
+
+# <a href="https://www.uvui.cn/components/calendars.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-cell/changelog.md b/ledApp/uni_modules/uv-cell/changelog.md
new file mode 100644
index 0000000..c342b0c
--- /dev/null
+++ b/ledApp/uni_modules/uv-cell/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.5(2023-12-06)
+1. 修复uv-cell right-icon插槽编译到APP端不显示的BUG,问题来源:https://gitee.com/climblee/uv-ui/issues/I8LXZI
+## 1.0.4(2023-09-19)
+1. 增加cellStyle参数,方便自定义单元格的样式
+## 1.0.3(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.2(2023-06-21)
+1. 优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-cell 单元格
diff --git a/ledApp/uni_modules/uv-cell/components/uv-cell-group/props.js b/ledApp/uni_modules/uv-cell/components/uv-cell-group/props.js
new file mode 100644
index 0000000..57deb4c
--- /dev/null
+++ b/ledApp/uni_modules/uv-cell/components/uv-cell-group/props.js
@@ -0,0 +1,15 @@
+export default {
+ props: {
+ // 分组标题
+ title: {
+ type: String,
+ default: ''
+ },
+ // 是否显示外边框
+ border: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.cellGroup
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-cell/components/uv-cell-group/uv-cell-group.vue b/ledApp/uni_modules/uv-cell/components/uv-cell-group/uv-cell-group.vue
new file mode 100644
index 0000000..0f17865
--- /dev/null
+++ b/ledApp/uni_modules/uv-cell/components/uv-cell-group/uv-cell-group.vue
@@ -0,0 +1,63 @@
+<template>
+ <view :style="[$uv.addStyle(customStyle)]" :class="[customClass]" class="uv-cell-group">
+ <view v-if="title" class="uv-cell-group__title">
+ <slot name="title">
+ <text class="uv-cell-group__title__text">{{ title }}</text>
+ </slot>
+ </view>
+ <view class="uv-cell-group__wrapper">
+ <uv-line v-if="border"></uv-line>
+ <slot />
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * cellGroup 单元格
+ * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+ * @tutorial https://www.uvui.cn/components/cell.html
+ *
+ * @property {String} title 分组标题
+ * @property {Boolean} border 是否显示外边框 (默认 true )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} click 点击cell列表时触发
+ * @example <uv-cell-group title="设置喜好">
+ */
+ export default {
+ name: 'uv-cell-group',
+ mixins: [mpMixin, mixin, props]
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-cell-group-title-padding: 16px 16px 8px !default;
+ $uv-cell-group-title-font-size: 15px !default;
+ $uv-cell-group-title-line-height: 16px !default;
+ $uv-cell-group-title-color: $uv-main-color !default;
+
+ .uv-cell-group {
+ flex: 1;
+
+ &__title {
+ padding: $uv-cell-group-title-padding;
+
+ &__text {
+ font-size: $uv-cell-group-title-font-size;
+ line-height: $uv-cell-group-title-line-height;
+ color: $uv-cell-group-title-color;
+ }
+ }
+
+ &__wrapper {
+ position: relative;
+ }
+ }
+</style>
+
diff --git a/ledApp/uni_modules/uv-cell/components/uv-cell/props.js b/ledApp/uni_modules/uv-cell/components/uv-cell/props.js
new file mode 100644
index 0000000..6579636
--- /dev/null
+++ b/ledApp/uni_modules/uv-cell/components/uv-cell/props.js
@@ -0,0 +1,116 @@
+export default {
+ props: {
+ // 标题
+ title: {
+ type: [String, Number],
+ default: ''
+ },
+ // 标题下方的描述信息
+ label: {
+ type: [String, Number],
+ default: ''
+ },
+ // 右侧的内容
+ value: {
+ type: [String, Number],
+ default: ''
+ },
+ // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+ icon: {
+ type: String,
+ default: ''
+ },
+ // 是否禁用cell
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示下边框
+ border: {
+ type: Boolean,
+ default: true
+ },
+ // 内容是否垂直居中(主要是针对右侧的value部分)
+ center: {
+ type: Boolean,
+ default: true
+ },
+ // 点击后跳转的URL地址
+ url: {
+ type: String,
+ default: ''
+ },
+ // 链接跳转的方式,内部使用的是uvui封装的route方法,可能会进行拦截操作
+ linkType: {
+ type: String,
+ default: 'navigateTo'
+ },
+ // 是否开启点击反馈(表现为点击时加上灰色背景)
+ clickable: {
+ type: Boolean,
+ default: false
+ },
+ // 是否展示右侧箭头并开启点击反馈
+ isLink: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
+ required: {
+ type: Boolean,
+ default: false
+ },
+ // 右侧的图标箭头
+ rightIcon: {
+ type: String,
+ default: 'arrow-right'
+ },
+ // 右侧箭头的方向,可选值为:left,up,down
+ arrowDirection: {
+ type: String,
+ default: ''
+ },
+ // 左侧图标样式
+ iconStyle: {
+ type: [Object, String],
+ default: () => {
+ return {}
+ }
+ },
+ // 右侧箭头图标的样式
+ rightIconStyle: {
+ type: [Object, String],
+ default: () => {
+ return {}
+ }
+ },
+ // 标题的样式
+ titleStyle: {
+ type: [Object, String],
+ default: () => {
+ return {}
+ }
+ },
+ // 单位元的大小,可选值为large
+ size: {
+ type: String,
+ default: ''
+ },
+ // 点击cell是否阻止事件传播
+ stop: {
+ type: Boolean,
+ default: true
+ },
+ // 标识符,cell被点击时返回
+ name: {
+ type: [Number, String],
+ default: ''
+ },
+ // 单元格自定义样式
+ cellStyle: {
+ type: [Object, String],
+ default: () => {}
+ },
+ ...uni.$uv?.props?.cell
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-cell/components/uv-cell/uv-cell.vue b/ledApp/uni_modules/uv-cell/components/uv-cell/uv-cell.vue
new file mode 100644
index 0000000..bdd268e
--- /dev/null
+++ b/ledApp/uni_modules/uv-cell/components/uv-cell/uv-cell.vue
@@ -0,0 +1,209 @@
+<template>
+ <view class="uv-cell" :class="[customClass]" :style="[$uv.addStyle(customStyle)]"
+ :hover-class="(!disabled && (clickable || isLink)) ? 'uv-cell--clickable' : ''" :hover-stay-time="250"
+ @click="clickHandler">
+ <view class="uv-cell__body"
+ :class="[ center && 'uv-cell--center', size === 'large' && 'uv-cell__body--large']"
+ :style="[cellStyle]"
+ >
+ <view class="uv-cell__body__content">
+ <view class="uv-cell__left-icon-wrap">
+ <slot name="icon">
+ <uv-icon v-if="icon" :name="icon" :custom-style="iconStyle" :size="size === 'large' ? 22 : 18"></uv-icon>
+ </slot>
+ </view>
+ <view class="uv-cell__title">
+ <slot name="title">
+ <text v-if="title" class="uv-cell__title-text" :style="[titleTextStyle]"
+ :class="[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__title-text--large']">{{ title }}</text>
+ </slot>
+ <slot name="label">
+ <text class="uv-cell__label" v-if="label"
+ :class="[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__label--large']">{{ label }}</text>
+ </slot>
+ </view>
+ </view>
+ <slot name="value">
+ <text class="uv-cell__value"
+ :class="[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__value--large']"
+ v-if="!$uv.test.empty(value)">{{ value }}</text>
+ </slot>
+ <view class="uv-cell__right-icon-wrap"
+ :class="[`uv-cell__right-icon-wrap--${arrowDirection}`]">
+ <slot name="right-icon">
+ <uv-icon v-if="isLink" :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
+ :size="size === 'large' ? 18 : 16"></uv-icon>
+ </slot>
+ </view>
+ </view>
+ <uv-line v-if="border"></uv-line>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * cell 单元格
+ * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+ * @tutorial https://www.uvui.cn/components/cell.html
+ * @property {String | Number} title 标题
+ * @property {String | Number} label 标题下方的描述信息
+ * @property {String | Number} value 右侧的内容
+ * @property {String} icon 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+ * @property {Boolean} disabled 是否禁用cell
+ * @property {Boolean} border 是否显示下边框 (默认 true )
+ * @property {Boolean} center 内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
+ * @property {String} url 点击后跳转的URL地址
+ * @property {String} linkType 链接跳转的方式,内部使用的是uvui封装的route方法,可能会进行拦截操作 (默认 'navigateTo' )
+ * @property {Boolean} clickable 是否开启点击反馈(表现为点击时加上灰色背景) (默认 false )
+ * @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 (默认 false )
+ * @property {Boolean} required 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false )
+ * @property {String} rightIcon 右侧的图标箭头 (默认 'arrow-right')
+ * @property {String} arrowDirection 右侧箭头的方向,可选值为:left,up,down
+ * @property {Object | String} rightIconStyle 右侧箭头图标的样式
+ * @property {Object | String} titleStyle 标题的样式
+ * @property {Object | String} iconStyle 左侧图标样式
+ * @property {String} size 单位元的大小,可选值为 large,normal,mini
+ * @property {Boolean} stop 点击cell是否阻止事件传播 (默认 true )
+ * @property {Object | String} cellStyle 单元格自定义样式
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} click 点击cell列表时触发
+ * @example 该组件需要搭配cell-group组件使用,见官方文档示例
+ */
+ export default {
+ name: 'uv-cell',
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ titleTextStyle() {
+ return this.$uv.addStyle(this.titleStyle)
+ }
+ },
+ methods: {
+ // 点击cell
+ clickHandler(e) {
+ if (this.disabled) return
+ this.$emit('click', {
+ name: this.name
+ })
+ // 如果配置了url(此props参数通过mixin引入)参数,跳转页面
+ this.openPage()
+ // 是否阻止事件传播
+ this.stop && this.preventEvent(e)
+ },
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-cell-padding: 10px 15px !default;
+ $uv-cell-font-size: 15px !default;
+ $uv-cell-line-height: 24px !default;
+ $uv-cell-color: $uv-main-color !default;
+ $uv-cell-icon-size: 16px !default;
+ $uv-cell-title-font-size: 15px !default;
+ $uv-cell-title-line-height: 22px !default;
+ $uv-cell-title-color: $uv-main-color !default;
+ $uv-cell-label-font-size: 12px !default;
+ $uv-cell-label-color: $uv-tips-color !default;
+ $uv-cell-label-line-height: 18px !default;
+ $uv-cell-value-font-size: 14px !default;
+ $uv-cell-value-color: $uv-content-color !default;
+ $uv-cell-clickable-color: $uv-bg-color !default;
+ $uv-cell-disabled-color: #c8c9cc !default;
+ $uv-cell-padding-top-large: 13px !default;
+ $uv-cell-padding-bottom-large: 13px !default;
+ $uv-cell-value-font-size-large: 15px !default;
+ $uv-cell-label-font-size-large: 14px !default;
+ $uv-cell-title-font-size-large: 16px !default;
+ $uv-cell-left-icon-wrap-margin-right: 4px !default;
+ $uv-cell-right-icon-wrap-margin-left: 4px !default;
+ $uv-cell-title-flex: 1 !default;
+ $uv-cell-label-margin-top: 5px !default;
+ .uv-cell {
+ &__body {
+ @include flex();
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ /* #endif */
+ padding: $uv-cell-padding;
+ font-size: $uv-cell-font-size;
+ color: $uv-cell-color;
+ &__content {
+ @include flex(row);
+ align-items: center;
+ flex: 1;
+ }
+ &--large {
+ padding-top: $uv-cell-padding-top-large;
+ padding-bottom: $uv-cell-padding-bottom-large;
+ }
+ }
+ &__left-icon-wrap,
+ &__right-icon-wrap {
+ @include flex();
+ align-items: center;
+ // height: $uv-cell-line-height;
+ font-size: $uv-cell-icon-size;
+ }
+ &__left-icon-wrap {
+ margin-right: $uv-cell-left-icon-wrap-margin-right;
+ }
+ &__right-icon-wrap {
+ margin-left: $uv-cell-right-icon-wrap-margin-left;
+ transition: transform 0.3s;
+ &--up {
+ transform: rotate(-90deg);
+ }
+ &--down {
+ transform: rotate(90deg);
+ }
+ }
+ &__title {
+ flex: $uv-cell-title-flex;
+ &-text {
+ font-size: $uv-cell-title-font-size;
+ line-height: $uv-cell-title-line-height;
+ color: $uv-cell-title-color;
+ &--large {
+ font-size: $uv-cell-title-font-size-large;
+ }
+ }
+ }
+ &__label {
+ margin-top: $uv-cell-label-margin-top;
+ font-size: $uv-cell-label-font-size;
+ color: $uv-cell-label-color;
+ line-height: $uv-cell-label-line-height;
+ &--large {
+ font-size: $uv-cell-label-font-size-large;
+ }
+ }
+ &__value {
+ text-align: right;
+ font-size: $uv-cell-value-font-size;
+ line-height: $uv-cell-line-height;
+ color: $uv-cell-value-color;
+ &--large {
+ font-size: $uv-cell-value-font-size-large;
+ }
+ }
+ &--clickable {
+ background-color: $uv-cell-clickable-color;
+ }
+ &--disabled {
+ color: $uv-cell-disabled-color;
+ /* #ifndef APP-NVUE */
+ cursor: not-allowed;
+ /* #endif */
+ }
+ &--center {
+ align-items: center;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-cell/package.json b/ledApp/uni_modules/uv-cell/package.json
new file mode 100644
index 0000000..8ff5cf6
--- /dev/null
+++ b/ledApp/uni_modules/uv-cell/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-cell",
+ "displayName": "uv-cell 单元格 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.5",
+ "description": "cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。",
+ "keywords": [
+ "uv-cell",
+ "uvui",
+ "uv-ui",
+ "单元格",
+ "设置页"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-line"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-cell/readme.md b/ledApp/uni_modules/uv-cell/readme.md
new file mode 100644
index 0000000..3ea9937
--- /dev/null
+++ b/ledApp/uni_modules/uv-cell/readme.md
@@ -0,0 +1,11 @@
+## Cell 单元格
+
+> **组件名:uv-cell**
+
+cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+
+### <a href="https://www.uvui.cn/components/cell.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-checkbox/changelog.md b/ledApp/uni_modules/uv-checkbox/changelog.md
new file mode 100644
index 0000000..2a142ce
--- /dev/null
+++ b/ledApp/uni_modules/uv-checkbox/changelog.md
@@ -0,0 +1,34 @@
+## 1.0.14(2023-11-04)
+1. 修复label文字较多不分行的问题
+## 1.0.13(2023-10-11)
+1. 优化同类问题:https://gitee.com/climblee/uv-ui/issues/I872VD
+## 1.0.12(2023-09-22)
+1. 修复change回调中v-model值不更新的BUG
+## 1.0.11(2023-09-01)
+1. 修复点击空隙处无效的问题
+2. label支持插槽下可点击
+## 1.0.10(2023-08-27)
+1. 修复label设置布尔值不生效的BUG
+## 1.0.9(2023-08-16)
+1. 解决数据多不换行的BUG
+## 1.0.8(2023-07-13)
+1. 修复 uv-checkbox设置value属性不生效的BUG
+## 1.0.7(2023-07-05)
+修复vue3模式下,动态修改v-model绑定的值无效的BUG
+## 1.0.6(2023-06-29)
+1. 增加label插槽,与radio保持一致
+2. 优化文档
+## 1.0.5(2023-06-12)
+1. 修复1.0.4改出的问题
+## 1.0.4(2023-06-08)
+1. 复选框修复全局设置不生效的BUG
+## 1.0.3(2023-06-06)
+1. uv-checkbox-group 兼容自定义样式customStyle,方便通过样式调整整体位置等;
+2. .uv-checkbox-group--row增加flex-wrap: wrap;允许换行
+## 1.0.2(2023-05-30)
+1. 修复error报错的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-checkbox 复选框
diff --git a/ledApp/uni_modules/uv-checkbox/components/uv-checkbox-group/props.js b/ledApp/uni_modules/uv-checkbox/components/uv-checkbox-group/props.js
new file mode 100644
index 0000000..47bf005
--- /dev/null
+++ b/ledApp/uni_modules/uv-checkbox/components/uv-checkbox-group/props.js
@@ -0,0 +1,84 @@
+export default {
+ props: {
+ // 绑定的值
+ value: {
+ type: Array,
+ default: () => []
+ },
+ modelValue: {
+ type: Array,
+ default: () => []
+ },
+ // 标识符
+ name: {
+ type: String,
+ default: ''
+ },
+ // 形状,circle-圆形,square-方形
+ shape: {
+ type: String,
+ default: 'square'
+ },
+ // 是否禁用全部checkbox
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+ activeColor: {
+ type: String,
+ default: '#2979ff'
+ },
+ // 未选中的颜色
+ inactiveColor: {
+ type: String,
+ default: '#c8c9cc'
+ },
+ // 整个组件的尺寸,默认px
+ size: {
+ type: [String, Number],
+ default: 18
+ },
+ // 布局方式,row-横向,column-纵向
+ placement: {
+ type: String,
+ default: 'row'
+ },
+ // label的字体大小,px单位
+ labelSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // label的字体颜色
+ labelColor: {
+ type: [String],
+ default: '#303133'
+ },
+ // 是否禁止点击文本操作
+ labelDisabled: {
+ type: Boolean,
+ default: false
+ },
+ // 图标颜色
+ iconColor: {
+ type: String,
+ default: '#fff'
+ },
+ // 图标的大小,单位px
+ iconSize: {
+ type: [String, Number],
+ default: 12
+ },
+ // 勾选图标的对齐方式,left-左边,right-右边
+ iconPlacement: {
+ type: String,
+ default: 'left'
+ },
+ // 竖向配列时,是否显示下划线
+ borderBottom: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.checkboxGroup
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue b/ledApp/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue
new file mode 100644
index 0000000..d5ea81f
--- /dev/null
+++ b/ledApp/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue
@@ -0,0 +1,119 @@
+<template>
+ <view
+ class="uv-checkbox-group"
+ :class="bemClass"
+ :style="[$uv.addStyle(this.customStyle)]"
+ >
+ <slot></slot>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * checkboxGroup 复选框组
+ * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+ * @tutorial https://www.uvui.cn/components/checkbox.html
+ * @property {String} name 标识符
+ * @property {Array} value 绑定的值
+ * @property {String} shape 形状,circle-圆形,square-方形 (默认 'square' )
+ * @property {Boolean} disabled 是否禁用全部checkbox (默认 false )
+ * @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' )
+ * @property {String} inactiveColor 未选中的颜色 (默认 '#c8c9cc' )
+ * @property {String | Number} size 整个组件的尺寸 单位px (默认 18 )
+ * @property {String} placement 布局方式,row-横向,column-纵向 (默认 'row' )
+ * @property {String | Number} labelSize label的字体大小,px单位 (默认 14 )
+ * @property {String} labelColor label的字体颜色 (默认 '#303133' )
+ * @property {Boolean} labelDisabled 是否禁止点击文本操作 (默认 false )
+ * @property {String} iconColor 图标颜色 (默认 '#ffffff' )
+ * @property {String | Number} iconSize 图标的大小,单位px (默认 12 )
+ * @property {String} iconPlacement 勾选图标的对齐方式,left-左边,right-右边 (默认 'left' )
+ * @property {Boolean} borderBottom placement为row时,是否显示下边框 (默认 false )
+ * @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
+ * @event {Function} input 修改通过v-model绑定的值时触发,回调为一个对象
+ * @example <uv-checkbox-group></uv-checkbox-group>
+ */
+ export default {
+ name: 'uv-checkbox-group',
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ // 这里computed的变量,都是子组件uv-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
+ // 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(uv-checkbox-group)
+ // 拉取父组件新的变化后的参数
+ parentData() {
+ let value = [];
+ if(this.value.length) {
+ value = this.value;
+ } else if (this.modelValue.length){
+ value = this.modelValue;
+ }
+ return [value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,
+ this.iconSize, this.borderBottom, this.placement, this.labelSize, this.labelColor]
+ },
+ bemClass() {
+ // this.bem为一个computed变量,在mixin中
+ return this.bem('checkbox-group', ['placement'])
+ },
+ },
+ watch: {
+ // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+ parentData() {
+ if (this.children.length) {
+ this.children.map(child => {
+ // 判断子组件(uv-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+ typeof(child.init) === 'function' && child.init()
+ })
+ }
+ },
+ },
+ data() {
+ return {
+
+ }
+ },
+ created() {
+ this.children = []
+ },
+ methods: {
+ // 将其他的checkbox设置为未选中的状态
+ unCheckedOther(childInstance) {
+ const values = []
+ this.children.map(child => {
+ // 将被选中的checkbox,放到数组中返回
+ if (child.isChecked) {
+ values.push(child.name)
+ }
+ })
+ // 修改通过v-model绑定的值
+ // #ifdef VUE2
+ this.$emit('input', values)
+ // #endif
+ // #ifdef VUE3
+ this.$emit('update:modelValue',values)
+ // #endif
+ // 发出事件
+ this.$emit('change', values)
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+
+ .uv-checkbox-group {
+ flex: 1;
+
+ &--row {
+ @include flex;
+ flex-wrap: wrap;
+ }
+
+ &--column {
+ @include flex(column);
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-checkbox/components/uv-checkbox/props.js b/ledApp/uni_modules/uv-checkbox/components/uv-checkbox/props.js
new file mode 100644
index 0000000..58ec890
--- /dev/null
+++ b/ledApp/uni_modules/uv-checkbox/components/uv-checkbox/props.js
@@ -0,0 +1,70 @@
+export default {
+ props: {
+ // checkbox的名称
+ name: {
+ type: [String, Number, Boolean],
+ default: ''
+ },
+ // 形状,square为方形,circle为圆型
+ shape: {
+ type: String,
+ default: ''
+ },
+ // 整体的大小
+ size: {
+ type: [String, Number],
+ default: ''
+ },
+ // 是否默认选中
+ checked: {
+ type: Boolean,
+ default: false
+ },
+ // 是否禁用
+ disabled: {
+ type: [String, Boolean],
+ default: ''
+ },
+ // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+ activeColor: {
+ type: String,
+ default: ''
+ },
+ // 未选中的颜色
+ inactiveColor: {
+ type: String,
+ default: ''
+ },
+ // 图标的大小,单位px
+ iconSize: {
+ type: [String, Number],
+ default: ''
+ },
+ // 图标颜色
+ iconColor: {
+ type: String,
+ default: ''
+ },
+ // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+ label: {
+ type: [String, Number, Boolean],
+ default: ''
+ },
+ // label的字体大小,px单位
+ labelSize: {
+ type: [String, Number],
+ default: ''
+ },
+ // label的颜色
+ labelColor: {
+ type: String,
+ default: ''
+ },
+ // 是否禁止点击提示语选中复选框
+ labelDisabled: {
+ type: [String, Boolean],
+ default: ''
+ },
+ ...uni.$uv?.props?.checkbox
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue b/ledApp/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue
new file mode 100644
index 0000000..dfd1912
--- /dev/null
+++ b/ledApp/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue
@@ -0,0 +1,370 @@
+<template>
+ <view
+ class="uv-checkbox"
+ :style="[checkboxStyle]"
+ @tap.stop="wrapperClickHandler"
+ :class="[`uv-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'uv-border-bottom']"
+ >
+ <view
+ class="uv-checkbox__icon-wrap"
+ @tap.stop="iconClickHandler"
+ :class="iconClasses"
+ :style="[iconWrapStyle]"
+ >
+ <slot name="icon">
+ <uv-icon
+ class="uv-checkbox__icon-wrap__icon"
+ name="checkbox-mark"
+ :size="elIconSize"
+ :color="elIconColor"
+ />
+ </slot>
+ </view>
+ <view
+ class="uv-checkbox__label-wrap"
+ @tap.stop="labelClickHandler">
+ <slot>
+ <text
+ :style="{
+ color: elDisabled ? elInactiveColor : elLabelColor,
+ fontSize: elLabelSize,
+ lineHeight: elLabelSize
+ }"
+ >{{label}}</text>
+ </slot>
+ </view>
+
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * checkbox 复选框
+ * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+ * @tutorial https://www.uvui.cn/components/checkbox.html
+ * @property {String | Number | Boolean} name checkbox组件的标示符
+ * @property {String} shape 形状,square为方形,circle为圆型
+ * @property {String | Number} size 整体的大小
+ * @property {Boolean} checked 是否默认选中
+ * @property {String | Boolean} disabled 是否禁用
+ * @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+ * @property {String} inactiveColor 未选中的颜色
+ * @property {String | Number} iconSize 图标的大小,单位px
+ * @property {String} iconColor 图标颜色
+ * @property {String | Number} label label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+ * @property {String} labelColor label的颜色
+ * @property {String | Number} labelSize label的字体大小,px单位
+ * @property {String | Boolean} labelDisabled 是否禁止点击提示语选中复选框
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
+ * @example <uv-checkbox v-model="checked" :disabled="false">天涯</uv-checkbox>
+ */
+ export default {
+ name: "uv-checkbox",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ isChecked: false,
+ // 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式
+ // 故只能使用如此方法
+ parentData: {
+ iconSize: 12,
+ labelDisabled: null,
+ disabled: null,
+ shape: 'square',
+ activeColor: null,
+ inactiveColor: null,
+ size: 18,
+ value: null,
+ modelValue: null,
+ iconColor: null,
+ placement: 'row',
+ borderBottom: false,
+ iconPlacement: 'left',
+ labelSize: 14,
+ labelColor: '#303133'
+ }
+ }
+ },
+ computed: {
+ // 是否禁用,如果父组件uv-raios-group禁用的话,将会忽略子组件的配置
+ elDisabled() {
+ return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
+ },
+ // 是否禁用label点击
+ elLabelDisabled() {
+ return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :
+ false;
+ },
+ // 组件尺寸,对应size的值,默认值为21px
+ elSize() {
+ return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);
+ },
+ // 组件的勾选图标的尺寸,默认12px
+ elIconSize() {
+ return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);
+ },
+ // 组件选中激活时的颜色
+ elActiveColor() {
+ return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
+ },
+ // 组件选未中激活时的颜色
+ elInactiveColor() {
+ return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
+ '#c8c9cc');
+ },
+ // label的颜色
+ elLabelColor() {
+ return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
+ },
+ // 组件的形状
+ elShape() {
+ return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
+ },
+ // label大小
+ elLabelSize() {
+ return this.$uv.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :
+ '15'))
+ },
+ elIconColor() {
+ const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
+ '#ffffff');
+ // 图标的颜色
+ if (this.elDisabled) {
+ // disabled状态下,已勾选的checkbox图标改为elInactiveColor
+ return this.isChecked ? this.elInactiveColor : 'transparent'
+ } else {
+ return this.isChecked ? iconColor : 'transparent'
+ }
+ },
+ iconClasses() {
+ let classes = []
+ // 组件的形状
+ classes.push('uv-checkbox__icon-wrap--' + this.elShape)
+ if (this.elDisabled) {
+ classes.push('uv-checkbox__icon-wrap--disabled')
+ }
+ if (this.isChecked && this.elDisabled) {
+ classes.push('uv-checkbox__icon-wrap--disabled--checked')
+ }
+ // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+ // #ifdef MP-ALIPAY || MP-TOUTIAO
+ classes = classes.join(' ')
+ // #endif
+ return classes
+ },
+ iconWrapStyle() {
+ // checkbox的整体样式
+ const style = {}
+ style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'
+ style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
+ style.width = this.$uv.addUnit(this.elSize)
+ style.height = this.$uv.addUnit(this.elSize)
+ // 如果是图标在右边的话,移除它的右边距
+ if (this.parentData.iconPlacement === 'right') {
+ style.marginRight = 0
+ }
+ return style
+ },
+ checkboxStyle() {
+ const style = {}
+ if (this.parentData.borderBottom && this.parentData.placement === 'row') {
+ this.$uv.error('检测到您将borderBottom设置为true,需要同时将uv-checkbox-group的placement设置为column才有效')
+ }
+ // 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
+ if (this.parentData.borderBottom && this.parentData.placement === 'column') {
+ style.paddingBottom = '8px'
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ created() {
+ this.init()
+ },
+ methods: {
+ init() {
+ // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+ this.updateParentData()
+ if (!this.parent) {
+ this.$uv.error('uv-checkbox必须搭配uv-checkbox-group组件使用')
+ }
+ this.$nextTick(()=>{
+ let parentValue = [];
+ if(this.parentData.value.length) {
+ parentValue = this.parentData.value;
+ } else if (this.parentData.modelValue.length){
+ parentValue = this.parentData.modelValue;
+ }
+ // 设置初始化时,是否默认选中的状态,父组件uv-checkbox-group的value可能是array,所以额外判断
+ if (this.checked) {
+ this.isChecked = true
+ } else if (this.$uv.test.array(parentValue)) {
+ // 查找数组是是否存在this.name元素值
+ this.isChecked = parentValue.some(item => {
+ return item === this.name
+ })
+ }
+ })
+ },
+ updateParentData() {
+ this.getParentData('uv-checkbox-group')
+ },
+ // 横向两端排列时,点击组件即可触发选中事件
+ wrapperClickHandler(e) {
+ this.parentData.iconPlacement === 'right' && this.iconClickHandler(e)
+ },
+ // 点击图标
+ iconClickHandler(e) {
+ this.preventEvent(e)
+ // 如果整体被禁用,不允许被点击
+ if (!this.elDisabled) {
+ this.setRadioCheckedStatus()
+ }
+ },
+ // 点击label
+ labelClickHandler(e) {
+ this.preventEvent(e)
+ // 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态
+ if (!this.elLabelDisabled && !this.elDisabled) {
+ this.setRadioCheckedStatus()
+ }
+ },
+ emitEvent() {
+ this.$emit('change', this.isChecked)
+ // 尝试调用uv-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时
+ this.$nextTick(() => {
+ this.$uv.formValidate(this, 'change')
+ })
+ },
+ // 改变组件选中状态
+ // 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有uv-checkbox实例
+ // 将本组件外的其他uv-checkbox的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态
+ setRadioCheckedStatus() {
+ // 将本组件标记为与原来相反的状态
+ this.isChecked = !this.isChecked
+ this.emitEvent()
+ typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)
+ }
+ },
+ watch:{
+ checked(){
+ this.isChecked = this.checked
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-border: 1;
+ $show-border-bottom: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-checkbox-label-wrap-padding-right:6px !default;
+ $uv-checkbox-icon-wrap-font-size:6px !default;
+ $uv-checkbox-icon-wrap-border-width:1px !default;
+ $uv-checkbox-icon-wrap-border-color:#c8c9cc !default;
+ $uv-checkbox-icon-wrap-icon-line-height:0 !default;
+ $uv-checkbox-icon-wrap-circle-border-radius:100% !default;
+ $uv-checkbox-icon-wrap-square-border-radius:3px !default;
+ $uv-checkbox-icon-wrap-checked-color:#fff !default;
+ $uv-checkbox-icon-wrap-checked-background-color:red !default;
+ $uv-checkbox-icon-wrap-checked-border-color:#2979ff !default;
+ $uv-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;
+ $uv-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;
+ $uv-checkbox-label-margin-left:5px !default;
+ $uv-checkbox-label-margin-right:12px !default;
+ $uv-checkbox-label-color:$uv-content-color !default;
+ $uv-checkbox-label-font-size:15px !default;
+ $uv-checkbox-label-disabled-color:#c8c9cc !default;
+
+ .uv-checkbox {
+ /* #ifndef APP-NVUE */
+ @include flex(row);
+ /* #endif */
+ overflow: hidden;
+ flex-direction: row;
+ align-items: center;
+
+ &-label--left {
+ flex-direction: row
+ }
+
+ &-label--right {
+ flex-direction: row-reverse;
+ justify-content: space-between
+ }
+
+ &__icon-wrap {
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ // nvue下,border-color过渡有问题
+ transition-property: border-color, background-color, color;
+ transition-duration: 0.2s;
+ /* #endif */
+ color: $uv-content-color;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ color: transparent;
+ text-align: center;
+ font-size: $uv-checkbox-icon-wrap-font-size;
+ border-width: $uv-checkbox-icon-wrap-border-width;
+ border-color: $uv-checkbox-icon-wrap-border-color;
+ border-style: solid;
+
+ /* #ifdef MP-TOUTIAO */
+ // 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
+ &__icon {
+ line-height: $uv-checkbox-icon-wrap-icon-line-height;
+ }
+
+ /* #endif */
+
+ &--circle {
+ border-radius: $uv-checkbox-icon-wrap-circle-border-radius;
+ }
+
+ &--square {
+ border-radius: $uv-checkbox-icon-wrap-square-border-radius;
+ }
+
+ &--checked {
+ color: $uv-checkbox-icon-wrap-checked-color;
+ background-color: $uv-checkbox-icon-wrap-checked-background-color;
+ border-color: $uv-checkbox-icon-wrap-checked-border-color;
+ }
+
+ &--disabled {
+ background-color: $uv-checkbox-icon-wrap-disabled-background-color !important;
+ }
+
+ &--disabled--checked {
+ color: $uv-checkbox-icon-wrap-disabled-checked-color !important;
+ }
+ }
+
+ &__label {
+ /* #ifndef APP-NVUE */
+ word-wrap: break-word;
+ /* #endif */
+ margin-left: $uv-checkbox-label-margin-left;
+ margin-right: $uv-checkbox-label-margin-right;
+ color: $uv-checkbox-label-color;
+ font-size: $uv-checkbox-label-font-size;
+
+ &--disabled {
+ color: $uv-checkbox-label-disabled-color;
+ }
+ }
+
+ &__label-wrap {
+ flex: 1;
+ padding-left: $uv-checkbox-label-wrap-padding-right;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-checkbox/package.json b/ledApp/uni_modules/uv-checkbox/package.json
new file mode 100644
index 0000000..44944d9
--- /dev/null
+++ b/ledApp/uni_modules/uv-checkbox/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-checkbox",
+ "displayName": "uv-checkbox 复选框 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.14",
+ "description": "复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便。",
+ "keywords": [
+ "uv-checkbox",
+ "uvui",
+ "uv-ui",
+ "checkbox",
+ "复选框"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-checkbox/readme.md b/ledApp/uni_modules/uv-checkbox/readme.md
new file mode 100644
index 0000000..43b6f2e
--- /dev/null
+++ b/ledApp/uni_modules/uv-checkbox/readme.md
@@ -0,0 +1,19 @@
+## Checkbox 复选框
+
+> **组件名:uv-checkbox**
+
+复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便。可配合 `uv-form` 组件进行表单验证等场景使用。
+
+# <a href="https://www.uvui.cn/components/checkbox.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-code-input/changelog.md b/ledApp/uni_modules/uv-code-input/changelog.md
new file mode 100644
index 0000000..1fefc03
--- /dev/null
+++ b/ledApp/uni_modules/uv-code-input/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.5(2023-08-05)
+在vue2模式下,v-model设置为0时不生效的BUG
+## 1.0.4(2023-07-13)
+1. 修复value/v-model更改不生效的BUG
+## 1.0.3(2023-06-28)
+修复:使用:disabledKeyboard="true"属性,事件全部失效的BUG
+## 1.0.2(2023-06-23)
+优化下边框
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-code-input 验证码输入
diff --git a/ledApp/uni_modules/uv-code-input/components/uv-code-input/props.js b/ledApp/uni_modules/uv-code-input/components/uv-code-input/props.js
new file mode 100644
index 0000000..4a5e460
--- /dev/null
+++ b/ledApp/uni_modules/uv-code-input/components/uv-code-input/props.js
@@ -0,0 +1,83 @@
+export default {
+ props: {
+ value: {
+ type: [String, Number],
+ default: ''
+ },
+ modelValue: {
+ type: [String, Number],
+ default: ''
+ },
+ // 键盘弹起时,是否自动上推页面
+ adjustPosition: {
+ type: Boolean,
+ default: true
+ },
+ // 最大输入长度
+ maxlength: {
+ type: [String, Number],
+ default: 6
+ },
+ // 是否用圆点填充
+ dot: {
+ type: Boolean,
+ default: false
+ },
+ // 显示模式,box-盒子模式,line-底部横线模式
+ mode: {
+ type: String,
+ default: 'box'
+ },
+ // 是否细边框
+ hairline: {
+ type: Boolean,
+ default: false
+ },
+ // 字符间的距离
+ space: {
+ type: [String, Number],
+ default: 10
+ },
+ // 是否自动获取焦点
+ focus: {
+ type: Boolean,
+ default: false
+ },
+ // 字体是否加粗
+ bold: {
+ type: Boolean,
+ default: false
+ },
+ // 字体颜色
+ color: {
+ type: String,
+ default: '#606266'
+ },
+ // 字体大小
+ fontSize: {
+ type: [String, Number],
+ default: 18
+ },
+ // 输入框的大小,宽等于高
+ size: {
+ type: [String, Number],
+ default: 35
+ },
+ // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
+ disabledKeyboard: {
+ type: Boolean,
+ default: false
+ },
+ // 边框和线条颜色
+ borderColor: {
+ type: String,
+ default: '#c9cacc'
+ },
+ // 是否禁止输入"."符号
+ disabledDot: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.codeInput
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-code-input/components/uv-code-input/uv-code-input.vue b/ledApp/uni_modules/uv-code-input/components/uv-code-input/uv-code-input.vue
new file mode 100644
index 0000000..14ad8a2
--- /dev/null
+++ b/ledApp/uni_modules/uv-code-input/components/uv-code-input/uv-code-input.vue
@@ -0,0 +1,272 @@
+<template>
+ <view class="uv-code-input">
+ <view
+ class="uv-code-input__item"
+ :style="[itemStyle(index)]"
+ v-for="(item, index) in codeLength"
+ :key="index"
+ >
+ <view
+ class="uv-code-input__item__dot"
+ v-if="dot && codeArray.length > index"
+ ></view>
+ <text
+ v-else
+ :style="{
+ fontSize: $uv.addUnit(fontSize),
+ fontWeight: bold ? 'bold' : 'normal',
+ color: color
+ }"
+ >{{codeArray[index]}}</text>
+ <view
+ class="uv-code-input__item__line"
+ v-if="mode === 'line'"
+ :style="[lineStyle]"
+ ></view>
+ <!-- #ifndef APP-PLUS -->
+ <view v-if="isFocus && codeArray.length === index" :style="{backgroundColor: color}" class="uv-code-input__item__cursor"></view>
+ <!-- #endif -->
+ </view>
+ <input
+ :disabled="disabledKeyboard"
+ type="number"
+ :focus="focus"
+ :value="inputValue"
+ :maxlength="maxlength"
+ :adjustPosition="adjustPosition"
+ class="uv-code-input__input"
+ @input="inputHandler"
+ :style="{
+ height: $uv.addUnit(size)
+ }"
+ @focus="isFocus = true"
+ @blur="isFocus = false"
+ />
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * CodeInput 验证码输入
+ * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uvui的键盘组件使用
+ * @tutorial https://www.uvui.cn/components/codeInput.html
+ * @property {String | Number} value / v-model 预置值
+ * @property {String | Number} maxlength 最大输入长度 (默认 6 )
+ * @property {Boolean} dot 是否用圆点填充 (默认 false )
+ * @property {String} mode 显示模式,box-盒子模式,line-底部横线模式 (默认 'box' )
+ * @property {Boolean} hairline 是否细边框 (默认 false )
+ * @property {String | Number} space 字符间的距离 (默认 10 )
+ * @property {Boolean} focus 是否自动获取焦点 (默认 false )
+ * @property {Boolean} bold 字体和输入横线是否加粗 (默认 false )
+ * @property {String} color 字体颜色 (默认 '#606266' )
+ * @property {String | Number} fontSize 字体大小,单位px (默认 18 )
+ * @property {String | Number} size 输入框的大小,宽等于高 (默认 35 )
+ * @property {Boolean} disabledKeyboard 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false )
+ * @property {String} borderColor 边框和线条颜色 (默认 '#c9cacc' )
+ * @property {Boolean} disabledDot 是否禁止输入"."符号 (默认 true )
+ *
+ * @event {Function} change 输入内容发生改变时触发,具体见上方说明 value:当前输入的值
+ * @event {Function} finish 输入字符个数达maxlength值时触发,见上方说明 value:当前输入的值
+ * @example <uv-code-input v-model="value4" :focus="true"></uv-code-input>
+ */
+ export default {
+ name: 'uv-code-input',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ inputValue: '',
+ isFocus: this.focus
+ }
+ },
+ created() {
+ const value = String(this.value) || String(this.modelValue);
+ this.inputValue = String(value).substring(0, this.maxlength);
+ },
+ watch: {
+ value(newVal) {
+ // 转为字符串,超出部分截掉
+ this.inputValue = String(newVal).substring(0, this.maxlength);
+ if (this.disabledKeyboard) {
+ this.customInput();
+ }
+ },
+ modelValue(newVal) {
+ // 转为字符串,超出部分截掉
+ this.inputValue = String(newVal).substring(0, this.maxlength);
+ if (this.disabledKeyboard) {
+ this.customInput();
+ }
+ }
+ },
+ computed: {
+ // 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
+ codeLength() {
+ return new Array(Number(this.maxlength))
+ },
+ // 循环item的样式
+ itemStyle() {
+ return index => {
+ const addUnit = this.$uv.addUnit
+ const style = {
+ width: addUnit(this.size),
+ height: addUnit(this.size)
+ }
+ // 盒子模式下,需要额外进行处理
+ if (this.mode === 'box') {
+ // 设置盒子的边框,如果是细边框,则设置为0.5px宽度
+ style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
+ // 如果盒子间距为0的话
+ if (this.$uv.getPx(this.space) === 0) {
+ // 给第一和最后一个盒子设置圆角
+ if (index === 0) {
+ style.borderTopLeftRadius = '3px'
+ style.borderBottomLeftRadius = '3px'
+ }
+ if (index === this.codeLength.length - 1) {
+ style.borderTopRightRadius = '3px'
+ style.borderBottomRightRadius = '3px'
+ }
+ // 最后一个盒子的右边框需要保留
+ if (index !== this.codeLength.length - 1) {
+ style.borderRight = 'none'
+ }
+ }
+ }
+ if (index !== this.codeLength.length - 1) {
+ // 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框
+ style.marginRight = addUnit(this.space)
+ } else {
+ // 最后一个盒子的有边框需要保留
+ style.marginRight = 0
+ }
+ return style
+ }
+ },
+ // 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素
+ codeArray() {
+ return String(this.inputValue).split('')
+ },
+ // 下划线模式下,横线的样式
+ lineStyle() {
+ const style = {}
+ style.height = this.hairline ? '1px' : '4px'
+ style.width = this.$uv.addUnit(this.size)
+ // 线条模式下,背景色即为边框颜色
+ style.backgroundColor = this.borderColor
+ return style
+ }
+ },
+ methods: {
+ // 监听输入框的值发生变化
+ inputHandler(e) {
+ const value = e.detail.value
+ this.inputValue = value
+ // 是否允许输入“.”符号
+ if (this.disabledDot) {
+ this.$nextTick(() => {
+ this.inputValue = value.replace('.', '')
+ })
+ }
+ // 未达到maxlength之前,发送change事件,达到后发送finish事件
+ this.$emit('change', value)
+ // 修改通过v-model双向绑定的值
+ this.$emit('input', value)
+ this.$emit('update:modelValue', value)
+ // 达到用户指定输入长度时,发出完成事件
+ if (String(value).length >= Number(this.maxlength)) {
+ this.$emit('finish', value)
+ }
+ },
+ // 自定义键盘输入值监听
+ customInput() {
+ const value = this.inputValue;
+ // 是否允许输入“.”符号
+ if (this.disabledDot) {
+ this.$nextTick(() => {
+ this.inputValue = value.replace('.', '')
+ })
+ }
+ // 未达到maxlength之前,发送change事件,达到后发送finish事件
+ this.$emit('change', value)
+ // 达到用户指定输入长度时,发出完成事件
+ if (String(value).length >= Number(this.maxlength)) {
+ this.$emit('finish', value)
+ }
+ }
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-code-input-cursor-width: 1px;
+ $uv-code-input-cursor-height: 40%;
+ $uv-code-input-cursor-animation-duration: 1s;
+ $uv-code-input-cursor-animation-name: uv-cursor-flicker;
+ .uv-code-input {
+ @include flex;
+ position: relative;
+ overflow: hidden;
+ &__item {
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ &__text {
+ font-size: 15px;
+ color: $uv-content-color;
+ }
+ &__dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 100px;
+ background-color: $uv-content-color;
+ }
+ &__line {
+ position: absolute;
+ bottom: 0;
+ height: 4px;
+ border-radius: 100px;
+ width: 40px;
+ background-color: $uv-content-color;
+ }
+ /* #ifndef APP-PLUS */
+ &__cursor {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: $uv-code-input-cursor-width;
+ height: $uv-code-input-cursor-height;
+ animation: $uv-code-input-cursor-animation-duration uv-cursor-flicker infinite;
+ }
+ /* #endif */
+ }
+ &__input {
+ // 之所以需要input输入框,是因为有它才能唤起键盘
+ // 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容
+ position: absolute;
+ left: -750rpx;
+ width: 1500rpx;
+ top: 0;
+ background-color: transparent;
+ text-align: left;
+ }
+ }
+ /* #ifndef APP-PLUS */
+ @keyframes uv-cursor-flicker {
+ 0% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+ }
+ /* #endif */
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-code-input/package.json b/ledApp/uni_modules/uv-code-input/package.json
new file mode 100644
index 0000000..ab8762c
--- /dev/null
+++ b/ledApp/uni_modules/uv-code-input/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-code-input",
+ "displayName": "uv-code-input 验证码输入 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.5",
+ "description": "验证码输入组件一般用于验证用户短信验证码的场景,输入框或横线多种模式可选。",
+ "keywords": [
+ "uv-code-input",
+ "uvui",
+ "uv-ui",
+ "code",
+ "验证码输入"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-code-input/readme.md b/ledApp/uni_modules/uv-code-input/readme.md
new file mode 100644
index 0000000..aa5b0a1
--- /dev/null
+++ b/ledApp/uni_modules/uv-code-input/readme.md
@@ -0,0 +1,19 @@
+## CodeInput 验证码输入框
+
+> **组件名:uv-code-input**
+
+该组件一般用于验证用户短信验证码的场景,输入框或横线多种模式可选。
+
+# <a href="https://www.uvui.cn/components/codeInput.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-code/changelog.md b/ledApp/uni_modules/uv-code/changelog.md
new file mode 100644
index 0000000..7054a25
--- /dev/null
+++ b/ledApp/uni_modules/uv-code/changelog.md
@@ -0,0 +1,9 @@
+## 1.0.3(2023-10-13)
+1. 优化
+## 1.0.2(2023-10-13)
+1. unmounted兼容vue3
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-code 验证码倒计时
diff --git a/ledApp/uni_modules/uv-code/components/uv-code/props.js b/ledApp/uni_modules/uv-code/components/uv-code/props.js
new file mode 100644
index 0000000..afc9d3c
--- /dev/null
+++ b/ledApp/uni_modules/uv-code/components/uv-code/props.js
@@ -0,0 +1,35 @@
+export default {
+ props: {
+ // 倒计时总秒数
+ seconds: {
+ type: [String, Number],
+ default: 60
+ },
+ // 尚未开始时提示
+ startText: {
+ type: String,
+ default: '获取验证码'
+ },
+ // 正在倒计时中的提示
+ changeText: {
+ type: String,
+ default: 'X秒重新获取'
+ },
+ // 倒计时结束时的提示
+ endText: {
+ type: String,
+ default: '重新获取'
+ },
+ // 是否在H5刷新或各端返回再进入时继续倒计时
+ keepRunning: {
+ type: Boolean,
+ default: false
+ },
+ // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
+ uniqueKey: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.code
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-code/components/uv-code/uv-code.vue b/ledApp/uni_modules/uv-code/components/uv-code/uv-code.vue
new file mode 100644
index 0000000..de02c83
--- /dev/null
+++ b/ledApp/uni_modules/uv-code/components/uv-code/uv-code.vue
@@ -0,0 +1,136 @@
+<template>
+ <view class="uv-code">
+ <!-- 此组件功能由js完成,无需写html逻辑 -->
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Code 验证码倒计时
+ * @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件不提供界面显示,只提供倒计时文本,由用户将文本嵌入到具体的场景
+ * @tutorial https://www.uvui.cn/components/code.html
+ * @property {String | Number} seconds 倒计时所需的秒数(默认 60 )
+ * @property {String} startText 开始前的提示语,见官网说明(默认 '获取验证码' )
+ * @property {String} changeText 倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' )
+ * @property {String} endText 倒计结束的提示语,见官网说明(默认 '重新获取' )
+ * @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时( 默认false )
+ * @property {String} uniqueKey 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
+ *
+ * @event {Function} change 倒计时期间,每秒触发一次
+ * @event {Function} start 开始倒计时触发
+ * @event {Function} end 结束倒计时触发
+ * @example <uv-code ref="uCode" @change="codeChange" seconds="20"></uv-code>
+ */
+ export default {
+ name: "uv-code",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ secNum: this.seconds,
+ timer: null,
+ canGetCode: true, // 是否可以执行验证码操作
+ }
+ },
+ mounted() {
+ this.checkKeepRunning()
+ },
+ watch: {
+ seconds: {
+ immediate: true,
+ handler(n) {
+ this.secNum = n
+ }
+ }
+ },
+ methods: {
+ checkKeepRunning() {
+ // 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空
+ let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))
+ if (!lastTimestamp) return this.changeEvent(this.startText)
+ // 当前秒的时间戳
+ let nowTimestamp = Math.floor((+new Date()) / 1000)
+ // 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳
+ if (this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
+ // 剩余尚未执行完的倒计秒数
+ this.secNum = lastTimestamp - nowTimestamp
+ // 清除本地保存的变量
+ uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')
+ // 开始倒计时
+ this.start()
+ } else {
+ // 如果不存在需要继续上一次的倒计时,执行正常的逻辑
+ this.changeEvent(this.startText)
+ }
+ },
+ // 开始倒计时
+ start() {
+ // 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱
+ if (this.timer) {
+ clearInterval(this.timer)
+ this.timer = null
+ }
+ this.$emit('start')
+ this.canGetCode = false
+ // 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
+ this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
+ this.timer = setInterval(() => {
+ if (--this.secNum) {
+ // 用当前倒计时的秒数替换提示字符串中的"x"字母
+ this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
+ } else {
+ clearInterval(this.timer)
+ this.timer = null
+ this.changeEvent(this.endText)
+ this.secNum = this.seconds
+ this.$emit('end')
+ this.canGetCode = true
+ }
+ }, 1000)
+ this.setTimeToStorage()
+ },
+ // 重置,可以让用户再次获取验证码
+ reset() {
+ this.canGetCode = true
+ clearInterval(this.timer)
+ this.secNum = this.seconds
+ this.changeEvent(this.endText)
+ },
+ changeEvent(text) {
+ this.$emit('change', text)
+ },
+ // 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
+ setTimeToStorage() {
+ if (!this.keepRunning || !this.timer) return
+ // 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
+ // 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
+ if (this.secNum > 0 && this.secNum <= this.seconds) {
+ // 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分
+ let nowTimestamp = Math.floor((+new Date()) / 1000)
+ // 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数
+ uni.setStorage({
+ key: this.uniqueKey + '_$uCountDownTimestamp',
+ data: nowTimestamp + Number(this.secNum)
+ })
+ }
+ }
+ },
+ // #ifdef VUE2
+ // 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除
+ beforeDestroy() {
+ this.setTimeToStorage()
+ clearTimeout(this.timer)
+ this.timer = null
+ },
+ // #endif
+ // #ifdef VUE3
+ // 组件销毁,兼容vue3
+ unmounted() {
+ this.setTimeToStorage()
+ clearTimeout(this.timer)
+ this.timer = null
+ }
+ // #endif
+ }
+</script>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-code/package.json b/ledApp/uni_modules/uv-code/package.json
new file mode 100644
index 0000000..d4392c0
--- /dev/null
+++ b/ledApp/uni_modules/uv-code/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-code",
+ "displayName": "uv-code 验证码倒计时 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.3",
+ "description": "考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件不提供界面显示,只提供倒计时文本,由用户将文本嵌入到具体的场景。",
+ "keywords": [
+ "uv-code",
+ "uvui",
+ "uv-ui",
+ "code",
+ "验证码倒计时"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-code/readme.md b/ledApp/uni_modules/uv-code/readme.md
new file mode 100644
index 0000000..cf2eb51
--- /dev/null
+++ b/ledApp/uni_modules/uv-code/readme.md
@@ -0,0 +1,11 @@
+## Code 验证码输入框
+
+> **组件名:uv-code**
+
+考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件不提供界面显示,只提供倒计时文本,由用户将文本嵌入到具体的场景。
+
+### <a href="https://www.uvui.cn/components/code.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-collapse/changelog.md b/ledApp/uni_modules/uv-collapse/changelog.md
new file mode 100644
index 0000000..0219560
--- /dev/null
+++ b/ledApp/uni_modules/uv-collapse/changelog.md
@@ -0,0 +1,5 @@
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-collapse 折叠面板
diff --git a/ledApp/uni_modules/uv-collapse/components/uv-collapse-item/props.js b/ledApp/uni_modules/uv-collapse/components/uv-collapse-item/props.js
new file mode 100644
index 0000000..18983c1
--- /dev/null
+++ b/ledApp/uni_modules/uv-collapse/components/uv-collapse-item/props.js
@@ -0,0 +1,60 @@
+export default {
+ props: {
+ // 标题
+ title: {
+ type: String,
+ default: ''
+ },
+ // 标题右侧内容
+ value: {
+ type: String,
+ default: ''
+ },
+ // 标题下方的描述信息
+ label: {
+ type: String,
+ default: ''
+ },
+ // 是否禁用折叠面板
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否展示右侧箭头并开启点击反馈
+ isLink: {
+ type: Boolean,
+ default: true
+ },
+ // 是否开启点击反馈
+ clickable: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示内边框
+ border: {
+ type: Boolean,
+ default: true
+ },
+ // 标题的对齐方式
+ align: {
+ type: String,
+ default: 'left'
+ },
+ // 唯一标识符
+ name: {
+ type: [String, Number],
+ default: ''
+ },
+ // 标题左侧图片,可为绝对路径的图片或内置图标
+ icon: {
+ type: String,
+ default: ''
+ },
+ // 面板展开收起的过渡时间,单位ms
+ duration: {
+ type: Number,
+ default: 300
+ },
+ ...uni.$uv?.props?.collapseItem
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-collapse/components/uv-collapse-item/uv-collapse-item.vue b/ledApp/uni_modules/uv-collapse/components/uv-collapse-item/uv-collapse-item.vue
new file mode 100644
index 0000000..840f28d
--- /dev/null
+++ b/ledApp/uni_modules/uv-collapse/components/uv-collapse-item/uv-collapse-item.vue
@@ -0,0 +1,229 @@
+<template>
+ <view class="uv-collapse-item">
+ <uv-cell
+ :title="title"
+ :value="value"
+ :label="label"
+ :icon="icon"
+ :isLink="isLink"
+ :clickable="clickable"
+ :border="parentData.border && showBorder"
+ @click="clickHandler"
+ :arrowDirection="expanded ? 'up' : 'down'"
+ :disabled="disabled"
+ >
+ <!-- #ifndef MP-WEIXIN -->
+ <!-- 微信小程序不支持,因为微信中不支持 <slot name="title" slot="title" />的写法 -->
+ <template slot="title">
+ <slot name="title"></slot>
+ </template>
+ <template slot="icon">
+ <slot name="icon"></slot>
+ </template>
+ <template slot="value">
+ <slot name="value"></slot>
+ </template>
+ <template slot="right-icon">
+ <slot name="right-icon"></slot>
+ </template>
+ <!-- #endif -->
+ </uv-cell>
+ <view
+ class="uv-collapse-item__content"
+ :animation="animationData"
+ ref="animation"
+ >
+ <view
+ class="uv-collapse-item__content__text content-class"
+ :id="elId"
+ :ref="elId"
+ ><slot /></view>
+ </view>
+ <uv-line v-if="parentData.border"></uv-line>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const animation = uni.requireNativePlugin('animation')
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * collapseItem 折叠面板Item
+ * @description 通过折叠面板收纳内容区域(搭配uv-collapse使用)
+ * @tutorial https://www.uvui.cn/components/collapse.html
+ * @property {String} title 标题
+ * @property {String} value 标题右侧内容
+ * @property {String} label 标题下方的描述信息
+ * @property {Boolean} disbled 是否禁用折叠面板 ( 默认 false )
+ * @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 ( 默认 true )
+ * @property {Boolean} clickable 是否开启点击反馈 ( 默认 true )
+ * @property {Boolean} border 是否显示内边框 ( 默认 true )
+ * @property {String} align 标题的对齐方式 ( 默认 'left' )
+ * @property {String | Number} name 唯一标识符
+ * @property {String} icon 标题左侧图片,可为绝对路径的图片或内置图标
+ * @event {Function} change 某个item被打开或者收起时触发
+ * @example <uv-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</uv-collapse-item>
+ */
+ export default {
+ name: "uv-collapse-item",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ elId: '',
+ // uni.createAnimation的导出数据
+ animationData: {},
+ // 是否展开状态
+ expanded: false,
+ // 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时
+ showBorder: false,
+ // 是否动画中,如果是则不允许继续触发点击
+ animating: false,
+ // 父组件uv-collapse的参数
+ parentData: {
+ accordion: false,
+ border: false
+ }
+ };
+ },
+ watch: {
+ expanded(n) {
+ clearTimeout(this.timer)
+ this.timer = null
+ // 这里根据expanded的值来进行一定的延时,是为了cell的下划线更好的显示效果
+ this.timer = setTimeout(() => {
+ this.showBorder = n
+ }, n ? 10 : 290)
+ }
+ },
+ created() {
+ this.elId = this.$uv.guid();
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ // 异步获取内容,或者动态修改了内容时,需要重新初始化
+ init() {
+ // 初始化数据
+ this.updateParentData()
+ if (!this.parent) {
+ return this.$uv.error('uv-collapse-item必须要搭配uv-collapse组件使用')
+ }
+ const {
+ value,
+ accordion,
+ children = []
+ } = this.parent
+
+ if (accordion) {
+ if (this.$uv.test.array(value)) {
+ return this.$uv.error('手风琴模式下,uv-collapse组件的value参数不能为数组')
+ }
+ this.expanded = this.name == value
+ } else {
+ if (!this.$uv.test.array(value) && value !== null) {
+ return this.$uv.error('非手风琴模式下,uv-collapse组件的value参数必须为数组')
+ }
+ this.expanded = (value || []).some(item => item == this.name)
+ }
+ // 设置组件的展开或收起状态
+ this.$nextTick(function() {
+ this.setContentAnimate()
+ })
+ },
+ updateParentData() {
+ // 此方法在mixin中
+ this.getParentData('uv-collapse')
+ },
+ async setContentAnimate() {
+ // 每次面板打开或者收起时,都查询元素尺寸
+ // 好处是,父组件从服务端获取内容后,变更折叠面板后可以获得最新的高度
+ const rect = await this.queryRect()
+ const height = this.expanded ? rect.height : 0
+ this.animating = true
+ // #ifdef APP-NVUE
+ const ref = this.$refs['animation'].ref
+ animation.transition(ref, {
+ styles: {
+ height: height + 'px'
+ },
+ duration: this.duration,
+ // 必须设置为true,否则会到面板收起或展开时,页面其他元素不会随之调整它们的布局
+ needLayout: true,
+ timingFunction: 'ease-in-out',
+ }, () => {
+ this.animating = false
+ })
+ // #endif
+
+ // #ifndef APP-NVUE
+ const animation = uni.createAnimation({
+ timingFunction: 'ease-in-out',
+ });
+ animation
+ .height(height)
+ .step({
+ duration: this.duration,
+ })
+ .step()
+ // 导出动画数据给面板的animationData值
+ this.animationData = animation.export()
+ // 标识动画结束
+ this.$uv.sleep(this.duration).then(() => {
+ this.animating = false
+ })
+ // #endif
+ },
+ // 点击collapsehead头部
+ clickHandler() {
+ if (this.disabled && this.animating) return
+ // 设置本组件为相反的状态
+ this.parent && this.parent.onChange(this)
+ },
+ // 查询内容高度
+ queryRect() {
+ // #ifndef APP-NVUE
+ // 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+ return new Promise(resolve => {
+ this.$uvGetRect(`#${this.elId}`).then(size => {
+ resolve(size)
+ })
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ // nvue下,使用dom模块查询元素高度
+ // 返回一个promise,让调用此方法的主体能使用then回调
+ return new Promise(resolve => {
+ dom.getComponentRect(this.$refs[this.elId], res => {
+ resolve(res.size)
+ })
+ })
+ // #endif
+ }
+ },
+ };
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-collapse-item {
+
+ &__content {
+ overflow: hidden;
+ height: 0;
+
+ &__text {
+ padding: 12px 15px;
+ color: $uv-content-color;
+ font-size: 14px;
+ line-height: 18px;
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-collapse/components/uv-collapse/props.js b/ledApp/uni_modules/uv-collapse/components/uv-collapse/props.js
new file mode 100644
index 0000000..70fa04c
--- /dev/null
+++ b/ledApp/uni_modules/uv-collapse/components/uv-collapse/props.js
@@ -0,0 +1,20 @@
+export default {
+ props: {
+ // 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
+ value: {
+ type: [String, Number, Array, null],
+ default: null
+ },
+ // 是否手风琴模式
+ accordion: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示外边框
+ border: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.collapse
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-collapse/components/uv-collapse/uv-collapse.vue b/ledApp/uni_modules/uv-collapse/components/uv-collapse/uv-collapse.vue
new file mode 100644
index 0000000..5e5c96d
--- /dev/null
+++ b/ledApp/uni_modules/uv-collapse/components/uv-collapse/uv-collapse.vue
@@ -0,0 +1,86 @@
+<template>
+ <view class="uv-collapse">
+ <uv-line v-if="border"></uv-line>
+ <slot />
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * collapse 折叠面板
+ * @description 通过折叠面板收纳内容区域
+ * @tutorial https://www.uvui.cn/components/collapse.html
+ * @property {String | Number | Array} value 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
+ * @property {Boolean} accordion 是否手风琴模式( 默认 false )
+ * @property {Boolean} border 是否显示外边框 ( 默认 true )
+ * @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
+ * @example <uv-collapse></uv-collapse>
+ */
+ export default {
+ name: "uv-collapse",
+ mixins: [mpMixin, mixin, props],
+ watch: {
+ needInit() {
+ this.init()
+ },
+ // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+ parentData() {
+ if (this.children.length) {
+ this.children.map(child => {
+ // 判断子组件(uv-checkbox)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+ typeof(child.updateParentData) === 'function' && child.updateParentData()
+ })
+ }
+ }
+ },
+ created() {
+ this.children = []
+ },
+ computed: {
+ needInit() {
+ // 通过computed,同时监听accordion和value值的变化
+ // 再通过watch去执行init()方法,进行再一次的初始化
+ return [this.accordion, this.value]
+ }
+ },
+ methods: {
+ // 重新初始化一次内部的所有子元素
+ init() {
+ this.children.map(child => {
+ child.init()
+ })
+ },
+ /**
+ * collapse-item被点击时触发,由collapse统一处理各子组件的状态
+ * @param {Object} target 被操作的面板的实例
+ */
+ onChange(target) {
+ let changeArr = []
+ this.children.map((child, index) => {
+ // 如果是手风琴模式,将其他的折叠面板收起来
+ if (this.accordion) {
+ child.expanded = child === target ? !target.expanded : false
+ child.setContentAnimate()
+ } else {
+ if(child === target) {
+ child.expanded = !child.expanded
+ child.setContentAnimate()
+ }
+ }
+ // 拼接change事件中,数组元素的状态
+ changeArr.push({
+ // 如果没有定义name属性,则默认返回组件的index索引
+ name: child.name || index,
+ status: child.expanded ? 'open' : 'close'
+ })
+ })
+
+ this.$emit('change', changeArr)
+ this.$emit(target.expanded ? 'open' : 'close', target.name)
+ }
+ }
+ }
+</script>
diff --git a/ledApp/uni_modules/uv-collapse/package.json b/ledApp/uni_modules/uv-collapse/package.json
new file mode 100644
index 0000000..cf9565f
--- /dev/null
+++ b/ledApp/uni_modules/uv-collapse/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-collapse",
+ "displayName": "uv-collapse 折叠面板 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.1",
+ "description": "折叠面板组件,通过折叠面板收纳内容区域,点击可展开收起,多功能参数可配置。",
+ "keywords": [
+ "uv-collapse",
+ "uvui",
+ "uv-ui",
+ "collapse",
+ "折叠面板"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-line",
+ "uv-cell"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-collapse/readme.md b/ledApp/uni_modules/uv-collapse/readme.md
new file mode 100644
index 0000000..cb340ba
--- /dev/null
+++ b/ledApp/uni_modules/uv-collapse/readme.md
@@ -0,0 +1,11 @@
+## Collapse 折叠面板
+
+> **组件名:uv-collapse**
+
+通过折叠面板收纳内容区域,点击可展开收起,多功能参数可配置。
+
+### <a href="https://www.uvui.cn/components/collapse.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-count-down/changelog.md b/ledApp/uni_modules/uv-count-down/changelog.md
new file mode 100644
index 0000000..44392c2
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-down/changelog.md
@@ -0,0 +1,9 @@
+## 1.0.3(2023-10-13)
+1. unmounted兼容vue3
+## 1.0.2(2023-06-20)
+1. 增加外部样式customStyle参数
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-count-down 倒计时
diff --git a/ledApp/uni_modules/uv-count-down/components/uv-count-down/props.js b/ledApp/uni_modules/uv-count-down/components/uv-count-down/props.js
new file mode 100644
index 0000000..c5b5461
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-down/components/uv-count-down/props.js
@@ -0,0 +1,25 @@
+export default {
+ props: {
+ // 倒计时时长,单位ms
+ time: {
+ type: [String, Number],
+ default: 0
+ },
+ // 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒
+ format: {
+ type: String,
+ default: 'HH:mm:ss'
+ },
+ // 是否自动开始倒计时
+ autoStart: {
+ type: Boolean,
+ default: true
+ },
+ // 是否展示毫秒倒计时
+ millisecond: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.countDown
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-count-down/components/uv-count-down/utils.js b/ledApp/uni_modules/uv-count-down/components/uv-count-down/utils.js
new file mode 100644
index 0000000..8c75005
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-down/components/uv-count-down/utils.js
@@ -0,0 +1,62 @@
+// 补0,如1 -> 01
+function padZero(num, targetLength = 2) {
+ let str = `${num}`
+ while (str.length < targetLength) {
+ str = `0${str}`
+ }
+ return str
+}
+const SECOND = 1000
+const MINUTE = 60 * SECOND
+const HOUR = 60 * MINUTE
+const DAY = 24 * HOUR
+export function parseTimeData(time) {
+ const days = Math.floor(time / DAY)
+ const hours = Math.floor((time % DAY) / HOUR)
+ const minutes = Math.floor((time % HOUR) / MINUTE)
+ const seconds = Math.floor((time % MINUTE) / SECOND)
+ const milliseconds = Math.floor(time % SECOND)
+ return {
+ days,
+ hours,
+ minutes,
+ seconds,
+ milliseconds
+ }
+}
+export function parseFormat(format, timeData) {
+ let {
+ days,
+ hours,
+ minutes,
+ seconds,
+ milliseconds
+ } = timeData
+ // 如果格式化字符串中不存在DD(天),则将天的时间转为小时中去
+ if (format.indexOf('DD') === -1) {
+ hours += days * 24
+ } else {
+ // 对天补0
+ format = format.replace('DD', padZero(days))
+ }
+ // 其他同理于DD的格式化处理方式
+ if (format.indexOf('HH') === -1) {
+ minutes += hours * 60
+ } else {
+ format = format.replace('HH', padZero(hours))
+ }
+ if (format.indexOf('mm') === -1) {
+ seconds += minutes * 60
+ } else {
+ format = format.replace('mm', padZero(minutes))
+ }
+ if (format.indexOf('ss') === -1) {
+ milliseconds += seconds * 1000
+ } else {
+ format = format.replace('ss', padZero(seconds))
+ }
+ return format.replace('SSS', padZero(milliseconds, 3))
+}
+export function isSameSecond(time1, time2) {
+ return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
+}
diff --git a/ledApp/uni_modules/uv-count-down/components/uv-count-down/uv-count-down.vue b/ledApp/uni_modules/uv-count-down/components/uv-count-down/uv-count-down.vue
new file mode 100644
index 0000000..a758d31
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-down/components/uv-count-down/uv-count-down.vue
@@ -0,0 +1,169 @@
+<template>
+ <view
+ class="uv-count-down"
+ :style="[$uv.addStyle(customStyle)]">
+ <slot>
+ <text class="uv-count-down__text">{{ formattedTime }}</text>
+ </slot>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ import {
+ isSameSecond,
+ parseFormat,
+ parseTimeData
+ } from './utils';
+ /**
+ * uv-count-down 倒计时
+ * @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
+ * @tutorial https://www.uvui.cn/components/countDown.html
+ * @property {String | Number} time 倒计时时长,单位ms (默认 0 )
+ * @property {String} format 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒 (默认 'HH:mm:ss' )
+ * @property {Boolean} autoStart 是否自动开始倒计时 (默认 true )
+ * @property {Boolean} millisecond 是否展示毫秒倒计时 (默认 false )
+ * @event {Function} finish 倒计时结束时触发
+ * @event {Function} change 倒计时变化时触发
+ * @event {Function} start 开始倒计时
+ * @event {Function} pause 暂停倒计时
+ * @event {Function} reset 重设倒计时,若 auto-start 为 true,重设后会自动开始倒计时
+ * @example <uv-count-down :time="time"></uv-count-down>
+ */
+ export default {
+ name: 'uv-count-down',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ timer: null,
+ // 各单位(天,时,分等)剩余时间
+ timeData: parseTimeData(0),
+ // 格式化后的时间,如"03:23:21"
+ formattedTime: '0',
+ // 倒计时是否正在进行中
+ runing: false,
+ endTime: 0, // 结束的毫秒时间戳
+ remainTime: 0, // 剩余的毫秒时间
+ }
+ },
+ watch: {
+ time(n) {
+ this.reset()
+ }
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ this.reset()
+ },
+ // 开始倒计时
+ start() {
+ if (this.runing) return
+ // 标识为进行中
+ this.runing = true
+ // 结束时间戳 = 此刻时间戳 + 剩余的时间
+ this.endTime = Date.now() + this.remainTime
+ this.toTick()
+ },
+ // 根据是否展示毫秒,执行不同操作函数
+ toTick() {
+ if (this.millisecond) {
+ this.microTick()
+ } else {
+ this.macroTick()
+ }
+ },
+ macroTick() {
+ this.clearTimeout()
+ // 每隔一定时间,更新一遍定时器的值
+ // 同时此定时器的作用也能带来毫秒级的更新
+ this.timer = setTimeout(() => {
+ // 获取剩余时间
+ const remain = this.getRemainTime()
+ // 重设剩余时间
+ if (!isSameSecond(remain, this.remainTime) || remain === 0) {
+ this.setRemainTime(remain)
+ }
+ // 如果剩余时间不为0,则继续检查更新倒计时
+ if (this.remainTime !== 0) {
+ this.macroTick()
+ }
+ }, 30)
+ },
+ microTick() {
+ this.clearTimeout()
+ this.timer = setTimeout(() => {
+ this.setRemainTime(this.getRemainTime())
+ if (this.remainTime !== 0) {
+ this.microTick()
+ }
+ }, 50)
+ },
+ // 获取剩余的时间
+ getRemainTime() {
+ // 取最大值,防止出现小于0的剩余时间值
+ return Math.max(this.endTime - Date.now(), 0)
+ },
+ // 设置剩余的时间
+ setRemainTime(remain) {
+ this.remainTime = remain
+ // 根据剩余的毫秒时间,得出该有天,小时,分钟等的值,返回一个对象
+ const timeData = parseTimeData(remain)
+ this.$emit('change', timeData)
+ // 得出格式化后的时间
+ this.formattedTime = parseFormat(this.format, timeData)
+ // 如果时间已到,停止倒计时
+ if (remain <= 0) {
+ this.pause()
+ this.$emit('finish')
+ }
+ },
+ // 重置倒计时
+ reset() {
+ this.pause()
+ this.remainTime = this.time
+ this.setRemainTime(this.remainTime)
+ if (this.autoStart) {
+ this.start()
+ }
+ },
+ // 暂停倒计时
+ pause() {
+ this.runing = false;
+ this.clearTimeout()
+ },
+ // 清空定时器
+ clearTimeout() {
+ clearTimeout(this.timer)
+ this.timer = null
+ }
+ },
+ // #ifdef VUE2
+ beforeDestroy() {
+ this.clearTimeout()
+ },
+ // #endif
+ // #ifdef VUE3
+ unmounted() {
+ this.clearTimeout()
+ }
+ // #endif
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-count-down-text-color: $uv-content-color !default;
+ $uv-count-down-text-font-size: 15px !default;
+ $uv-count-down-text-line-height: 22px !default;
+ .uv-count-down {
+ &__text {
+ color: $uv-count-down-text-color;
+ font-size: $uv-count-down-text-font-size;
+ line-height: $uv-count-down-text-line-height;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-count-down/package.json b/ledApp/uni_modules/uv-count-down/package.json
new file mode 100644
index 0000000..da501f1
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-down/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-count-down",
+ "displayName": "uv-count-down 倒计时 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.3",
+ "description": "该倒计时组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。",
+ "keywords": [
+ "uv-count-down",
+ "uvui",
+ "uv-ui",
+ "countDown",
+ "倒计时"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-count-down/readme.md b/ledApp/uni_modules/uv-count-down/readme.md
new file mode 100644
index 0000000..0f326a8
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-down/readme.md
@@ -0,0 +1,11 @@
+## CountDown 倒计时
+
+> **组件名:uv-count-down**
+
+该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
+
+### <a href="https://www.uvui.cn/components/countDown.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-count-to/changelog.md b/ledApp/uni_modules/uv-count-to/changelog.md
new file mode 100644
index 0000000..cf323f9
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-to/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.4(2023-06-20)
+1. 优化
+## 1.0.3(2023-06-20)
+1. 修复继续滚动的函数
+2. 修复其他
+## 1.0.2(2023-06-20)
+1. 适配px和rpx的单位
+2. 适配customStyle参数
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-count-to 数字滚动
diff --git a/ledApp/uni_modules/uv-count-to/components/uv-count-to/props.js b/ledApp/uni_modules/uv-count-to/components/uv-count-to/props.js
new file mode 100644
index 0000000..498371a
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-to/components/uv-count-to/props.js
@@ -0,0 +1,60 @@
+export default {
+ props: {
+ // 开始的数值,默认从0增长到某一个数
+ startVal: {
+ type: [String, Number],
+ default: 0
+ },
+ // 要滚动的目标数值,必须
+ endVal: {
+ type: [String, Number],
+ default: 0
+ },
+ // 滚动到目标数值的动画持续时间,单位为毫秒(ms)
+ duration: {
+ type: [String, Number],
+ default: 2000
+ },
+ // 设置数值后是否自动开始滚动
+ autoplay: {
+ type: Boolean,
+ default: true
+ },
+ // 要显示的小数位数
+ decimals: {
+ type: [String, Number],
+ default: 0
+ },
+ // 是否在即将到达目标数值的时候,使用缓慢滚动的效果
+ useEasing: {
+ type: Boolean,
+ default: true
+ },
+ // 十进制分割
+ decimal: {
+ type: [String, Number],
+ default: '.'
+ },
+ // 字体颜色
+ color: {
+ type: String,
+ default: '#606266'
+ },
+ // 字体大小
+ fontSize: {
+ type: [String, Number],
+ default: 22
+ },
+ // 是否加粗字体
+ bold: {
+ type: Boolean,
+ default: false
+ },
+ // 千位分隔符,类似金额的分割(¥23,321.05中的",")
+ separator: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.countTo
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-count-to/components/uv-count-to/uv-count-to.vue b/ledApp/uni_modules/uv-count-to/components/uv-count-to/uv-count-to.vue
new file mode 100644
index 0000000..5f34167
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-to/components/uv-count-to/uv-count-to.vue
@@ -0,0 +1,187 @@
+<template>
+ <text
+ class="uv-count-num"
+ :style="[{
+ fontSize: $uv.addUnit(fontSize),
+ fontWeight: bold ? 'bold' : 'normal',
+ color: color
+ },$uv.addStyle(customStyle)]"
+ >{{ displayValue }}</text>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+/**
+ * countTo 数字滚动
+ * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
+ * @tutorial https://www.uvui.cn/components/countTo.html
+ * @property {String | Number} startVal 开始的数值,默认从0增长到某一个数(默认 0 )
+ * @property {String | Number} endVal 要滚动的目标数值,必须 (默认 0 )
+ * @property {String | Number} duration 滚动到目标数值的动画持续时间,单位为毫秒(ms) (默认 2000 )
+ * @property {Boolean} autoplay 设置数值后是否自动开始滚动 (默认 true )
+ * @property {String | Number} decimals 要显示的小数位数,见官网说明(默认 0 )
+ * @property {Boolean} useEasing 滚动结束时,是否缓动结尾,见官网说明(默认 true )
+ * @property {String} decimal 十进制分割 ( 默认 "." )
+ * @property {String} color 字体颜色( 默认 '#606266' )
+ * @property {String | Number} fontSize 字体大小,单位px( 默认 22 )
+ * @property {Boolean} bold 字体是否加粗(默认 false )
+ * @property {String} separator 千位分隔符,见官网说明
+ * @event {Function} end 数值滚动到目标值时触发
+ * @example <uv-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></uv-count-to>
+ */
+export default {
+ name: 'uv-count-to',
+ data() {
+ return {
+ localStartVal: this.startVal,
+ displayValue: this.formatNumber(this.startVal),
+ printVal: null,
+ paused: false, // 是否暂停
+ localDuration: Number(this.duration),
+ startTime: null, // 开始的时间
+ timestamp: null, // 时间戳
+ remaining: null, // 停留的时间
+ rAF: null,
+ lastTime: 0 // 上一次的时间
+ };
+ },
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ countDown() {
+ return this.startVal > this.endVal;
+ }
+ },
+ watch: {
+ startVal() {
+ this.autoplay && this.start();
+ },
+ endVal() {
+ this.autoplay && this.start();
+ }
+ },
+ mounted() {
+ this.autoplay && this.start();
+ },
+ methods: {
+ easingFn(t, b, c, d) {
+ return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
+ },
+ requestAnimationFrame(callback) {
+ const currTime = new Date().getTime();
+ // 为了使setTimteout的尽可能的接近每秒60帧的效果
+ const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
+ const id = setTimeout(() => {
+ callback(currTime + timeToCall);
+ }, timeToCall);
+ this.lastTime = currTime + timeToCall;
+ return id;
+ },
+ cancelAnimationFrame(id) {
+ clearTimeout(id);
+ },
+ // 开始滚动数字
+ start() {
+ this.localStartVal = this.startVal;
+ this.startTime = null;
+ this.localDuration = this.duration;
+ this.paused = false;
+ this.rAF = this.requestAnimationFrame(this.count);
+ },
+ // 暂定状态,从暂停状态开始滚动;或者滚动状态下,暂停
+ restart() {
+ if (this.paused) {
+ this.resume();
+ this.paused = false;
+ } else {
+ this.stop();
+ this.paused = true;
+ }
+ },
+ // 暂停
+ stop() {
+ this.cancelAnimationFrame(this.rAF);
+ this.paused = true;
+ },
+ // 重新开始(暂停的情况下)
+ resume() {
+ if (!this.remaining) return;
+ this.startTime = 0;
+ this.localDuration = this.remaining;
+ this.localStartVal = this.printVal;
+ this.requestAnimationFrame(this.count);
+ },
+ // 重置
+ reset() {
+ this.startTime = null;
+ this.cancelAnimationFrame(this.rAF);
+ this.displayValue = this.formatNumber(this.startVal);
+ },
+ count(timestamp) {
+ if (!this.startTime) this.startTime = timestamp;
+ this.timestamp = timestamp;
+ const progress = timestamp - this.startTime;
+ this.remaining = this.localDuration - progress;
+ if (this.useEasing) {
+ if (this.countDown) {
+ this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
+ } else {
+ this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
+ }
+ } else {
+ if (this.countDown) {
+ this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
+ } else {
+ this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
+ }
+ }
+ if (this.countDown) {
+ this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
+ } else {
+ this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
+ }
+ this.displayValue = this.formatNumber(this.printVal) || 0;
+ if (progress < this.localDuration) {
+ this.rAF = this.requestAnimationFrame(this.count);
+ } else {
+ this.$emit('end');
+ }
+ },
+ // 判断是否数字
+ isNumber(val) {
+ return !isNaN(parseFloat(val));
+ },
+ formatNumber(num) {
+ // 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错
+ num = Number(num);
+ num = num.toFixed(Number(this.decimals));
+ num += '';
+ const x = num.split('.');
+ let x1 = x[0];
+ const x2 = x.length > 1 ? `${this.decimal}${x[1]}` : '';
+ const rgx = /(\d+)(\d{3})/;
+ if (this.separator && !this.isNumber(this.separator)) {
+ while (rgx.test(x1)) {
+ x1 = x1.replace(rgx, '$1' + this.separator + '$2');
+ }
+ }
+ return x1 + x2;
+ },
+ destroyed() {
+ this.cancelAnimationFrame(this.rAF);
+ }
+ }
+};
+</script>
+
+<style lang="scss" scoped>
+@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+.uv-count-num {
+ /* #ifndef APP-NVUE */
+ display: inline-flex;
+ /* #endif */
+ text-align: center;
+}
+</style>
diff --git a/ledApp/uni_modules/uv-count-to/package.json b/ledApp/uni_modules/uv-count-to/package.json
new file mode 100644
index 0000000..1ed07dc
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-to/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-count-to",
+ "displayName": "uv-count-to 数字滚动 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.4",
+ "description": "该数字滚动组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值,一种数字上升的视觉冲击效果。",
+ "keywords": [
+ "countTo",
+ "uvui",
+ "uv-ui",
+ "数字滚动",
+ "数字变化"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-count-to/readme.md b/ledApp/uni_modules/uv-count-to/readme.md
new file mode 100644
index 0000000..5d5f996
--- /dev/null
+++ b/ledApp/uni_modules/uv-count-to/readme.md
@@ -0,0 +1,11 @@
+## CountTo 数字滚动
+
+> **组件名:uv-count-to**
+
+该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
+
+### <a href="https://www.uvui.cn/components/countTo.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-datetime-picker/changelog.md b/ledApp/uni_modules/uv-datetime-picker/changelog.md
new file mode 100644
index 0000000..c56eabd
--- /dev/null
+++ b/ledApp/uni_modules/uv-datetime-picker/changelog.md
@@ -0,0 +1,34 @@
+## 1.0.15(2024-06-14)
+1. 修复上次更改引出的BUG
+## 1.0.14(2024-05-31)
+1. 修复设置maxDate后存在选择不准确的BUG
+## 1.0.13(2024-03-22)
+1. 修复VUE3中出现的BUG
+## 1.0.12(2023-11-27)
+1. 增加round圆角属性
+## 1.0.11(2023-10-11)
+1. 修复设置minDate出现选择错乱的BUG
+## 1.0.10(2023-09-01)
+1. 增加clearDate参数,是否清除上次选择,默认false
+## 1.0.9(2023-08-31)
+1. 增加mode="year",方便只选择年
+## 1.0.8(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.7(2023-07-13)
+1. 修复 uv-datetime-picker 设置value属性不生效的BUG
+## 1.0.6(2023-07-05)
+修复vue3模式下,动态修改v-model绑定的值无效的BUG
+## 1.0.5(2023-07-02)
+uv-datetime-picker 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/datetimePicker.html
+## 1.0.4(2023-06-29)
+1. 修复抖音小程序报错的BUG
+## 1.0.3(2023-06-07)
+1. 取消defaultIndex参数,传该值没实际意义,后续更新文档
+## 1.0.2(2023-06-02)
+1. 修复v-model重新赋值不更新的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-datetime-picker 时间选择器
diff --git a/ledApp/uni_modules/uv-datetime-picker/components/uv-datetime-picker/props.js b/ledApp/uni_modules/uv-datetime-picker/components/uv-datetime-picker/props.js
new file mode 100644
index 0000000..6031d84
--- /dev/null
+++ b/ledApp/uni_modules/uv-datetime-picker/components/uv-datetime-picker/props.js
@@ -0,0 +1,130 @@
+export default {
+ props: {
+ value: {
+ type: [String, Number],
+ default: ''
+ },
+ modelValue: {
+ type: [String, Number],
+ default: ''
+ },
+ // 是否打开组件
+ show: {
+ type: Boolean,
+ default: false
+ },
+ // 是否展示顶部的操作栏
+ showToolbar: {
+ type: Boolean,
+ default: true
+ },
+ // 顶部标题
+ title: {
+ type: String,
+ default: ''
+ },
+ // 展示格式,mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择
+ mode: {
+ type: String,
+ default: 'datetime'
+ },
+ // 可选的最大时间
+ maxDate: {
+ type: Number,
+ // 最大默认值为后10年
+ default: new Date(new Date().getFullYear() + 10, 0, 1).getTime()
+ },
+ // 可选的最小时间
+ minDate: {
+ type: Number,
+ // 最小默认值为前10年
+ default: new Date(new Date().getFullYear() - 10, 0, 1).getTime()
+ },
+ // 可选的最小小时,仅mode=time有效
+ minHour: {
+ type: Number,
+ default: 0
+ },
+ // 可选的最大小时,仅mode=time有效
+ maxHour: {
+ type: Number,
+ default: 23
+ },
+ // 可选的最小分钟,仅mode=time有效
+ minMinute: {
+ type: Number,
+ default: 0
+ },
+ // 可选的最大分钟,仅mode=time有效
+ maxMinute: {
+ type: Number,
+ default: 59
+ },
+ // 选项过滤函数
+ filter: {
+ type: [Function, null],
+ default: null
+ },
+ // 选项格式化函数
+ formatter: {
+ type: [Function, null],
+ default: null
+ },
+ // 是否显示加载中状态
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ // 各列中,单个选项的高度
+ itemHeight: {
+ type: [String, Number],
+ default: 44
+ },
+ // 取消按钮的文字
+ cancelText: {
+ type: String,
+ default: '取消'
+ },
+ // 确认按钮的文字
+ confirmText: {
+ type: String,
+ default: '确认'
+ },
+ // 取消按钮的颜色
+ cancelColor: {
+ type: String,
+ default: '#909193'
+ },
+ // 确认按钮的颜色
+ confirmColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 每列中可见选项的数量
+ visibleItemCount: {
+ type: [String, Number],
+ default: 5
+ },
+ // 是否允许点击遮罩关闭选择器
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: true
+ },
+ // 是否允许点击确认关闭选择器
+ closeOnClickConfirm: {
+ type: Boolean,
+ default: true
+ },
+ // 是否清空上次选择内容
+ clearDate: {
+ type: Boolean,
+ default: false
+ },
+ // 圆角
+ round: {
+ type: [String, Number],
+ default: 0
+ },
+ ...uni.$uv?.props?.datetimePicker
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-datetime-picker/components/uv-datetime-picker/uv-datetime-picker.vue b/ledApp/uni_modules/uv-datetime-picker/components/uv-datetime-picker/uv-datetime-picker.vue
new file mode 100644
index 0000000..d45c822
--- /dev/null
+++ b/ledApp/uni_modules/uv-datetime-picker/components/uv-datetime-picker/uv-datetime-picker.vue
@@ -0,0 +1,360 @@
+<template>
+ <uv-picker
+ ref="picker"
+ :closeOnClickOverlay="closeOnClickOverlay"
+ :closeOnClickConfirm="closeOnClickConfirm"
+ :columns="columns"
+ :title="title"
+ :itemHeight="itemHeight"
+ :showToolbar="showToolbar"
+ :visibleItemCount="visibleItemCount"
+ :defaultIndex="innerDefaultIndex"
+ :cancelText="cancelText"
+ :confirmText="confirmText"
+ :cancelColor="cancelColor"
+ :confirmColor="confirmColor"
+ :round="round"
+ @close="close"
+ @cancel="cancel"
+ @confirm="confirm"
+ @change="change"
+ >
+ </uv-picker>
+</template>
+<script>
+ function times(n, iteratee) {
+ let index = -1
+ const result = Array(n < 0 ? 0 : n)
+ while (++index < n) {
+ result[index] = iteratee(index)
+ }
+ return result
+ }
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ import dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'
+ /**
+ * DatetimePicker 时间日期选择器
+ * @description 此选择器用于时间日期
+ * @tutorial https://www.uvui.cn/components/datetimePicker.html
+ * @property {Boolean} showToolbar 是否显示顶部的操作栏 ( 默认 true )
+ * @property {String | Number} value 绑定值
+ * @property {String} title 顶部标题
+ * @property {String} mode 展示格式 mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择 ( 默认 ‘datetime )
+ * @property {Number} maxDate 可选的最大时间 默认值为后10年
+ * @property {Number} minDate 可选的最小时间 默认值为前10年
+ * @property {Number} minHour 可选的最小小时,仅mode=time有效 ( 默认 0 )
+ * @property {Number} maxHour 可选的最大小时,仅mode=time有效 ( 默认 23 )
+ * @property {Number} minMinute 可选的最小分钟,仅mode=time有效 ( 默认 0 )
+ * @property {Number} maxMinute 可选的最大分钟,仅mode=time有效 ( 默认 59 )
+ * @property {Function} filter 选项过滤函数
+ * @property {Function} formatter 选项格式化函数
+ * @property {Boolean} loading 是否显示加载中状态 ( 默认 false )
+ * @property {String | Number} itemHeight 各列中,单个选项的高度 ( 默认 44 )
+ * @property {String} cancelText 取消按钮的文字 ( 默认 '取消' )
+ * @property {String} confirmText 确认按钮的文字 ( 默认 '确认' )
+ * @property {String} cancelColor 取消按钮的颜色 ( 默认 '#909193' )
+ * @property {String} confirmColor 确认按钮的颜色 ( 默认 '#3c9cff' )
+ * @property {String | Number} visibleItemCount 每列中可见选项的数量 ( 默认 5 )
+ * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭选择器 ( 默认 true )
+ * @property {String | Number} round 圆角 ( 默认 0 )
+ * @event {Function} close 关闭选择器时触发
+ * @event {Function} confirm 点击确定按钮,返回当前选择的值
+ * @event {Function} change 当选择值变化时触发
+ * @event {Function} cancel 点击取消按钮
+ * @example <uv-datetime-picker ref="datetimepicker" :value="value1" mode="datetime" ></uv-datetime-picker>
+ */
+ export default {
+ name: 'uv-datetime-picker',
+ emits: ['close', 'cancel', 'confirm', 'input', 'change', 'update:modelValue'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ columns: [],
+ innerDefaultIndex: [],
+ innerFormatter: (type, value) => value
+ }
+ },
+ watch: {
+ propsChange() {
+ this.$uv.sleep(100).then(res=>{
+ this.init()
+ })
+ }
+ },
+ computed: {
+ // 如果以下这些变量发生了变化,意味着需要重新初始化各列的值
+ propsChange() {
+ const propsValue = this.value || this.modelValue;
+ return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, propsValue]
+ }
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ this.getValue();
+ this.updateColumnValue(this.innerValue)
+ },
+ getValue() {
+ const propsValue = this.value || this.modelValue;
+ this.innerValue = this.correctValue(propsValue)
+ },
+ // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+ setFormatter(e) {
+ this.innerFormatter = e
+ },
+ open() {
+ this.$refs.picker.open();
+ // 解决打开的前一次和当前日期错乱的BUG
+ setTimeout(()=>{
+ this.getValue();
+ this.updateColumnValue(this.innerValue);
+ },10)
+ },
+ close() {
+ this.$emit('close');
+ },
+ // 点击工具栏的取消按钮
+ cancel() {
+ this.$emit('cancel')
+ },
+ // 点击工具栏的确定按钮
+ confirm() {
+ this.$emit('confirm', {
+ value: this.innerValue,
+ mode: this.mode
+ })
+ if(!this.clearDate) {
+ this.$emit('input', this.innerValue)
+ this.$emit('update:modelValue', this.innerValue)
+ }
+ },
+ //用正则截取输出值,当出现多组数字时,抛出错误
+ intercept(e, type) {
+ let judge = e.match(/\d+/g)
+ //判断是否掺杂数字
+ if (judge.length > 1) {
+ this.$uv.error("请勿在过滤或格式化函数时添加数字")
+ return 0
+ } else if (type && judge[0].length == 4) { //判断是否是年份
+ return judge[0]
+ } else if (judge[0].length > 2) {
+ this.$uv.error("请勿在过滤或格式化函数时添加数字")
+ return 0
+ } else {
+ return judge[0]
+ }
+ },
+ // 列发生变化时触发
+ change(e) {
+ const { indexs, values } = e
+ let selectValue = ''
+ if (this.mode === 'time') {
+ // 根据value各列索引,从各列数组中,取出当前时间的选中值
+ selectValue = `${this.intercept(values[0][indexs[0]])}:${this.intercept(values[1][indexs[1]])}`
+ } else if (this.mode === 'year') {
+ const year = parseInt(this.intercept(values[0][indexs[0]], 'year'));
+ selectValue = Number(new Date(year, 0));
+ } else {
+ // 将选择的值转为数值,比如'03'转为数值的3,'2019'转为数值的2019
+ const year = parseInt(this.intercept(values[0][indexs[0]], 'year'))
+ const month = parseInt(this.intercept(values[1][indexs[1]]))
+ let date = parseInt(values[2] ? this.intercept(values[2][indexs[2]]) : 1)
+ let hour = 0,
+ minute = 0
+ // 此月份的最大天数
+ const maxDate = dayjs(`${year}-${month}`).daysInMonth()
+ // year-month模式下,date不会出现在列中,设置为1,为了符合后边需要减1的需求
+ if (this.mode === 'year-month') {
+ date = 1
+ }
+ // 不允许超过maxDate值
+ date = Math.min(maxDate, date)
+ if (this.mode === 'datetime') {
+ hour = parseInt(this.intercept(values[3][indexs[3]]))
+ minute = parseInt(this.intercept(values[4][indexs[4]]))
+ }
+ // 转为时间模式
+ selectValue = Number(new Date(year, month - 1, date, hour, minute))
+ }
+ // 取出准确的合法值,防止超越边界的情况
+ selectValue = this.correctValue(selectValue)
+ this.innerValue = selectValue
+ this.updateColumnValue(selectValue)
+ // 发出change时间,value为当前选中的时间戳
+ this.$emit('change', {
+ value: selectValue,
+ mode: this.mode
+ })
+ },
+ // 更新各列的值,进行补0、格式化等操作
+ updateColumnValue(value) {
+ this.innerValue = value
+ this.updateColumns()
+ this.updateIndexs(value)
+ },
+ // 更新索引
+ updateIndexs(value) {
+ let values = []
+ const formatter = this.formatter || this.innerFormatter;
+ if (this.mode === 'time') {
+ // 将time模式的时间用:分隔成数组
+ const timeArr = value.split(':')
+ // 使用formatter格式化方法进行管道处理
+ values = [formatter('hour', timeArr[0]), formatter('minute', timeArr[1])]
+ } else {
+ const date = new Date(value)
+ values = [
+ formatter('year', `${dayjs(value).year()}`),
+ // 月份补0
+ formatter('month', this.$uv.padZero(dayjs(value).month() + 1))
+ ]
+ if (this.mode === 'date') {
+ // date模式,需要添加天列
+ values.push(formatter('day', this.$uv.padZero(dayjs(value).date())))
+ }
+ if (this.mode === 'datetime') {
+ // 数组的push方法,可以写入多个参数
+ values.push(formatter('day', this.$uv.padZero(dayjs(value).date())), formatter('hour', this.$uv.padZero(dayjs(value).hour())), formatter('minute', this.$uv.padZero(dayjs(value).minute())))
+ }
+ }
+ // 根据当前各列的所有值,从各列默认值中找到默认值在各列中的索引
+ const indexs = this.columns.map((column, index) => {
+ // 通过取大值,可以保证不会出现找不到索引的-1情况
+ return Math.max(0, column.findIndex(item => item === values[index]))
+ })
+ this.$nextTick(()=>{
+ this.$uv.sleep(100).then(res=>{
+ this.$refs.picker.setIndexs(indexs,true);
+ })
+ })
+ },
+ // 更新各列的值
+ updateColumns() {
+ const formatter = this.formatter || this.innerFormatter
+ // 获取各列的值,并且map后,对各列的具体值进行补0操作
+ const results = this.getOriginColumns().map((column) => column.values.map((value) => formatter(column.type, value)))
+ this.columns = results
+ },
+ getOriginColumns() {
+ // 生成各列的值
+ const results = this.getRanges().map(({ type, range }) => {
+ let values = times(range[1] - range[0] + 1, (index) => {
+ let value = range[0] + index
+ value = type === 'year' ? `${value}` : this.$uv.padZero(value)
+ return value
+ })
+ // 进行过滤
+ if (this.filter) {
+ values = this.filter(type, values)
+ }
+ return { type, values }
+ })
+ return results
+ },
+ // 通过最大值和最小值生成数组
+ generateArray(start, end) {
+ return Array.from(new Array(end + 1).keys()).slice(start)
+ },
+ // 得出合法的时间
+ correctValue(value) {
+ const isDateMode = this.mode !== 'time'
+ if (isDateMode && !this.$uv.test.date(value)) {
+ // 如果是日期类型,但是又没有设置合法的当前时间的话,使用最小时间为当前时间
+ value = this.minDate
+ } else if (!isDateMode && !value) {
+ // 如果是时间类型,而又没有默认值的话,就用最小时间
+ value = `${this.$uv.padZero(this.minHour)}:${this.$uv.padZero(this.minMinute)}`
+ }
+ // 时间类型
+ if (!isDateMode) {
+ if (String(value).indexOf(':') === -1) return this.$uv.error('时间错误,请传递如12:24的格式')
+ let [hour, minute] = value.split(':')
+ // 对时间补零,同时控制在最小值和最大值之间
+ hour = this.$uv.padZero(this.$uv.range(this.minHour, this.maxHour, Number(hour)))
+ minute = this.$uv.padZero(this.$uv.range(this.minMinute, this.maxMinute, Number(minute)))
+ return `${ hour }:${ minute }`
+ } else {
+ // 如果是日期格式,控制在最小日期和最大日期之间
+ value = dayjs(value).isBefore(dayjs(this.minDate)) ? this.minDate : value
+ value = dayjs(value).isAfter(dayjs(this.maxDate)) ? this.maxDate : value
+ return value
+ }
+ },
+ // 获取每列的最大和最小值
+ getRanges() {
+ if (this.mode === 'time') {
+ return [{
+ type: 'hour',
+ range: [this.minHour, this.maxHour],
+ }, {
+ type: 'minute',
+ range: [this.minMinute, this.maxMinute],
+ }, ];
+ }
+ const { maxYear, maxDate, maxMonth, maxHour, maxMinute, } = this.getBoundary('max', this.innerValue);
+ const { minYear, minDate, minMonth, minHour, minMinute, } = this.getBoundary('min', this.innerValue);
+ const result = [{
+ type: 'year',
+ range: [minYear, maxYear],
+ }, {
+ type: 'month',
+ range: [minMonth, maxMonth],
+ }, {
+ type: 'day',
+ range: [minDate, maxDate],
+ }, {
+ type: 'hour',
+ range: [minHour, maxHour],
+ }, {
+ type: 'minute',
+ range: [minMinute, maxMinute],
+ }, ];
+ if (this.mode === 'date') result.splice(3, 2);
+ if (this.mode === 'year-month') result.splice(2, 3);
+ if (this.mode === 'year') result.splice(1, 4);
+ return result;
+ },
+ // 根据minDate、maxDate、minHour、maxHour等边界值,判断各列的开始和结束边界值
+ getBoundary(type, innerValue) {
+ const value = new Date(innerValue)
+ const boundary = new Date(this[`${type}Date`])
+ const year = dayjs(boundary).year()
+ let month = 1
+ let date = 1
+ let hour = 0
+ let minute = 0
+ if (type === 'max') {
+ month = 12
+ // 月份的天数
+ date = dayjs(value).daysInMonth()
+ hour = 23
+ minute = 59
+ }
+ // 获取边界值,逻辑是:当年达到了边界值(最大或最小年),就检查月允许的最大和最小值,以此类推
+ if (dayjs(value).year() === year) {
+ month = dayjs(boundary).month() + 1
+ if (dayjs(value).month() + 1 === month) {
+ date = dayjs(boundary).date()
+ if (dayjs(value).date() === date) {
+ hour = dayjs(boundary).hour()
+ if (dayjs(value).hour() === hour) {
+ minute = dayjs(boundary).minute()
+ }
+ }
+ }
+ }
+ return {
+ [`${type}Year`]: year,
+ [`${type}Month`]: month,
+ [`${type}Date`]: date,
+ [`${type}Hour`]: hour,
+ [`${type}Minute`]: minute
+ }
+ },
+ },
+ }
+</script>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-datetime-picker/package.json b/ledApp/uni_modules/uv-datetime-picker/package.json
new file mode 100644
index 0000000..a2415b1
--- /dev/null
+++ b/ledApp/uni_modules/uv-datetime-picker/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-datetime-picker",
+ "displayName": "uv-datetime-picker 时间选择器 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.15",
+ "description": "时间选择器组件用于时间日期,主要用于年月日时分的选择,具体选择的精确度由参数控制。",
+ "keywords": [
+ "datetime-picker",
+ "uvui",
+ "uv-ui",
+ "datetime",
+ "时间选择"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-picker"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y",
+ "alipay": "n"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-datetime-picker/readme.md b/ledApp/uni_modules/uv-datetime-picker/readme.md
new file mode 100644
index 0000000..0db9d9c
--- /dev/null
+++ b/ledApp/uni_modules/uv-datetime-picker/readme.md
@@ -0,0 +1,19 @@
+## DatetimePicker 时间选择器
+
+> **组件名:uv-datetime-picker**
+
+此选择器用于时间日期,主要用于年月日时分的选择,具体选择的精确度由参数控制。
+
+# <a href="https://www.uvui.cn/components/datetimePicker.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-divider/changelog.md b/ledApp/uni_modules/uv-divider/changelog.md
new file mode 100644
index 0000000..9ff8c52
--- /dev/null
+++ b/ledApp/uni_modules/uv-divider/changelog.md
@@ -0,0 +1,11 @@
+## 1.0.4(2023-12-06)
+1. 优化
+## 1.0.3(2023-12-06)
+1. 阻止事件冒泡问题
+## 1.0.2(2023-06-01)
+1. 修复点击触发两次事件的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-divider 分割线
diff --git a/ledApp/uni_modules/uv-divider/components/uv-divider/props.js b/ledApp/uni_modules/uv-divider/components/uv-divider/props.js
new file mode 100644
index 0000000..7b97c2c
--- /dev/null
+++ b/ledApp/uni_modules/uv-divider/components/uv-divider/props.js
@@ -0,0 +1,45 @@
+export default {
+ props: {
+ // 是否虚线
+ dashed: {
+ type: Boolean,
+ default: false
+ },
+ // 是否细线
+ hairline: {
+ type: Boolean,
+ default: true
+ },
+ // 是否以点替代文字,优先于text字段起作用
+ dot: {
+ type: Boolean,
+ default: false
+ },
+ // 内容文本的位置,left-左边,center-中间,right-右边
+ textPosition: {
+ type: String,
+ default: 'center'
+ },
+ // 文本内容
+ text: {
+ type: [String, Number],
+ default: ''
+ },
+ // 文本大小
+ textSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 文本颜色
+ textColor: {
+ type: String,
+ default: '#909399'
+ },
+ // 线条颜色
+ lineColor: {
+ type: String,
+ default: '#dcdfe6'
+ },
+ ...uni.$uv?.props?.divider
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-divider/components/uv-divider/uv-divider.vue b/ledApp/uni_modules/uv-divider/components/uv-divider/uv-divider.vue
new file mode 100644
index 0000000..ba0dfa7
--- /dev/null
+++ b/ledApp/uni_modules/uv-divider/components/uv-divider/uv-divider.vue
@@ -0,0 +1,113 @@
+<template>
+ <view
+ class="uv-divider"
+ :style="[$uv.addStyle(customStyle)]"
+ @tap="click"
+ >
+ <uv-line
+ :color="lineColor"
+ :customStyle="leftLineStyle"
+ :hairline="hairline"
+ :dashed="dashed"></uv-line>
+ <text
+ v-if="dot"
+ class="uv-divider__dot"
+ >●</text>
+ <text
+ v-else-if="text"
+ class="uv-divider__text"
+ :style="[textStyle]"
+ >{{text}}</text>
+ <uv-line
+ :color="lineColor"
+ :customStyle="rightLineStyle"
+ :hairline="hairline"
+ :dashed="dashed"
+ ></uv-line>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * divider 分割线
+ * @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。
+ * @tutorial https://www.uvui.cn/components/divider.html
+ * @property {Boolean} dashed 是否虚线 (默认 false )
+ * @property {Boolean} hairline 是否细线 (默认 true )
+ * @property {Boolean} dot 是否以点替代文字,优先于text字段起作用 (默认 false )
+ * @property {String} textPosition 内容文本的位置,left-左边,center-中间,right-右边 (默认 'center' )
+ * @property {String | Number} text 文本内容
+ * @property {String | Number} textSize 文本大小 (默认 14)
+ * @property {String} textColor 文本颜色 (默认 '#909399' )
+ * @property {String} lineColor 线条颜色 (默认 '#dcdfe6' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} click divider组件被点击时触发
+ * @example <uv-divider :color="color">锦瑟无端五十弦</uv-divider>
+ */
+ export default {
+ name: 'uv-divider',
+ mixins: [mpMixin, mixin, props],
+ emits: ['click'],
+ computed: {
+ textStyle() {
+ const style = {}
+ style.fontSize = this.$uv.addUnit(this.textSize)
+ style.color = this.textColor
+ return style
+ },
+ // 左边线条的的样式
+ leftLineStyle() {
+ const style = {}
+ // 如果是在左边,设置左边的宽度为固定值
+ if (this.textPosition === 'left') {
+ style.width = '80rpx'
+ } else {
+ style.flex = 1
+ }
+ return style
+ },
+ // 右边线条的的样式
+ rightLineStyle() {
+ const style = {}
+ // 如果是在右边,设置右边的宽度为固定值
+ if (this.textPosition === 'right') {
+ style.width = '80rpx'
+ } else {
+ style.flex = 1
+ }
+ return style
+ }
+ },
+ methods: {
+ // divider组件被点击时触发
+ click() {
+ this.$emit('click');
+ }
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ $uv-divider-margin: 15px 0 !default;
+ $uv-divider-text-margin: 0 15px !default;
+ $uv-divider-dot-font-size: 12px !default;
+ $uv-divider-dot-margin: 0 12px !default;
+ $uv-divider-dot-color: #c0c4cc !default;
+ .uv-divider {
+ @include flex;
+ flex-direction: row;
+ align-items: center;
+ margin: $uv-divider-margin;
+ &__text {
+ margin: $uv-divider-text-margin;
+ }
+ &__dot {
+ font-size: $uv-divider-dot-font-size;
+ margin: $uv-divider-dot-margin;
+ color: $uv-divider-dot-color;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-divider/package.json b/ledApp/uni_modules/uv-divider/package.json
new file mode 100644
index 0000000..a54b458
--- /dev/null
+++ b/ledApp/uni_modules/uv-divider/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-divider",
+ "displayName": "uv-divider 分割线 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.4",
+ "description": "区隔内容的分割线,一般用于页面底部没有更多的提示。",
+ "keywords": [
+ "divider",
+ "uvui",
+ "uv-ui",
+ "分割线",
+ "没有更多"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-line"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-divider/readme.md b/ledApp/uni_modules/uv-divider/readme.md
new file mode 100644
index 0000000..2384cba
--- /dev/null
+++ b/ledApp/uni_modules/uv-divider/readme.md
@@ -0,0 +1,11 @@
+## Divider 分割线
+
+> **组件名:uv-divider**
+
+区隔内容的分割线,一般用于页面底部"没有更多"的提示。
+
+### <a href="https://www.uvui.cn/components/divider.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-drop-down/changelog.md b/ledApp/uni_modules/uv-drop-down/changelog.md
new file mode 100644
index 0000000..637c605
--- /dev/null
+++ b/ledApp/uni_modules/uv-drop-down/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.5(2024-01-02)
+1. 修复parentData不变的BUG
+## 1.0.4(2023-09-28)
+1. 增加uv-sticky依赖
+## 1.0.3(2023-08-29)
+1. 修复自定义内容,点击自定义内容时会自动关闭弹窗的问题
+## 1.0.2(2023-08-22)
+1. 优化
+## 1.0.1(2023-08-22)
+1. 增加@change回调,返回弹窗关闭状态
+2. 增加init方法,方便位置改变进行调整
+## 1.0.0(2023-07-30)
+新增uv-drop-down 下拉筛选组件
diff --git a/ledApp/uni_modules/uv-drop-down/components/uv-drop-down-item/uv-drop-down-item.vue b/ledApp/uni_modules/uv-drop-down/components/uv-drop-down-item/uv-drop-down-item.vue
new file mode 100644
index 0000000..9438a57
--- /dev/null
+++ b/ledApp/uni_modules/uv-drop-down/components/uv-drop-down-item/uv-drop-down-item.vue
@@ -0,0 +1,169 @@
+<template>
+ <view class="uv-drop-down-item" @click="clickHandler">
+ <uv-text :text="label" :size="getTextStyle.size" :color="getTextStyle.color" lines="1" :custom-style="{marginRight: '10rpx',maxWidth:'200rpx'}"></uv-text>
+ <uv-icon :name="getDownIcon.name" :size="getDownIcon.size" :color="getDownIcon.color" v-if="[1,'1'].indexOf(type)==-1"></uv-icon>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';
+ /**
+ * DropDown 下拉框
+ * @description 下拉筛选
+ * @tutorial https://ext.dcloud.net.cn/plugin?name=uv-drop-down
+ * @property {String | Number} name 字段标识
+ * @property {String | Number} type 类型 1 没有筛选项,直接进行选中和不选中 2 有多个选项
+ * @property {String | Number} label 筛选项的文本
+ * @property {Boolean} isDrop 该项是否打开
+ */
+ export default {
+ name: 'uv-drop-down-item',
+ mixins: [mpMixin, mixin],
+ emits: ['click'],
+ props: {
+ name: {
+ type: [String, Number],
+ default: ''
+ },
+ // 类型 1 没有筛选项,直接进行选中和不选中 2 有多个选项
+ type: {
+ type: [String, Number],
+ default: '2'
+ },
+ // 筛选的文本
+ label: {
+ type: [String],
+ default: ''
+ },
+ // 筛选值
+ value: {
+ type: [String, Number, null],
+ default: ''
+ },
+ // 是否下拉菜单打开
+ isDrop: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data() {
+ return {
+ parentData: {
+ defaultValue: [0, '0', 'all'],
+ textSize: '30rpx',
+ textColor: '#333',
+ textActiveSize: '30rpx',
+ textActiveColor: '#3c9cff',
+ extraIcon: {},
+ extraActiveIcon: {},
+ sign: '',
+ clickHandler: Function
+ },
+ active: false,
+ isDroped: false,
+ elId: ''
+ }
+ },
+ watch: {
+ isDrop: {
+ handler(newVal) {
+ this.isDroped = newVal;
+ },
+ immediate: true
+ },
+ value: {
+ handler(newVal) {
+ this.$nextTick(()=>{
+ this.active = this.parentData.defaultValue.indexOf(newVal) == -1;
+ })
+ },
+ immediate: true
+ }
+ },
+ computed: {
+ getDownIcon() {
+ const style = {
+ size: '30rpx',
+ color: '#333',
+ ...this.parentData.extraIcon
+ }
+ if (this.active || this.isDroped) {
+ style.color = this.parentData.extraActiveIcon?.color ? this.parentData.extraActiveIcon?.color : '#3c9cff';
+ style.size = this.parentData.extraActiveIcon?.size ? this.parentData.extraActiveIcon?.size : '30rpx';
+ }
+ if (this.isDroped) {
+ style.name = this.parentData.extraActiveIcon?.name;
+ }
+ return style;
+ },
+ getTextStyle() {
+ const style = {
+ size: this.parentData.textSize,
+ color: this.parentData.textColor
+ };
+ if (this.active || this.isDroped) {
+ style.size = this.parentData.textActiveSize;
+ style.color = this.parentData.textActiveColor;
+ }
+ return style;
+ }
+ },
+ created() {
+ this.init();
+ },
+ methods: {
+ init() {
+ this.elId = this.$uv.guid();
+ this.getParentData('uv-drop-down');
+ if (!this.parent) {
+ this.$uv.error('uv-drop-down必须搭配uv-drop-down-item组件使用');
+ }
+ uni.$on('HANDLE_DROPDOWN_ONE', id => {
+ if (this.isDroped && this.elId != id) {
+ this.isDroped = false;
+ }
+ })
+ uni.$on(`${this.parentData.sign}_CLOSEPOPUP`, async () => {
+ if (this.isDroped) {
+ this.isDroped = false;
+ }
+ })
+ },
+ async clickHandler() {
+ let data = {};
+ uni.$emit('HANDLE_DROPDOWN_ONE', this.elId);
+ switch (+this.type) {
+ case 1:
+ this.active = !this.active;
+ data = {
+ name: this.name,
+ active: this.active,
+ type: this.type
+ };
+ break;
+ case 2:
+ this.isDroped = !this.isDroped;
+ data = {
+ name: this.name,
+ active: this.isDroped,
+ type: this.type
+ };
+ break;
+ }
+ this.parentData.clickHandler(data);
+ this.$emit('click', data);
+ uni.$emit(`${this.parentData.sign}_CLICKMENU`, {
+ show: +this.type > 1 && this.isDroped
+ });
+ }
+ }
+ }
+</script>
+<style scoped lang="scss">
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-drop-down-item {
+ @include flex;
+ align-items: center;
+ padding: 20rpx;
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-drop-down/components/uv-drop-down-popup/uv-drop-down-popup.vue b/ledApp/uni_modules/uv-drop-down/components/uv-drop-down-popup/uv-drop-down-popup.vue
new file mode 100644
index 0000000..6a1efc8
--- /dev/null
+++ b/ledApp/uni_modules/uv-drop-down/components/uv-drop-down-popup/uv-drop-down-popup.vue
@@ -0,0 +1,242 @@
+<template>
+ <view class="uv-drop-down-popup">
+ <uv-transition :show="show" mode="fade" :duration="300" :custom-style="overlayStyle" @click="clickOverlay">
+ <view class="uv-dp__container" ref="uvDPContainer" :style="{height: `${height}px`}" @click.stop="blockClick">
+ <view class="uv-dp__container__list" ref="uvDPList">
+ <slot>
+ <view class="uv-dp__container__list--item" v-for="(item,index) in list" :key="index" @click="clickHandler(item,index)" :style="[itemCustomStyle(index)]">
+ <uv-text :text="item[keyName]" :size="getTextSize(index)" :color="getTextColor(index)"></uv-text>
+ </view>
+ </slot>
+ </view>
+ </view>
+ </uv-transition>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';
+ // #ifdef APP-NVUE
+ const animation = uni.requireNativePlugin('animation');
+ const dom = uni.requireNativePlugin('dom');
+ // #endif
+ /**
+ * DropDownPopup 下拉框
+ * @description 下拉筛选框
+ * @tutorial https://ext.dcloud.net.cn/plugin?name=uv-drop-down
+ * @property {String | Number} name 字段标识
+ * @property {String | Number} zIndex 弹出层的层级
+ * @property {String | Number} opacity 遮罩层的透明度
+ * @property {Boolean} clickOverlayOnClose 是否允许点击遮罩层关闭弹窗
+ * @property {Object} currentDropItem 当前下拉筛选菜单对象
+ * @property {String} keyName 指定从当前下拉筛选菜单对象元素中读取哪个属性作为文本展示
+ */
+ export default {
+ name: 'uv-drop-down-popup',
+ mixins: [mpMixin, mixin],
+ props: {
+ sign: {
+ type: [String, Number],
+ default: 'UVDROPDOWN'
+ },
+ zIndex: {
+ type: [Number, String],
+ default: 999
+ },
+ opacity: {
+ type: [Number, String],
+ default: 0.5
+ },
+ clickOverlayOnClose: {
+ type: Boolean,
+ default: true
+ },
+ // 当前下拉选项对象
+ currentDropItem: {
+ type: Object,
+ default () {
+ return {
+ activeIndex: 0,
+ child: []
+ }
+ }
+ },
+ keyName: {
+ type: String,
+ default: 'label'
+ }
+ },
+ data() {
+ return {
+ show: false,
+ rect: {},
+ height: 0
+ }
+ },
+ computed: {
+ overlayStyle() {
+ let { height = 0, top = 0 } = this.rect;
+ // #ifdef H5
+ top += this.$uv.sys().windowTop;
+ // #endif
+ const style = {
+ position: 'fixed',
+ top: `${top+height}px`,
+ left: 0,
+ right: 0,
+ zIndex: this.zIndex,
+ bottom: 0,
+ 'background-color': `rgba(0, 0, 0, ${this.opacity})`
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ },
+ list() {
+ try {
+ return Array.isArray(this.currentDropItem.child) ? this.currentDropItem.child : [];
+ } catch (e) {
+ return [];
+ }
+ },
+ getTextColor(index) {
+ return index => {
+ const active = this.currentDropItem.activeIndex == index;
+ const color = this.currentDropItem.color;
+ const activeColor = this.currentDropItem.activeColor;
+ if (active) {
+ return activeColor ? activeColor : '#3c9cff';
+ }
+ return color ? color : '#333';
+ }
+ },
+ getTextSize(index) {
+ return index => {
+ const active = this.currentDropItem.activeIndex == index;
+ const size = this.currentDropItem.size;
+ const activeSize = this.currentDropItem.activeSize;
+ if (active) {
+ return activeSize ? activeSize : '30rpx';
+ }
+ return size ? size : '30rpx';
+ }
+ },
+ itemCustomStyle() {
+ return index => {
+ const active = this.currentDropItem.activeIndex == index;
+ const style = {};
+ if (active && this.currentDropItem.itemActiveCustomStyle) {
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.currentDropItem.itemActiveCustomStyle));
+ }
+ if (this.currentDropItem.itemCustomStyle) {
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.currentDropItem.itemCustomStyle))
+ }
+ return style;
+ }
+ }
+ },
+ created() {
+ this.init();
+ },
+ methods: {
+ blockClick() {},
+ clickHandler(item, index) {
+ this.currentDropItem.activeIndex = index;
+ this.$emit('clickItem', item);
+ this.close();
+ },
+ init() {
+ uni.$off(`${this.sign}_GETRECT`);
+ uni.$on(`${this.sign}_GETRECT`, rect => {
+ this.rect = rect;
+ })
+ uni.$off(`${this.sign}_CLICKMENU`);
+ uni.$on(`${this.sign}_CLICKMENU`, async res => {
+ if (res.show) {
+ this.open();
+ } else {
+ this.close();
+ }
+ })
+ },
+ open() {
+ this.show = true;
+ this.$nextTick(async () => {
+ // #ifndef H5 || MP-WEIXIN
+ await this.$uv.sleep(60);
+ // #endif
+ const res = await this.queryRect();
+ // #ifndef APP-NVUE
+ this.height = res.height;
+ // #endif
+ // #ifdef APP-NVUE
+ this.animation(res.height);
+ // #endif
+ this.$emit('popupChange', { show: true });
+ })
+ },
+ close() {
+ if(!this.show) return;
+ this.height = 0;
+ // #ifndef APP-NVUE
+ this.height = 0;
+ // #endif
+ // #ifdef APP-NVUE
+ this.animation(0);
+ // #endif
+ this.show = false;
+ uni.$emit(`${this.sign}_CLOSEPOPUP`);
+ this.$emit('popupChange', { show: false });
+ },
+ clickOverlay() {
+ if (this.clickOverlayOnClose) {
+ this.close();
+ }
+ },
+ // 查询内容高度
+ queryRect() {
+ // #ifndef APP-NVUE
+ // 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+ return new Promise(resolve => {
+ this.$uvGetRect(`.uv-dp__container__list`).then(size => {
+ resolve(size)
+ })
+ })
+ // #endif
+ // #ifdef APP-NVUE
+ // nvue下,使用dom模块查询元素高度
+ // 返回一个promise,让调用此方法的主体能使用then回调
+ return new Promise(resolve => {
+ dom.getComponentRect(this.$refs.uvDPList, res => {
+ resolve(res.size)
+ })
+ })
+ // #endif
+ },
+ // nvue下设置高度
+ animation(height, duration = 200) {
+ // #ifdef APP-NVUE
+ const ref = this.$refs['uvDPContainer'];
+ animation.transition(ref, {
+ styles: {
+ height: `${height}px`
+ },
+ duration
+ })
+ // #endif
+ }
+ }
+ }
+</script>
+<style scoped lang="scss">
+ .uv-dp__container {
+ /* #ifndef APP-NVUE */
+ overflow: hidden;
+ transition: all .15s;
+ /* #endif */
+ background-color: #fff;
+ }
+ .uv-dp__container__list {
+ &--item {
+ padding: 20rpx 60rpx;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-drop-down/components/uv-drop-down/uv-drop-down.vue b/ledApp/uni_modules/uv-drop-down/components/uv-drop-down/uv-drop-down.vue
new file mode 100644
index 0000000..9978b40
--- /dev/null
+++ b/ledApp/uni_modules/uv-drop-down/components/uv-drop-down/uv-drop-down.vue
@@ -0,0 +1,135 @@
+<template>
+ <uv-sticky :disabled="!isSticky">
+ <view ref="dropDownRef" class="uv-drop-down" :style="[$uv.addStyle(customStyle)]">
+ <slot></slot>
+ </view>
+ </uv-sticky>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom');
+ // #endif
+ /**
+ * DropDown 下拉框
+ * @description 下拉筛选
+ * @tutorial https://ext.dcloud.net.cn/plugin?name=uv-drop-down
+ * @property {String | Number} sign 组件唯一标识,需要手动传
+ * @property {Boolean} is-sticky = [true|false] 是否吸顶
+ * @property {Array} default-value 默认值,表示参数value属于这里面的值,就说明是未选中即是默认展示的值。eg:上面示例中的{label: '全部',value: 'all'} 即是默认值。后续处理逻辑也可以根据是否是其中值进行过滤。
+ * @property {String} textSize 每项字体大小
+ * @property {String} textColor 每项文本颜色
+ * @property {String} textActiveSize 每项选中状态字体大小
+ * @property {String} textActiveColor 每项选中状态文本颜色
+ * @property {Object} extraIcon 每项右侧图标
+ * @property {Object} extraActiveIcon 每项选中后右侧图标
+ */
+ export default {
+ name: 'uv-drop-down',
+ mixins: [mpMixin, mixin],
+ emits: ['click'],
+ props: {
+ isSticky: {
+ type: Boolean,
+ default: true
+ },
+ sign: {
+ type: [String, Number],
+ default: 'UVDROPDOWN'
+ },
+ defaultValue: {
+ type: Array,
+ default: () => [0, '0', 'all']
+ },
+ textSize: {
+ type: String,
+ default: '30rpx'
+ },
+ textColor: {
+ type: String,
+ default: '#333'
+ },
+ textActiveSize: {
+ type: String,
+ default: '30rpx'
+ },
+ textActiveColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ extraIcon: {
+ type: Object,
+ default () {
+ return {
+ name: 'arrow-down',
+ size: '30rpx',
+ color: '#333'
+ }
+ }
+ },
+ extraActiveIcon: {
+ type: Object,
+ default () {
+ return {
+ name: 'arrow-up',
+ size: '30rpx',
+ color: '#3c9cff'
+ }
+ }
+ }
+ },
+ computed: {
+ parentData() {
+ return [this.defaultValue, this.textSize, this.textColor, this.textActiveColor, this.textActiveSize, this.extraIcon, this.extraActiveIcon, this.sign, this.clickHandler];
+ }
+ },
+ mounted() {
+ this.init();
+ },
+ methods: {
+ init() {
+ uni.$emit(`${this.sign}_CLICKMENU`, {
+ show: false
+ });
+ this.$nextTick(async () => {
+ const rect = await this.queryRect();
+ uni.$emit(`${this.sign}_GETRECT`, rect);
+ })
+ },
+ // 查询内容高度
+ queryRect() {
+ // #ifndef APP-NVUE
+ // 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+ return new Promise(resolve => {
+ this.$uvGetRect(`.uv-drop-down`).then(size => {
+ resolve(size)
+ })
+ })
+ // #endif
+ // #ifdef APP-NVUE
+ // nvue下,使用dom模块查询元素高度
+ // 返回一个promise,让调用此方法的主体能使用then回调
+ return new Promise(resolve => {
+ dom.getComponentRect(this.$refs.dropDownRef, res => {
+ res.size.top = res.size.top <= 0 ? 0 : res.size.top;
+ resolve(res.size)
+ })
+ })
+ // #endif
+ },
+ clickHandler(data) {
+ this.$emit('click', data);
+ }
+ }
+ }
+</script>
+<style scoped lang="scss">
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-drop-down {
+ @include flex;
+ justify-content: space-between;
+ background-color: #fff;
+ border-bottom: 1px solid #dadbde;
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-drop-down/package.json b/ledApp/uni_modules/uv-drop-down/package.json
new file mode 100644
index 0000000..f4ad683
--- /dev/null
+++ b/ledApp/uni_modules/uv-drop-down/package.json
@@ -0,0 +1,91 @@
+{
+ "id": "uv-drop-down",
+ "displayName": "uv-drop-down 下拉筛选 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.5",
+ "description": "该组件主要提供筛选下拉筛选框,内置基础筛选功能,可以根据自己的需求自定义筛选项",
+ "keywords": [
+ "uv-drop-down",
+ "uvui",
+ "uv-ui",
+ "下拉筛选",
+ "筛选"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-text",
+ "uv-transition",
+ "uv-sticky"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-drop-down/readme.md b/ledApp/uni_modules/uv-drop-down/readme.md
new file mode 100644
index 0000000..31cbd24
--- /dev/null
+++ b/ledApp/uni_modules/uv-drop-down/readme.md
@@ -0,0 +1,23 @@
+## DropDown 下拉筛选
+
+> **组件名:uv-drop-down**
+
+该组件主要提供筛选下拉筛选框,内置基础筛选功能,可以根据自己的需求自定义筛选项。
+
+为了兼容app-nvue,需要内置三个组件进行配合使用,uv-drop-down属于菜单项(其实还包括子组件uv-drop-down-item),uv-drop-down-popup属于筛选框。
+
+只需要做简单的约定式配置,即可使用该功能,兼容性良好,已经在多端进行了多次测试。
+
+# <a href="https://www.uvui.cn/components/dropDown.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-empty/changelog.md b/ledApp/uni_modules/uv-empty/changelog.md
new file mode 100644
index 0000000..a1cc2ad
--- /dev/null
+++ b/ledApp/uni_modules/uv-empty/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.5(2023-12-20)
+1. 优化
+## 1.0.4(2023-08-04)
+1. icon支持base64图片
+## 1.0.3(2023-07-17)
+1. 修复 uv-empty 恢复设置mode属性的内置图标
+## 1.0.2(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-empty 内容为空
diff --git a/ledApp/uni_modules/uv-empty/components/uv-empty/props.js b/ledApp/uni_modules/uv-empty/components/uv-empty/props.js
new file mode 100644
index 0000000..26c282d
--- /dev/null
+++ b/ledApp/uni_modules/uv-empty/components/uv-empty/props.js
@@ -0,0 +1,60 @@
+export default {
+ props: {
+ // 内置图标名称,或图片路径,建议绝对路径
+ icon: {
+ type: String,
+ default: ''
+ },
+ // 提示文字
+ text: {
+ type: String,
+ default: ''
+ },
+ // 文字颜色
+ textColor: {
+ type: String,
+ default: '#c0c4cc'
+ },
+ // 文字大小
+ textSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 图标的颜色
+ iconColor: {
+ type: String,
+ default: '#c0c4cc'
+ },
+ // 图标的大小
+ iconSize: {
+ type: [String, Number],
+ default: 90
+ },
+ // 选择预置的图标类型
+ mode: {
+ type: String,
+ default: 'data'
+ },
+ // 图标宽度,单位px
+ width: {
+ type: [String, Number],
+ default: 160
+ },
+ // 图标高度,单位px
+ height: {
+ type: [String, Number],
+ default: 160
+ },
+ // 是否显示组件
+ show: {
+ type: Boolean,
+ default: true
+ },
+ // 组件距离上一个元素之间的距离,默认px单位
+ marginTop: {
+ type: [String, Number],
+ default: 0
+ },
+ ...uni.$uv?.props?.empty
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-empty/components/uv-empty/uv-empty.vue b/ledApp/uni_modules/uv-empty/components/uv-empty/uv-empty.vue
new file mode 100644
index 0000000..22a9264
--- /dev/null
+++ b/ledApp/uni_modules/uv-empty/components/uv-empty/uv-empty.vue
@@ -0,0 +1,126 @@
+<template>
+ <view
+ class="uv-empty"
+ :style="[emptyStyle]"
+ v-if="show"
+ >
+ <uv-icon
+ v-if="!isImg"
+ :name="mode === 'message' ? 'chat' : `empty-${mode}`"
+ :size="iconSize"
+ :color="iconColor"
+ margin-top="14"
+ ></uv-icon>
+ <image
+ v-else
+ :style="{
+ width: $uv.addUnit(width),
+ height: $uv.addUnit(height)
+ }"
+ :src="icon"
+ mode="widthFix"
+ ></image>
+ <text
+ class="uv-empty__text"
+ :style="[textStyle]"
+ >{{text ? text : icons[mode]}}</text>
+ <view class="uv-empty__wrap">
+ <slot />
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * empty 内容为空
+ * @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
+ * @tutorial https://www.uvui.cn/components/empty.html
+ * @property {String} icon 内置图标名称,或图片路径,建议绝对路径
+ * @property {String} text 提示文字
+ * @property {String} textColor 文字颜色 (默认 '#c0c4cc' )
+ * @property {String | Number} textSize 文字大小 (默认 14 )
+ * @property {String} iconColor 图标的颜色 (默认 '#c0c4cc' )
+ * @property {String | Number} iconSize 图标的大小 (默认 90 )
+ * @property {String} mode 选择预置的图标类型 (默认 'data' )
+ * @property {String | Number} width 图标宽度,单位px (默认 160 )
+ * @property {String | Number} height 图标高度,单位px (默认 160 )
+ * @property {Boolean} show 是否显示组件 (默认 true )
+ * @property {String | Number} marginTop 组件距离上一个元素之间的距离,默认px单位 (默认 0 )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} click 点击组件时触发
+ * @event {Function} close 点击关闭按钮时触发
+ * @example <uv-empty text="所谓伊人,在水一方" mode="list"></uv-empty>
+ */
+ export default {
+ name: "uv-empty",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ icons: {
+ car: '购物车为空',
+ page: '页面不存在',
+ search: '没有搜索结果',
+ address: '没有收货地址',
+ 'wifi-off': '没有WiFi',
+ order: '订单为空',
+ coupon: '没有优惠券',
+ favor: '暂无收藏',
+ permission: '无权限',
+ history: '无历史记录',
+ news: '无新闻列表',
+ message: '消息列表为空',
+ list: '列表为空',
+ data: '数据为空',
+ comment: '暂无评论',
+ }
+ }
+ },
+ computed: {
+ // 组件样式
+ emptyStyle() {
+ const style = {}
+ style.marginTop = this.$uv.addUnit(this.marginTop)
+ // 合并customStyle样式,此参数通过mixin中的props传递
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ },
+ // 文本样式
+ textStyle() {
+ const style = {}
+ style.color = this.textColor
+ style.fontSize = this.$uv.addUnit(this.textSize)
+ return style
+ },
+ // 判断icon是否图片路径
+ isImg() {
+ const isBase64 = this.icon.indexOf('data:') > -1 && this.icon.indexOf('base64') > -1;
+ return this.icon.indexOf('/') !== -1 || isBase64;
+ }
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ $uv-empty-text-margin-top: 20rpx !default;
+ $uv-empty-slot-margin-top: 20rpx !default;
+ .uv-empty {
+ @include flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ &__text {
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: $uv-empty-text-margin-top;
+ }
+ }
+ .uv-slot-wrap {
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: $uv-empty-slot-margin-top;
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-empty/package.json b/ledApp/uni_modules/uv-empty/package.json
new file mode 100644
index 0000000..e10f451
--- /dev/null
+++ b/ledApp/uni_modules/uv-empty/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-empty",
+ "displayName": "uv-empty 内容为空 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.5",
+ "description": "该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个 没有内容 的场景, 我们精心挑选了十几个场景的图标,方便您使用。",
+ "keywords": [
+ "empty",
+ "uvui",
+ "uv-ui",
+ "空数据",
+ "暂无数据"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-empty/readme.md b/ledApp/uni_modules/uv-empty/readme.md
new file mode 100644
index 0000000..ecef14d
--- /dev/null
+++ b/ledApp/uni_modules/uv-empty/readme.md
@@ -0,0 +1,19 @@
+## Empty 内容为空
+
+> **组件名:uv-empty**
+
+该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
+
+# <a href="https://www.uvui.cn/components/empty.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-form/changelog.md b/ledApp/uni_modules/uv-form/changelog.md
new file mode 100644
index 0000000..b229bca
--- /dev/null
+++ b/ledApp/uni_modules/uv-form/changelog.md
@@ -0,0 +1,23 @@
+## 1.0.9(2023-08-14)
+1. 修复设置labelWidth属性时,节点渲染有闪动的BUG
+## 1.0.8(2023-08-13)
+1. 修复未设置rules的情况下报错的BUG
+2. 优化错误提示
+## 1.0.7(2023-08-10)
+1. 修复在vue3+setup语法糖中错误文字动画错乱
+## 1.0.6(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.5(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.4(2023-07-02)
+uv-form 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+## 1.0.3(2023-06-18)
+1. 修改某些情况下的BUG
+## 1.0.2(2023-06-15)
+1. 修复支付宝报错的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-form 表单
diff --git a/ledApp/uni_modules/uv-form/components/uv-form-item/props.js b/ledApp/uni_modules/uv-form/components/uv-form-item/props.js
new file mode 100644
index 0000000..00a26bc
--- /dev/null
+++ b/ledApp/uni_modules/uv-form/components/uv-form-item/props.js
@@ -0,0 +1,49 @@
+export default {
+ props: {
+ // input的label提示语
+ label: {
+ type: String,
+ default: ''
+ },
+ // 绑定的值
+ prop: {
+ type: String,
+ default: ''
+ },
+ // 是否显示表单域的下划线边框
+ borderBottom: {
+ type: [Boolean],
+ default: false
+ },
+ // label的位置,left-左边,top-上边
+ labelPosition: {
+ type: String,
+ default: ''
+ },
+ // label的宽度,单位px
+ labelWidth: {
+ type: [String, Number],
+ default: ''
+ },
+ // 右侧图标
+ rightIcon: {
+ type: String,
+ default: ''
+ },
+ // 左侧图标
+ leftIcon: {
+ type: String,
+ default: ''
+ },
+ // 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置
+ required: {
+ type: Boolean,
+ default: false
+ },
+ leftIconStyle: {
+ type: [String, Object],
+ default: ''
+ },
+ ...uni.$uv?.props?.formItem
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-form/components/uv-form-item/uv-form-item.vue b/ledApp/uni_modules/uv-form/components/uv-form-item/uv-form-item.vue
new file mode 100644
index 0000000..25d93ea
--- /dev/null
+++ b/ledApp/uni_modules/uv-form/components/uv-form-item/uv-form-item.vue
@@ -0,0 +1,226 @@
+<template>
+ <view class="uv-form-item">
+ <view
+ class="uv-form-item__body"
+ @tap="clickHandler"
+ :style="[$uv.addStyle(customStyle), {
+ flexDirection: (labelPosition || parentData.labelPosition) === 'left' ? 'row' : 'column'
+ }]"
+ >
+ <!-- 微信小程序中,将一个参数设置空字符串,结果会变成字符串"true" -->
+ <slot name="label">
+ <view
+ class="uv-form-item__body__left"
+ v-if="required || leftIcon || label"
+ :style="{
+ width: $uv.addUnit(labelWidth || parentData.labelWidth),
+ marginBottom: parentData.labelPosition === 'left' ? 0 : '5px',
+ }"
+ >
+ <!-- 为了块对齐 -->
+ <view class="uv-form-item__body__left__content">
+ <!-- nvue不支持伪元素before -->
+ <text
+ v-if="required"
+ class="uv-form-item__body__left__content__required"
+ >*</text>
+ <view
+ class="uv-form-item__body__left__content__icon"
+ v-if="leftIcon"
+ >
+ <uv-icon
+ :name="leftIcon"
+ :custom-style="leftIconStyle"
+ ></uv-icon>
+ </view>
+ <text
+ class="uv-form-item__body__left__content__label"
+ :style="[parentData.labelStyle, {
+ justifyContent: parentData.labelAlign === 'left' ? 'flex-start' : parentData.labelAlign === 'center' ? 'center' : 'flex-end'
+ }]"
+ >{{ label }}</text>
+ </view>
+ </view>
+ </slot>
+ <view class="uv-form-item__body__right">
+ <view class="uv-form-item__body__right__content">
+ <view class="uv-form-item__body__right__content__slot">
+ <slot />
+ </view>
+ <view class="item__body__right__content__icon">
+ <slot name="right" />
+ </view>
+ </view>
+ </view>
+ </view>
+ <slot name="error">
+ <uv-transition
+ :show="true"
+ :duration="100"
+ mode="fade"
+ v-if="!!message && parentData.errorType === 'message'"
+ >
+ <text
+ class="uv-form-item__body__right__message"
+ :style="{
+ marginLeft: $uv.addUnit(parentData.labelPosition === 'top' ? 0 : (labelWidth || parentData.labelWidth))
+ }"
+ >{{ message }}</text>
+ </uv-transition>
+ </slot>
+ <uv-line
+ v-if="borderBottom"
+ :color="message && parentData.errorType === 'border-bottom' ? '#f56c6c' : '#d6d7d9'"
+ ></uv-line>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Form 表单
+ * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
+ * @tutorial https://www.uvui.cn/components/form.html
+ * @property {String} label input的label提示语
+ * @property {String} prop 绑定的值
+ * @property {String | Boolean} borderBottom 是否显示表单域的下划线边框
+ * @property {String | Number} labelWidth label的宽度,单位px
+ * @property {String} rightIcon 右侧图标
+ * @property {String} leftIcon 左侧图标
+ * @property {String | Object} leftIconStyle 左侧图标的样式
+ * @property {Boolean} required 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置 (默认 false )
+ *
+ * @example <uv-form-item label="姓名" prop="userInfo.name" borderBottom ref="item1"></uv-form-item>
+ */
+ export default {
+ name: 'uv-form-item',
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 错误提示语
+ message: '',
+ parentData: {
+ // 提示文本的位置
+ labelPosition: 'left',
+ // 提示文本对齐方式
+ labelAlign: 'left',
+ // 提示文本的样式
+ labelStyle: {},
+ // 提示文本的宽度
+ labelWidth: 45,
+ // 错误提示方式
+ errorType: 'message'
+ }
+ }
+ },
+ created() {
+ this.init()
+ },
+ methods: {
+ init() {
+ // 父组件的实例
+ this.updateParentData()
+ if (!this.parent) {
+ this.$uv.error('uv-form-item需要结合uv-form组件使用')
+ }
+ },
+ // 获取父组件的参数
+ updateParentData() {
+ // 此方法写在mixin中
+ this.getParentData('uv-form');
+ },
+ // 移除uv-form-item的校验结果
+ clearValidate() {
+ this.message = null
+ },
+ // 清空当前的组件的校验结果,并重置为初始值
+ resetField() {
+ // 找到原始值
+ const value = this.$uv.getProperty(this.parent.originalModel, this.prop)
+ // 将uv-form的model的prop属性链还原原始值
+ this.$uv.setProperty(this.parent.model, this.prop, value)
+ // 移除校验结果
+ this.message = null
+ },
+ // 点击组件
+ clickHandler() {
+ this.$emit('click')
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-form-item {
+ @include flex(column);
+ font-size: 14px;
+ color: $uv-main-color;
+ &__body {
+ @include flex;
+ padding: 10px 0;
+ &__left {
+ @include flex;
+ align-items: center;
+ &__content {
+ position: relative;
+ @include flex;
+ align-items: center;
+ padding-right: 10rpx;
+ flex: 1;
+ &__icon {
+ margin-right: 8rpx;
+ }
+ &__required {
+ position: absolute;
+ left: -9px;
+ color: $uv-error;
+ line-height: 20px;
+ font-size: 20px;
+ top: 3px;
+ }
+ &__label {
+ @include flex;
+ align-items: center;
+ flex: 1;
+ color: $uv-main-color;
+ font-size: 15px;
+ }
+ }
+ }
+ &__right {
+ flex: 1;
+ &__content {
+ @include flex;
+ align-items: center;
+ flex: 1;
+ &__slot {
+ flex: 1;
+ /* #ifndef MP */
+ @include flex;
+ align-items: center;
+ /* #endif */
+ }
+ &__icon {
+ margin-left: 10rpx;
+ color: $uv-light-color;
+ font-size: 30rpx;
+ }
+ }
+ &__message__box {
+ height: 16px;
+ line-height: 16px;
+ }
+ &__message {
+ margin-top: -6px;
+ line-height: 24px;
+ font-size: 12px;
+ color: $uv-error;
+ }
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-form/components/uv-form/props.js b/ledApp/uni_modules/uv-form/components/uv-form/props.js
new file mode 100644
index 0000000..6cffb95
--- /dev/null
+++ b/ledApp/uni_modules/uv-form/components/uv-form/props.js
@@ -0,0 +1,46 @@
+export default {
+ props: {
+ // 当前form的需要验证字段的集合
+ model: {
+ type: Object,
+ default: () => ({})
+ },
+ // 验证规则
+ rules: {
+ type: [Object, Function, Array],
+ default: () => ({})
+ },
+ // 有错误时的提示方式,message-提示信息,toast-进行toast提示
+ // border-bottom-下边框呈现红色,none-无提示
+ errorType: {
+ type: String,
+ default: 'message'
+ },
+ // 是否显示表单域的下划线边框
+ borderBottom: {
+ type: Boolean,
+ default: true
+ },
+ // label的位置,left-左边,top-上边
+ labelPosition: {
+ type: String,
+ default: 'left'
+ },
+ // label的宽度,单位px
+ labelWidth: {
+ type: [String, Number],
+ default: 45
+ },
+ // lable字体的对齐方式
+ labelAlign: {
+ type: String,
+ default: 'left'
+ },
+ // lable的样式,对象形式
+ labelStyle: {
+ type: Object,
+ default: () => ({})
+ },
+ ...uni.$uv?.props?.form
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-form/components/uv-form/uv-form.vue b/ledApp/uni_modules/uv-form/components/uv-form/uv-form.vue
new file mode 100644
index 0000000..bc0876f
--- /dev/null
+++ b/ledApp/uni_modules/uv-form/components/uv-form/uv-form.vue
@@ -0,0 +1,209 @@
+<template>
+ <view class="uv-form">
+ <slot />
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from "./props.js";
+ import Schema from "./valid.js";
+ // 去除警告信息
+ Schema.warning = function() {};
+ /**
+ * Form 表单
+ * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
+ * @tutorial https://www.uvui.cn/components/form.html
+ * @property {Object} model 当前form的需要验证字段的集合
+ * @property {Object | Function | Array} rules 验证规则
+ * @property {String} errorType 错误的提示方式,见上方说明 ( 默认 message )
+ * @property {Boolean} borderBottom 是否显示表单域的下划线边框 ( 默认 true )
+ * @property {String} labelPosition 表单域提示文字的位置,left-左侧,top-上方 ( 默认 'left' )
+ * @property {String | Number} labelWidth 提示文字的宽度,单位px ( 默认 45 )
+ * @property {String} labelAlign lable字体的对齐方式 ( 默认 ‘left' )
+ * @property {Object} labelStyle lable的样式,对象形式
+ * @example <uv-form labelPosition="left" :model="model1" :rules="rules" ref="form1"></uv-form>
+ */
+ export default {
+ name: "uv-form",
+ mixins: [mpMixin, mixin, props],
+ provide() {
+ return {
+ uForm: this,
+ };
+ },
+ data() {
+ return {
+ formRules: {},
+ // 规则校验器
+ validator: {},
+ // 原始的model快照,用于resetFields方法重置表单时使用
+ originalModel: null,
+ };
+ },
+ watch: {
+ // 监听规则的变化
+ rules: {
+ immediate: true,
+ handler(n) {
+ this.setRules(n);
+ },
+ },
+ // 监听属性的变化,通知子组件uv-form-item重新获取信息
+ propsChange(n) {
+ if (this.children?.length) {
+ this.children.map((child) => {
+ // 判断子组件(uv-form-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+ typeof child.updateParentData == "function" &&
+ child.updateParentData();
+ });
+ }
+ },
+ // 监听model的初始值作为重置表单的快照
+ model: {
+ immediate: true,
+ handler(n) {
+ if (!this.originalModel) {
+ this.originalModel = this.$uv.deepClone(n);
+ }
+ },
+ },
+ },
+ computed: {
+ propsChange() {
+ return [
+ this.errorType,
+ this.borderBottom,
+ this.labelPosition,
+ this.labelWidth,
+ this.labelAlign,
+ this.labelStyle,
+ ];
+ },
+ },
+ created() {
+ // 存储当前form下的所有uv-form-item的实例
+ // 不能定义在data中,否则微信小程序会造成循环引用而报错
+ this.children = [];
+ },
+ methods: {
+ // 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
+ setRules(rules) {
+ // 判断是否有规则
+ if (Object.keys(rules).length === 0) return;
+ if (process.env.NODE_ENV === 'development' && Object.keys(this.model).length === 0) {
+ this.$uv.error('设置rules,model必须设置!如果已经设置,请刷新页面。');
+ return;
+ };
+ this.formRules = rules;
+ // 重新将规则赋予Validator
+ this.validator = new Schema(rules);
+ },
+ // 清空所有uv-form-item组件的内容,本质上是调用了uv-form-item组件中的resetField()方法
+ resetFields() {
+ this.resetModel();
+ },
+ // 重置model为初始值的快照
+ resetModel(obj) {
+ // 历遍所有uv-form-item,根据其prop属性,还原model的原始快照
+ this.children.map((child) => {
+ const prop = child?.prop;
+ const value = this.$uv.getProperty(this.originalModel, prop);
+ this.$uv.setProperty(this.model, prop, value);
+ });
+ },
+ // 清空校验结果
+ clearValidate(props) {
+ props = [].concat(props);
+ this.children.map((child) => {
+ // 如果uv-form-item的prop在props数组中,则清除对应的校验结果信息
+ if (props[0] === undefined || props.includes(child.prop)) {
+ child.message = null;
+ }
+ });
+ },
+ // 对部分表单字段进行校验
+ async validateField(value, callback, event = null) {
+ // $nextTick是必须的,否则model的变更,可能会延后于此方法的执行
+ this.$nextTick(() => {
+ // 校验错误信息,返回给回调方法,用于存放所有form-item的错误信息
+ const errorsRes = [];
+ // 如果为字符串,转为数组
+ value = [].concat(value);
+ // 历遍children所有子form-item
+ this.children.map((child) => {
+ // 用于存放form-item的错误信息
+ const childErrors = [];
+ if (value.includes(child.prop)) {
+ // 获取对应的属性,通过类似'a.b.c'的形式
+ const propertyVal = this.$uv.getProperty(
+ this.model,
+ child.prop
+ );
+ // 属性链数组
+ const propertyChain = child.prop.split(".");
+ const propertyName =
+ propertyChain[propertyChain.length - 1];
+
+ const rule = this.formRules[child.prop];
+ // 如果不存在对应的规则,直接返回,否则校验器会报错
+ if (!rule) return;
+ // rule规则可为数组形式,也可为对象形式,此处拼接成为数组
+ const rules = [].concat(rule);
+
+ // 对rules数组进行校验
+ for (let i = 0; i < rules.length; i++) {
+ const ruleItem = rules[i];
+ // 将uv-form-item的触发器转为数组形式
+ const trigger = [].concat(ruleItem?.trigger);
+ // 如果是有传入触发事件,但是此form-item却没有配置此触发器的话,不执行校验操作
+ if (event && !trigger.includes(event)) continue;
+ // 实例化校验对象,传入构造规则
+ const validator = new Schema({
+ [propertyName]: ruleItem,
+ });
+ validator.validate({
+ [propertyName]: propertyVal,
+ },
+ (errors, fields) => {
+ if (this.$uv.test.array(errors)) {
+ errorsRes.push(...errors);
+ childErrors.push(...errors);
+ }
+ this.$nextTick(() => {
+ child.message = childErrors[0]?.message ? childErrors[0]?.message : null;
+ })
+ }
+ );
+ }
+ }
+ });
+ // 执行回调函数
+ typeof callback === "function" && callback(errorsRes);
+ });
+ },
+ // 校验全部数据
+ validate(callback) {
+ return new Promise((resolve, reject) => {
+ // $nextTick是必须的,否则model的变更,可能会延后于validate方法
+ this.$nextTick(() => {
+ // 获取所有form-item的prop,交给validateField方法进行校验
+ const formItemProps = this.children.map(
+ (item) => item.prop
+ );
+ this.validateField(formItemProps, (errors) => {
+ if (errors.length) {
+ // 如果错误提示方式为toast,则进行提示
+ this.errorType === 'toast' && this.$uv.toast(errors[0].message)
+ reject(errors)
+ } else {
+ resolve(true)
+ }
+ });
+ });
+ });
+ },
+ },
+ };
+</script>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-form/components/uv-form/valid.js b/ledApp/uni_modules/uv-form/components/uv-form/valid.js
new file mode 100644
index 0000000..9e114df
--- /dev/null
+++ b/ledApp/uni_modules/uv-form/components/uv-form/valid.js
@@ -0,0 +1,1343 @@
+function _extends() {
+ _extends = Object.assign || function (target) {
+ for (let i = 1; i < arguments.length; i++) {
+ const source = arguments[i]
+
+ for (const key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key]
+ }
+ }
+ }
+
+ return target
+ }
+
+ return _extends.apply(this, arguments)
+}
+
+/* eslint no-console:0 */
+const formatRegExp = /%[sdj%]/g
+let warning = function warning() {} // don't print warning message when in production env or node runtime
+
+if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production' && typeof window
+ !== 'undefined' && typeof document !== 'undefined') {
+ warning = function warning(type, errors) {
+ if (typeof console !== 'undefined' && console.warn) {
+ if (errors.every((e) => typeof e === 'string')) {
+ console.warn(type, errors)
+ }
+ }
+ }
+}
+
+function convertFieldsError(errors) {
+ if (!errors || !errors.length) return null
+ const fields = {}
+ errors.forEach((error) => {
+ const { field } = error
+ fields[field] = fields[field] || []
+ fields[field].push(error)
+ })
+ return fields
+}
+
+function format() {
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key]
+ }
+
+ let i = 1
+ const f = args[0]
+ const len = args.length
+
+ if (typeof f === 'function') {
+ return f.apply(null, args.slice(1))
+ }
+
+ if (typeof f === 'string') {
+ let str = String(f).replace(formatRegExp, (x) => {
+ if (x === '%%') {
+ return '%'
+ }
+
+ if (i >= len) {
+ return x
+ }
+
+ switch (x) {
+ case '%s':
+ return String(args[i++])
+
+ case '%d':
+ return Number(args[i++])
+
+ case '%j':
+ try {
+ return JSON.stringify(args[i++])
+ } catch (_) {
+ return '[Circular]'
+ }
+
+ break
+
+ default:
+ return x
+ }
+ })
+
+ for (let arg = args[i]; i < len; arg = args[++i]) {
+ str += ` ${arg}`
+ }
+
+ return str
+ }
+
+ return f
+}
+
+function isNativeStringType(type) {
+ return type === 'string' || type === 'url' || type === 'hex' || type === 'email' || type === 'pattern'
+}
+
+function isEmptyValue(value, type) {
+ if (value === undefined || value === null) {
+ return true
+ }
+
+ if (type === 'array' && Array.isArray(value) && !value.length) {
+ return true
+ }
+
+ if (isNativeStringType(type) && typeof value === 'string' && !value) {
+ return true
+ }
+
+ return false
+}
+
+function asyncParallelArray(arr, func, callback) {
+ const results = []
+ let total = 0
+ const arrLength = arr.length
+
+ function count(errors) {
+ results.push.apply(results, errors)
+ total++
+
+ if (total === arrLength) {
+ callback(results)
+ }
+ }
+
+ arr.forEach((a) => {
+ func(a, count)
+ })
+}
+
+function asyncSerialArray(arr, func, callback) {
+ let index = 0
+ const arrLength = arr.length
+
+ function next(errors) {
+ if (errors && errors.length) {
+ callback(errors)
+ return
+ }
+
+ const original = index
+ index += 1
+
+ if (original < arrLength) {
+ func(arr[original], next)
+ } else {
+ callback([])
+ }
+ }
+
+ next([])
+}
+
+function flattenObjArr(objArr) {
+ const ret = []
+ Object.keys(objArr).forEach((k) => {
+ ret.push.apply(ret, objArr[k])
+ })
+ return ret
+}
+
+function asyncMap(objArr, option, func, callback) {
+ if (option.first) {
+ const _pending = new Promise((resolve, reject) => {
+ const next = function next(errors) {
+ callback(errors)
+ return errors.length ? reject({
+ errors,
+ fields: convertFieldsError(errors)
+ }) : resolve()
+ }
+
+ const flattenArr = flattenObjArr(objArr)
+ asyncSerialArray(flattenArr, func, next)
+ })
+
+ _pending.catch((e) => e)
+
+ return _pending
+ }
+
+ let firstFields = option.firstFields || []
+
+ if (firstFields === true) {
+ firstFields = Object.keys(objArr)
+ }
+
+ const objArrKeys = Object.keys(objArr)
+ const objArrLength = objArrKeys.length
+ let total = 0
+ const results = []
+ const pending = new Promise((resolve, reject) => {
+ const next = function next(errors) {
+ results.push.apply(results, errors)
+ total++
+
+ if (total === objArrLength) {
+ callback(results)
+ return results.length ? reject({
+ errors: results,
+ fields: convertFieldsError(results)
+ }) : resolve()
+ }
+ }
+
+ if (!objArrKeys.length) {
+ callback(results)
+ resolve()
+ }
+
+ objArrKeys.forEach((key) => {
+ const arr = objArr[key]
+
+ if (firstFields.indexOf(key) !== -1) {
+ asyncSerialArray(arr, func, next)
+ } else {
+ asyncParallelArray(arr, func, next)
+ }
+ })
+ })
+ pending.catch((e) => e)
+ return pending
+}
+
+function complementError(rule) {
+ return function (oe) {
+ if (oe && oe.message) {
+ oe.field = oe.field || rule.fullField
+ return oe
+ }
+
+ return {
+ message: typeof oe === 'function' ? oe() : oe,
+ field: oe.field || rule.fullField
+ }
+ }
+}
+
+function deepMerge(target, source) {
+ if (source) {
+ for (const s in source) {
+ if (source.hasOwnProperty(s)) {
+ const value = source[s]
+
+ if (typeof value === 'object' && typeof target[s] === 'object') {
+ target[s] = { ...target[s], ...value }
+ } else {
+ target[s] = value
+ }
+ }
+ }
+ }
+
+ return target
+}
+
+/**
+ * Rule for validating required fields.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param source The source object being validated.
+ * @param errors An array of errors that this rule may add
+ * validation errors to.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function required(rule, value, source, errors, options, type) {
+ if (rule.required && (!source.hasOwnProperty(rule.field) || isEmptyValue(value, type || rule.type))) {
+ errors.push(format(options.messages.required, rule.fullField))
+ }
+}
+
+/**
+ * Rule for validating whitespace.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param source The source object being validated.
+ * @param errors An array of errors that this rule may add
+ * validation errors to.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function whitespace(rule, value, source, errors, options) {
+ if (/^\s+$/.test(value) || value === '') {
+ errors.push(format(options.messages.whitespace, rule.fullField))
+ }
+}
+
+/* eslint max-len:0 */
+
+const pattern = {
+ // http://emailregex.com/
+ email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
+ url: new RegExp(
+ '^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$',
+ 'i'
+ ),
+ hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i
+}
+var types = {
+ integer: function integer(value) {
+ return /^(-)?\d+$/.test(value);
+ },
+ float: function float(value) {
+ return /^(-)?\d+(\.\d+)?$/.test(value);
+ },
+ array: function array(value) {
+ return Array.isArray(value)
+ },
+ regexp: function regexp(value) {
+ if (value instanceof RegExp) {
+ return true
+ }
+
+ try {
+ return !!new RegExp(value)
+ } catch (e) {
+ return false
+ }
+ },
+ date: function date(value) {
+ return typeof value.getTime === 'function' && typeof value.getMonth === 'function' && typeof value.getYear
+ === 'function'
+ },
+ number: function number(value) {
+ if (isNaN(value)) {
+ return false
+ }
+
+ // 修改源码,将字符串数值先转为数值
+ return typeof +value === 'number'
+ },
+ object: function object(value) {
+ return typeof value === 'object' && !types.array(value)
+ },
+ method: function method(value) {
+ return typeof value === 'function'
+ },
+ email: function email(value) {
+ return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255
+ },
+ url: function url(value) {
+ return typeof value === 'string' && !!value.match(pattern.url)
+ },
+ hex: function hex(value) {
+ return typeof value === 'string' && !!value.match(pattern.hex)
+ }
+}
+/**
+ * Rule for validating the type of a value.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param source The source object being validated.
+ * @param errors An array of errors that this rule may add
+ * validation errors to.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function type(rule, value, source, errors, options) {
+ if (rule.required && value === undefined) {
+ required(rule, value, source, errors, options)
+ return
+ }
+
+ const custom = ['integer', 'float', 'array', 'regexp', 'object', 'method', 'email', 'number', 'date', 'url', 'hex']
+ const ruleType = rule.type
+
+ if (custom.indexOf(ruleType) > -1) {
+ if (!types[ruleType](value)) {
+ errors.push(format(options.messages.types[ruleType], rule.fullField, rule.type))
+ } // straight typeof check
+ } else if (ruleType && typeof value !== rule.type) {
+ errors.push(format(options.messages.types[ruleType], rule.fullField, rule.type))
+ }
+}
+
+/**
+ * Rule for validating minimum and maximum allowed values.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param source The source object being validated.
+ * @param errors An array of errors that this rule may add
+ * validation errors to.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function range(rule, value, source, errors, options) {
+ const len = typeof rule.len === 'number'
+ const min = typeof rule.min === 'number'
+ const max = typeof rule.max === 'number' // 正则匹配码点范围从U+010000一直到U+10FFFF的文字(补充平面Supplementary Plane)
+
+ const spRegexp = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g
+ let val = value
+ let key = null
+ const num = typeof value === 'number'
+ const str = typeof value === 'string'
+ const arr = Array.isArray(value)
+
+ if (num) {
+ key = 'number'
+ } else if (str) {
+ key = 'string'
+ } else if (arr) {
+ key = 'array'
+ } // if the value is not of a supported type for range validation
+ // the validation rule rule should use the
+ // type property to also test for a particular type
+
+ if (!key) {
+ return false
+ }
+
+ if (arr) {
+ val = value.length
+ }
+
+ if (str) {
+ // 处理码点大于U+010000的文字length属性不准确的bug,如"𠮷𠮷𠮷".lenght !== 3
+ val = value.replace(spRegexp, '_').length
+ }
+
+ if (len) {
+ if (val !== rule.len) {
+ errors.push(format(options.messages[key].len, rule.fullField, rule.len))
+ }
+ } else if (min && !max && val < rule.min) {
+ errors.push(format(options.messages[key].min, rule.fullField, rule.min))
+ } else if (max && !min && val > rule.max) {
+ errors.push(format(options.messages[key].max, rule.fullField, rule.max))
+ } else if (min && max && (val < rule.min || val > rule.max)) {
+ errors.push(format(options.messages[key].range, rule.fullField, rule.min, rule.max))
+ }
+}
+
+const ENUM = 'enum'
+/**
+ * Rule for validating a value exists in an enumerable list.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param source The source object being validated.
+ * @param errors An array of errors that this rule may add
+ * validation errors to.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function enumerable(rule, value, source, errors, options) {
+ rule[ENUM] = Array.isArray(rule[ENUM]) ? rule[ENUM] : []
+
+ if (rule[ENUM].indexOf(value) === -1) {
+ errors.push(format(options.messages[ENUM], rule.fullField, rule[ENUM].join(', ')))
+ }
+}
+
+/**
+ * Rule for validating a regular expression pattern.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param source The source object being validated.
+ * @param errors An array of errors that this rule may add
+ * validation errors to.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function pattern$1(rule, value, source, errors, options) {
+ if (rule.pattern) {
+ if (rule.pattern instanceof RegExp) {
+ // if a RegExp instance is passed, reset `lastIndex` in case its `global`
+ // flag is accidentally set to `true`, which in a validation scenario
+ // is not necessary and the result might be misleading
+ rule.pattern.lastIndex = 0
+
+ if (!rule.pattern.test(value)) {
+ errors.push(format(options.messages.pattern.mismatch, rule.fullField, value, rule.pattern))
+ }
+ } else if (typeof rule.pattern === 'string') {
+ const _pattern = new RegExp(rule.pattern)
+
+ if (!_pattern.test(value)) {
+ errors.push(format(options.messages.pattern.mismatch, rule.fullField, value, rule.pattern))
+ }
+ }
+ }
+}
+
+const rules = {
+ required,
+ whitespace,
+ type,
+ range,
+ enum: enumerable,
+ pattern: pattern$1
+}
+
+/**
+ * Performs validation for string types.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function string(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value, 'string') && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options, 'string')
+
+ if (!isEmptyValue(value, 'string')) {
+ rules.type(rule, value, source, errors, options)
+ rules.range(rule, value, source, errors, options)
+ rules.pattern(rule, value, source, errors, options)
+
+ if (rule.whitespace === true) {
+ rules.whitespace(rule, value, source, errors, options)
+ }
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates a function.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function method(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (value !== undefined) {
+ rules.type(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates a number.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function number(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (value === '') {
+ value = undefined
+ }
+
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (value !== undefined) {
+ rules.type(rule, value, source, errors, options)
+ rules.range(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates a boolean.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function _boolean(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (value !== undefined) {
+ rules.type(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates the regular expression type.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function regexp(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (!isEmptyValue(value)) {
+ rules.type(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates a number is an integer.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function integer(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (value !== undefined) {
+ rules.type(rule, value, source, errors, options)
+ rules.range(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates a number is a floating point number.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function floatFn(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (value !== undefined) {
+ rules.type(rule, value, source, errors, options)
+ rules.range(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates an array.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function array(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value, 'array') && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options, 'array')
+
+ if (!isEmptyValue(value, 'array')) {
+ rules.type(rule, value, source, errors, options)
+ rules.range(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates an object.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function object(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (value !== undefined) {
+ rules.type(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+const ENUM$1 = 'enum'
+/**
+ * Validates an enumerable list.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function enumerable$1(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (value !== undefined) {
+ rules[ENUM$1](rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Validates a regular expression pattern.
+ *
+ * Performs validation when a rule only contains
+ * a pattern property but is not declared as a string type.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function pattern$2(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value, 'string') && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (!isEmptyValue(value, 'string')) {
+ rules.pattern(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+function date(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+
+ if (!isEmptyValue(value)) {
+ let dateObject
+
+ if (typeof value === 'number') {
+ dateObject = new Date(value)
+ } else {
+ dateObject = value
+ }
+
+ rules.type(rule, dateObject, source, errors, options)
+
+ if (dateObject) {
+ rules.range(rule, dateObject.getTime(), source, errors, options)
+ }
+ }
+ }
+
+ callback(errors)
+}
+
+function required$1(rule, value, callback, source, options) {
+ const errors = []
+ const type = Array.isArray(value) ? 'array' : typeof value
+ rules.required(rule, value, source, errors, options, type)
+ callback(errors)
+}
+
+function type$1(rule, value, callback, source, options) {
+ const ruleType = rule.type
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value, ruleType) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options, ruleType)
+
+ if (!isEmptyValue(value, ruleType)) {
+ rules.type(rule, value, source, errors, options)
+ }
+ }
+
+ callback(errors)
+}
+
+/**
+ * Performs validation for any type.
+ *
+ * @param rule The validation rule.
+ * @param value The value of the field on the source object.
+ * @param callback The callback function.
+ * @param source The source object being validated.
+ * @param options The validation options.
+ * @param options.messages The validation messages.
+ */
+
+function any(rule, value, callback, source, options) {
+ const errors = []
+ const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)
+
+ if (validate) {
+ if (isEmptyValue(value) && !rule.required) {
+ return callback()
+ }
+
+ rules.required(rule, value, source, errors, options)
+ }
+
+ callback(errors)
+}
+
+const validators = {
+ string,
+ method,
+ number,
+ boolean: _boolean,
+ regexp,
+ integer,
+ float: floatFn,
+ array,
+ object,
+ enum: enumerable$1,
+ pattern: pattern$2,
+ date,
+ url: type$1,
+ hex: type$1,
+ email: type$1,
+ required: required$1,
+ any
+}
+
+function newMessages() {
+ return {
+ default: 'Validation error on field %s',
+ required: '%s is required',
+ enum: '%s must be one of %s',
+ whitespace: '%s cannot be empty',
+ date: {
+ format: '%s date %s is invalid for format %s',
+ parse: '%s date could not be parsed, %s is invalid ',
+ invalid: '%s date %s is invalid'
+ },
+ types: {
+ string: '%s is not a %s',
+ method: '%s is not a %s (function)',
+ array: '%s is not an %s',
+ object: '%s is not an %s',
+ number: '%s is not a %s',
+ date: '%s is not a %s',
+ boolean: '%s is not a %s',
+ integer: '%s is not an %s',
+ float: '%s is not a %s',
+ regexp: '%s is not a valid %s',
+ email: '%s is not a valid %s',
+ url: '%s is not a valid %s',
+ hex: '%s is not a valid %s'
+ },
+ string: {
+ len: '%s must be exactly %s characters',
+ min: '%s must be at least %s characters',
+ max: '%s cannot be longer than %s characters',
+ range: '%s must be between %s and %s characters'
+ },
+ number: {
+ len: '%s must equal %s',
+ min: '%s cannot be less than %s',
+ max: '%s cannot be greater than %s',
+ range: '%s must be between %s and %s'
+ },
+ array: {
+ len: '%s must be exactly %s in length',
+ min: '%s cannot be less than %s in length',
+ max: '%s cannot be greater than %s in length',
+ range: '%s must be between %s and %s in length'
+ },
+ pattern: {
+ mismatch: '%s value %s does not match pattern %s'
+ },
+ clone: function clone() {
+ const cloned = JSON.parse(JSON.stringify(this))
+ cloned.clone = this.clone
+ return cloned
+ }
+ }
+}
+const messages = newMessages()
+
+/**
+ * Encapsulates a validation schema.
+ *
+ * @param descriptor An object declaring validation rules
+ * for this schema.
+ */
+
+function Schema(descriptor) {
+ this.rules = null
+ this._messages = messages
+ this.define(descriptor)
+}
+
+Schema.prototype = {
+ messages: function messages(_messages) {
+ if (_messages) {
+ this._messages = deepMerge(newMessages(), _messages)
+ }
+
+ return this._messages
+ },
+ define: function define(rules) {
+ if (!rules) {
+ throw new Error('Cannot configure a schema with no rules')
+ }
+
+ if (typeof rules !== 'object' || Array.isArray(rules)) {
+ throw new Error('Rules must be an object')
+ }
+
+ this.rules = {}
+ let z
+ let item
+
+ for (z in rules) {
+ if (rules.hasOwnProperty(z)) {
+ item = rules[z]
+ this.rules[z] = Array.isArray(item) ? item : [item]
+ }
+ }
+ },
+ validate: function validate(source_, o, oc) {
+ const _this = this
+
+ if (o === void 0) {
+ o = {}
+ }
+
+ if (oc === void 0) {
+ oc = function oc() {}
+ }
+
+ let source = source_
+ let options = o
+ let callback = oc
+
+ if (typeof options === 'function') {
+ callback = options
+ options = {}
+ }
+
+ if (!this.rules || Object.keys(this.rules).length === 0) {
+ if (callback) {
+ callback()
+ }
+
+ return Promise.resolve()
+ }
+
+ function complete(results) {
+ let i
+ let errors = []
+ let fields = {}
+
+ function add(e) {
+ if (Array.isArray(e)) {
+ let _errors
+
+ errors = (_errors = errors).concat.apply(_errors, e)
+ } else {
+ errors.push(e)
+ }
+ }
+
+ for (i = 0; i < results.length; i++) {
+ add(results[i])
+ }
+
+ if (!errors.length) {
+ errors = null
+ fields = null
+ } else {
+ fields = convertFieldsError(errors)
+ }
+
+ callback(errors, fields)
+ }
+
+ if (options.messages) {
+ let messages$1 = this.messages()
+
+ if (messages$1 === messages) {
+ messages$1 = newMessages()
+ }
+
+ deepMerge(messages$1, options.messages)
+ options.messages = messages$1
+ } else {
+ options.messages = this.messages()
+ }
+
+ let arr
+ let value
+ const series = {}
+ const keys = options.keys || Object.keys(this.rules)
+ keys.forEach((z) => {
+ arr = _this.rules[z]
+ value = source[z]
+ arr.forEach((r) => {
+ let rule = r
+
+ if (typeof rule.transform === 'function') {
+ if (source === source_) {
+ source = { ...source }
+ }
+
+ value = source[z] = rule.transform(value)
+ }
+
+ if (typeof rule === 'function') {
+ rule = {
+ validator: rule
+ }
+ } else {
+ rule = { ...rule }
+ }
+
+ rule.validator = _this.getValidationMethod(rule)
+ rule.field = z
+ rule.fullField = rule.fullField || z
+ rule.type = _this.getType(rule)
+
+ if (!rule.validator) {
+ return
+ }
+
+ series[z] = series[z] || []
+ series[z].push({
+ rule,
+ value,
+ source,
+ field: z
+ })
+ })
+ })
+ const errorFields = {}
+ return asyncMap(series, options, (data, doIt) => {
+ const { rule } = data
+ let deep = (rule.type === 'object' || rule.type === 'array') && (typeof rule.fields === 'object' || typeof rule.defaultField
+ === 'object')
+ deep = deep && (rule.required || !rule.required && data.value)
+ rule.field = data.field
+
+ function addFullfield(key, schema) {
+ return { ...schema, fullField: `${rule.fullField}.${key}` }
+ }
+
+ function cb(e) {
+ if (e === void 0) {
+ e = []
+ }
+
+ let errors = e
+
+ if (!Array.isArray(errors)) {
+ errors = [errors]
+ }
+
+ if (!options.suppressWarning && errors.length) {
+ Schema.warning('async-validator:', errors)
+ }
+
+ if (errors.length && rule.message) {
+ errors = [].concat(rule.message)
+ }
+
+ errors = errors.map(complementError(rule))
+
+ if (options.first && errors.length) {
+ errorFields[rule.field] = 1
+ return doIt(errors)
+ }
+
+ if (!deep) {
+ doIt(errors)
+ } else {
+ // if rule is required but the target object
+ // does not exist fail at the rule level and don't
+ // go deeper
+ if (rule.required && !data.value) {
+ if (rule.message) {
+ errors = [].concat(rule.message).map(complementError(rule))
+ } else if (options.error) {
+ errors = [options.error(rule, format(options.messages.required, rule.field))]
+ } else {
+ errors = []
+ }
+
+ return doIt(errors)
+ }
+
+ let fieldsSchema = {}
+
+ if (rule.defaultField) {
+ for (const k in data.value) {
+ if (data.value.hasOwnProperty(k)) {
+ fieldsSchema[k] = rule.defaultField
+ }
+ }
+ }
+
+ fieldsSchema = { ...fieldsSchema, ...data.rule.fields }
+
+ for (const f in fieldsSchema) {
+ if (fieldsSchema.hasOwnProperty(f)) {
+ const fieldSchema = Array.isArray(fieldsSchema[f]) ? fieldsSchema[f] : [fieldsSchema[f]]
+ fieldsSchema[f] = fieldSchema.map(addFullfield.bind(null, f))
+ }
+ }
+
+ const schema = new Schema(fieldsSchema)
+ schema.messages(options.messages)
+
+ if (data.rule.options) {
+ data.rule.options.messages = options.messages
+ data.rule.options.error = options.error
+ }
+
+ schema.validate(data.value, data.rule.options || options, (errs) => {
+ const finalErrors = []
+
+ if (errors && errors.length) {
+ finalErrors.push.apply(finalErrors, errors)
+ }
+
+ if (errs && errs.length) {
+ finalErrors.push.apply(finalErrors, errs)
+ }
+
+ doIt(finalErrors.length ? finalErrors : null)
+ })
+ }
+ }
+
+ let res
+
+ if (rule.asyncValidator) {
+ res = rule.asyncValidator(rule, data.value, cb, data.source, options)
+ } else if (rule.validator) {
+ res = rule.validator(rule, data.value, cb, data.source, options)
+
+ if (res === true) {
+ cb()
+ } else if (res === false) {
+ cb(rule.message || `${rule.field} fails`)
+ } else if (res instanceof Array) {
+ cb(res)
+ } else if (res instanceof Error) {
+ cb(res.message)
+ }
+ }
+
+ if (res && res.then) {
+ res.then(() => cb(), (e) => cb(e))
+ }
+ }, (results) => {
+ complete(results)
+ })
+ },
+ getType: function getType(rule) {
+ if (rule.type === undefined && rule.pattern instanceof RegExp) {
+ rule.type = 'pattern'
+ }
+
+ if (typeof rule.validator !== 'function' && rule.type && !validators.hasOwnProperty(rule.type)) {
+ throw new Error(format('Unknown rule type %s', rule.type))
+ }
+
+ return rule.type || 'string'
+ },
+ getValidationMethod: function getValidationMethod(rule) {
+ if (typeof rule.validator === 'function') {
+ return rule.validator
+ }
+
+ const keys = Object.keys(rule)
+ const messageIndex = keys.indexOf('message')
+
+ if (messageIndex !== -1) {
+ keys.splice(messageIndex, 1)
+ }
+
+ if (keys.length === 1 && keys[0] === 'required') {
+ return validators.required
+ }
+
+ return validators[this.getType(rule)] || false
+ }
+}
+
+Schema.register = function register(type, validator) {
+ if (typeof validator !== 'function') {
+ throw new Error('Cannot register a validator by type, validator is not a function')
+ }
+
+ validators[type] = validator
+}
+
+Schema.warning = warning
+Schema.messages = messages
+
+export default Schema
+// # sourceMappingURL=index.js.map
diff --git a/ledApp/uni_modules/uv-form/package.json b/ledApp/uni_modules/uv-form/package.json
new file mode 100644
index 0000000..5e612c2
--- /dev/null
+++ b/ledApp/uni_modules/uv-form/package.json
@@ -0,0 +1,93 @@
+{
+ "id": "uv-form",
+ "displayName": "uv-form 表单 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.9",
+ "description": "此组件一般用于表单场景,可以配置Input输入框,Textarea文本域,Checkbox复选框,Radio单选框,开关选择器等,进行表单验证等。",
+ "keywords": [
+ "form",
+ "uvui",
+ "uv-ui",
+ "表单",
+ "表单验证"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-line",
+ "uv-transition",
+ "uv-action-sheet",
+ "uv-input",
+ "uv-button"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-form/readme.md b/ledApp/uni_modules/uv-form/readme.md
new file mode 100644
index 0000000..0458a6a
--- /dev/null
+++ b/ledApp/uni_modules/uv-form/readme.md
@@ -0,0 +1,19 @@
+## Form 表单
+
+> **组件名:uv-form**
+
+此组件一般用于表单场景,可以配置`Input`输入框,`Textarea`文本域,`Checkbox`复选框,`Radio`单选框,开关选择器等,进行表单验证等。
+
+# <a href="https://www.uvui.cn/components/form.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small style="font-size:14px;font-weight:700;">(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-gap/changelog.md b/ledApp/uni_modules/uv-gap/changelog.md
new file mode 100644
index 0000000..7d603b4
--- /dev/null
+++ b/ledApp/uni_modules/uv-gap/changelog.md
@@ -0,0 +1,5 @@
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增间隔槽组件
diff --git a/ledApp/uni_modules/uv-gap/components/uv-gap/props.js b/ledApp/uni_modules/uv-gap/components/uv-gap/props.js
new file mode 100644
index 0000000..0ad787c
--- /dev/null
+++ b/ledApp/uni_modules/uv-gap/components/uv-gap/props.js
@@ -0,0 +1,25 @@
+export default {
+ props: {
+ // 背景颜色(默认transparent)
+ bgColor: {
+ type: String,
+ default: 'transparent'
+ },
+ // 分割槽高度,单位px(默认20)
+ height: {
+ type: [String, Number],
+ default: 20
+ },
+ // 与上一个组件的距离
+ marginTop: {
+ type: [String, Number],
+ default: 0
+ },
+ // 与下一个组件的距离
+ marginBottom: {
+ type: [String, Number],
+ default: 0
+ },
+ ...uni.$uv?.props?.gap
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-gap/components/uv-gap/uv-gap.vue b/ledApp/uni_modules/uv-gap/components/uv-gap/uv-gap.vue
new file mode 100644
index 0000000..55720a1
--- /dev/null
+++ b/ledApp/uni_modules/uv-gap/components/uv-gap/uv-gap.vue
@@ -0,0 +1,36 @@
+<template>
+ <view class="uv-gap" :style="[gapStyle]"></view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * gap 间隔槽
+ * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量
+ * @tutorial https://www.uvui.cn/components/gap.html
+ * @property {String} bgColor 背景颜色 (默认 'transparent' )
+ * @property {String | Number} height 分割槽高度,单位px (默认 20 )
+ * @property {String | Number} marginTop 与前一个组件的距离,单位px( 默认 0 )
+ * @property {String | Number} marginBottom 与后一个组件的距离,单位px (默认 0 )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @example <uv-gap height="80" bg-color="#bbb"></uv-gap>
+ */
+ export default {
+ name: "uv-gap",
+ mixins: [mpMixin, mixin,props],
+ computed: {
+ gapStyle() {
+ const style = {
+ backgroundColor: this.bgColor,
+ height: this.$uv.addUnit(this.height),
+ marginTop: this.$uv.addUnit(this.marginTop),
+ marginBottom: this.$uv.addUnit(this.marginBottom),
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ }
+ };
+</script>
diff --git a/ledApp/uni_modules/uv-gap/package.json b/ledApp/uni_modules/uv-gap/package.json
new file mode 100644
index 0000000..3a17ec3
--- /dev/null
+++ b/ledApp/uni_modules/uv-gap/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-gap",
+ "displayName": "uv-gap 间隔槽 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.1",
+ "description": "该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量。",
+ "keywords": [
+ "gap",
+ "uvui",
+ "uv-ui",
+ "间隔槽",
+ "内容块"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-gap/readme.md b/ledApp/uni_modules/uv-gap/readme.md
new file mode 100644
index 0000000..dedd565
--- /dev/null
+++ b/ledApp/uni_modules/uv-gap/readme.md
@@ -0,0 +1,12 @@
+## Gap 间隔槽
+
+> **组件名:uv-gap**
+
+该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量。
+
+### <a href="https://www.uvui.cn/components/gap.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
+
diff --git a/ledApp/uni_modules/uv-grid/changelog.md b/ledApp/uni_modules/uv-grid/changelog.md
new file mode 100644
index 0000000..8c61261
--- /dev/null
+++ b/ledApp/uni_modules/uv-grid/changelog.md
@@ -0,0 +1,21 @@
+## 1.0.9(2023-12-06)
+1. 优化
+## 1.0.8(2023-12-06)
+1. 阻止事件冒泡问题
+## 1.0.7(2023-10-13)
+1. unmounted兼容vue3
+## 1.0.6(2023-08-14)
+1. 修复初始的时候闪动的BUG
+## 1.0.5(2023-06-22)
+1. 优化修改
+## 1.0.4(2023-06-21)
+1. 修复BUG
+## 1.0.3(2023-06-01)
+1. 修复点击触发两次事件的BUG
+## 1.0.2(2023-05-23)
+1. 优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-grid 宫格布局
diff --git a/ledApp/uni_modules/uv-grid/components/uv-grid-item/props.js b/ledApp/uni_modules/uv-grid/components/uv-grid-item/props.js
new file mode 100644
index 0000000..6b86298
--- /dev/null
+++ b/ledApp/uni_modules/uv-grid/components/uv-grid-item/props.js
@@ -0,0 +1,15 @@
+export default {
+ props: {
+ // 宫格的name
+ name: {
+ type: [String, Number, null],
+ default: null
+ },
+ // 背景颜色
+ bgColor: {
+ type: String,
+ default: 'transparent'
+ },
+ ...uni.$uv?.props?.gridItem
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-grid/components/uv-grid-item/uv-grid-item.vue b/ledApp/uni_modules/uv-grid/components/uv-grid-item/uv-grid-item.vue
new file mode 100644
index 0000000..9412852
--- /dev/null
+++ b/ledApp/uni_modules/uv-grid/components/uv-grid-item/uv-grid-item.vue
@@ -0,0 +1,226 @@
+<template>
+ <!-- #ifndef APP-NVUE -->
+ <view
+ class="uv-grid-item"
+ hover-class="uv-grid-item--hover-class"
+ :hover-stay-time="200"
+ @tap="clickHandler"
+ :class="classes"
+ :style="[itemStyle]"
+ >
+ <slot />
+ </view>
+ <!-- #endif -->
+ <!-- #ifdef APP-NVUE -->
+ <view
+ class="uv-grid-item"
+ :hover-stay-time="200"
+ @tap="clickHandler"
+ :class="classes"
+ :style="[itemStyle]"
+ >
+ <slot />
+ </view>
+ <!-- #endif -->
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * gridItem 提示
+ * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配uv-grid使用
+ * @tutorial https://www.uvui.cn/components/grid.html
+ * @property {String | Number} name 宫格的name ( 默认 null )
+ * @property {String} bgColor 宫格的背景颜色 (默认 'transparent' )
+ * @property {Object} customStyle 自定义样式,对象形式
+ * @event {Function} click 点击宫格触发
+ * @example <uv-grid-item></uv-grid-item>
+ */
+ export default {
+ name: "uv-grid-item",
+ mixins: [mpMixin, mixin, props],
+ emits: ['$uvGridItem','click'],
+ data() {
+ return {
+ parentData: {
+ col: 3, // 父组件划分的宫格数
+ border: true, // 是否显示边框,根据父组件决定
+ },
+ // #ifdef APP-NVUE
+ width: 0, // nvue下才这么计算,vue下放到computed中,否则会因为延时造成闪烁
+ // #endif
+ classes: [], // 类名集合,用于判断是否显示右边和下边框
+ };
+ },
+ created() {
+ // 父组件的实例
+ this.updateParentData()
+ },
+ mounted() {
+ this.init()
+ },
+ computed: {
+ // #ifndef APP-NVUE
+ // vue下放到computed中,否则会因为延时造成闪烁
+ width() {
+ return 100 / Number(this.parentData.col) + '%'
+ },
+ // #endif
+ itemStyle() {
+ const style = {
+ background: this.bgColor,
+ width: this.width
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ methods: {
+ init() {
+ // 用于在父组件uv-grid的children中被添加入子组件时,
+ // 重新计算item的边框
+ uni.$on('$uvGridItem', () => {
+ this.gridItemClasses()
+ })
+ // #ifdef APP-NVUE
+ // 获取元素该有的长度,nvue下要延时才准确
+ this.$nextTick(function(){
+ this.getItemWidth()
+ })
+ // #endif
+ // 发出事件,通知所有的grid-item都重新计算自己的边框
+ uni.$emit('$uvGridItem')
+ this.gridItemClasses()
+ },
+ // 获取父组件的参数
+ updateParentData() {
+ // 此方法写在mixin中
+ this.getParentData('uv-grid');
+ },
+ clickHandler() {
+ let name = this.name
+ // 如果没有设置name属性,历遍父组件的children数组,判断当前的元素是否和本实例this相等,找出当前组件的索引
+ const children = this.parent?.children
+ if(children && this.name === null) {
+ name = children.findIndex(child => child === this)
+ }
+ // 调用父组件方法,发出事件
+ this.parent && this.parent.childClick(name)
+ this.$emit('click', name)
+ },
+ async getItemWidth() {
+ // 如果是nvue,不能使用百分比,只能使用固定宽度
+ let width = 0
+ if(this.parent) {
+ // 获取父组件宽度后,除以栅格数,得出每个item的宽度
+ const parentWidth = await this.getParentWidth()
+ width = parentWidth / Number(this.parentData.col) + 'px'
+ }
+ this.width = width
+ },
+ // 获取父元素的尺寸
+ getParentWidth() {
+ // #ifdef APP-NVUE
+ // 返回一个promise,让调用者可以用await同步获取
+ const dom = uni.requireNativePlugin('dom')
+ return new Promise(resolve => {
+ // 调用父组件的ref
+ dom.getComponentRect(this.parent.$refs['uv-grid'], res => {
+ resolve(res.size.width)
+ })
+ })
+ // #endif
+ },
+ gridItemClasses() {
+ if(this.parentData.border) {
+ let classes = []
+ this.parent.children.map((child, index) =>{
+ if(this === child) {
+ const len = this.parent.children.length
+ // 贴近右边屏幕边沿的child,并且最后一个(比如只有横向2个的时候),无需右边框
+ if((index + 1) % this.parentData.col !== 0 && index + 1 !== len) {
+ classes.push('uv-border-right')
+ }
+ // 总的宫格数量对列数取余的值
+ // 如果取余后,值为0,则意味着要将最后一排的宫格,都不需要下边框
+ const lessNum = len % this.parentData.col === 0 ? this.parentData.col : len % this.parentData.col
+ // 最下面的一排child,无需下边框
+ if(index < len - lessNum) {
+ classes.push('uv-border-bottom')
+ }
+ }
+ })
+ // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+ // #ifdef MP-ALIPAY || MP-TOUTIAO
+ classes = classes.join(' ')
+ // #endif
+ this.classes = classes
+ }
+ }
+ },
+ // #ifdef VUE2
+ beforeDestroy() {
+ // 移除事件监听,释放性能
+ uni.$off('$uvGridItem')
+ },
+ // #endif
+ // #ifdef VUE3
+ unmounted() {
+ // 移除事件监听,释放性能
+ uni.$off('$uvGridItem')
+ }
+ // #endif
+ };
+</script>
+
+<style lang="scss" scoped>
+ $show-border: 1;
+ $show-border-right: 1;
+ $show-border-bottom: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-grid-item-hover-class-opcatiy:.5 !default;
+ $uv-grid-item-margin-top:1rpx !default;
+ $uv-grid-item-border-right-width:0.5px !default;
+ $uv-grid-item-border-bottom-width:0.5px !default;
+ $uv-grid-item-border-right-color:$uv-border-color !default;
+ $uv-grid-item-border-bottom-color:$uv-border-color !default;
+ .uv-grid-item {
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ flex-direction: column;
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ display: flex;
+ /* #endif */
+
+ /* #ifdef MP */
+ position: relative;
+ float: left;
+ /* #endif */
+
+ /* #ifdef MP-WEIXIN */
+ margin-top:$uv-grid-item-margin-top;
+ /* #endif */
+
+ &--hover-class {
+ opacity:$uv-grid-item-hover-class-opcatiy;
+ }
+ }
+
+ /* #ifdef APP-NVUE */
+ // 由于nvue不支持组件内引入app.vue中再引入的样式,所以需要写在这里
+ .uv-border-right {
+ border-right-width:$uv-grid-item-border-right-width;
+ border-color: $uv-grid-item-border-right-color;
+ }
+
+ .uv-border-bottom {
+ border-bottom-width:$uv-grid-item-border-bottom-width;
+ border-color:$uv-grid-item-border-bottom-color;
+ }
+ /* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-grid/components/uv-grid/props.js b/ledApp/uni_modules/uv-grid/components/uv-grid/props.js
new file mode 100644
index 0000000..d5f8474
--- /dev/null
+++ b/ledApp/uni_modules/uv-grid/components/uv-grid/props.js
@@ -0,0 +1,20 @@
+export default {
+ props: {
+ // 分成几列
+ col: {
+ type: [String, Number],
+ default: 3
+ },
+ // 是否显示边框
+ border: {
+ type: Boolean,
+ default: false
+ },
+ // 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右
+ align: {
+ type: String,
+ default: 'left'
+ },
+ ...uni.$uv?.props?.grid
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-grid/components/uv-grid/uv-grid.vue b/ledApp/uni_modules/uv-grid/components/uv-grid/uv-grid.vue
new file mode 100644
index 0000000..0314b18
--- /dev/null
+++ b/ledApp/uni_modules/uv-grid/components/uv-grid/uv-grid.vue
@@ -0,0 +1,100 @@
+<template>
+ <view
+ class="uv-grid"
+ ref='uv-grid'
+ :style="[gridStyle]"
+ >
+ <slot />
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * grid 宫格布局
+ * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。
+ * @tutorial https://www.uvui.cn/components/grid.html
+ * @property {String | Number} col 宫格的列数(默认 3 )
+ * @property {Boolean} border 是否显示宫格的边框(默认 false )
+ * @property {String} align 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右 (默认 'left' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @event {Function} click 点击宫格触发
+ * @example <uv-grid :col="3" @click="click"></uv-grid>
+ */
+ export default {
+ name: 'uv-grid',
+ mixins: [mpMixin, mixin, props],
+ emits: ['click'],
+ data() {
+ return {
+ index: 0,
+ width: 0
+ }
+ },
+ watch: {
+ // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+ parentData() {
+ if (this.children.length) {
+ this.children.map(child => {
+ // 判断子组件(uv-radio)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+ typeof(child.updateParentData) == 'function' && child.updateParentData();
+ })
+ }
+ },
+ },
+ created() {
+ // 如果将children定义在data中,在微信小程序会造成循环引用而报错
+ this.children = []
+ },
+ computed: {
+ // 计算父组件的值是否发生变化
+ parentData() {
+ return [this.hoverClass, this.col, this.size, this.border];
+ },
+ // 宫格对齐方式
+ gridStyle() {
+ let style = {};
+ switch (this.align) {
+ case 'left':
+ style.justifyContent = 'flex-start';
+ break;
+ case 'center':
+ style.justifyContent = 'center';
+ break;
+ case 'right':
+ style.justifyContent = 'flex-end';
+ break;
+ default:
+ style.justifyContent = 'flex-start';
+ };
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+ }
+ },
+ methods: {
+ // 此方法由uv-grid-item触发,用于在uv-grid发出事件
+ childClick(name) {
+ this.$emit('click', name)
+ }
+ }
+ };
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ $uv-grid-width:100% !default;
+ .uv-grid {
+ /* #ifdef MP */
+ width: $uv-grid-width;
+ position: relative;
+ box-sizing: border-box;
+ overflow: hidden;
+ display: block;
+ /* #endif */
+ justify-content: center;
+ @include flex;
+ flex-wrap: wrap;
+ align-items: center;
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-grid/package.json b/ledApp/uni_modules/uv-grid/package.json
new file mode 100644
index 0000000..0c93379
--- /dev/null
+++ b/ledApp/uni_modules/uv-grid/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-grid",
+ "displayName": "uv-grid 宫格布局 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.9",
+ "description": "uv-grid 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。",
+ "keywords": [
+ "uv-grid",
+ "uvui",
+ "uv-ui",
+ "宫格布局",
+ "grid"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-grid/readme.md b/ledApp/uni_modules/uv-grid/readme.md
new file mode 100644
index 0000000..cd13cef
--- /dev/null
+++ b/ledApp/uni_modules/uv-grid/readme.md
@@ -0,0 +1,17 @@
+## Grid 宫格布局
+
+> **组件名:uv-grid**
+
+宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。
+
+# <a href="https://www.uvui.cn/components/grid.html" target="_blank">查看文档</a>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-icon/changelog.md b/ledApp/uni_modules/uv-icon/changelog.md
new file mode 100644
index 0000000..c610827
--- /dev/null
+++ b/ledApp/uni_modules/uv-icon/changelog.md
@@ -0,0 +1,31 @@
+## 1.0.13(2023-12-06)
+1. 优化
+## 1.0.12(2023-12-06)
+1. 阻止事件冒泡处理
+## 1.0.11(2023-10-29)
+1. imgMode默认值改成aspectFit
+## 1.0.10(2023-08-13)
+1. 优化nvue,方便自定义图标
+## 1.0.9(2023-07-28)
+1. 修改几个对应错误图标的BUG
+## 1.0.8(2023-07-24)
+1. 优化 支持base64图片
+## 1.0.7(2023-07-17)
+1. 修复 uv-icon 恢复uv-empty相关的图标
+## 1.0.6(2023-07-13)
+1. 修复icon设置name属性对应图标错误的BUG
+## 1.0.5(2023-07-04)
+1. 更新图标,删除一些不常用的图标
+2. 删除base64,修改成ttf文件引入读取图标
+3. 自定义图标文档说明:https://www.uvui.cn/guide/customIcon.html
+## 1.0.4(2023-07-03)
+1. 修复主题颜色在APP不生效的BUG
+## 1.0.3(2023-05-24)
+1. 将线上ttf字体包替换成base64,避免加载时或者网络差时候显示白色方块
+## 1.0.2(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.1(2023-05-10)
+1. 修复小程序中异常显示
+## 1.0.0(2023-05-04)
+新发版
diff --git a/ledApp/uni_modules/uv-icon/components/uv-icon/icons.js b/ledApp/uni_modules/uv-icon/components/uv-icon/icons.js
new file mode 100644
index 0000000..8469a2d
--- /dev/null
+++ b/ledApp/uni_modules/uv-icon/components/uv-icon/icons.js
@@ -0,0 +1,160 @@
+export default {
+ 'uvicon-level': 'e68f',
+ 'uvicon-checkbox-mark': 'e659',
+ 'uvicon-folder': 'e694',
+ 'uvicon-movie': 'e67c',
+ 'uvicon-star-fill': 'e61e',
+ 'uvicon-star': 'e618',
+ 'uvicon-phone-fill': 'e6ac',
+ 'uvicon-phone': 'e6ba',
+ 'uvicon-apple-fill': 'e635',
+ 'uvicon-backspace': 'e64d',
+ 'uvicon-attach': 'e640',
+ 'uvicon-empty-data': 'e671',
+ 'uvicon-empty-address': 'e68a',
+ 'uvicon-empty-favor': 'e662',
+ 'uvicon-empty-car': 'e657',
+ 'uvicon-empty-order': 'e66b',
+ 'uvicon-empty-list': 'e672',
+ 'uvicon-empty-search': 'e677',
+ 'uvicon-empty-permission': 'e67d',
+ 'uvicon-empty-news': 'e67e',
+ 'uvicon-empty-history': 'e685',
+ 'uvicon-empty-coupon': 'e69b',
+ 'uvicon-empty-page': 'e60e',
+ 'uvicon-empty-wifi-off': 'e6cc',
+ 'uvicon-reload': 'e627',
+ 'uvicon-order': 'e695',
+ 'uvicon-server-man': 'e601',
+ 'uvicon-search': 'e632',
+ 'uvicon-more-dot-fill': 'e66f',
+ 'uvicon-scan': 'e631',
+ 'uvicon-map': 'e665',
+ 'uvicon-map-fill': 'e6a8',
+ 'uvicon-tags': 'e621',
+ 'uvicon-tags-fill': 'e613',
+ 'uvicon-eye': 'e664',
+ 'uvicon-eye-fill': 'e697',
+ 'uvicon-eye-off': 'e69c',
+ 'uvicon-eye-off-outline': 'e688',
+ 'uvicon-mic': 'e66d',
+ 'uvicon-mic-off': 'e691',
+ 'uvicon-calendar': 'e65c',
+ 'uvicon-trash': 'e623',
+ 'uvicon-trash-fill': 'e6ce',
+ 'uvicon-play-left': 'e6bf',
+ 'uvicon-play-right': 'e6b3',
+ 'uvicon-minus': 'e614',
+ 'uvicon-plus': 'e625',
+ 'uvicon-info-circle': 'e69f',
+ 'uvicon-info-circle-fill': 'e6a7',
+ 'uvicon-question-circle': 'e622',
+ 'uvicon-question-circle-fill': 'e6bc',
+ 'uvicon-close': 'e65a',
+ 'uvicon-checkmark': 'e64a',
+ 'uvicon-checkmark-circle': 'e643',
+ 'uvicon-checkmark-circle-fill': 'e668',
+ 'uvicon-setting': 'e602',
+ 'uvicon-setting-fill': 'e6d0',
+ 'uvicon-heart': 'e6a2',
+ 'uvicon-heart-fill': 'e68b',
+ 'uvicon-camera': 'e642',
+ 'uvicon-camera-fill': 'e650',
+ 'uvicon-more-circle': 'e69e',
+ 'uvicon-more-circle-fill': 'e684',
+ 'uvicon-chat': 'e656',
+ 'uvicon-chat-fill': 'e63f',
+ 'uvicon-bag': 'e647',
+ 'uvicon-error-circle': 'e66e',
+ 'uvicon-error-circle-fill': 'e655',
+ 'uvicon-close-circle': 'e64e',
+ 'uvicon-close-circle-fill': 'e666',
+ 'uvicon-share': 'e629',
+ 'uvicon-share-fill': 'e6bb',
+ 'uvicon-share-square': 'e6c4',
+ 'uvicon-shopping-cart': 'e6cb',
+ 'uvicon-shopping-cart-fill': 'e630',
+ 'uvicon-bell': 'e651',
+ 'uvicon-bell-fill': 'e604',
+ 'uvicon-list': 'e690',
+ 'uvicon-list-dot': 'e6a9',
+ 'uvicon-zhifubao-circle-fill': 'e617',
+ 'uvicon-weixin-circle-fill': 'e6cd',
+ 'uvicon-weixin-fill': 'e620',
+ 'uvicon-qq-fill': 'e608',
+ 'uvicon-qq-circle-fill': 'e6b9',
+ 'uvicon-moments-circel-fill': 'e6c2',
+ 'uvicon-moments': 'e6a0',
+ 'uvicon-car': 'e64f',
+ 'uvicon-car-fill': 'e648',
+ 'uvicon-warning-fill': 'e6c7',
+ 'uvicon-warning': 'e6c1',
+ 'uvicon-clock-fill': 'e64b',
+ 'uvicon-clock': 'e66c',
+ 'uvicon-edit-pen': 'e65d',
+ 'uvicon-edit-pen-fill': 'e679',
+ 'uvicon-email': 'e673',
+ 'uvicon-email-fill': 'e683',
+ 'uvicon-minus-circle': 'e6a5',
+ 'uvicon-plus-circle': 'e603',
+ 'uvicon-plus-circle-fill': 'e611',
+ 'uvicon-file-text': 'e687',
+ 'uvicon-file-text-fill': 'e67f',
+ 'uvicon-pushpin': 'e6d1',
+ 'uvicon-pushpin-fill': 'e6b6',
+ 'uvicon-grid': 'e68c',
+ 'uvicon-grid-fill': 'e698',
+ 'uvicon-play-circle': 'e6af',
+ 'uvicon-play-circle-fill': 'e62a',
+ 'uvicon-pause-circle-fill': 'e60c',
+ 'uvicon-pause': 'e61c',
+ 'uvicon-pause-circle': 'e696',
+ 'uvicon-gift-fill': 'e6b0',
+ 'uvicon-gift': 'e680',
+ 'uvicon-kefu-ermai': 'e660',
+ 'uvicon-server-fill': 'e610',
+ 'uvicon-coupon-fill': 'e64c',
+ 'uvicon-coupon': 'e65f',
+ 'uvicon-integral': 'e693',
+ 'uvicon-integral-fill': 'e6b1',
+ 'uvicon-home-fill': 'e68e',
+ 'uvicon-home': 'e67b',
+ 'uvicon-account': 'e63a',
+ 'uvicon-account-fill': 'e653',
+ 'uvicon-thumb-down-fill': 'e628',
+ 'uvicon-thumb-down': 'e60a',
+ 'uvicon-thumb-up': 'e612',
+ 'uvicon-thumb-up-fill': 'e62c',
+ 'uvicon-lock-fill': 'e6a6',
+ 'uvicon-lock-open': 'e68d',
+ 'uvicon-lock-opened-fill': 'e6a1',
+ 'uvicon-lock': 'e69d',
+ 'uvicon-red-packet': 'e6c3',
+ 'uvicon-photo-fill': 'e6b4',
+ 'uvicon-photo': 'e60d',
+ 'uvicon-volume-off-fill': 'e6c8',
+ 'uvicon-volume-off': 'e6bd',
+ 'uvicon-volume-fill': 'e624',
+ 'uvicon-volume': 'e605',
+ 'uvicon-download': 'e670',
+ 'uvicon-arrow-up-fill': 'e636',
+ 'uvicon-arrow-down-fill': 'e638',
+ 'uvicon-play-left-fill': 'e6ae',
+ 'uvicon-play-right-fill': 'e6ad',
+ 'uvicon-arrow-downward': 'e634',
+ 'uvicon-arrow-leftward': 'e63b',
+ 'uvicon-arrow-rightward': 'e644',
+ 'uvicon-arrow-upward': 'e641',
+ 'uvicon-arrow-down': 'e63e',
+ 'uvicon-arrow-right': 'e63c',
+ 'uvicon-arrow-left': 'e646',
+ 'uvicon-arrow-up': 'e633',
+ 'uvicon-skip-back-left': 'e6c5',
+ 'uvicon-skip-forward-right': 'e61f',
+ 'uvicon-arrow-left-double': 'e637',
+ 'uvicon-man': 'e675',
+ 'uvicon-woman': 'e626',
+ 'uvicon-en': 'e6b8',
+ 'uvicon-twitte': 'e607',
+ 'uvicon-twitter-circle-fill': 'e6cf'
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-icon/components/uv-icon/props.js b/ledApp/uni_modules/uv-icon/components/uv-icon/props.js
new file mode 100644
index 0000000..7668cf9
--- /dev/null
+++ b/ledApp/uni_modules/uv-icon/components/uv-icon/props.js
@@ -0,0 +1,90 @@
+export default {
+ props: {
+ // 图标类名
+ name: {
+ type: String,
+ default: ''
+ },
+ // 图标颜色,可接受主题色
+ color: {
+ type: String,
+ default: '#606266'
+ },
+ // 字体大小,单位px
+ size: {
+ type: [String, Number],
+ default: '16px'
+ },
+ // 是否显示粗体
+ bold: {
+ type: Boolean,
+ default: false
+ },
+ // 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
+ index: {
+ type: [String, Number],
+ default: null
+ },
+ // 触摸图标时的类名
+ hoverClass: {
+ type: String,
+ default: ''
+ },
+ // 自定义扩展前缀,方便用户扩展自己的图标库
+ customPrefix: {
+ type: String,
+ default: 'uvicon'
+ },
+ // 图标右边或者下面的文字
+ label: {
+ type: [String, Number],
+ default: ''
+ },
+ // label的位置,只能右边或者下边
+ labelPos: {
+ type: String,
+ default: 'right'
+ },
+ // label的大小
+ labelSize: {
+ type: [String, Number],
+ default: '15px'
+ },
+ // label的颜色
+ labelColor: {
+ type: String,
+ default: '#606266'
+ },
+ // label与图标的距离
+ space: {
+ type: [String, Number],
+ default: '3px'
+ },
+ // 图片的mode
+ imgMode: {
+ type: String,
+ default: 'aspectFit'
+ },
+ // 用于显示图片小图标时,图片的宽度
+ width: {
+ type: [String, Number],
+ default: ''
+ },
+ // 用于显示图片小图标时,图片的高度
+ height: {
+ type: [String, Number],
+ default: ''
+ },
+ // 用于解决某些情况下,让图标垂直居中的用途
+ top: {
+ type: [String, Number],
+ default: 0
+ },
+ // 是否阻止事件传播
+ stop: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.icon
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-icon/components/uv-icon/uv-icon.vue b/ledApp/uni_modules/uv-icon/components/uv-icon/uv-icon.vue
new file mode 100644
index 0000000..d61c9e5
--- /dev/null
+++ b/ledApp/uni_modules/uv-icon/components/uv-icon/uv-icon.vue
@@ -0,0 +1,226 @@
+<template>
+ <view
+ class="uv-icon"
+ @tap="clickHandler"
+ :class="['uv-icon--' + labelPos]"
+ >
+ <image
+ class="uv-icon__img"
+ v-if="isImg"
+ :src="name"
+ :mode="imgMode"
+ :style="[imgStyle, $uv.addStyle(customStyle)]"
+ ></image>
+ <text
+ v-else
+ class="uv-icon__icon"
+ :class="uClasses"
+ :style="[iconStyle, $uv.addStyle(customStyle)]"
+ :hover-class="hoverClass"
+ >{{icon}}</text>
+ <!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 -->
+ <text
+ v-if="label !== ''"
+ class="uv-icon__label"
+ :style="{
+ color: labelColor,
+ fontSize: $uv.addUnit(labelSize),
+ marginLeft: labelPos == 'right' ? $uv.addUnit(space) : 0,
+ marginTop: labelPos == 'bottom' ? $uv.addUnit(space) : 0,
+ marginRight: labelPos == 'left' ? $uv.addUnit(space) : 0,
+ marginBottom: labelPos == 'top' ? $uv.addUnit(space) : 0
+ }"
+ >{{ label }}</text>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef APP-NVUE
+ // nvue通过weex的dom模块引入字体,相关文档地址如下:
+ // https://weex.apache.org/zh/docs/modules/dom.html#addrule
+ import iconUrl from './uvicons.ttf';
+ const domModule = weex.requireModule('dom')
+ domModule.addRule('fontFace', {
+ 'fontFamily': "uvicon-iconfont",
+ 'src': "url('" + iconUrl + "')"
+ })
+ // #endif
+ // 引入图标名称,已经对应的unicode
+ import icons from './icons';
+ import props from './props.js';
+ /**
+ * icon 图标
+ * @description 基于字体的图标集,包含了大多数常见场景的图标。
+ * @tutorial https://www.uvui.cn/components/icon.html
+ * @property {String} name 图标名称,见示例图标集
+ * @property {String} color 图标颜色,可接受主题色 (默认 color['uv-content-color'] )
+ * @property {String | Number} size 图标字体大小,单位px (默认 '16px' )
+ * @property {Boolean} bold 是否显示粗体 (默认 false )
+ * @property {String | Number} index 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
+ * @property {String} hoverClass 图标按下去的样式类,用法同uni的view组件的hoverClass参数,详情见官网
+ * @property {String} customPrefix 自定义扩展前缀,方便用户扩展自己的图标库 (默认 'uicon' )
+ * @property {String | Number} label 图标右侧的label文字
+ * @property {String} labelPos label相对于图标的位置,只能right或bottom (默认 'right' )
+ * @property {String | Number} labelSize label字体大小,单位px (默认 '15px' )
+ * @property {String} labelColor 图标右侧的label文字颜色 ( 默认 color['uv-content-color'] )
+ * @property {String | Number} space label与图标的距离,单位px (默认 '3px' )
+ * @property {String} imgMode 图片的mode
+ * @property {String | Number} width 显示图片小图标时的宽度
+ * @property {String | Number} height 显示图片小图标时的高度
+ * @property {String | Number} top 图标在垂直方向上的定位 用于解决某些情况下,让图标垂直居中的用途 (默认 0 )
+ * @property {Boolean} stop 是否阻止事件传播 (默认 false )
+ * @property {Object} customStyle icon的样式,对象形式
+ * @event {Function} click 点击图标时触发
+ * @event {Function} touchstart 事件触摸时触发
+ * @example <uv-icon name="photo" color="#2979ff" size="28"></uv-icon>
+ */
+ export default {
+ name: 'uv-icon',
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ colorType: [
+ 'primary',
+ 'success',
+ 'info',
+ 'error',
+ 'warning'
+ ]
+ }
+ },
+ computed: {
+ uClasses() {
+ let classes = []
+ classes.push(this.customPrefix)
+ classes.push(this.customPrefix + '-' + this.name)
+ // 主题色,通过类配置
+ if (this.color && this.colorType.includes(this.color)) classes.push('uv-icon__icon--' + this.color)
+ // 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
+ // 故需将其拆成一个字符串的形式,通过空格隔开各个类名
+ //#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
+ classes = classes.join(' ')
+ //#endif
+ return classes
+ },
+ iconStyle() {
+ let style = {}
+ style = {
+ fontSize: this.$uv.addUnit(this.size),
+ lineHeight: this.$uv.addUnit(this.size),
+ fontWeight: this.bold ? 'bold' : 'normal',
+ // 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
+ top: this.$uv.addUnit(this.top)
+ }
+ // 非主题色值时,才当作颜色值
+ if (this.color && !this.colorType.includes(this.color)) style.color = this.color
+ return style
+ },
+ // 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
+ isImg() {
+ const isBase64 = this.name.indexOf('data:') > -1 && this.name.indexOf('base64') > -1;
+ return this.name.indexOf('/') !== -1 || isBase64;
+ },
+ imgStyle() {
+ let style = {}
+ // 如果设置width和height属性,则优先使用,否则使用size属性
+ style.width = this.width ? this.$uv.addUnit(this.width) : this.$uv.addUnit(this.size)
+ style.height = this.height ? this.$uv.addUnit(this.height) : this.$uv.addUnit(this.size)
+ return style
+ },
+ // 通过图标名,查找对应的图标
+ icon() {
+ // 如果内置的图标中找不到对应的图标,就直接返回name值,因为用户可能传入的是unicode代码
+ const code = icons['uvicon-' + this.name];
+ // #ifdef APP-NVUE
+ if(!code) {
+ return code ? unescape(`%u${code}`) : ['uvicon'].indexOf(this.customPrefix) > -1 ? unescape(`%u${this.name}`) : '';
+ }
+ // #endif
+ return code ? unescape(`%u${code}`) : ['uvicon'].indexOf(this.customPrefix) > -1 ? this.name : '';
+ }
+ },
+ methods: {
+ clickHandler(e) {
+ this.$emit('click', this.index)
+ // 是否阻止事件冒泡
+ this.stop && this.preventEvent(e)
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ // 变量定义
+ $uv-icon-primary: $uv-primary !default;
+ $uv-icon-success: $uv-success !default;
+ $uv-icon-info: $uv-info !default;
+ $uv-icon-warning: $uv-warning !default;
+ $uv-icon-error: $uv-error !default;
+ $uv-icon-label-line-height: 1 !default;
+ /* #ifndef APP-NVUE */
+ // 非nvue下加载字体
+ @font-face {
+ font-family: 'uvicon-iconfont';
+ src: url('./uvicons.ttf') format('truetype');
+ }
+ /* #endif */
+ .uv-icon {
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ align-items: center;
+ &--left {
+ flex-direction: row-reverse;
+ align-items: center;
+ }
+ &--right {
+ flex-direction: row;
+ align-items: center;
+ }
+ &--top {
+ flex-direction: column-reverse;
+ justify-content: center;
+ }
+ &--bottom {
+ flex-direction: column;
+ justify-content: center;
+ }
+ &__icon {
+ font-family: uvicon-iconfont;
+ position: relative;
+ @include flex;
+ align-items: center;
+ &--primary {
+ color: $uv-icon-primary;
+ }
+ &--success {
+ color: $uv-icon-success;
+ }
+ &--error {
+ color: $uv-icon-error;
+ }
+ &--warning {
+ color: $uv-icon-warning;
+ }
+ &--info {
+ color: $uv-icon-info;
+ }
+ }
+ &__img {
+ /* #ifndef APP-NVUE */
+ height: auto;
+ will-change: transform;
+ /* #endif */
+ }
+ &__label {
+ /* #ifndef APP-NVUE */
+ line-height: $uv-icon-label-line-height;
+ /* #endif */
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-icon/components/uv-icon/uvicons.ttf b/ledApp/uni_modules/uv-icon/components/uv-icon/uvicons.ttf
new file mode 100644
index 0000000..9aedef8
--- /dev/null
+++ b/ledApp/uni_modules/uv-icon/components/uv-icon/uvicons.ttf
Binary files differ
diff --git a/ledApp/uni_modules/uv-icon/package.json b/ledApp/uni_modules/uv-icon/package.json
new file mode 100644
index 0000000..0a838d5
--- /dev/null
+++ b/ledApp/uni_modules/uv-icon/package.json
@@ -0,0 +1,83 @@
+{
+ "id": "uv-icon",
+ "displayName": "uv-icon 图标 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.13",
+ "description": "基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。可自定义颜色、大小。",
+ "keywords": [
+ "uv-ui,uvui,uv-icon,icon,图标,字体图标"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-icon/readme.md b/ledApp/uni_modules/uv-icon/readme.md
new file mode 100644
index 0000000..d526e1a
--- /dev/null
+++ b/ledApp/uni_modules/uv-icon/readme.md
@@ -0,0 +1,15 @@
+## uv-icon 图标库
+
+> **组件名:uv-icon**
+
+基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。
+
+# <a href="https://www.uvui.cn/components/icon.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-image/changelog.md b/ledApp/uni_modules/uv-image/changelog.md
new file mode 100644
index 0000000..e5a633d
--- /dev/null
+++ b/ledApp/uni_modules/uv-image/changelog.md
@@ -0,0 +1,36 @@
+## 1.0.16(2023-12-21)
+1. 修复设置show-menu-by-longpress不生效的BUG
+## 1.0.15(2023-12-06)
+1. 优化
+## 1.0.14(2023-12-06)
+1. 阻止事件冒泡问题
+## 1.0.13(2023-11-15)
+1. 修复webp之前未使用的BUG
+## 1.0.12(2023-10-11)
+1. 修复懒加载报错:https://gitee.com/climblee/uv-ui/issues/I869JS
+## 1.0.11(2023-08-31)
+1. 修复设置widthFix时出现显示不全的BUG
+2. 修复抖音等平台在width和height属性改变时出现不显示的BUG
+## 1.0.10(2023-08-29)
+1. 修复异步修改宽高不生效的问题,问题来源:https://gitee.com/climblee/uv-ui/issues/I7WUQ3
+## 1.0.9(2023-08-21)
+1. 修复设置宽高为百分比不生效的BUG
+## 1.0.8(2023-07-24)
+1. 优化 nvue模式下增加cellChild参数,是否在list中cell节点下,nvue中cell下建议设置成true
+## 1.0.7(2023-07-02)
+修复VUE3模式下可能不显示的BUG
+## 1.0.6(2023-07-02)
+优化修改
+## 1.0.5(2023-06-28)
+修复duration属性不生效的BUG
+## 1.0.4(2023-05-27)
+1. 修复可能报错的问题
+## 1.0.3(2023-05-24)
+1. 去掉template中存在的this.导致头条小程序编译警告
+## 1.0.2(2023-05-23)
+1. 优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-image 图片
diff --git a/ledApp/uni_modules/uv-image/components/uv-image/props.js b/ledApp/uni_modules/uv-image/components/uv-image/props.js
new file mode 100644
index 0000000..6ef04a5
--- /dev/null
+++ b/ledApp/uni_modules/uv-image/components/uv-image/props.js
@@ -0,0 +1,95 @@
+export default {
+ props: {
+ // 图片地址
+ src: {
+ type: String,
+ default: ''
+ },
+ // 裁剪模式
+ mode: {
+ type: String,
+ default: 'aspectFill'
+ },
+ // 宽度,单位任意
+ width: {
+ type: [String, Number],
+ default: '300'
+ },
+ // 高度,单位任意
+ height: {
+ type: [String, Number],
+ default: '225'
+ },
+ // 图片形状,circle-圆形,square-方形
+ shape: {
+ type: String,
+ default: 'square'
+ },
+ // 圆角,单位任意
+ radius: {
+ type: [String, Number],
+ default: 0
+ },
+ // 是否懒加载,微信小程序、App、百度小程序、字节跳动小程序
+ lazyLoad: {
+ type: Boolean,
+ default: true
+ },
+ // 是否开启observer懒加载,nvue不生效
+ observeLazyLoad: {
+ type: Boolean,
+ default: false
+ },
+ // 开启长按图片显示识别微信小程序码菜单
+ showMenuByLongpress: {
+ type: Boolean,
+ default: true
+ },
+ // 加载中的图标,或者小图片
+ loadingIcon: {
+ type: String,
+ default: 'photo'
+ },
+ // 加载失败的图标,或者小图片
+ errorIcon: {
+ type: String,
+ default: 'error-circle'
+ },
+ // 是否显示加载中的图标或者自定义的slot
+ showLoading: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示加载错误的图标或者自定义的slot
+ showError: {
+ type: Boolean,
+ default: true
+ },
+ // 是否需要淡入效果
+ fade: {
+ type: Boolean,
+ default: true
+ },
+ // 只支持网络资源,只对微信小程序有效
+ webp: {
+ type: Boolean,
+ default: false
+ },
+ // 过渡时间,单位ms
+ duration: {
+ type: [String, Number],
+ default: 500
+ },
+ // 背景颜色,用于深色页面加载图片时,为了和背景色融合
+ bgColor: {
+ type: String,
+ default: '#f3f4f6'
+ },
+ // nvue模式下 是否直接显示,在uv-list等cell下面使用就需要设置
+ cellChild: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.image
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-image/components/uv-image/uv-image.vue b/ledApp/uni_modules/uv-image/components/uv-image/uv-image.vue
new file mode 100644
index 0000000..2ab4a78
--- /dev/null
+++ b/ledApp/uni_modules/uv-image/components/uv-image/uv-image.vue
@@ -0,0 +1,287 @@
+<template>
+ <uv-transition
+ v-if="show"
+ :show="show"
+ mode="fade"
+ :duration="fade ? duration : 0"
+ :cell-child="cellChild"
+ :custom-style="wrapStyle"
+ >
+ <view
+ class="uv-image"
+ :class="[`uv-image--${elIndex}`]"
+ @tap="onClick"
+ :style="[wrapStyle, backgroundStyle]"
+ >
+ <image
+ v-if="!isError && observeShow"
+ :src="src"
+ :mode="mode"
+ @error="onErrorHandler"
+ @load="onLoadHandler"
+ :show-menu-by-longpress="showMenuByLongpress"
+ :lazy-load="lazyLoad"
+ class="uv-image__image"
+ :style="[imageStyle]"
+ :webp="webp"
+ ></image>
+ <view
+ v-if="showLoading && loading"
+ class="uv-image__loading"
+ :style="{
+ borderRadius: shape == 'circle' ? '50%' : $uv.addUnit(radius),
+ backgroundColor: bgColor,
+ width: $uv.addUnit(width),
+ height: $uv.addUnit(height)
+ }"
+ >
+ <slot name="loading">
+ <uv-icon
+ :name="loadingIcon"
+ :width="width"
+ :height="height"
+ ></uv-icon>
+ </slot>
+ </view>
+ <view
+ v-if="showError && isError && !loading"
+ class="uv-image__error"
+ :style="{
+ borderRadius: shape == 'circle' ? '50%' : $uv.addUnit(radius),
+ width: $uv.addUnit(width),
+ height: $uv.addUnit(height)
+ }"
+ >
+ <slot name="error">
+ <uv-icon
+ :name="errorIcon"
+ :width="width"
+ :height="height"
+ ></uv-icon>
+ </slot>
+ </view>
+ </view>
+ </uv-transition>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Image 图片
+ * @description 此组件为uni-app的image组件的加强版,在继承了原有功能外,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。
+ * @tutorial https://www.uvui.cn/components/image.html
+ * @property {String} src 图片地址
+ * @property {String} mode 裁剪模式,见官网说明 (默认 'aspectFill' )
+ * @property {String | Number} width 宽度,单位任意,如果为数值,则为px单位 (默认 '300' )
+ * @property {String | Number} height 高度,单位任意,如果为数值,则为px单位 (默认 '225' )
+ * @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' )
+ * @property {String | Number} radius 圆角值,单位任意,如果为数值,则为px单位 (默认 0 )
+ * @property {Boolean} lazyLoad 是否懒加载,仅微信小程序、App、百度小程序、字节跳动小程序有效 (默认 true )
+ * @property {Boolean} showMenuByLongpress 是否开启长按图片显示识别小程序码菜单,仅微信小程序有效 (默认 true )
+ * @property {String} loadingIcon 加载中的图标,或者小图片 (默认 'photo' )
+ * @property {String} errorIcon 加载失败的图标,或者小图片 (默认 'error-circle' )
+ * @property {Boolean} showLoading 是否显示加载中的图标或者自定义的slot (默认 true )
+ * @property {Boolean} showError 是否显示加载错误的图标或者自定义的slot (默认 true )
+ * @property {Boolean} fade 是否需要淡入效果 (默认 true )
+ * @property {Boolean} webp 只支持网络资源,只对微信小程序有效 (默认 false )
+ * @property {String | Number} duration 搭配fade参数的过渡时间,单位ms (默认 500 )
+ * @property {String} bgColor 背景颜色,用于深色页面加载图片时,为了和背景色融合 (默认 '#f3f4f6' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @event {Function} click 点击图片时触发
+ * @event {Function} error 图片加载失败时触发
+ * @event {Function} load 图片加载成功时触发
+ * @example <uv-image width="100%" height="300px" :src="src"></uv-image>
+ */
+ export default {
+ name: 'uv-image',
+ emits: ['click','load','error'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 图片是否加载错误,如果是,则显示错误占位图
+ isError: false,
+ // 初始化组件时,默认为加载中状态
+ loading: true,
+ // 图片加载完成时,去掉背景颜色,因为如果是png图片,就会显示灰色的背景
+ backgroundStyle: {},
+ // 用于fade模式的控制组件显示与否
+ show: false,
+ // 是否开启图片出现在可视范围进行加载(另一种懒加载)
+ observeShow: !this.observeLazyLoad,
+ elIndex: '',
+ // 因为props的值无法修改,故需要一个中间值
+ imgWidth: this.width,
+ // 因为props的值无法修改,故需要一个中间值
+ imgHeight: this.height,
+ thresholdValue: 50
+ };
+ },
+ watch: {
+ src: {
+ immediate: true,
+ handler(n) {
+ if (!n) {
+ // 如果传入null或者'',或者false,或者undefined,标记为错误状态
+ this.isError = true
+ } else {
+ this.isError = false;
+ this.loading = true;
+ }
+ }
+ },
+ width(newVal){
+ // 这样做的目的是避免在更新时候,某些平台动画会恢复关闭状态
+ this.show = false;
+ this.$uv.sleep(2).then(res=>{
+ this.show = true;
+ });
+ this.imgWidth = newVal;
+ },
+ height(newVal){
+ // 这样做的目的是避免在更新时候,某些平台动画会恢复关闭状态
+ this.show = false;
+ this.$uv.sleep(2).then(res=>{
+ this.show = true;
+ });
+ this.imgHeight = newVal;
+ }
+ },
+ computed: {
+ wrapStyle() {
+ let style = {};
+ // 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位
+ if(this.mode !== 'heightFix') {
+ style.width = this.$uv.addUnit(this.imgWidth);
+ }
+ if(this.mode !== 'widthFix') {
+ style.height = this.$uv.addUnit(this.imgHeight);
+ }
+ // 如果是显示圆形,设置一个很多的半径值即可
+ style.borderRadius = this.shape == 'circle' ? '10000px' : this.$uv.addUnit(this.radius)
+ // 如果设置圆角,必须要有hidden,否则可能圆角无效
+ style.overflow = this.radius > 0 ? 'hidden' : 'visible'
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+ },
+ imageStyle() {
+ let style = {};
+ style.borderRadius = this.shape == 'circle' ? '10000px' : this.$uv.addUnit(this.radius);
+ // #ifdef APP-NVUE
+ style.width = this.$uv.addUnit(this.imgWidth);
+ style.height = this.$uv.addUnit(this.imgHeight);
+ // #endif
+ return style;
+ }
+ },
+ created() {
+ this.elIndex = this.$uv.guid();
+ this.observer = {}
+ this.observerName = 'lazyLoadContentObserver'
+ },
+ mounted() {
+ this.show = true;
+ this.$nextTick(()=>{
+ if(this.observeLazyLoad) this.observerFn();
+ })
+ },
+ methods: {
+ // 点击图片
+ onClick() {
+ this.$emit('click')
+ },
+ // 图片加载失败
+ onErrorHandler(err) {
+ this.loading = false
+ this.isError = true
+ this.$emit('error', err)
+ },
+ // 图片加载完成,标记loading结束
+ onLoadHandler(event) {
+ if(this.mode == 'widthFix') this.imgHeight = 'auto'
+ if(this.mode == 'heightFix') this.imgWidth = 'auto'
+ this.loading = false
+ this.isError = false
+ this.$emit('load', event)
+ this.removeBgColor()
+ },
+ // 移除图片的背景色
+ removeBgColor() {
+ // 淡入动画过渡完成后,将背景设置为透明色,否则png图片会看到灰色的背景
+ this.backgroundStyle = {
+ backgroundColor: 'transparent'
+ };
+ },
+ // 观察图片是否在可见视口
+ observerFn(){
+ // 在需要用到懒加载的页面,在触发底部的时候触发tOnLazyLoadReachBottom事件,保证所有图片进行加载
+ this.$nextTick(() => {
+ uni.$once('onLazyLoadReachBottom', () => {
+ if (!this.observeShow) this.observeShow = true
+ })
+ })
+ setTimeout(() => {
+ // #ifndef APP-NVUE
+ this.disconnectObserver(this.observerName)
+ const contentObserver = uni.createIntersectionObserver(this)
+ contentObserver.relativeToViewport({
+ bottom: this.thresholdValue
+ }).observe(`.uv-image--${this.elIndex}`, (res) => {
+ if (res.intersectionRatio > 0) {
+ // 懒加载状态改变
+ this.observeShow = true
+ // 如果图片已经加载,去掉监听,减少性能消耗
+ this.disconnectObserver(this.observerName)
+ }
+ })
+ this[this.observerName] = contentObserver
+ // #endif
+ // #ifdef APP-NVUE
+ this.observeShow = true;
+ // #endif
+ }, 50)
+ },
+ disconnectObserver(observerName) {
+ const observer = this[observerName]
+ observer && observer.disconnect()
+ }
+ }
+ };
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-image-error-top:0px !default;
+ $uv-image-error-left:0px !default;
+ $uv-image-error-width:100% !default;
+ $uv-image-error-hight:100% !default;
+ $uv-image-error-background-color:$uv-bg-color !default;
+ $uv-image-error-color:$uv-tips-color !default;
+ $uv-image-error-font-size: 46rpx !default;
+
+ .uv-image {
+ position: relative;
+ transition: opacity 0.5s ease-in-out;
+
+ &__image {
+ width: 100%;
+ height: 100%;
+ }
+
+ &__loading,
+ &__error {
+ position: absolute;
+ top: $uv-image-error-top;
+ left: $uv-image-error-left;
+ width: $uv-image-error-width;
+ height: $uv-image-error-hight;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ background-color: $uv-image-error-background-color;
+ color: $uv-image-error-color;
+ font-size: $uv-image-error-font-size;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-image/package.json b/ledApp/uni_modules/uv-image/package.json
new file mode 100644
index 0000000..796089e
--- /dev/null
+++ b/ledApp/uni_modules/uv-image/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-image",
+ "displayName": "uv-image 图片 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.16",
+ "description": "uv-image 此组件为uni-app的image组件的加强版,在继承了原有功能外,增加observer懒加载功能,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。",
+ "keywords": [
+ "uv-image",
+ "uvui",
+ "uv-ui",
+ "image",
+ "图片"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-transition",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-image/readme.md b/ledApp/uni_modules/uv-image/readme.md
new file mode 100644
index 0000000..0ecc120
--- /dev/null
+++ b/ledApp/uni_modules/uv-image/readme.md
@@ -0,0 +1,15 @@
+## Image 图片
+
+> **组件名:uv-image**
+
+此组件为`uni-app`的`image`组件的加强版,在继承了原有功能外,增加`observer`懒加载功能,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。
+
+# <a href="https://www.uvui.cn/components/image.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-index-list/changelog.md b/ledApp/uni_modules/uv-index-list/changelog.md
new file mode 100644
index 0000000..deda715
--- /dev/null
+++ b/ledApp/uni_modules/uv-index-list/changelog.md
@@ -0,0 +1,18 @@
+## 1.0.7(2023-11-07)
+1. 修复sticky属性不生效的BUG
+## 1.0.6(2023-09-01)
+1. 修复设置customNavHeight导致定位不准确的BUG
+## 1.0.5(2023-08-23)
+1. 修复ios端快速滑动+点击右侧导航会出现白屏的BUG
+## 1.0.4(2023-07-25)
+1. 修复全局设置成rpx存在的高度BUG
+2. 修复其他BUG
+## 1.0.3(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.2(2023-05-27)
+1. select事件修复
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-index-list 索引列表
diff --git a/ledApp/uni_modules/uv-index-list/components/uv-index-anchor/props.js b/ledApp/uni_modules/uv-index-list/components/uv-index-anchor/props.js
new file mode 100644
index 0000000..9ec949e
--- /dev/null
+++ b/ledApp/uni_modules/uv-index-list/components/uv-index-anchor/props.js
@@ -0,0 +1,30 @@
+export default {
+ props: {
+ // 列表锚点文本内容
+ text: {
+ type: [String, Number],
+ default: ''
+ },
+ // 列表锚点文字颜色
+ color: {
+ type: String,
+ default: '#606266'
+ },
+ // 列表锚点文字大小,单位默认px
+ size: {
+ type: [String, Number],
+ default: 14
+ },
+ // 列表锚点背景颜色
+ bgColor: {
+ type: String,
+ default: '#dedede'
+ },
+ // 列表锚点高度,单位默认px
+ height: {
+ type: [String, Number],
+ default: 32
+ },
+ ...uni.$uv?.props?.indexAnchor
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-index-list/components/uv-index-anchor/uv-index-anchor.vue b/ledApp/uni_modules/uv-index-list/components/uv-index-anchor/uv-index-anchor.vue
new file mode 100644
index 0000000..e566aa7
--- /dev/null
+++ b/ledApp/uni_modules/uv-index-list/components/uv-index-anchor/uv-index-anchor.vue
@@ -0,0 +1,98 @@
+<template>
+ <!-- #ifdef APP-NVUE -->
+ <header>
+ <!-- #endif -->
+ <view
+ :class="['uv-index-anchor',{'uv-index-anchor-sticky': parentData.sticky}]"
+ :ref="`uv-index-anchor-${text}`"
+ :style="{
+ height: $uv.addUnit(height),
+ backgroundColor: bgColor
+ }"
+ >
+ <text
+ class="uv-index-anchor__text"
+ :style="{
+ fontSize: $uv.addUnit(size),
+ color: color
+ }"
+ >{{ text }}</text>
+ </view>
+ <!-- #ifdef APP-NVUE -->
+ </header>
+ <!-- #endif -->
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * IndexAnchor 列表锚点
+ * @description
+ * @tutorial https://www.uvui.cn/components/indexList.html
+ * @property {String | Number} text 列表锚点文本内容
+ * @property {String} color 列表锚点文字颜色 ( 默认 '#606266' )
+ * @property {String | Number} size 列表锚点文字大小,单位默认px ( 默认 14 )
+ * @property {String} bgColor 列表锚点背景颜色 ( 默认 '#dedede' )
+ * @property {String | Number} height 列表锚点高度,单位默认px ( 默认 32 )
+ * @example <uv-index-anchor :text="indexList[index]"></uv-index-anchor>
+ */
+ export default {
+ name: 'uv-index-anchor',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ parentData: {
+ sticky: true
+ }
+ }
+ },
+ created() {
+ this.init()
+ },
+ methods: {
+ init() {
+ // 此处会活动父组件实例,并赋值给实例的parent属性
+ const indexList = this.$uv.$parent.call(this, 'uv-index-list')
+ if (!indexList) {
+ return this.$uv.error('uv-index-anchor必须要搭配uv-index-list组件使用')
+ }
+ this.parentData.sticky = indexList.sticky;
+ // 将当前实例放入到uv-index-list中
+ indexList.anchors.push(this)
+ const indexListItem = this.$uv.$parent.call(this, 'uv-index-item')
+ // #ifndef APP-NVUE
+ // 只有在非nvue下,uv-index-anchor才是嵌套在uv-index-item中的
+ if (!indexListItem) {
+ return this.$uv.error('uv-index-anchor必须要搭配uv-index-item组件使用')
+ }
+ // 设置uv-index-item的id为anchor的text标识符,因为非nvue下滚动列表需要依赖scroll-view滚动到元素的特性
+ indexListItem.id = this.text.charCodeAt(0)
+ // #endif
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-index-anchor {
+ @include flex;
+ align-items: center;
+ padding-left: 15px;
+ z-index: 1;
+ &-sticky {
+ position: sticky;
+ top: 0;
+ }
+ &__text {
+ @include flex;
+ align-items: center;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-index-list/components/uv-index-item/uv-index-item.vue b/ledApp/uni_modules/uv-index-list/components/uv-index-item/uv-index-item.vue
new file mode 100644
index 0000000..f6e8901
--- /dev/null
+++ b/ledApp/uni_modules/uv-index-list/components/uv-index-item/uv-index-item.vue
@@ -0,0 +1,87 @@
+<template>
+ <!-- #ifdef APP-NVUE -->
+ <cell ref="uv-index-item">
+ <!-- #endif -->
+ <view
+ class="uv-index-item"
+ :id="`uv-index-item-${id}`"
+ :class="[`uv-index-item-${id}`]"
+ >
+ <slot />
+ </view>
+ <!-- #ifdef APP-NVUE -->
+ </cell>
+ <!-- #endif -->
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef APP-NVUE
+ // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * IndexItem
+ * @description
+ * @tutorial https://www.uvui.cn/components/indexList.html
+ * @property {String}
+ * @event {Function}
+ * @example
+ */
+ export default {
+ name: 'uv-index-item',
+ mixins: [mpMixin, mixin],
+ data() {
+ return {
+ // 本组件到滚动条顶部的距离
+ top: 0,
+ height: 0,
+ id: ''
+ }
+ },
+ created() {
+ // 子组件uv-index-anchor的实例
+ this.anchor = {}
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ // 此处会活动父组件实例,并赋值给实例的parent属性
+ this.getParentData('uv-index-list')
+ if (!this.parent) {
+ return this.$uv.error('uv-index-item必须要搭配uv-index-list组件使用')
+ }
+ this.$uv.sleep().then(() =>{
+ this.getIndexItemRect().then(size => {
+ // 由于对象的引用特性,此处会同时生效到父组件的children数组的本实例的top属性中,供父组件判断读取
+ this.top = Math.ceil(size.top)
+ this.height = Math.ceil(size.height)
+ })
+ })
+ },
+ getIndexItemRect() {
+ return new Promise(resolve => {
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-index-item').then(size => {
+ resolve(size)
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ const ref = this.$refs['uv-index-item']
+ dom.getComponentRect(ref, res => {
+ resolve(res.size)
+ })
+ // #endif
+ })
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+</style>
diff --git a/ledApp/uni_modules/uv-index-list/components/uv-index-list/props.js b/ledApp/uni_modules/uv-index-list/components/uv-index-list/props.js
new file mode 100644
index 0000000..0cf6356
--- /dev/null
+++ b/ledApp/uni_modules/uv-index-list/components/uv-index-list/props.js
@@ -0,0 +1,30 @@
+export default {
+ props: {
+ // 右边锚点非激活的颜色
+ inactiveColor: {
+ type: String,
+ default: '#606266'
+ },
+ // 右边锚点激活的颜色
+ activeColor: {
+ type: String,
+ default: '#5677fc'
+ },
+ // 索引字符列表,数组形式
+ indexList: {
+ type: Array,
+ default: () => []
+ },
+ // 是否开启锚点自动吸顶
+ sticky: {
+ type: Boolean,
+ default: true
+ },
+ // 自定义导航栏的高度
+ customNavHeight: {
+ type: [String, Number],
+ default: 0
+ },
+ ...uni.$uv?.props?.indexList
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-index-list/components/uv-index-list/uv-index-list.vue b/ledApp/uni_modules/uv-index-list/components/uv-index-list/uv-index-list.vue
new file mode 100644
index 0000000..e610323
--- /dev/null
+++ b/ledApp/uni_modules/uv-index-list/components/uv-index-list/uv-index-list.vue
@@ -0,0 +1,461 @@
+<template>
+ <view class="uv-index-list">
+ <!-- #ifdef APP-NVUE -->
+ <list
+ :scrollTop="scrollTop"
+ enable-back-to-top
+ :offset-accuracy="1"
+ :style="{
+ maxHeight: $uv.addUnit(scrollViewHeight,'px')
+ }"
+ @scroll="scrollHandler"
+ ref="uvList"
+ >
+ <cell ref="header">
+ <slot name="header" />
+ </cell>
+ <slot />
+ <cell>
+ <slot name="footer" />
+ </cell>
+ </list>
+ <!-- #endif -->
+ <!-- #ifndef APP-NVUE -->
+ <scroll-view
+ :scrollTop="scrollTop"
+ :scrollIntoView="scrollIntoView"
+ :offset-accuracy="1"
+ :style="{
+ maxHeight: $uv.addUnit(scrollViewHeight,'px')
+ }"
+ scroll-y
+ @scroll="scrollHandler"
+ ref="uvList"
+ >
+ <view>
+ <slot name="header" />
+ </view>
+ <slot />
+ <view>
+ <slot name="footer" />
+ </view>
+ </scroll-view>
+ <!-- #endif -->
+ <view
+ class="uv-index-list__letter"
+ ref="uv-index-list__letter"
+ :style="{ top: $uv.addUnit(letterInfo.top || 100 ,'px') }"
+ @touchstart="touchStart"
+ @touchmove.stop.prevent="touchMove"
+ @touchend.stop.prevent="touchEnd"
+ @touchcancel.stop.prevent="touchEnd"
+ >
+ <view
+ class="uv-index-list__letter__item"
+ v-for="(item, index) in uIndexList"
+ :key="index"
+ :style="{
+ backgroundColor: activeIndex === index ? activeColor : 'transparent'
+ }"
+ >
+ <text
+ class="uv-index-list__letter__item__index"
+ :style="{color: activeIndex === index ? '#fff' : inactiveColor}"
+ >{{ item }}</text>
+ </view>
+ </view>
+ <uv-transition
+ mode="fade"
+ :show="touching"
+ :customStyle="{
+ position: 'fixed',
+ right: '40px',
+ top: $uv.addUnit(indicatorTop,'px'),
+ zIndex: 2
+ }"
+ >
+ <view class="uv-index-list__indicator__box">
+ <view
+ class="uv-index-list__indicator"
+ :class="['uv-index-list__indicator--show']"
+ :style="{
+ height: $uv.addUnit(indicatorHeight,'px'),
+ width: $uv.addUnit(indicatorHeight,'px')
+ }"
+ >
+ <text class="uv-index-list__indicator__text">{{ uIndexList[activeIndex] }}</text>
+ </view>
+ </view>
+ </uv-transition>
+ </view>
+</template>
+
+<script>
+ const indexList = () => {
+ const indexList = [];
+ const charCodeOfA = 'A'.charCodeAt(0);
+ for (let i = 0; i < 26; i++) {
+ indexList.push(String.fromCharCode(charCodeOfA + i));
+ }
+ return indexList;
+ }
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * IndexList 索引列表
+ * @description 通过折叠面板收纳内容区域
+ * @tutorial https://www.uvui.cn/components/indexList.html
+ * @property {String} inactiveColor 右边锚点非激活的颜色 ( 默认 '#606266' )
+ * @property {String} activeColor 右边锚点激活的颜色 ( 默认 '#5677fc' )
+ * @property {Array} indexList 索引字符列表,数组形式
+ * @property {Boolean} sticky 是否开启锚点自动吸顶 ( 默认 true )
+ * @property {String | Number} customNavHeight 自定义导航栏的高度 ( 默认 0 )
+ * */
+ export default {
+ name: 'uv-index-list',
+ mixins: [mpMixin, mixin, props],
+ // #ifdef MP-WEIXIN
+ // 将自定义节点设置成虚拟的,更加接近Vue组件的表现,能更好的使用flex属性
+ options: {
+ virtualHost: true
+ },
+ // #endif
+ data() {
+ return {
+ // 当前正在被选中的字母索引
+ activeIndex: -1,
+ touchmoveIndex: 1,
+ // 索引字母的信息
+ letterInfo: {
+ height: 0,
+ itemHeight: 0,
+ top: 0
+ },
+ // 设置字母指示器的高度,后面为了让指示器跟随字母,并将尖角部分指向字母的中部,需要依赖此值
+ indicatorHeight: 50,
+ // 字母放大指示器的top值,为了让其指向当前激活的字母
+ // indicatorTop: 0
+ // 当前是否正在被触摸状态
+ touching: false,
+ // 滚动条顶部top值
+ scrollTop: 0,
+ // scroll-view的高度
+ scrollViewHeight: 0,
+ // 系统信息
+ sys: '',
+ scrolling: false,
+ scrollIntoView: '',
+ hasHeight: 0,
+ timer: 0,
+ disTap: false
+ }
+ },
+ computed: {
+ // 如果有传入外部的indexList锚点数组则使用,否则使用内部生成A-Z字母
+ uIndexList() {
+ return this.indexList.length ? this.indexList : indexList()
+ },
+ // 字母放大指示器的top值,为了让其指向当前激活的字母
+ indicatorTop() {
+ const {
+ top,
+ itemHeight
+ } = this.letterInfo
+ return Math.floor(top + itemHeight * this.activeIndex + itemHeight / 2 - this.indicatorHeight / 2) - 8
+ }
+ },
+ watch: {
+ // 监听字母索引的变化,重新设置尺寸
+ uIndexList: {
+ immediate: true,
+ handler() {
+ this.$uv.sleep().then(() => {
+ this.setIndexListLetterInfo()
+ })
+ }
+ }
+ },
+ created() {
+ this.sys = this.$uv.sys()
+ this.children = []
+ this.anchors = []
+ this.init()
+ },
+ mounted() {
+ this.setIndexListLetterInfo()
+ },
+ methods: {
+ init() {
+ // 设置列表的高度为整个屏幕的高度
+ //减去this.customNavHeight,并将this.scrollViewHeight设置为maxHeight
+ //解决当uv-index-list组件放在tabbar页面时,scroll-view内容较少时,还能滚动
+ this.scrollViewHeight = this.sys.windowHeight - this.$uv.getPx(this.customNavHeight)
+ },
+ // 索引列表被触摸
+ touchStart(e) {
+ // 获取触摸点信息
+ const touchStart = e.changedTouches[0]
+ if (!touchStart || this.disTap) return
+ this.touching = true
+ const {
+ pageY
+ } = touchStart
+ // 根据当前触摸点的坐标,获取当前触摸的为第几个字母
+ const currentIndex = this.getIndexListLetter(pageY)
+ this.setValueForTouch(currentIndex)
+ },
+ // 索引字母列表被触摸滑动中
+ touchMove(e) {
+ // 获取触摸点信息
+ let touchMove = e.changedTouches[0]
+ if (!touchMove || this.disTap) return;
+
+ // 滑动结束后迅速开始第二次滑动时候 touching 为 false 造成不显示 indicator 问题
+ if (!this.touching) {
+ this.touching = true
+ }
+ const {
+ pageY
+ } = touchMove
+ const currentIndex = this.getIndexListLetter(pageY)
+ this.setValueForTouch(currentIndex)
+ },
+ // 触摸结束
+ touchEnd(e) {
+ // 延时一定时间后再隐藏指示器,为了让用户看的更直观,同时也是为了消除快速切换uv-transition的show带来的影响
+ this.$uv.sleep(300).then(() => {
+ this.touching = false
+ })
+ this.$emit('select',this.activeIndex);
+ },
+ // 获取索引列表的尺寸以及单个字符的尺寸信息
+ getIndexListLetterRect() {
+ return new Promise(resolve => {
+ // 延时一定时间,以获取dom尺寸
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-index-list__letter').then(size => {
+ resolve(size)
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ const ref = this.$refs['uv-index-list__letter']
+ dom.getComponentRect(ref, res => {
+ resolve(res.size)
+ })
+ // #endif
+ })
+ },
+ // 设置indexList索引的尺寸信息
+ setIndexListLetterInfo() {
+ this.getIndexListLetterRect().then(size => {
+ const {
+ top,
+ height
+ } = size
+ const windowHeight = this.$uv.sys().windowHeight;
+ let customNavHeight = 0
+ // 消除各端导航栏非原生和原生导致的差异,让索引列表字母对屏幕垂直居中
+ if (this.customNavHeight == 0) {
+ // #ifdef H5
+ customNavHeight = this.$uv.sys().windowTop
+ // #endif
+ // #ifndef H5
+ // 在非H5中,为原生导航栏,其高度不算在windowHeight内,这里设置为负值,后面相加时变成减去其高度的一半
+ customNavHeight = 0
+ // #endif
+ } else {
+ customNavHeight = this.$uv.getPx(this.customNavHeight)
+ }
+ this.letterInfo = {
+ height,
+ // 为了让字母列表对屏幕绝对居中,让其对导航栏进行修正,也即往上偏移导航栏的一半高度
+ top: (windowHeight - height) / 2 + customNavHeight / 2,
+ itemHeight: Math.floor(height / this.uIndexList.length)
+ }
+ })
+ },
+ // 获取当前被触摸的索引字母
+ getIndexListLetter(pageY) {
+ const {
+ top,
+ height,
+ itemHeight
+ } = this.letterInfo
+ // 对H5的pageY进行修正,这是由于uni-app自作多情在H5中将触摸点的坐标跟H5的导航栏结合导致的问题
+ // #ifdef H5
+ pageY += this.$uv.sys().windowTop
+ // #endif
+ // #ifdef APP-NVUE
+ pageY += top
+ // #endif
+ // 对第一和最后一个字母做边界处理,因为用户可能在字母列表上触摸到两端的尽头后依然继续滑动
+ if (pageY < top) {
+ return 0
+ } else if (pageY >= top + height) {
+ // 如果超出了,取最后一个字母
+ return this.uIndexList.length - 1
+ } else {
+ // 将触摸点的Y轴偏移值,减去索引字母的top值,除以每个字母的高度,即可得到当前触摸点落在哪个字母上
+ return Math.floor((pageY - top) / itemHeight);
+ }
+ },
+ // 设置各项由触摸而导致变化的值
+ setValueForTouch(currentIndex) {
+ // 如果偏移量太小,前后得出的会是同一个索引字母,为了防抖,进行返回
+ if (currentIndex === this.activeIndex) return
+ this.activeIndex = currentIndex
+ // #ifndef APP-NVUE || MP-WEIXIN
+ // 在非nvue中,由于anchor和item都在uv-index-item中,所以需要对index-item进行偏移
+ this.scrollIntoView = `uv-index-item-${this.uIndexList[currentIndex].charCodeAt(0)}`
+ // #endif
+ // #ifdef MP-WEIXIN
+ // 微信小程序下,scroll-view的scroll-into-view属性无法对slot中的内容的id生效,只能通过设置scrollTop的形式去移动滚动条
+ this.scrollTop = this.children[currentIndex].top
+ // #endif
+ // #ifdef APP-NVUE
+ // 在nvue中,由于cell和header为同级元素,所以实际是需要对header(anchor)进行偏移
+ const anchor = `uv-index-anchor-${this.uIndexList[currentIndex]}`
+ dom.scrollToElement(this.anchors[currentIndex].$refs[anchor], {
+ offset: 0,
+ animated: false
+ })
+ // #endif
+ },
+ getHeaderRect() {
+ // 获取header slot的高度,因为list组件中获取元素的尺寸是没有top值的
+ return new Promise(resolve => {
+ dom.getComponentRect(this.$refs.header, res => {
+ resolve(res.size)
+ })
+ })
+ },
+ // scroll-view的滚动事件
+ async scrollHandler(e) {
+ if (this.touching || this.scrolling) return
+ // 每过一定时间取样一次,减少资源损耗以及可能带来的卡顿
+ this.scrolling = true
+ this.disTap = true;
+ clearTimeout(this.timer);
+ this.timer = setTimeout(()=>{
+ this.disTap = false;
+ },200)
+ this.$uv.sleep(30).then(() => {
+ this.scrolling = false
+ })
+ let scrollTop = 0
+ const len = this.children.length
+ let children = this.children
+ const anchors = this.anchors
+ const customNavHeight = this.$uv.getPx(this.customNavHeight);
+ // #ifdef APP-NVUE
+ // nvue下获取的滚动条偏移为负数,需要转为正数
+ scrollTop = Math.abs(e.contentOffset.y)
+ // 获取header slot的尺寸信息
+ const header = await this.getHeaderRect()
+ // item的top值,在nvue下,模拟出的anchor的top,类似非nvue下的index-item的top
+ let top = header.height
+ // 由于list组件无法获取cell的top值,这里通过header slot和各个item之间的height,模拟出类似非nvue下的位置信息
+ children = this.children.map((item, index) => {
+ if(item.height>0) this.hasHeight = item.height;
+ item.height = item.height>0?item.height:this.hasHeight;
+ const child = {
+ height: item.height,
+ top
+ }
+ // 进行累加,给下一个item提供计算依据
+ top += item.height + anchors[index].height
+ return child
+ })
+ // #endif
+ // #ifndef APP-NVUE
+ // 非nvue通过detail获取滚动条位移
+ scrollTop = e.detail.scrollTop
+ // #endif
+ for (let i = 0; i < len; i++) {
+ const item = children[i],
+ nextItem = children[i + 1]
+ // 如果滚动条高度小于第一个item的top值,此时无需设置任意字母为高亮
+ if (scrollTop + customNavHeight <= children[0].top || scrollTop >= children[len - 1].top + children[len -
+ 1].height) {
+ this.activeIndex = -1
+ break
+ } else if (!nextItem) {
+ // 当不存在下一个item时,意味着历遍到了最后一个
+ this.activeIndex = len - 1
+ break
+ } else if (scrollTop + customNavHeight > item.top && scrollTop + customNavHeight < nextItem.top) {
+ this.activeIndex = i
+ break
+ }
+ }
+ },
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-index-list {
+
+ &__letter {
+ position: fixed;
+ right: 0;
+ text-align: center;
+ z-index: 3;
+ padding: 0 6px;
+
+ &__item {
+ width: 16px;
+ height: 16px;
+ border-radius: 100px;
+ margin: 1px 0;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+
+ &--active {
+ background-color: $uv-primary;
+ }
+
+ &__index {
+ font-size: 12px;
+ text-align: center;
+ line-height: 12px;
+ }
+ }
+ }
+ &__indicator__box {
+ width: 65px;
+ height: 65px;
+ padding: 6px;
+ transform: rotate(-45deg);
+ }
+ &__indicator {
+ width: 50px;
+ height: 50px;
+ border-radius: 100px 100px 0 100px;
+ text-align: center;
+ color: #ffffff;
+ background-color: #c9c9c9;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+
+ &__text {
+ font-size: 28px;
+ line-height: 28px;
+ font-weight: bold;
+ color: #fff;
+ transform: rotate(45deg);
+ text-align: center;
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-index-list/package.json b/ledApp/uni_modules/uv-index-list/package.json
new file mode 100644
index 0000000..133736b
--- /dev/null
+++ b/ledApp/uni_modules/uv-index-list/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-index-list",
+ "displayName": "uv-index-list 索引列表 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.7",
+ "description": "该组件用于展示索引列表,右侧带索引的列表,方便快速定位到具体内容,通常用于城市/机场选择等场景。类似于微信通讯录页面",
+ "keywords": [
+ "uv-index-list",
+ "uvui",
+ "uv-ui",
+ "index-list",
+ "索引列表"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-transition"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-index-list/readme.md b/ledApp/uni_modules/uv-index-list/readme.md
new file mode 100644
index 0000000..605741c
--- /dev/null
+++ b/ledApp/uni_modules/uv-index-list/readme.md
@@ -0,0 +1,19 @@
+## IndexList 索引列表
+
+> **组件名:uv-index-list**
+
+用于展示索引列表,右侧带索引的列表,方便快速定位到具体内容,通常用于城市/机场选择等场景,类似于微信通讯录页面。
+
+# <a href="https://www.uvui.cn/components/indexList.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-input/changelog.md b/ledApp/uni_modules/uv-input/changelog.md
new file mode 100644
index 0000000..2b354d0
--- /dev/null
+++ b/ledApp/uni_modules/uv-input/changelog.md
@@ -0,0 +1,29 @@
+## 1.0.13(2023-12-06)
+1. 优化
+## 1.0.12(2023-12-06)
+1. 阻止事件冒泡问题
+## 1.0.11(2023-11-10)
+1. 调整清除按钮样式的marginLeft,避免微信上多数情况触发不了的BUG
+## 1.0.10(2023-10-07)
+1. 修复搜狗输入法下存在不可清空的情况
+## 1.0.9(2023-09-14)
+1. 修复H5等情况设置禁用或可读情况下,点击事件无效的问题
+## 1.0.8(2023-08-22)
+1. 修复无法@keyboardheightchange无法获取键盘高度的BUG
+## 1.0.7(2023-08-18)
+1. 修复ios端不能输入的BUG
+## 1.0.6(2023-08-05)
+1. 修复在vue2模式下,v-model设置为0时不生效的BUG
+## 1.0.5(2023-07-18)
+1. 修复在微信小程序端清除内容存在不能清除的BUG
+## 1.0.4(2023-07-13)
+1. 修复value/v-model更改不生效的BUG
+## 1.0.3(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.2(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.1(2023-05-12)
+1. 修复vue3双向绑定的BUG
+## 1.0.0(2023-05-10)
+uv-input 输入框
diff --git a/ledApp/uni_modules/uv-input/components/uv-input/props.js b/ledApp/uni_modules/uv-input/components/uv-input/props.js
new file mode 100644
index 0000000..e969493
--- /dev/null
+++ b/ledApp/uni_modules/uv-input/components/uv-input/props.js
@@ -0,0 +1,175 @@
+export default {
+ props: {
+ value: {
+ type: [String, Number],
+ default: ''
+ },
+ modelValue: {
+ type: [String, Number],
+ default: ''
+ },
+ // 输入框类型
+ // number-数字输入键盘,app-vue下可以输入浮点数,app-nvue和小程序平台下只能输入整数
+ // idcard-身份证输入键盘,微信、支付宝、百度、QQ小程序
+ // digit-带小数点的数字键盘,App的nvue页面、微信、支付宝、百度、头条、QQ小程序
+ // text-文本输入键盘
+ type: {
+ type: String,
+ default: 'text'
+ },
+ // 是否禁用输入框
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 禁用状态时的背景色
+ disabledColor: {
+ type: String,
+ default: '#f5f7fa'
+ },
+ // 是否显示清除控件
+ clearable: {
+ type: Boolean,
+ default: false
+ },
+ // 是否密码类型
+ password: {
+ type: Boolean,
+ default: false
+ },
+ // 最大输入长度,设置为 -1 的时候不限制最大长度
+ maxlength: {
+ type: [String, Number],
+ default: -1
+ },
+ // 输入框为空时的占位符
+ placeholder: {
+ type: String,
+ default: null
+ },
+ // 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/
+ placeholderClass: {
+ type: String,
+ default: 'input-placeholder'
+ },
+ // 指定placeholder的样式
+ placeholderStyle: {
+ type: [String, Object],
+ default: 'color: #c0c4cc'
+ },
+ // 设置右下角按钮的文字,有效值:send|search|next|go|done,兼容性详见uni-app文档
+ // https://uniapp.dcloud.io/component/input
+ // https://uniapp.dcloud.io/component/textarea
+ confirmType: {
+ type: String,
+ default: 'done'
+ },
+ // 点击键盘右下角按钮时是否保持键盘不收起,H5无效
+ confirmHold: {
+ type: Boolean,
+ default: false
+ },
+ // focus时,点击页面的时候不收起键盘,微信小程序有效
+ holdKeyboard: {
+ type: Boolean,
+ default: false
+ },
+ // 自动获取焦点
+ // 在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点
+ focus: {
+ type: Boolean,
+ default: false
+ },
+ // 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效
+ autoBlur: {
+ type: Boolean,
+ default: false
+ },
+ // 指定focus时光标的位置
+ cursor: {
+ type: [String, Number],
+ default: -1
+ },
+ // 输入框聚焦时底部与键盘的距离
+ cursorSpacing: {
+ type: [String, Number],
+ default: 30
+ },
+ // 光标起始位置,自动聚集时有效,需与selection-end搭配使用
+ selectionStart: {
+ type: [String, Number],
+ default: -1
+ },
+ // 光标结束位置,自动聚集时有效,需与selection-start搭配使用
+ selectionEnd: {
+ type: [String, Number],
+ default: -1
+ },
+ // 键盘弹起时,是否自动上推页面
+ adjustPosition: {
+ type: Boolean,
+ default: true
+ },
+ // 输入框内容对齐方式,可选值为:left|center|right
+ inputAlign: {
+ type: String,
+ default: 'left'
+ },
+ // 输入框字体的大小
+ fontSize: {
+ type: [String, Number],
+ default: '14px'
+ },
+ // 输入框字体颜色
+ color: {
+ type: String,
+ default: '#303133'
+ },
+ // 输入框前置图标
+ prefixIcon: {
+ type: String,
+ default: ''
+ },
+ // 前置图标样式,对象或字符串
+ prefixIconStyle: {
+ type: [String, Object],
+ default: ''
+ },
+ // 输入框后置图标
+ suffixIcon: {
+ type: String,
+ default: ''
+ },
+ // 后置图标样式,对象或字符串
+ suffixIconStyle: {
+ type: [String, Object],
+ default: ''
+ },
+ // 边框类型,surround-四周边框,bottom-底部边框,none-无边框
+ border: {
+ type: String,
+ default: 'surround'
+ },
+ // 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会
+ readonly: {
+ type: Boolean,
+ default: false
+ },
+ // 输入框形状,circle-圆形,square-方形
+ shape: {
+ type: String,
+ default: 'square'
+ },
+ // 用于处理或者过滤输入框内容的方法
+ formatter: {
+ type: [Function, null],
+ default: null
+ },
+ // 是否忽略组件内对文本合成系统事件的处理
+ ignoreCompositionEvent: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.input
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-input/components/uv-input/uv-input.vue b/ledApp/uni_modules/uv-input/components/uv-input/uv-input.vue
new file mode 100644
index 0000000..20c1801
--- /dev/null
+++ b/ledApp/uni_modules/uv-input/components/uv-input/uv-input.vue
@@ -0,0 +1,348 @@
+<template>
+ <view class="uv-input" :class="inputClass" :style="[wrapperStyle]">
+ <view class="uv-input__content">
+ <view class="uv-input__content__prefix-icon">
+ <slot name="prefix">
+ <uv-icon
+ v-if="prefixIcon"
+ :name="prefixIcon"
+ size="18"
+ :customStyle="prefixIconStyle"
+ ></uv-icon>
+ </slot>
+ </view>
+ <view class="uv-input__content__field-wrapper" @click="clickHandler">
+ <!-- 根据uni-app的input组件文档,H5和APP中只要声明了password参数(无论true还是false),type均失效,此时
+ 为了防止type=number时,又存在password属性,type无效,此时需要设置password为undefined
+ -->
+ <input
+ class="uv-input__content__field-wrapper__field"
+ :style="[inputStyle]"
+ :type="type"
+ :focus="focus"
+ :cursor="cursor"
+ :value="innerValue"
+ :auto-blur="autoBlur"
+ :disabled="disabled || readonly"
+ :maxlength="maxlength"
+ :placeholder="placeholder"
+ :placeholder-style="placeholderStyle"
+ :placeholder-class="placeholderClass"
+ :confirm-type="confirmType"
+ :confirm-hold="confirmHold"
+ :hold-keyboard="holdKeyboard"
+ :cursor-spacing="cursorSpacing"
+ :adjust-position="adjustPosition"
+ :selection-end="selectionEnd"
+ :selection-start="selectionStart"
+ :password="password || type === 'password' || undefined"
+ :ignoreCompositionEvent="ignoreCompositionEvent"
+ @input="onInput"
+ @blur="onBlur"
+ @focus="onFocus"
+ @confirm="onConfirm"
+ @keyboardheightchange="onkeyboardheightchange"
+ />
+ </view>
+ <view
+ class="uv-input__content__clear"
+ v-if="isShowClear"
+ @tap="onClear"
+ >
+ <uv-icon
+ name="close"
+ size="11"
+ color="#ffffff"
+ customStyle="line-height: 12px"
+ ></uv-icon>
+ </view>
+ <view class="uv-input__content__subfix-icon">
+ <slot name="suffix">
+ <uv-icon
+ v-if="suffixIcon"
+ :name="suffixIcon"
+ size="18"
+ :customStyle="suffixIconStyle"
+ ></uv-icon>
+ </slot>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from "./props.js";
+ /**
+ * Input 输入框
+ * @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件uv-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
+ * @tutorial https://www.uvui.cn/components/input.html
+ * @property {String | Number} value 输入的值
+ * @property {String} type 输入框类型,见上方说明 ( 默认 'text' )
+ * @property {Boolean} fixed 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序 ( 默认 false )
+ * @property {Boolean} disabled 是否禁用输入框 ( 默认 false )
+ * @property {String} disabledColor 禁用状态时的背景色( 默认 '#f5f7fa' )
+ * @property {Boolean} clearable 是否显示清除控件 ( 默认 false )
+ * @property {Boolean} password 是否密码类型 ( 默认 false )
+ * @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度 ( 默认 -1 )
+ * @property {String} placeholder 输入框为空时的占位符
+ * @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
+ * @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
+ * @property {Boolean} showWordLimit 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效 ( 默认 false )
+ * @property {String} confirmType 设置右下角按钮的文字,兼容性详见uni-app文档 ( 默认 'done' )
+ * @property {Boolean} confirmHold 点击键盘右下角按钮时是否保持键盘不收起,H5无效 ( 默认 false )
+ * @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,微信小程序有效 ( 默认 false )
+ * @property {Boolean} focus 自动获取焦点,在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 ( 默认 false )
+ * @property {Boolean} autoBlur 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 ( 默认 false )
+ * @property {Boolean} disableDefaultPadding 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 ( 默认 false )
+ * @property {String | Number} cursor 指定focus时光标的位置( 默认 -1 )
+ * @property {String | Number} cursorSpacing 输入框聚焦时底部与键盘的距离 ( 默认 30 )
+ * @property {String | Number} selectionStart 光标起始位置,自动聚集时有效,需与selection-end搭配使用 ( 默认 -1 )
+ * @property {String | Number} selectionEnd 光标结束位置,自动聚集时有效,需与selection-start搭配使用 ( 默认 -1 )
+ * @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面 ( 默认 true )
+ * @property {String} inputAlign 输入框内容对齐方式( 默认 'left' )
+ * @property {String | Number} fontSize 输入框字体的大小 ( 默认 '15px' )
+ * @property {String} color 输入框字体颜色 ( 默认 '#303133' )
+ * @property {Function} formatter 内容式化函数
+ * @property {String} prefixIcon 输入框前置图标
+ * @property {String | Object} prefixIconStyle 前置图标样式,对象或字符串
+ * @property {String} suffixIcon 输入框后置图标
+ * @property {String | Object} suffixIconStyle 后置图标样式,对象或字符串
+ * @property {String} border 边框类型,surround-四周边框,bottom-底部边框,none-无边框 ( 默认 'surround' )
+ * @property {Boolean} readonly 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 ( 默认 false )
+ * @property {String} shape 输入框形状,circle-圆形,square-方形 ( 默认 'square' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理。
+ * @example <uv-input v-model="value" :password="true" suffix-icon="lock-fill" />
+ */
+ export default {
+ name: "uv-input",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 输入框的值
+ innerValue: "",
+ // 是否处于获得焦点状态
+ focused: false,
+ // 过滤处理方法
+ innerFormatter: value => value
+ }
+ },
+ created() {
+ // #ifdef VUE2
+ this.innerValue = this.value;
+ // #endif
+ // #ifdef VUE3
+ this.innerValue = this.modelValue;
+ // #endif
+ },
+ watch: {
+ value(newVal){
+ this.innerValue = newVal;
+ },
+ modelValue(newVal){
+ this.innerValue = newVal;
+ }
+ },
+ computed: {
+ // 是否显示清除控件
+ isShowClear() {
+ const { clearable, readonly, focused, innerValue } = this;
+ return !!clearable && !readonly && !!focused && innerValue !== "";
+ },
+ // 组件的类名
+ inputClass() {
+ let classes = [],
+ { border, disabled, shape } = this;
+ border === "surround" &&
+ (classes = classes.concat(["uv-border", "uv-input--radius"]));
+ classes.push(`uv-input--${shape}`);
+ border === "bottom" &&
+ (classes = classes.concat([
+ "uv-border-bottom",
+ "uv-input--no-radius",
+ ]));
+ return classes.join(" ");
+ },
+ // 组件的样式
+ wrapperStyle() {
+ const style = {};
+ // 禁用状态下,被背景色加上对应的样式
+ if (this.disabled) {
+ style.backgroundColor = this.disabledColor;
+ }
+ // 无边框时,去除内边距
+ if (this.border === "none") {
+ style.padding = "0";
+ } else {
+ // 由于uni-app的iOS开发者能力有限,导致需要分开写才有效
+ style.paddingTop = "6px";
+ style.paddingBottom = "6px";
+ style.paddingLeft = "9px";
+ style.paddingRight = "9px";
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+ },
+ // 输入框的样式
+ inputStyle() {
+ const style = {
+ color: this.color,
+ fontSize: this.$uv.addUnit(this.fontSize),
+ textAlign: this.inputAlign
+ };
+ // #ifndef APP-NVUE
+ if(this.disabled || this.readonly) {
+ style['pointer-events'] = 'none';
+ }
+ // #endif
+ return style;
+ }
+ },
+ methods: {
+ // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+ setFormatter(e) {
+ this.innerFormatter = e
+ },
+ // 当键盘输入时,触发input事件
+ onInput(e) {
+ let { value = "" } = e.detail || {};
+ // 格式化过滤方法
+ const formatter = this.formatter || this.innerFormatter
+ const formatValue = formatter(value)
+ // 为了避免props的单向数据流特性,需要先将innerValue值设置为当前值,再在$nextTick中重新赋予设置后的值才有效
+ this.innerValue = value
+ this.$nextTick(() => {
+ this.innerValue = formatValue;
+ this.valueChange();
+ })
+ },
+ // 输入框失去焦点时触发
+ onBlur(event) {
+ this.$emit("blur", event.detail.value);
+ // H5端的blur会先于点击清除控件的点击click事件触发,导致focused
+ // 瞬间为false,从而隐藏了清除控件而无法被点击到
+ this.$uv.sleep(100).then(() => {
+ this.focused = false;
+ });
+ // 尝试调用uv-form的验证方法
+ this.$uv.formValidate(this, "blur");
+ },
+ // 输入框聚焦时触发
+ onFocus(event) {
+ this.focused = true;
+ this.$emit("focus");
+ },
+ // 点击完成按钮时触发
+ onConfirm(event) {
+ this.$emit("confirm", this.innerValue);
+ },
+ // 键盘高度发生变化的时候触发此事件
+ // 兼容性:微信小程序2.7.0+、App 3.1.0+
+ onkeyboardheightchange(e) {
+ this.$emit("keyboardheightchange",e);
+ },
+ // 内容发生变化,进行处理
+ valueChange() {
+ if(this.isClear) this.innerValue = '';
+ const value = this.innerValue;
+ this.$nextTick(() => {
+ this.$emit("input", value);
+ this.$emit("update:modelValue", value);
+ this.$emit("change", value);
+ // 尝试调用uv-form的验证方法
+ this.$uv.formValidate(this, "change");
+ });
+ },
+ // 点击清除控件
+ onClear() {
+ this.innerValue = "";
+ this.isClear = true;
+ this.$uv.sleep(200).then(res=>{
+ this.isClear = false;
+ })
+ this.$nextTick(() => {
+ this.$emit("clear");
+ this.valueChange();
+ });
+ },
+ /**
+ * 在安卓nvue上,事件无法冒泡
+ * 在某些时间,我们希望监听uv-from-item的点击事件,此时会导致点击uv-form-item内的uv-input后
+ * 无法触发uv-form-item的点击事件,这里通过手动调用uv-form-item的方法进行触发
+ */
+ clickHandler() {
+ // #ifdef APP-NVUE
+ if (this.$uv.os() === "android") {
+ const formItem = this.$uv.$parent.call(this, "uv-form-item");
+ if (formItem) {
+ formItem.clickHandler();
+ }
+ }
+ // #endif
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-border: 1;
+ $show-border-surround: 1;
+ $show-border-bottom: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-input {
+ @include flex(row);
+ align-items: center;
+ justify-content: space-between;
+ flex: 1;
+ &--radius,
+ &--square {
+ border-radius: 4px;
+ }
+ &--no-radius {
+ border-radius: 0;
+ }
+ &--circle {
+ border-radius: 100px;
+ }
+ &__content {
+ flex: 1;
+ @include flex(row);
+ align-items: center;
+ justify-content: space-between;
+ &__field-wrapper {
+ position: relative;
+ @include flex(row);
+ margin: 0;
+ flex: 1;
+ &__field {
+ line-height: 26px;
+ text-align: left;
+ color: $uv-main-color;
+ height: 24px;
+ font-size: 15px;
+ flex: 1;
+ }
+ }
+ &__clear {
+ width: 20px;
+ height: 20px;
+ border-radius: 100px;
+ background-color: #c6c7cb;
+ @include flex(row);
+ align-items: center;
+ justify-content: center;
+ transform: scale(0.82);
+ margin-left: 15px;
+ }
+ &__subfix-icon {
+ margin-left: 4px;
+ }
+ &__prefix-icon {
+ margin-right: 4px;
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-input/package.json b/ledApp/uni_modules/uv-input/package.json
new file mode 100644
index 0000000..6981fa9
--- /dev/null
+++ b/ledApp/uni_modules/uv-input/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-input",
+ "displayName": "uv-input 输入框 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.13",
+ "description": "uv-input 该组件为一个输入框,默认没有边框和样式,是专门为配合表单组件uv-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。",
+ "keywords": [
+ "uv-input",
+ "uvui",
+ "uv-ui",
+ "input",
+ "输入框"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-input/readme.md b/ledApp/uni_modules/uv-input/readme.md
new file mode 100644
index 0000000..00f47e4
--- /dev/null
+++ b/ledApp/uni_modules/uv-input/readme.md
@@ -0,0 +1,19 @@
+## Input 输入框
+
+> **组件名:uv-input**
+
+此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件uv-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
+
+# <a href="https://www.uvui.cn/components/input.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-keyboard/changelog.md b/ledApp/uni_modules/uv-keyboard/changelog.md
new file mode 100644
index 0000000..1d1be01
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/changelog.md
@@ -0,0 +1,17 @@
+## 1.0.5(2023-10-12)
+1. 增加disKeys参数,mode = "car"下,被禁用的键,如:['I','O']
+2. 增加customabc参数,mode = "car"下,是否启用自定义中英文切换内容模式,为了兼容支付宝等小程序不兼容嵌套插槽,导致同时显示自定义内容和原始内容
+3. 增加ref方法changeCarMode,mode = "car"下, 调用此方法可以切换中英文模式
+4. 增加@changeCarInputMode,mode = "car"下,调用此方法可以进行切换中英文
+5. 增加插槽abc,mode = "car"下,自定义中英文切换内容,具体参考[车牌键盘自定义中英文切换及禁用键等设置](https://www.uvui.cn/components/keyboard.html#车牌键盘自定义中英文切换及禁用键等设置)
+## 1.0.4(2023-09-04)
+1. 优化,修改文件名称
+## 1.0.3(2023-09-04)
+1. 修复键盘change回调事件产生冲突的BUG
+## 1.0.2(2023-07-02)
+uv-keyboard 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/keyboard.html
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-keyboard 键盘
diff --git a/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-car/props.js b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-car/props.js
new file mode 100644
index 0000000..3dd93cc
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-car/props.js
@@ -0,0 +1,24 @@
+export default {
+ props: {
+ // 是否打乱键盘按键的顺序
+ random: {
+ type: Boolean,
+ default: false
+ },
+ // 输入一个中文后,是否自动切换到英文
+ autoChange: {
+ type: Boolean,
+ default: false
+ },
+ // 被禁用的键
+ disKeys: {
+ type: Array,
+ default: ()=>[]
+ },
+ // 是否自定义abc
+ customabc: {
+ type: Boolean,
+ default: false
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-car/uv-keyboard-car.vue b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-car/uv-keyboard-car.vue
new file mode 100644
index 0000000..e99e910
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-car/uv-keyboard-car.vue
@@ -0,0 +1,347 @@
+<template>
+ <view
+ class="uv-keyboard"
+ @touchmove.stop.prevent="noop"
+ >
+ <view
+ v-for="(group, i) in abc ? engKeyBoardList : areaList"
+ :key="i"
+ class="uv-keyboard__button"
+ :index="i"
+ :class="[i + 1 === 4 && 'uv-keyboard__button--center']"
+ >
+ <view
+ v-if="i === 3"
+ class="uv-keyboard__button__inner-wrapper"
+ >
+ <view
+ class="uv-keyboard__button__inner-wrapper__left"
+ hover-class="uv-hover-class"
+ :hover-stay-time="200"
+ @tap="changeCarInputMode"
+ >
+ <slot>
+ <template v-if="!customabc">
+ <text
+ class="uv-keyboard__button__inner-wrapper__left__lang"
+ :class="[!abc && 'uv-keyboard__button__inner-wrapper__left__lang--active']"
+ >中</text>
+ <text class="uv-keyboard__button__inner-wrapper__left__line">/</text>
+ <text
+ class="uv-keyboard__button__inner-wrapper__left__lang"
+ :class="[abc && 'uv-keyboard__button__inner-wrapper__left__lang--active']"
+ >英</text>
+ </template>
+ </slot>
+ </view>
+ </view>
+ <view
+ class="uv-keyboard__button__inner-wrapper"
+ v-for="(item, j) in group"
+ :key="j"
+ >
+ <view
+ :class="['uv-keyboard__button__inner-wrapper__inner',{'uv-keyboard__button__inner-wrapper__inner--disabled': isDisabled(i,j)}]"
+ :hover-stay-time="200"
+ @tap="carInputClick(i, j)"
+ :hover-class="isDisabled(i,j)?'none':'uv-hover-class'"
+ >
+ <text class="uv-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
+ </view>
+ <view class="uv-keyboard__button__inner-wrapper__disabled--mask" v-if="isDisabled(i,j)"></view>
+ </view>
+ <view
+ v-if="i === 3"
+ @touchstart="backspaceClick"
+ @touchend="clearTimer"
+ class="uv-keyboard__button__inner-wrapper"
+ >
+ <view
+ class="uv-keyboard__button__inner-wrapper__right"
+ hover-class="uv-hover-class"
+ :hover-stay-time="200"
+ >
+ <uv-icon
+ size="28"
+ name="backspace"
+ color="#303133"
+ ></uv-icon>
+ </view>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * keyboard 键盘组件
+ * @description 此为uvui自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
+ * @tutorial https://www.uvui.cn/components/keyboard.html
+ * @property {Boolean} random 是否打乱键盘的顺序
+ * @event {Function} change 点击键盘触发
+ * @event {Function} backspace 点击退格键触发
+ * @example <uv-keyboard ref="uKeyboard" mode="car" v-model="show"></uv-keyboard>
+ */
+ export default {
+ name: "uv-keyboard",
+ mixins: [mpMixin, mixin, props],
+ emits: ['backspace','change','changeCarInputMode'],
+ data() {
+ return {
+ // 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
+ abc: false
+ };
+ },
+ computed: {
+ areaList() {
+ let data = [
+ '京',
+ '沪',
+ '粤',
+ '津',
+ '冀',
+ '豫',
+ '云',
+ '辽',
+ '黑',
+ '湘',
+ '皖',
+ '鲁',
+ '苏',
+ '浙',
+ '赣',
+ '鄂',
+ '桂',
+ '甘',
+ '晋',
+ '陕',
+ '蒙',
+ '吉',
+ '闽',
+ '贵',
+ '渝',
+ '川',
+ '青',
+ '琼',
+ '宁',
+ '挂',
+ '藏',
+ '港',
+ '澳',
+ '新',
+ '使',
+ '学'
+ ];
+ let tmp = [];
+ // 打乱顺序
+ if (this.random) data = this.$uv.randomArray(data);
+ // 切割成二维数组
+ tmp[0] = data.slice(0, 10);
+ tmp[1] = data.slice(10, 20);
+ tmp[2] = data.slice(20, 30);
+ tmp[3] = data.slice(30, 36);
+ return tmp;
+ },
+ engKeyBoardList() {
+ let data = [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 0,
+ 'Q',
+ 'W',
+ 'E',
+ 'R',
+ 'T',
+ 'Y',
+ 'U',
+ 'I',
+ 'O',
+ 'P',
+ 'A',
+ 'S',
+ 'D',
+ 'F',
+ 'G',
+ 'H',
+ 'J',
+ 'K',
+ 'L',
+ 'Z',
+ 'X',
+ 'C',
+ 'V',
+ 'B',
+ 'N',
+ 'M'
+ ];
+ let tmp = [];
+ if (this.random) data = this.$uv.randomArray(data);
+ tmp[0] = data.slice(0, 10);
+ tmp[1] = data.slice(10, 20);
+ tmp[2] = data.slice(20, 30);
+ tmp[3] = data.slice(30, 36);
+ return tmp;
+ },
+ isDisabled(i,j) {
+ return (i,j)=>{
+ let value = '';
+ if (this.abc) value = this.engKeyBoardList[i][j];
+ else value = this.areaList[i][j];
+ return this.disKeys.indexOf(value) > -1;
+ }
+ }
+ },
+ methods: {
+ // 点击键盘按钮
+ carInputClick(i, j) {
+ if(this.isDisabled(i,j)) return;
+ let value = '';
+ // 不同模式,获取不同数组的值
+ if (this.abc) value = this.engKeyBoardList[i][j];
+ else value = this.areaList[i][j];
+ // 如果允许自动切换,则将中文状态切换为英文
+ if (!this.abc && this.autoChange) this.$uv.sleep(200).then(() => this.abc = true)
+ this.$emit('change', value);
+ },
+ // 修改汽车牌键盘的输入模式,中文|英文
+ changeCarInputMode() {
+ this.abc = !this.abc;
+ this.$emit('changeCarInputMode',this.abc);
+ },
+ // 点击退格键
+ backspaceClick() {
+ this.$emit('backspace');
+ clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
+ this.timer = null;
+ this.timer = setInterval(() => {
+ this.$emit('backspace');
+ }, 250);
+ },
+ clearTimer() {
+ clearInterval(this.timer);
+ this.timer = null;
+ },
+ }
+ };
+</script>
+
+<style lang="scss" scoped>
+ $show-hover: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-car-keyboard-background-color: rgb(224, 228, 230) !default;
+ $uv-car-keyboard-padding:6px 0 6px !default;
+ $uv-car-keyboard-button-inner-width:64rpx !default;
+ $uv-car-keyboard-button-inner-background-color:#FFFFFF !default;
+ $uv-car-keyboard-button-height:80rpx !default;
+ $uv-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
+ $uv-car-keyboard-button-border-radius:4px !default;
+ $uv-car-keyboard-button-inner-margin:8rpx 5rpx !default;
+ $uv-car-keyboard-button-text-font-size:16px !default;
+ $uv-car-keyboard-button-text-color:$uv-main-color !default;
+ $uv-car-keyboard-center-inner-margin: 0 4rpx !default;
+ $uv-car-keyboard-special-button-width:134rpx !default;
+ $uv-car-keyboard-lang-font-size:16px !default;
+ $uv-car-keyboard-lang-color:$uv-main-color !default;
+ $uv-car-keyboard-active-color:$uv-primary !default;
+ $uv-car-keyboard-line-font-size:15px !default;
+ $uv-car-keyboard-line-color:$uv-main-color !default;
+ $uv-car-keyboard-line-margin:0 1px !default;
+ $uv-car-keyboard-uv-hover-class-background-color:#BBBCC6 !default;
+
+ .uv-keyboard {
+ @include flex(column);
+ justify-content: space-around;
+ background-color: $uv-car-keyboard-background-color;
+ align-items: stretch;
+ padding: $uv-car-keyboard-padding;
+
+ &__button {
+ @include flex;
+ justify-content: center;
+ flex: 1;
+ /* #ifndef APP-NVUE */
+ /* #endif */
+
+ &__inner-wrapper {
+ position: relative;
+ box-shadow: $uv-car-keyboard-button-inner-box-shadow;
+ margin: $uv-car-keyboard-button-inner-margin;
+ border-radius: $uv-car-keyboard-button-border-radius;
+
+ &__disabled--mask {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ width: $uv-car-keyboard-button-inner-width;
+ height: $uv-car-keyboard-button-height;
+ }
+
+ &__inner {
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ width: $uv-car-keyboard-button-inner-width;
+ background-color: $uv-car-keyboard-button-inner-background-color;
+ height: $uv-car-keyboard-button-height;
+ border-radius: $uv-car-keyboard-button-border-radius;
+
+ &--disabled {
+ opacity: 0.5;
+ }
+
+ &__text {
+ font-size: $uv-car-keyboard-button-text-font-size;
+ color: $uv-car-keyboard-button-text-color;
+ }
+ }
+
+ &__left,
+ &__right {
+ border-radius: $uv-car-keyboard-button-border-radius;
+ width: $uv-car-keyboard-special-button-width;
+ height: $uv-car-keyboard-button-height;
+ background-color: $uv-car-keyboard-uv-hover-class-background-color;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: $uv-car-keyboard-button-inner-box-shadow;
+ }
+
+ &__left {
+ &__line {
+ font-size: $uv-car-keyboard-line-font-size;
+ color: $uv-car-keyboard-line-color;
+ margin: $uv-car-keyboard-line-margin;
+ }
+
+ &__lang {
+ font-size: $uv-car-keyboard-lang-font-size;
+ color: $uv-car-keyboard-lang-color;
+
+ &--active {
+ color: $uv-car-keyboard-active-color;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .uv-hover-class {
+ background-color: $uv-car-keyboard-uv-hover-class-background-color;
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-number/props.js b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-number/props.js
new file mode 100644
index 0000000..60d63d4
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-number/props.js
@@ -0,0 +1,19 @@
+export default {
+ props: {
+ // 键盘的类型,number-数字键盘,card-身份证键盘
+ mode: {
+ type: String,
+ default: 'number'
+ },
+ // 是否显示键盘的"."符号
+ dotDisabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否打乱键盘按键的顺序
+ random: {
+ type: Boolean,
+ default: false
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-number/uv-keyboard-number.vue b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-number/uv-keyboard-number.vue
new file mode 100644
index 0000000..b7c0b8b
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard-number/uv-keyboard-number.vue
@@ -0,0 +1,201 @@
+<template>
+ <view
+ class="uv-keyboard"
+ @touchmove.stop.prevent="noop"
+ >
+ <view
+ class="uv-keyboard__button-wrapper"
+ v-for="(item, index) in numList"
+ :key="index"
+ >
+ <view
+ class="uv-keyboard__button-wrapper__button"
+ :style="[itemStyle(index)]"
+ @tap="keyboardClick(item)"
+ hover-class="uv-hover-class"
+ :hover-stay-time="200"
+ >
+ <text class="uv-keyboard__button-wrapper__button__text">{{ item }}</text>
+ </view>
+ </view>
+ <view
+ class="uv-keyboard__button-wrapper"
+ >
+ <view
+ class="uv-keyboard__button-wrapper__button uv-keyboard__button-wrapper__button--gray"
+ hover-class="uv-hover-class"
+ :hover-stay-time="200"
+ @touchstart.stop="backspaceClick"
+ @touchend="clearTimer"
+ >
+ <uv-icon
+ name="backspace"
+ color="#303133"
+ size="28"
+ ></uv-icon>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * keyboard 键盘组件
+ * @description
+ * @tutorial
+ * @property {String} mode 键盘的类型,number-数字键盘,card-身份证键盘
+ * @property {Boolean} dotDisabled 是否显示键盘的"."符号
+ * @property {Boolean} random 是否打乱键盘按键的顺序
+ * @event {Function} change 点击键盘触发
+ * @event {Function} backspace 点击退格键触发
+ * @example
+ */
+ export default {
+ mixins: [mpMixin, mixin, props],
+ emits: ['backspace','change'],
+ data() {
+ return {
+ backspace: 'backspace', // 退格键内容
+ dot: '.', // 点
+ timer: null, // 长按多次删除的事件监听
+ cardX: 'X' // 身份证的X符号
+ };
+ },
+ computed: {
+ // 键盘需要显示的内容
+ numList() {
+ let tmp = [];
+ if (this.dotDisabled && this.mode == 'number') {
+ if (!this.random) {
+ return [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
+ } else {
+ return this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);
+ }
+ } else if (!this.dotDisabled && this.mode == 'number') {
+ if (!this.random) {
+ return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0];
+ } else {
+ return this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]);
+ }
+ } else if (this.mode == 'card') {
+ if (!this.random) {
+ return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0];
+ } else {
+ return this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]);
+ }
+ }
+ },
+ // 按键的样式,在非乱序&&数字键盘&&不显示点按钮时,index为9时,按键占位两个空间
+ itemStyle() {
+ return index => {
+ let style = {};
+ if (this.mode == 'number' && this.dotDisabled && index == 9) style.width = '464rpx';
+ return style;
+ };
+ },
+ // 是否让按键显示灰色,只在非乱序&&数字键盘&&且允许点按键的时候
+ btnBgGray() {
+ return index => {
+ if (!this.random && index == 9 && (this.mode != 'number' || (this.mode == 'number' && !this
+ .dotDisabled))) return true;
+ else return false;
+ };
+ },
+ },
+ created() {
+
+ },
+ methods: {
+ // 点击退格键
+ backspaceClick() {
+ this.$emit('backspace');
+ clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
+ this.timer = null;
+ this.timer = setInterval(() => {
+ this.$emit('backspace');
+ }, 250);
+ },
+ clearTimer() {
+ clearInterval(this.timer);
+ this.timer = null;
+ },
+ // 获取键盘显示的内容
+ keyboardClick(val) {
+ // 允许键盘显示点模式和触发非点按键时,将内容转为数字类型
+ if (!this.dotDisabled && val != this.dot && val != this.cardX) val = Number(val);
+ this.$emit('change', val);
+ }
+ }
+ };
+</script>
+
+<style lang="scss" scoped>
+ $show-hover: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-number-keyboard-background-color:rgb(224, 228, 230) !default;
+ $uv-number-keyboard-padding:8px 10rpx 8px 10rpx !default;
+ $uv-number-keyboard-button-width:222rpx !default;
+ $uv-number-keyboard-button-margin:4px 6rpx !default;
+ $uv-number-keyboard-button-border-top-left-radius:4px !default;
+ $uv-number-keyboard-button-border-top-right-radius:4px !default;
+ $uv-number-keyboard-button-border-bottom-left-radius:4px !default;
+ $uv-number-keyboard-button-border-bottom-right-radius:4px !default;
+ $uv-number-keyboard-button-height: 90rpx!default;
+ $uv-number-keyboard-button-background-color:#FFFFFF !default;
+ $uv-number-keyboard-button-box-shadow:0 2px 0px #BBBCBE !default;
+ $uv-number-keyboard-text-font-size:20px !default;
+ $uv-number-keyboard-text-font-weight:500 !default;
+ $uv-number-keyboard-text-color:$uv-main-color !default;
+ $uv-number-keyboard-gray-background-color:rgb(200, 202, 210) !default;
+ $uv-number-keyboard-uv-hover-class-background-color: #BBBCC6 !default;
+
+ .uv-keyboard {
+ @include flex;
+ flex-direction: row;
+ justify-content: space-around;
+ background-color: $uv-number-keyboard-background-color;
+ flex-wrap: wrap;
+ padding: $uv-number-keyboard-padding;
+
+ &__button-wrapper {
+ box-shadow: $uv-number-keyboard-button-box-shadow;
+ margin: $uv-number-keyboard-button-margin;
+ border-top-left-radius: $uv-number-keyboard-button-border-top-left-radius;
+ border-top-right-radius: $uv-number-keyboard-button-border-top-right-radius;
+ border-bottom-left-radius: $uv-number-keyboard-button-border-bottom-left-radius;
+ border-bottom-right-radius: $uv-number-keyboard-button-border-bottom-right-radius;
+
+ &__button {
+ width: $uv-number-keyboard-button-width;
+ height: $uv-number-keyboard-button-height;
+ background-color: $uv-number-keyboard-button-background-color;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ border-top-left-radius: $uv-number-keyboard-button-border-top-left-radius;
+ border-top-right-radius: $uv-number-keyboard-button-border-top-right-radius;
+ border-bottom-left-radius: $uv-number-keyboard-button-border-bottom-left-radius;
+ border-bottom-right-radius: $uv-number-keyboard-button-border-bottom-right-radius;
+
+ &__text {
+ font-size: $uv-number-keyboard-text-font-size;
+ font-weight: $uv-number-keyboard-text-font-weight;
+ color: $uv-number-keyboard-text-color;
+ }
+
+ &--gray {
+ background-color: $uv-number-keyboard-gray-background-color;
+ }
+ }
+ }
+ }
+
+ .uv-hover-class {
+ background-color: $uv-number-keyboard-uv-hover-class-background-color;
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-keyboard/components/uv-keyboard/props.js b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard/props.js
new file mode 100644
index 0000000..05dd76a
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard/props.js
@@ -0,0 +1,95 @@
+export default {
+ props: {
+ // 键盘的类型,number-数字键盘,card-身份证键盘,car-车牌号键盘
+ mode: {
+ type: String,
+ default: 'number'
+ },
+ // 是否显示键盘的"."符号
+ dotDisabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示顶部工具条
+ tooltip: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示工具条中间的提示
+ showTips: {
+ type: Boolean,
+ default: true
+ },
+ // 工具条中间的提示文字
+ tips: {
+ type: String,
+ default: ''
+ },
+ // 是否显示工具条左边的"取消"按钮
+ showCancel: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示工具条右边的"完成"按钮
+ showConfirm: {
+ type: Boolean,
+ default: true
+ },
+ // 是否打乱键盘按键的顺序
+ random: {
+ type: Boolean,
+ default: false
+ },
+ // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
+ safeAreaInsetBottom: {
+ type: Boolean,
+ default: true
+ },
+ // 是否允许通过点击遮罩关闭键盘
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: true
+ },
+ // 是否允许点击确认按钮关闭组件
+ closeOnClickConfirm: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示遮罩,某些时候数字键盘时,用户希望看到自己的数值,所以可能不想要遮罩
+ overlay: {
+ type: Boolean,
+ default: true
+ },
+ // z-index值
+ zIndex: {
+ type: [String, Number],
+ default: 10075
+ },
+ // 取消按钮的文字
+ cancelText: {
+ type: String,
+ default: '取消'
+ },
+ // 确认按钮的文字
+ confirmText: {
+ type: String,
+ default: '确定'
+ },
+ // 输入一个中文后,是否自动切换到英文
+ autoChange: {
+ type: Boolean,
+ default: false
+ },
+ // 被禁用的键
+ disKeys: {
+ type: Array,
+ default: ()=>[]
+ },
+ // 是否自定义abc
+ customabc: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.keyboard
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-keyboard/components/uv-keyboard/uv-keyboard.vue b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard/uv-keyboard.vue
new file mode 100644
index 0000000..60b15bf
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/components/uv-keyboard/uv-keyboard.vue
@@ -0,0 +1,180 @@
+<template>
+ <uv-popup
+ ref="keyboardPopup"
+ mode="bottom"
+ :overlay="overlay"
+ :closeOnClickOverlay="closeOnClickOverlay"
+ :safeAreaInsetBottom="safeAreaInsetBottom"
+ :zIndex="zIndex"
+ :customStyle="{ backgroundColor: 'rgb(214, 218, 220)' }"
+ @change="popupChange"
+ >
+ <view class="uv-keyboard">
+ <slot />
+ <view
+ class="uv-keyboard__tooltip"
+ v-if="tooltip"
+ >
+ <view
+ hover-class="uv-hover-class"
+ :hover-stay-time="100"
+ >
+ <text
+ class="uv-keyboard__tooltip__item uv-keyboard__tooltip__cancel"
+ v-if="showCancel"
+ @tap="onCancel"
+ >{{showCancel && cancelText}}</text>
+ </view>
+ <view>
+ <text
+ v-if="showTips"
+ class="uv-keyboard__tooltip__item uv-keyboard__tooltip__tips"
+ >{{tips ? tips : mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}}</text>
+ </view>
+ <view
+ hover-class="uv-hover-class"
+ :hover-stay-time="100"
+ >
+ <text
+ v-if="showConfirm"
+ @tap="onConfirm"
+ class="uv-keyboard__tooltip__item uv-keyboard__tooltip__submit"
+ hover-class="uv-hover-class"
+ >{{showConfirm && confirmText}}</text>
+ </view>
+ </view>
+ <template v-if="mode == 'number' || mode == 'card'">
+ <uv-keyboard-number
+ :random="random"
+ @backspace="backspace"
+ @change="change"
+ :mode="mode"
+ :dotDisabled="dotDisabled"
+ ></uv-keyboard-number>
+ </template>
+ <template v-else>
+ <uv-keyboard-car
+ ref="uvKeyboardCarRef"
+ :random="random"
+ :autoChange="autoChange"
+ :disKeys="disKeys"
+ :customabc="customabc"
+ @backspace="backspace"
+ @change="change"
+ @changeCarInputMode="changeCarInputMode"
+ >
+ <slot name="abc"></slot>
+ </uv-keyboard-car>
+ </template>
+ </view>
+ </uv-popup>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * keyboard 键盘
+ * @description 此为uViw自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3中模式,都有可以打乱按键顺序的选项。
+ * @tutorial https://www.uvui.cn/components/keyboard.html
+ * @property {String} mode 键盘类型,见官网基本使用的说明 (默认 'number' )
+ * @property {Boolean} dotDisabled 是否显示"."按键,只在mode=number时有效 (默认 false )
+ * @property {Boolean} tooltip 是否显示键盘顶部工具条 (默认 true )
+ * @property {Boolean} showTips 是否显示工具条中间的提示 (默认 true )
+ * @property {String} tips 工具条中间的提示文字,见上方基本使用的说明,如不需要,请传""空字符
+ * @property {Boolean} showCancel 是否显示工具条左边的"取消"按钮 (默认 true )
+ * @property {Boolean} showConfirm 是否显示工具条右边的"完成"按钮( 默认 true )
+ * @property {Boolean} random 是否打乱键盘按键的顺序 (默认 false )
+ * @property {Boolean} safeAreaInsetBottom 是否开启底部安全区适配 (默认 true )
+ * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩收起键盘 (默认 true )
+ * @property {Boolean} show 控制键盘的弹出与收起(默认 false )
+ * @property {Boolean} overlay 是否显示遮罩 (默认 true )
+ * @property {String | Number} zIndex 弹出键盘的z-index值 (默认 1075 )
+ * @property {String} cancelText 取消按钮的文字 (默认 '取消' )
+ * @property {String} confirmText 确认按钮的文字 (默认 '确认' )
+ * @property {Object} customStyle 自定义样式,对象形式
+ * @event {Function} change 按键被点击(不包含退格键被点击)
+ * @event {Function} cancel 键盘顶部工具条左边的"取消"按钮被点击
+ * @event {Function} confirm 键盘顶部工具条右边的"完成"按钮被点击
+ * @event {Function} backspace 键盘退格键被点击
+ * @example <uv-keyboard mode="number" v-model="show"></uv-keyboard>
+ */
+ export default {
+ name: "uv-keyboard",
+ mixins: [mpMixin, mixin, props],
+ emits: ['close','change','confirm','cancel','backspace','changeCarInputMode'],
+ methods: {
+ open() {
+ this.$refs.keyboardPopup.open();
+ },
+ close() {
+ this.$refs.keyboardPopup.close();
+ },
+ popupChange(e) {
+ if(!e.show) this.$emit('close');
+ },
+ change(e) {
+ this.$emit('change', e);
+ },
+ // 输入完成
+ onConfirm() {
+ this.$emit('confirm');
+ if(this.closeOnClickConfirm) this.close();
+ },
+ // 取消输入
+ onCancel() {
+ this.$emit('cancel');
+ this.close();
+ },
+ // 退格键
+ backspace() {
+ this.$emit('backspace');
+ },
+ // car模式切换中文|英文方法
+ changeCarInputMode(e) {
+ this.$emit('changeCarInputMode',e);
+ },
+ changeCarMode() {
+ this.$refs.uvKeyboardCarRef && this.$refs.uvKeyboardCarRef.changeCarInputMode();
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-hover: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-keyboard {
+
+ &__tooltip {
+ @include flex;
+ justify-content: space-between;
+ background-color: #FFFFFF;
+ padding: 14px 12px;
+
+ &__item {
+ color: #333333;
+ flex: 1;
+ text-align: center;
+ font-size: 15px;
+ }
+
+ &__submit {
+ text-align: right;
+ color: $uv-primary;
+ }
+
+ &__cancel {
+ text-align: left;
+ color: #888888;
+ }
+
+ &__tips {
+ color: $uv-tips-color;
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-keyboard/package.json b/ledApp/uni_modules/uv-keyboard/package.json
new file mode 100644
index 0000000..69844c6
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-keyboard",
+ "displayName": "uv-keyboard 键盘 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.5",
+ "description": "uv-keyboard 该组件为自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。",
+ "keywords": [
+ "uv-keyboard",
+ "uvui",
+ "uv-ui",
+ "keyboard",
+ "键盘"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-popup",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-keyboard/readme.md b/ledApp/uni_modules/uv-keyboard/readme.md
new file mode 100644
index 0000000..9cbef6c
--- /dev/null
+++ b/ledApp/uni_modules/uv-keyboard/readme.md
@@ -0,0 +1,19 @@
+## Keyboard 键盘
+
+> **组件名:uv-keyboard**
+
+该组件为自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
+
+# <a href="https://www.uvui.cn/components/keyboard.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-line-progress/changelog.md b/ledApp/uni_modules/uv-line-progress/changelog.md
new file mode 100644
index 0000000..975acce
--- /dev/null
+++ b/ledApp/uni_modules/uv-line-progress/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-20)
+1. 适配height参数携带单位
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-line-progress 线形进度条
diff --git a/ledApp/uni_modules/uv-line-progress/components/uv-line-progress/props.js b/ledApp/uni_modules/uv-line-progress/components/uv-line-progress/props.js
new file mode 100644
index 0000000..23138c7
--- /dev/null
+++ b/ledApp/uni_modules/uv-line-progress/components/uv-line-progress/props.js
@@ -0,0 +1,29 @@
+export default {
+ props: {
+ // 激活部分的颜色
+ activeColor: {
+ type: String,
+ default: '#19be6b'
+ },
+ inactiveColor: {
+ type: String,
+ default: '#ececec'
+ },
+ // 进度百分比,数值
+ percentage: {
+ type: [String, Number],
+ default: 0
+ },
+ // 是否在进度条内部显示百分比的值
+ showText: {
+ type: Boolean,
+ default: true
+ },
+ // 进度条的高度,单位px
+ height: {
+ type: [String, Number],
+ default: 12
+ },
+ ...uni.$uv?.props?.lineProgress
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-line-progress/components/uv-line-progress/uv-line-progress.vue b/ledApp/uni_modules/uv-line-progress/components/uv-line-progress/uv-line-progress.vue
new file mode 100644
index 0000000..229c2aa
--- /dev/null
+++ b/ledApp/uni_modules/uv-line-progress/components/uv-line-progress/uv-line-progress.vue
@@ -0,0 +1,146 @@
+<template>
+ <view
+ class="uv-line-progress"
+ :style="[$uv.addStyle(customStyle)]"
+ >
+ <view
+ class="uv-line-progress__background"
+ ref="uv-line-progress__background"
+ :style="[{
+ backgroundColor: inactiveColor,
+ height: $uv.addUnit($uv.getPx(height))
+ }]"
+ >
+ </view>
+ <view
+ class="uv-line-progress__line"
+ :style="[progressStyle]"
+ >
+ <slot>
+ <text v-if="showText && percentage >= 10" class="uv-line-progress__text">{{innserPercentage + '%'}}</text>
+ </slot>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * lineProgress 线型进度条
+ * @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
+ * @tutorial https://www.uvui.cn/components/lineProgress.html
+ * @property {String} activeColor 激活部分的颜色 ( 默认 '#19be6b' )
+ * @property {String} inactiveColor 背景色 ( 默认 '#ececec' )
+ * @property {String | Number} percentage 进度百分比,数值 ( 默认 0 )
+ * @property {Boolean} showText 是否在进度条内部显示百分比的值 ( 默认 true )
+ * @property {String | Number} height 进度条的高度,单位px ( 默认 12 )
+ *
+ * @example <uv-line-progress :percent="70" :show-percent="true"></uv-line-progress>
+ */
+ export default {
+ name: "uv-line-progress",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ lineWidth: 0,
+ }
+ },
+ watch: {
+ percentage(n) {
+ this.resizeProgressWidth()
+ }
+ },
+ computed: {
+ progressStyle() {
+ let style = {}
+ style.width = this.lineWidth
+ style.backgroundColor = this.activeColor
+ style.height = this.$uv.addUnit(this.$uv.getPx(this.height))
+ return style
+ },
+ innserPercentage() {
+ // 控制范围在0-100之间
+ return this.$uv.range(0, 100, this.percentage)
+ }
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ this.$uv.sleep(20).then(() => {
+ this.resizeProgressWidth()
+ })
+ },
+ getProgressWidth() {
+ // #ifndef APP-NVUE
+ return this.$uvGetRect('.uv-line-progress__background')
+ // #endif
+
+ // #ifdef APP-NVUE
+ // 返回一个promise
+ return new Promise(resolve => {
+ dom.getComponentRect(this.$refs['uv-line-progress__background'], (res) => {
+ resolve(res.size)
+ })
+ })
+ // #endif
+ },
+ resizeProgressWidth() {
+ this.getProgressWidth().then(size => {
+ const {
+ width
+ } = size
+ // 通过设置的percentage值,计算其所占总长度的百分比
+ this.lineWidth = width * this.innserPercentage / 100 + 'px'
+ })
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-line-progress {
+ align-items: stretch;
+ position: relative;
+ @include flex(row);
+ flex: 1;
+ overflow: hidden;
+ border-radius: 100px;
+
+ &__background {
+ background-color: #ececec;
+ border-radius: 100px;
+ flex: 1;
+ }
+
+ &__line {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ align-items: center;
+ @include flex(row);
+ color: #ffffff;
+ border-radius: 100px;
+ transition: width 0.5s ease;
+ justify-content: flex-end;
+ }
+
+ &__text {
+ font-size: 10px;
+ align-items: center;
+ text-align: right;
+ color: #FFFFFF;
+ margin-right: 5px;
+ transform: scale(0.9);
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-line-progress/package.json b/ledApp/uni_modules/uv-line-progress/package.json
new file mode 100644
index 0000000..01e550e
--- /dev/null
+++ b/ledApp/uni_modules/uv-line-progress/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-line-progress",
+ "displayName": "uv-line-progress 线形进度条 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "uv-line-progress 该组件展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。",
+ "keywords": [
+ "uv-line-progress",
+ "uvui",
+ "uv-ui",
+ "progress",
+ "进度条"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-line-progress/readme.md b/ledApp/uni_modules/uv-line-progress/readme.md
new file mode 100644
index 0000000..98e5d49
--- /dev/null
+++ b/ledApp/uni_modules/uv-line-progress/readme.md
@@ -0,0 +1,11 @@
+## LineProgress 线形进度条
+
+> **组件名:uv-line-progress**
+
+展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
+
+### <a href="https://www.uvui.cn/components/lineProgress.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-line/changelog.md b/ledApp/uni_modules/uv-line/changelog.md
new file mode 100644
index 0000000..5eb7ba8
--- /dev/null
+++ b/ledApp/uni_modules/uv-line/changelog.md
@@ -0,0 +1,5 @@
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增线条组件
diff --git a/ledApp/uni_modules/uv-line/components/uv-line/props.js b/ledApp/uni_modules/uv-line/components/uv-line/props.js
new file mode 100644
index 0000000..75b1007
--- /dev/null
+++ b/ledApp/uni_modules/uv-line/components/uv-line/props.js
@@ -0,0 +1,34 @@
+export default {
+ props: {
+ color: {
+ type: String,
+ default: '#d6d7d9'
+ },
+ // 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等
+ length: {
+ type: [String, Number],
+ default: '100%'
+ },
+ // 线条方向,col-竖向,row-横向
+ direction: {
+ type: String,
+ default: 'row'
+ },
+ // 是否显示细边框
+ hairline: {
+ type: Boolean,
+ default: true
+ },
+ // 线条与上下左右元素的间距,字符串形式,如"30px"、"20px 30px"
+ margin: {
+ type: [String, Number],
+ default: 0
+ },
+ // 是否虚线,true-虚线,false-实线
+ dashed: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.line
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-line/components/uv-line/uv-line.vue b/ledApp/uni_modules/uv-line/components/uv-line/uv-line.vue
new file mode 100644
index 0000000..866c507
--- /dev/null
+++ b/ledApp/uni_modules/uv-line/components/uv-line/uv-line.vue
@@ -0,0 +1,60 @@
+<template>
+ <view
+ class="uv-line"
+ :style="[lineStyle]"
+ >
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * line 线条
+ * @description 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单
+ * @tutorial https://www.uvui.cn/components/line.html
+ * @property {String} color 线条的颜色 ( 默认 '#d6d7d9' )
+ * @property {String | Number} length 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等 ( 默认 '100%' )
+ * @property {String} direction 线条的方向,row-横向,col-竖向 (默认 'row' )
+ * @property {Boolean} hairline 是否显示细线条 (默认 true )
+ * @property {String | Number} margin 线条与上下左右元素的间距,字符串形式,如"30px" (默认 0 )
+ * @property {Boolean} dashed 是否虚线,true-虚线,false-实线 (默认 false )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @example <uv-line color="red"></uv-line>
+ */
+ export default {
+ name: 'uv-line',
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ lineStyle() {
+ const style = {}
+ style.margin = this.margin
+ // 如果是水平线条,边框高度为1px,再通过transform缩小一半,就是0.5px了
+ if (this.direction === 'row') {
+ // 此处采用兼容分开写,兼容nvue的写法
+ style.borderBottomWidth = '1px'
+ style.borderBottomStyle = this.dashed ? 'dashed' : 'solid'
+ style.width = this.$uv.addUnit(this.length)
+ if (this.hairline) style.transform = 'scaleY(0.5)'
+ } else {
+ // 如果是竖向线条,边框宽度为1px,再通过transform缩小一半,就是0.5px了
+ style.borderLeftWidth = '1px'
+ style.borderLeftStyle = this.dashed ? 'dashed' : 'solid'
+ style.height = this.$uv.addUnit(this.length)
+ if (this.hairline) style.transform = 'scaleX(0.5)'
+ }
+ style.borderColor = this.color
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ .uv-line {
+ /* #ifndef APP-NVUE */
+ vertical-align: middle;
+ /* #endif */
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-line/package.json b/ledApp/uni_modules/uv-line/package.json
new file mode 100644
index 0000000..036446a
--- /dev/null
+++ b/ledApp/uni_modules/uv-line/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-line",
+ "displayName": "uv-line 线条 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.1",
+ "description": "uv-line 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单。",
+ "keywords": [
+ "uv-line",
+ "uvui",
+ "uv-ui",
+ "line",
+ "线条"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-line/readme.md b/ledApp/uni_modules/uv-line/readme.md
new file mode 100644
index 0000000..e47533c
--- /dev/null
+++ b/ledApp/uni_modules/uv-line/readme.md
@@ -0,0 +1,11 @@
+## Line 线条
+
+> **组件名:uv-line**
+
+此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单。
+
+### <a href="https://www.uvui.cn/components/line.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-link/changelog.md b/ledApp/uni_modules/uv-link/changelog.md
new file mode 100644
index 0000000..ce52f84
--- /dev/null
+++ b/ledApp/uni_modules/uv-link/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-08-13)
+1. 修复报错的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-link 超链接组件
diff --git a/ledApp/uni_modules/uv-link/components/uv-link/props.js b/ledApp/uni_modules/uv-link/components/uv-link/props.js
new file mode 100644
index 0000000..b3f56a1
--- /dev/null
+++ b/ledApp/uni_modules/uv-link/components/uv-link/props.js
@@ -0,0 +1,40 @@
+export default {
+ props: {
+ // 文字颜色
+ color: {
+ type: String,
+ default: ''
+ },
+ // 字体大小,单位px
+ fontSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 是否显示下划线
+ underLine: {
+ type: Boolean,
+ default: false
+ },
+ // 要跳转的链接
+ href: {
+ type: String,
+ default: ''
+ },
+ // 小程序中复制到粘贴板的提示语
+ mpTips: {
+ type: String,
+ default: '链接已复制,请在浏览器打开'
+ },
+ // 下划线颜色
+ lineColor: {
+ type: String,
+ default: ''
+ },
+ // 超链接的问题,不使用slot形式传入,是因为nvue下无法修改颜色
+ text: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.link
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-link/components/uv-link/uv-link.vue b/ledApp/uni_modules/uv-link/components/uv-link/uv-link.vue
new file mode 100644
index 0000000..4c9258e
--- /dev/null
+++ b/ledApp/uni_modules/uv-link/components/uv-link/uv-link.vue
@@ -0,0 +1,83 @@
+<template>
+ <text
+ class="uv-link"
+ @tap.stop="openLink"
+ :style="[linkStyle, $uv.addStyle(customStyle)]"
+ >{{text}}</text>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * link 超链接
+ * @description 该组件为超链接组件,在不同平台有不同表现形式:在APP平台会通过plus环境打开内置浏览器,在小程序中把链接复制到粘贴板,同时提示信息,在H5中通过window.open打开链接。
+ * @tutorial https://www.uvui.cn/components/link.html
+ * @property {String} color 文字颜色 (默认 color['uv-primary'] )
+ * @property {String | Number} fontSize 字体大小,单位px (默认 15 )
+ * @property {Boolean} underLine 是否显示下划线 (默认 false )
+ * @property {String} href 跳转的链接,要带上http(s)
+ * @property {String} mpTips 各个小程序平台把链接复制到粘贴板后的提示语(默认“链接已复制,请在浏览器打开”)
+ * @property {String} lineColor 下划线颜色,默认同color参数颜色
+ * @property {String} text 超链接的问题,不使用slot形式传入,是因为nvue下无法修改颜色
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @example <uv-link href="http://www.uvui.cn">蜀道难,难于上青天</uv-link>
+ */
+ export default {
+ name: "uv-link",
+ emits:['click'],
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ linkStyle() {
+ const style = {
+ color: this.color,
+ fontSize: this.$uv.addUnit(this.fontSize),
+ // line-height设置为比字体大小多2px
+ lineHeight: this.$uv.addUnit(this.$uv.getPx(this.fontSize) + 2),
+ textDecoration: this.underLine ? 'underline' : 'none'
+ }
+ return style
+ }
+ },
+ methods: {
+ openLink() {
+ // #ifdef APP-PLUS
+ plus.runtime.openURL(this.href)
+ // #endif
+ // #ifdef H5
+ window.open(this.href)
+ // #endif
+ // #ifdef MP
+ uni.setClipboardData({
+ data: this.href,
+ success: () => {
+ uni.hideToast();
+ this.$nextTick(() => {
+ this.$uv.toast(this.mpTips);
+ })
+ }
+ });
+ // #endif
+ this.$emit('click')
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-link-line-height:1 !default;
+
+ .uv-link {
+ /* #ifndef APP-NVUE */
+ line-height: $uv-link-line-height;
+ /* #endif */
+ @include flex;
+ flex-wrap: wrap;
+ flex: 1;
+ color: $uv-primary;
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-link/package.json b/ledApp/uni_modules/uv-link/package.json
new file mode 100644
index 0000000..1b115f4
--- /dev/null
+++ b/ledApp/uni_modules/uv-link/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-link",
+ "displayName": "uv-link 超链接 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "uv-link 该组件为超链接组件",
+ "keywords": [
+ "uv-link",
+ "uvui",
+ "uv-ui",
+ "link",
+ "超链接"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-link/readme.md b/ledApp/uni_modules/uv-link/readme.md
new file mode 100644
index 0000000..6e8ce92
--- /dev/null
+++ b/ledApp/uni_modules/uv-link/readme.md
@@ -0,0 +1,11 @@
+## Link 超链接
+
+> **组件名:uv-link**
+
+该组件为超链接组件,在不同平台有不同表现形式。
+
+### <a href="https://www.uvui.cn/components/link.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-list/changelog.md b/ledApp/uni_modules/uv-list/changelog.md
new file mode 100644
index 0000000..6c130f0
--- /dev/null
+++ b/ledApp/uni_modules/uv-list/changelog.md
@@ -0,0 +1,25 @@
+## 1.0.9(2023-11-10)
+1. 修复设置ellipsis不生效的BUG
+## 1.0.8(2023-09-20)
+1. listItem优化可使用customStyle变量进行样式控制
+## 1.0.7(2023-08-29)
+1. 修复边框的BUG
+## 1.0.6(2023-08-16)
+1. 修复switch开关返回undefined的问题
+2. 优化初始化可能导致的闪动
+## 1.0.5(2023-08-07)
+1. 修复分包页面在ios端,nvue编译不能滚动的BUG
+## 1.0.4(2023-08-04)
+1. nvue修复 触底不触发事件的BUG
+2. 更新文档说明事件触发
+## 1.0.3(2023-07-28)
+1. 修改可能造成样式污染的BUG
+## 1.0.2(2023-07-26)
+1. 全面重构,用法与之前保持一致,参数全部变化
+2. 新增多个功能参数,方便一键构建列表
+3. List列表组件,包含基本列表样式、默认插槽机制、可扩展插槽机制、长列表性能优化、多端兼容。
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-list 列表
diff --git a/ledApp/uni_modules/uv-list/components/uv-list-item/uv-list-item.vue b/ledApp/uni_modules/uv-list/components/uv-list-item/uv-list-item.vue
new file mode 100644
index 0000000..193b48d
--- /dev/null
+++ b/ledApp/uni_modules/uv-list/components/uv-list-item/uv-list-item.vue
@@ -0,0 +1,535 @@
+<template>
+ <!-- #ifdef APP-NVUE -->
+ <cell :keep-scroll-position="keepScrollPosition">
+ <!-- #endif -->
+ <view
+ :class="{ 'uv-list-item--disabled': disabled }"
+ :style="[$uv.addStyle(customStyle),{'background-color':customStyle.backgroundColor?customStyle.backgroundColor:'#fff'}]"
+ :hover-class="(!clickable && !link) || disabled || showSwitch ? '' : 'uv-list-item--hover'"
+ class="uv-list-item" @click="onClick">
+ <view v-if="!isFirstChild" class="border--left" :class="{ 'uv-list--border': border }"></view>
+ <view class="uv-list-item__wrapper">
+ <slot>
+ <view class="uv-list-item__container"
+ :class="{ 'container--right': showArrow || link, 'flex--direction': directionData === 'column'}"
+ :style="{paddingTop:padding.top,paddingLeft:padding.left,paddingRight:padding.right,paddingBottom:padding.bottom}">
+ <slot name="header">
+ <view class="uv-list-item__header">
+ <view v-if="thumb" class="uv-list-item__icon">
+ <image :src="thumb" class="uv-list-item__icon-img" :class="['uv-list--' + thumbSize]" />
+ </view>
+ <view v-else-if="showExtraIcon" class="uv-list-item__icon">
+ <uv-icon :name="extraIcon.icon" :customPrefix="extraIcon.customPrefix" :color="extraIcon.color" :size="extraIcon.size" />
+ </view>
+ </view>
+ </slot>
+ <slot name="body">
+ <view class="uv-list-item__content"
+ :class="{ 'uv-list-item__content--center': thumb || showExtraIcon || showBadge || showSwitch }">
+ <text v-if="title" class="uv-list-item__content-title"
+ :class="[ellipsis && `uv-line-${ellipsis}`]">{{ title }}</text>
+ <text v-if="note" class="uv-list-item__content-note">{{ note }}</text>
+ </view>
+ </slot>
+ <slot name="footer">
+ <view v-if="rightText || showBadge || showSwitch" class="uv-list-item__extra"
+ :class="{ 'flex--justify': directionData === 'column' }">
+ <text v-if="rightText" class="uv-list-item__extra-text">{{ rightText }}</text>
+ <uv-badge
+ v-if="showBadge"
+ :show="!!(badge.show || badge.isDot || badge.value)"
+ :isDot="badge.isDot"
+ :value="badge.value"
+ :max="badge.max"
+ :type="badge.type"
+ :showZero="badge.showZero"
+ :bgColor="badge.bgColor"
+ :color="badge.color"
+ :shape="badge.shape"
+ :numberType="badge.numberType"
+ :inverted="badge.inverted"
+ customStyle="margin-left: 4px;"
+ ></uv-badge>
+ <uv-switch v-if="showSwitch" :value="switchChecked" :disabled="disabled" @change="onSwitchChange"></uv-switch>
+ </view>
+ </slot>
+ </view>
+ </slot>
+ </view>
+ <uv-icon v-if="showArrow || link" size="34rpx" class="uv-icon-wrapper" color="#bbb" name="arrow-right" />
+ </view>
+ <!-- #ifdef APP-NVUE -->
+ </cell>
+ <!-- #endif -->
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ /**
+ * ListItem 列表子组件
+ * @description 列表子组件
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=24
+ * @property {String} title 标题
+ * @property {String} note 描述
+ * @property {String} thumb 左侧缩略图,若thumb有值,则不会显示扩展图标
+ * @property {String} thumbSize = [lg|base|sm] 略缩图大小
+ * @value lg 大图
+ * @value base 一般
+ * @value sm 小图
+ * @property {String} rightText 右侧文字内容
+ * @property {Boolean} disabled = [true|false] 是否禁用
+ * @property {Boolean} clickable = [true|false] 是否开启点击反馈
+ * @property {String} link = [navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈
+ * @value navigateTo 同 uni.navigateTo()
+ * @value redirectTo 同 uni.redirectTo()
+ * @value reLaunch 同 uni.reLaunch()
+ * @value switchTab 同 uni.switchTab()
+ * @property {String | PageURIString} to 跳转目标页面
+ * @property {Boolean} showBadge = [true|false] 是否显示数字角标
+ * @property {Object} badge 扩展数字角标的参数,格式为 :badge="{value: 122}"
+ * @property {Boolean} showSwitch = [true|false] 是否显示Switch
+ * @property {Boolean} switchChecked = [true|false] Switch是否被选中
+ * @property {Boolean} showExtraIcon = [true|false] 左侧是否显示扩展图标
+ * @property {Object} extraIcon 扩展图标参数,格式为 :extraIcon="{icon: 'photo',size: '30px'}"
+ * @property {String} direction = [row|column] 排版方向
+ * @value row 水平排列
+ * @value column 垂直排列
+ * @event {Function} click 点击 uniListItem 触发事件
+ * @event {Function} switchChange 点击切换 Switch 时触发
+ */
+ export default {
+ name: 'uv-list-item',
+ mixins: [mpMixin, mixin],
+ emits: ['click', 'switchChange'],
+ props: {
+ direction: {
+ type: String,
+ default: 'row'
+ },
+ title: {
+ type: String,
+ default: ''
+ },
+ note: {
+ type: String,
+ default: ''
+ },
+ ellipsis: {
+ type: [Number, String],
+ default: 0
+ },
+ disabled: {
+ type: [Boolean, String],
+ default: false
+ },
+ clickable: {
+ type: Boolean,
+ default: false
+ },
+ showArrow: {
+ type: [Boolean, String],
+ default: false
+ },
+ link: {
+ type: [Boolean, String],
+ default: false
+ },
+ to: {
+ type: String,
+ default: ''
+ },
+ showSwitch: {
+ type: [Boolean, String],
+ default: false
+ },
+ switchChecked: {
+ type: [Boolean, String],
+ default: false
+ },
+ showBadge: {
+ type: [Boolean, String],
+ default: false
+ },
+ badge: {
+ type: Object,
+ default () {
+ return {}
+ }
+ },
+ rightText: {
+ type: String,
+ default: ''
+ },
+ thumb: {
+ type: String,
+ default: ''
+ },
+ thumbSize: {
+ type: String,
+ default: 'base'
+ },
+ showExtraIcon: {
+ type: [Boolean, String],
+ default: false
+ },
+ extraIcon: {
+ type: Object,
+ default () {
+ return {
+ name: '',
+ color: '#000000',
+ size: 20,
+ customPrefix: ''
+ };
+ }
+ },
+ border: {
+ type: Boolean,
+ default: false
+ },
+ customStyle: {
+ type: Object,
+ default () {
+ return {
+ padding: '',
+ backgroundColor: '#FFFFFF'
+ }
+ }
+ },
+ keepScrollPosition: {
+ type: Boolean,
+ default: false
+ }
+ },
+ computed: {
+ directionData(){
+ return this.direction ? this.direction : (this.parentData.direction ? this.parentData.direction : 'row');
+ }
+ },
+ watch: {
+ 'customStyle.padding': {
+ handler(padding) {
+ if(padding) this.setPadding(padding);
+ },
+ immediate: true
+ }
+ },
+ data() {
+ return {
+ isFirstChild: false,
+ padding: {
+ top: "",
+ right: "",
+ bottom: "",
+ left: ""
+ },
+ parentData: {
+ direction: 'row',
+ padding: 0
+ }
+ };
+ },
+ created() {
+ this.updateParentData();
+ },
+ mounted() {
+ this.init();
+ this.list = this.getForm()
+ // 判断是否存在 uv-list 组件
+ if (this.list) {
+ if (!this.list.firstChildAppend) {
+ this.list.firstChildAppend = true;
+ this.isFirstChild = true;
+ }
+ }
+ },
+ methods: {
+ init(){
+ if (!this.parent) {
+ this.$uv.error('uv-list-item必须搭配uv-list组件使用');
+ }
+ this.$nextTick(()=>{
+ if(!(this.padding.top || this.padding.right|| this.padding.bottom|| this.padding.left)){
+ this.setPadding(this.parentData.padding);
+ }
+ })
+ },
+ updateParentData() {
+ this.getParentData('uv-list');
+ },
+ setPadding(padding){
+ if(typeof padding == 'number'){
+ padding += ''
+ }
+ let paddingArr = padding.split(' ')
+ if (paddingArr.length === 1) {
+ const allPadding = paddingArr[0]
+ this.padding = {
+ "top": allPadding,
+ "right": allPadding,
+ "bottom": allPadding,
+ "left": allPadding
+ }
+ } else if (paddingArr.length === 2) {
+ const [verticalPadding, horizontalPadding] = paddingArr;
+ this.padding = {
+ "top": verticalPadding,
+ "right": horizontalPadding,
+ "bottom": verticalPadding,
+ "left": horizontalPadding
+ }
+ } else if (paddingArr.length === 4) {
+ const [topPadding, rightPadding, bottomPadding, leftPadding] = paddingArr;
+ this.padding = {
+ "top": topPadding,
+ "right": rightPadding,
+ "bottom": bottomPadding,
+ "left": leftPadding
+ }
+ }
+ },
+ /**
+ * 获取父元素实例
+ */
+ getForm(name = 'uniList') {
+ let parent = this.$parent;
+ let parentName = parent.$options.name;
+ while (parentName !== name) {
+ parent = parent.$parent;
+ if (!parent) return false
+ parentName = parent.$options.name;
+ }
+ return parent;
+ },
+ onClick() {
+ if (this.to !== '') {
+ this.openPage();
+ return;
+ }
+ if (this.clickable || this.link) {
+ this.$emit('click', {
+ data: {}
+ });
+ }
+ },
+ onSwitchChange(e) {
+ this.$emit('switchChange', e);
+ },
+ openPage() {
+ if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {
+ this.pageApi(this.link);
+ } else {
+ this.pageApi('navigateTo');
+ }
+ },
+ pageApi(api) {
+ let callback = {
+ url: this.to,
+ success: res => {
+ this.$emit('click', {
+ data: res
+ });
+ },
+ fail: err => {
+ this.$emit('click', {
+ data: err
+ });
+ }
+ }
+ switch (api) {
+ case 'navigateTo':
+ uni.navigateTo(callback)
+ break
+ case 'redirectTo':
+ uni.redirectTo(callback)
+ break
+ case 'reLaunch':
+ uni.reLaunch(callback)
+ break
+ case 'switchTab':
+ uni.switchTab(callback)
+ break
+ default:
+ uni.navigateTo(callback)
+ }
+ }
+ }
+ };
+</script>
+
+<style lang="scss" scoped>
+ $show-lines: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ $uv-font-size-sm:12px;
+ $uv-font-size-base:14px;
+ $uv-font-size-lg:16px;
+ $uv-spacing-col-lg: 12px;
+ $uv-spacing-row-lg: 15px;
+ $uv-img-size-sm:20px;
+ $uv-img-size-base:26px;
+ $uv-img-size-lg:40px;
+ $uv-border-color:#e5e5e5;
+ $uv-bg-color-hover:#f1f1f1;
+ $uv-text-color-grey:#999;
+ $list-item-pd: $uv-spacing-col-lg $uv-spacing-row-lg;
+
+ .uv-list-item {
+ @include flex(row);
+ font-size: $uv-font-size-lg;
+ position: relative;
+ justify-content: space-between;
+ align-items: center;
+ background-color: #fff;
+ /* #ifdef H5 */
+ cursor: pointer;
+ /* #endif */
+ }
+
+ .uv-list-item--disabled {
+ opacity: 0.3;
+ }
+
+ .uv-list-item--hover {
+ background-color: $uv-bg-color-hover !important;
+ }
+
+ .uv-list-item__wrapper {
+ @include flex(column);
+ flex: 1;
+ }
+
+ .uv-list-item__container {
+ position: relative;
+ @include flex(row);
+ padding: $list-item-pd;
+ padding-left: $uv-spacing-row-lg;
+ flex: 1;
+ overflow: hidden;
+ }
+
+ .container--right {
+ padding-right: 0;
+ }
+
+ .uv-list--border {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ /* #ifdef APP-NVUE */
+ border-top-color: $uv-border-color;
+ border-top-style: solid;
+ border-top-width: 0.5px;
+ /* #endif */
+ }
+
+ /* #ifndef APP-NVUE */
+ .uv-list--border:after {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ height: 1px;
+ content: '';
+ -webkit-transform: scaleY(0.5);
+ transform: scaleY(0.5);
+ background-color: $uv-border-color;
+ }
+
+ /* #endif */
+ .uv-list-item__content {
+ @include flex(column);
+ padding-right: 8px;
+ flex: 1;
+ color: #3b4144;
+ justify-content: space-between;
+ overflow: hidden;
+ }
+
+ .uv-list-item__content--center {
+ justify-content: center;
+ }
+
+ .uv-list-item__content-title {
+ font-size: $uv-font-size-base;
+ color: #3b4144;
+ overflow: hidden;
+ }
+
+ .uv-list-item__content-note {
+ margin-top: 6rpx;
+ color: $uv-text-color-grey;
+ font-size: $uv-font-size-sm;
+ overflow: hidden;
+ }
+
+ .uv-list-item__extra {
+ @include flex(row);
+ justify-content: flex-end;
+ align-items: center;
+ }
+
+ .uv-list-item__header {
+ @include flex(row);
+ align-items: center;
+ }
+
+ .uv-list-item__icon {
+ margin-right: 18rpx;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .uv-list-item__icon-img {
+ /* #ifndef APP-NVUE */
+ display: block;
+ /* #endif */
+ height: $uv-img-size-base;
+ width: $uv-img-size-base;
+ margin-right: 10px;
+ }
+
+ .uv-icon-wrapper {
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ align-items: center;
+ padding: 0 10px;
+ }
+
+ .flex--direction {
+ flex-direction: column;
+ /* #ifndef APP-NVUE */
+ align-items: initial;
+ /* #endif */
+ }
+
+ .flex--justify {
+ /* #ifndef APP-NVUE */
+ justify-content: initial;
+ /* #endif */
+ }
+
+ .uv-list--lg {
+ height: $uv-img-size-lg;
+ width: $uv-img-size-lg;
+ }
+
+ .uv-list--base {
+ height: $uv-img-size-base;
+ width: $uv-img-size-base;
+ }
+
+ .uv-list--sm {
+ height: $uv-img-size-sm;
+ width: $uv-img-size-sm;
+ }
+
+ .uv-list-item__extra-text {
+ color: $uv-text-color-grey;
+ font-size: $uv-font-size-sm;
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-list/components/uv-list/uv-list.vue b/ledApp/uni_modules/uv-list/components/uv-list/uv-list.vue
new file mode 100644
index 0000000..3f3e5c2
--- /dev/null
+++ b/ledApp/uni_modules/uv-list/components/uv-list/uv-list.vue
@@ -0,0 +1,147 @@
+<template>
+ <!-- #ifndef APP-NVUE -->
+ <view
+ class="uv-list"
+ :style="[$uv.addStyle(customStyle)]"
+ >
+ <view
+ v-if="border"
+ class="uv-list--border-top"
+ :style="[{ 'background-color': borderColor }]"
+ ></view>
+ <slot />
+ <view
+ v-if="border"
+ class="uv-list--border-bottom"
+ :style="[{ 'background-color': borderColor }]"
+ ></view>
+ </view>
+ <!-- #endif -->
+ <!-- #ifdef APP-NVUE -->
+ <list
+ :bounce="true"
+ :scrollable="true"
+ show-scrollbar
+ :render-reverse="false"
+ class="uv-list"
+ :class="{ 'uv-list--border': border }"
+ :style="[
+ { 'border-top-color': borderColor, 'border-bottom-color':borderColor },
+ $uv.addStyle(customStyle)
+ ]"
+ :enableBackToTop="false"
+ :loadmoreoffset="15"
+ @scroll="scroll"
+ @loadmore="loadMore">
+ <slot />
+ </list>
+ <!-- #endif -->
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ /**
+ * List 列表
+ * @description 列表组件
+ * @tutorial https://www.uvui.cn/components/list.html
+ * @property {Boolean} border = [true|false] 是否显示边框
+ * @property {String} borderColor 边框颜色
+ * @property {String} direction 排版方向,默认row,列表里面使用其他组件 最好设置成column
+ */
+ export default {
+ name: 'uv-list',
+ mixins: [mpMixin, mixin],
+ 'mp-weixin': {
+ options: {
+ multipleSlots: false
+ }
+ },
+ props: {
+ border: {
+ type: Boolean,
+ default: false
+ },
+ borderColor: {
+ type: String,
+ default: '#dadbde'
+ },
+ // 排版方向,默认row,列表里面使用其他组件 最好设置成column
+ direction: {
+ type: String,
+ default: 'row'
+ },
+ // 内边距
+ padding: {
+ type: [String,Number],
+ default: '20rpx 30rpx'
+ }
+ },
+ created() {
+ this.firstChildAppend = false;
+ },
+ computed: {
+ parentData() {
+ return [this.direction,this.padding];
+ }
+ },
+ methods: {
+ loadMore(e) {
+ this.$emit('scrolltolower');
+ },
+ scroll(e) {
+ this.$emit('scroll', e);
+ }
+ }
+ };
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+
+ .uv-list {
+ position: relative;
+ @include flex(column);
+ background-color: #fff;
+ }
+
+ .uv-list--border {
+ position: relative;
+ /* #ifdef APP-NVUE */
+ border-top-color: $uv-border-color;
+ border-top-style: solid;
+ border-top-width: 0.5px;
+ border-bottom-color: $uv-border-color;
+ border-bottom-style: solid;
+ border-bottom-width: 0.5px;
+ /* #endif */
+ z-index: -1;
+ }
+
+ /* #ifndef APP-NVUE */
+
+ .uv-list--border-top {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ height: 1px;
+ -webkit-transform: scaleY(0.5);
+ transform: scaleY(0.5);
+ background-color: $uv-border-color;
+ z-index: 1;
+ }
+
+ .uv-list--border-bottom {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ height: 1px;
+ -webkit-transform: scaleY(0.5);
+ transform: scaleY(0.5);
+ background-color: $uv-border-color;
+ }
+
+ /* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-list/package.json b/ledApp/uni_modules/uv-list/package.json
new file mode 100644
index 0000000..1b1156c
--- /dev/null
+++ b/ledApp/uni_modules/uv-list/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-list",
+ "displayName": "uv-list 列表 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.9",
+ "description": "uv-list 多功能高性能列表组件",
+ "keywords": [
+ "uv-list",
+ "uvui",
+ "uv-ui",
+ "list",
+ "列表"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-list/readme.md b/ledApp/uni_modules/uv-list/readme.md
new file mode 100644
index 0000000..930cbd4
--- /dev/null
+++ b/ledApp/uni_modules/uv-list/readme.md
@@ -0,0 +1,27 @@
+## List 列表
+
+> **组件名:uv-list**
+
+List列表组件,包含基本列表样式、默认插槽机制、可扩展插槽机制、长列表性能优化、多端兼容。
+
+在vue页面里,它默认使用页面级滚动,这样做的目的是性能更加友好。在app-nvue页面里,它默认使用原生list组件滚动,这样的长列表,在滚动出屏幕外后,系统会回收不可见区域的渲染内存资源,不会造成滚动越长手机越卡的问题。
+
+uv-list组件是父容器,里面的核心是uv-list-item子组件,它代表列表中的一个可重复行,子组件可以无限循环。
+
+uv-list-item有很多风格,uv-list-item组件通过内置的属性,满足一些常用的场景。当内置属性不满足需求时,可以通过扩展插槽来自定义列表内容,插槽包括:默认插槽(完全自定义内容)、具名插槽(header | body | footer),根据需求进行扩展。
+
+内置属性可以覆盖的场景包括:导航列表、设置列表、小图标列表等,其他不能满足的场景使用插槽进行扩展。
+
+# <a href="https://www.uvui.cn/components/list.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-load-more/changelog.md b/ledApp/uni_modules/uv-load-more/changelog.md
new file mode 100644
index 0000000..a2e0fed
--- /dev/null
+++ b/ledApp/uni_modules/uv-load-more/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-21)
+1. 优化customStyle属性
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-load-more 加载更多
diff --git a/ledApp/uni_modules/uv-load-more/components/uv-load-more/props.js b/ledApp/uni_modules/uv-load-more/components/uv-load-more/props.js
new file mode 100644
index 0000000..c6bc287
--- /dev/null
+++ b/ledApp/uni_modules/uv-load-more/components/uv-load-more/props.js
@@ -0,0 +1,95 @@
+export default {
+ props: {
+ // 组件状态,loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
+ status: {
+ type: String,
+ default: 'loadmore'
+ },
+ // 组件背景色
+ bgColor: {
+ type: String,
+ default: 'transparent'
+ },
+ // 是否显示加载中的图标
+ icon: {
+ type: Boolean,
+ default: true
+ },
+ // 字体大小
+ fontSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 图标大小
+ iconSize: {
+ type: [String, Number],
+ default: 16
+ },
+ // 字体颜色
+ color: {
+ type: String,
+ default: '#606266'
+ },
+ // 加载中状态的图标,spinner-花朵状图标,circle-圆圈状,semicircle-半圆
+ loadingIcon: {
+ type: String,
+ default: 'spinner'
+ },
+ // 加载前的提示语
+ loadmoreText: {
+ type: String,
+ default: '加载更多'
+ },
+ // 加载中提示语
+ loadingText: {
+ type: String,
+ default: '正在加载...'
+ },
+ // 没有更多的提示语
+ nomoreText: {
+ type: String,
+ default: '没有更多了'
+ },
+ // 在“没有更多”状态下,是否显示粗点
+ isDot: {
+ type: Boolean,
+ default: false
+ },
+ // 加载中图标的颜色
+ iconColor: {
+ type: String,
+ default: '#b7b7b7'
+ },
+ // 上边距
+ marginTop: {
+ type: [String, Number],
+ default: 10
+ },
+ // 下边距
+ marginBottom: {
+ type: [String, Number],
+ default: 10
+ },
+ // 高度,单位px
+ height: {
+ type: [String, Number],
+ default: 'auto'
+ },
+ // 是否显示左边分割线
+ line: {
+ type: Boolean,
+ default: false
+ },
+ // 线条颜色
+ lineColor: {
+ type: String,
+ default: '#E6E8EB'
+ },
+ // 是否虚线,true-虚线,false-实线
+ dashed: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.loadmore
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-load-more/components/uv-load-more/uv-load-more.vue b/ledApp/uni_modules/uv-load-more/components/uv-load-more/uv-load-more.vue
new file mode 100644
index 0000000..2b5057c
--- /dev/null
+++ b/ledApp/uni_modules/uv-load-more/components/uv-load-more/uv-load-more.vue
@@ -0,0 +1,152 @@
+<template>
+ <view
+ class="uv-loadmore"
+ :style="[{
+ backgroundColor: bgColor,
+ marginBottom: $uv.addUnit(marginBottom),
+ marginTop: $uv.addUnit(marginTop),
+ height: $uv.addUnit(height),
+ },
+ $uv.addStyle(customStyle)]"
+ >
+ <uv-line
+ length="140rpx"
+ :color="lineColor"
+ :hairline="false"
+ :dashed="dashed"
+ v-if="line"
+ ></uv-line>
+ <!-- 加载中和没有更多的状态才显示两边的横线 -->
+ <view
+ :class="status == 'loadmore' || status == 'nomore' ? 'uv-more' : ''"
+ class="uv-loadmore__content"
+ >
+ <view
+ class="uv-loadmore__content__icon-wrap"
+ v-if="status === 'loading' && icon"
+ >
+ <uv-loading-icon
+ :color="iconColor"
+ :size="iconSize"
+ :mode="loadingIcon"
+ ></uv-loading-icon>
+ </view>
+ <!-- 如果没有更多的状态下,显示内容为dot(粗点),加载特定样式 -->
+ <text
+ class="uv-line-1"
+ :style="[loadTextStyle]"
+ :class="[(status == 'nomore' && isDot == true) ? 'uv-loadmore__content__dot-text' : 'uv-loadmore__content__text']"
+ @tap="loadMore"
+ >{{ showText }}</text>
+ </view>
+ <uv-line
+ length="140rpx"
+ :color="lineColor"
+ :hairline="false"
+ :dashed="dashed"
+ v-if="line"
+ ></uv-line>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+
+ /**
+ * loadmore 加载更多
+ * @description 此组件一般用于标识页面底部加载数据时的状态。
+ * @tutorial https://www.uvui.cn/components/loadMore.html
+ * @property {String} status 组件状态(默认 'loadmore' )
+ * @property {String} bgColor 组件背景颜色,在页面是非白色时会用到(默认 'transparent' )
+ * @property {Boolean} icon 加载中时是否显示图标(默认 true )
+ * @property {String | Number} fontSize 字体大小(默认 14 )
+ * @property {String | Number} iconSize 图标大小(默认 17 )
+ * @property {String} color 字体颜色(默认 '#606266' )
+ * @property {String} loadingIcon 加载图标(默认 'circle' )
+ * @property {String} loadmoreText 加载前的提示语(默认 '加载更多' )
+ * @property {String} loadingText 加载中提示语(默认 '正在加载...' )
+ * @property {String} nomoreText 没有更多的提示语(默认 '没有更多了' )
+ * @property {Boolean} isDot 到上一个相邻元素的距离 (默认 false )
+ * @property {String} iconColor 加载中图标的颜色 (默认 '#b7b7b7' )
+ * @property {String} lineColor 线条颜色(默认 #E6E8EB )
+ * @property {String | Number} marginTop 上边距 (默认 10 )
+ * @property {String | Number} marginBottom 下边距 (默认 10 )
+ * @property {String | Number} height 高度,单位px (默认 'auto' )
+ * @property {Boolean} line 是否显示左边分割线 (默认 false )
+ * @property {Boolean} dashed // 是否虚线,true-虚线,false-实线 (默认 false )
+ * @event {Function} loadmore status为loadmore时,点击组件会发出此事件
+ * @example <uv-loadmore :status="status" icon-type="iconType" load-text="loadText" />
+ */
+ export default {
+ name: "uv-loadmore",
+ mixins: [mpMixin, mixin,props],
+ data() {
+ return {
+ // 粗点
+ dotText: "●"
+ }
+ },
+ computed: {
+ // 加载的文字显示的样式
+ loadTextStyle() {
+ return {
+ color: this.color,
+ fontSize: this.$uv.addUnit(this.fontSize),
+ lineHeight: this.$uv.addUnit(this.fontSize),
+ backgroundColor: this.bgColor,
+ }
+ },
+ // 显示的提示文字
+ showText() {
+ let text = '';
+ if (this.status == 'loadmore') text = this.loadmoreText
+ else if (this.status == 'loading') text = this.loadingText
+ else if (this.status == 'nomore' && this.isDot) text = this.dotText;
+ else text = this.nomoreText;
+ return text;
+ }
+ },
+ methods: {
+ loadMore() {
+ // 只有在“加载更多”的状态下才发送点击事件,内容不满一屏时无法触发底部上拉事件,所以需要点击来触发
+ if (this.status == 'loadmore') this.$emit('loadmore');
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-lines: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-loadmore {
+ @include flex(row);
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+
+ &__content {
+ margin: 0 15px;
+ @include flex(row);
+ align-items: center;
+ justify-content: center;
+
+ &__icon-wrap {
+ margin-right: 8px;
+ }
+
+ &__text {
+ font-size: 14px;
+ color: $uv-content-color;
+ }
+
+ &__dot-text {
+ font-size: 15px;
+ color: $uv-tips-color;
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-load-more/package.json b/ledApp/uni_modules/uv-load-more/package.json
new file mode 100644
index 0000000..bc502e3
--- /dev/null
+++ b/ledApp/uni_modules/uv-load-more/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-load-more",
+ "displayName": "uv-load-more 加载更多 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "uv-load-more 此组件一般用于标识页面底部加载数据时的状态,共有三种状态:加载前、加载中、加载后。",
+ "keywords": [
+ "uv-load-more",
+ "uvui",
+ "uv-ui",
+ "more",
+ "加载更多"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-line",
+ "uv-loading-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-load-more/readme.md b/ledApp/uni_modules/uv-load-more/readme.md
new file mode 100644
index 0000000..2e9334e
--- /dev/null
+++ b/ledApp/uni_modules/uv-load-more/readme.md
@@ -0,0 +1,11 @@
+## LoadMore 加载更多
+
+> **组件名:uv-load-more**
+
+此组件一般用于标识页面底部加载数据时的状态,共有三种状态:加载前、加载中、加载后。
+
+### <a href="https://www.uvui.cn/components/loadMore.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-loading-icon/changelog.md b/ledApp/uni_modules/uv-loading-icon/changelog.md
new file mode 100644
index 0000000..f17e074
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-icon/changelog.md
@@ -0,0 +1,9 @@
+## 1.0.3(2023-08-14)
+1. 新增参数textStyle,自定义文本样式
+## 1.0.2(2023-06-27)
+优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-loading-icon组件
diff --git a/ledApp/uni_modules/uv-loading-icon/components/uv-loading-icon/props.js b/ledApp/uni_modules/uv-loading-icon/components/uv-loading-icon/props.js
new file mode 100644
index 0000000..4ef0e94
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-icon/components/uv-loading-icon/props.js
@@ -0,0 +1,67 @@
+export default {
+ props: {
+ // 是否显示组件
+ show: {
+ type: Boolean,
+ default: true
+ },
+ // 颜色
+ color: {
+ type: String,
+ default: '#909193'
+ },
+ // 提示文字颜色
+ textColor: {
+ type: String,
+ default: '#909193'
+ },
+ // 文字和图标是否垂直排列
+ vertical: {
+ type: Boolean,
+ default: false
+ },
+ // 模式选择,circle-圆形,spinner-花朵形,semicircle-半圆形
+ mode: {
+ type: String,
+ default: 'spinner'
+ },
+ // 图标大小,单位默认px
+ size: {
+ type: [String, Number],
+ default: 24
+ },
+ // 文字大小
+ textSize: {
+ type: [String, Number],
+ default: 15
+ },
+ // 文字样式
+ textStyle: {
+ type: Object,
+ default () {
+ return {}
+ }
+ },
+ // 文字内容
+ text: {
+ type: [String, Number],
+ default: ''
+ },
+ // 动画模式 https://www.runoob.com/cssref/css3-pr-animation-timing-function.html
+ timingFunction: {
+ type: String,
+ default: 'linear'
+ },
+ // 动画执行周期时间
+ duration: {
+ type: [String, Number],
+ default: 1200
+ },
+ // mode=circle时的暗边颜色
+ inactiveColor: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.loadingIcon
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue b/ledApp/uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue
new file mode 100644
index 0000000..5650470
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue
@@ -0,0 +1,347 @@
+<template>
+ <view
+ class="uv-loading-icon"
+ :style="[$uv.addStyle(customStyle)]"
+ :class="[vertical && 'uv-loading-icon--vertical']"
+ v-if="show"
+ >
+ <view
+ v-if="!webviewHide"
+ class="uv-loading-icon__spinner"
+ :class="[`uv-loading-icon__spinner--${mode}`]"
+ ref="ani"
+ :style="{
+ color: color,
+ width: $uv.addUnit(size),
+ height: $uv.addUnit(size),
+ borderTopColor: color,
+ borderBottomColor: otherBorderColor,
+ borderLeftColor: otherBorderColor,
+ borderRightColor: otherBorderColor,
+ 'animation-duration': `${duration}ms`,
+ 'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''
+ }"
+ >
+ <block v-if="mode === 'spinner'">
+ <!-- #ifndef APP-NVUE -->
+ <view
+ v-for="(item, index) in array12"
+ :key="index"
+ class="uv-loading-icon__dot"
+ >
+ </view>
+ <!-- #endif -->
+ <!-- #ifdef APP-NVUE -->
+ <!-- 此组件内部图标部分无法设置宽高,即使通过width和height配置了也无效 -->
+ <loading-indicator
+ v-if="!webviewHide"
+ class="uv-loading-indicator"
+ :animating="true"
+ :style="{
+ color: color,
+ width: $uv.addUnit(size),
+ height: $uv.addUnit(size)
+ }"
+ />
+ <!-- #endif -->
+ </block>
+ </view>
+ <text
+ v-if="text"
+ class="uv-loading-icon__text"
+ :style="[{
+ fontSize: $uv.addUnit(textSize),
+ color: textColor,
+ },$uv.addStyle(textStyle)]"
+ >{{text}}</text>
+ </view>
+</template>
+
+<script>
+ import { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js'
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const animation = weex.requireModule('animation');
+ // #endif
+ /**
+ * loading 加载动画
+ * @description 警此组件为一个小动画,目前用在uvui的loadmore加载更多和switch开关等组件的正在加载状态场景。
+ * @tutorial https://www.uvui.cn/components/loading.html
+ * @property {Boolean} show 是否显示组件 (默认 true)
+ * @property {String} color 动画活动区域的颜色,只对 mode = flower 模式有效(默认#909193)
+ * @property {String} textColor 提示文本的颜色(默认#909193)
+ * @property {Boolean} vertical 文字和图标是否垂直排列 (默认 false )
+ * @property {String} mode 模式选择,见官网说明(默认 'circle' )
+ * @property {String | Number} size 加载图标的大小,单位px (默认 24 )
+ * @property {String | Number} textSize 文字大小(默认 15 )
+ * @property {String | Number} text 文字内容
+ * @property {Object} textStyle 文字样式
+ * @property {String} timingFunction 动画模式 (默认 'ease-in-out' )
+ * @property {String | Number} duration 动画执行周期时间(默认 1200)
+ * @property {String} inactiveColor mode=circle时的暗边颜色
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @example <uv-loading mode="circle"></uv-loading>
+ */
+ export default {
+ name: 'uv-loading-icon',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // Array.form可以通过一个伪数组对象创建指定长度的数组
+ // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from
+ array12: Array.from({
+ length: 12
+ }),
+ // 这里需要设置默认值为360,否则在安卓nvue上,会延迟一个duration周期后才执行
+ // 在iOS nvue上,则会一开始默认执行两个周期的动画
+ aniAngel: 360, // 动画旋转角度
+ webviewHide: false, // 监听webview的状态,如果隐藏了页面,则停止动画,以免性能消耗
+ loading: false, // 是否运行中,针对nvue使用
+ }
+ },
+ computed: {
+ // 当为circle类型时,给其另外三边设置一个更轻一些的颜色
+ // 之所以需要这么做的原因是,比如父组件传了color为红色,那么需要另外的三个边为浅红色
+ // 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝,导致效果没有那么细腻良好)
+ otherBorderColor() {
+ const lightColor = colorGradient(this.color, '#ffffff', 100)[80]
+ if (this.mode === 'circle') {
+ return this.inactiveColor ? this.inactiveColor : lightColor
+ } else {
+ return 'transparent'
+ }
+ }
+ },
+ watch: {
+ show(n) {
+ // nvue中,show为true,且为非loading状态,就重新执行动画模块
+ // #ifdef APP-NVUE
+ if (n && !this.loading) {
+ setTimeout(() => {
+ this.startAnimate()
+ }, 30)
+ }
+ // #endif
+ }
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ setTimeout(() => {
+ // #ifdef APP-NVUE
+ this.show && this.nvueAnimate()
+ // #endif
+ // #ifdef APP-PLUS
+ this.show && this.addEventListenerToWebview()
+ // #endif
+ }, 20)
+ },
+ // 监听webview的显示与隐藏
+ addEventListenerToWebview() {
+ // webview的堆栈
+ const pages = getCurrentPages()
+ // 当前页面
+ const page = pages[pages.length - 1]
+ // 当前页面的webview实例
+ const currentWebview = page.$getAppWebview()
+ // 监听webview的显示与隐藏,从而停止或者开始动画(为了性能)
+ currentWebview.addEventListener('hide', () => {
+ this.webviewHide = true
+ })
+ currentWebview.addEventListener('show', () => {
+ this.webviewHide = false
+ })
+ },
+ // #ifdef APP-NVUE
+ nvueAnimate() {
+ // nvue下,非spinner类型时才需要旋转,因为nvue的spinner类型,使用了weex的
+ // loading-indicator组件,自带旋转功能
+ this.mode !== 'spinner' && this.startAnimate()
+ },
+ // 执行nvue的animate模块动画
+ startAnimate() {
+ this.loading = true
+ const ani = this.$refs.ani
+ if (!ani) return
+ animation.transition(ani, {
+ // 进行角度旋转
+ styles: {
+ transform: `rotate(${this.aniAngel}deg)`,
+ transformOrigin: 'center center'
+ },
+ duration: this.duration,
+ timingFunction: this.timingFunction,
+ // delay: 10
+ }, () => {
+ // 每次增加360deg,为了让其重新旋转一周
+ this.aniAngel += 360
+ // 动画结束后,继续循环执行动画,需要同时判断webviewHide变量
+ // nvue安卓,页面隐藏后依然会继续执行startAnimate方法
+ this.show && !this.webviewHide ? this.startAnimate() : this.loading = false
+ })
+ }
+ // #endif
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-loading-icon-color: #c8c9cc !default;
+ $uv-loading-icon-text-margin-left:4px !default;
+ $uv-loading-icon-text-color:$uv-content-color !default;
+ $uv-loading-icon-text-font-size:14px !default;
+ $uv-loading-icon-text-line-height:20px !default;
+ $uv-loading-width:30px !default;
+ $uv-loading-height:30px !default;
+ $uv-loading-max-width:100% !default;
+ $uv-loading-max-height:100% !default;
+ $uv-loading-semicircle-border-width: 2px !default;
+ $uv-loading-semicircle-border-color:transparent !default;
+ $uv-loading-semicircle-border-top-right-radius: 100px !default;
+ $uv-loading-semicircle-border-top-left-radius: 100px !default;
+ $uv-loading-semicircle-border-bottom-left-radius: 100px !default;
+ $uv-loading-semicircle-border-bottom-right-radiu: 100px !default;
+ $uv-loading-semicircle-border-style: solid !default;
+ $uv-loading-circle-border-top-right-radius: 100px !default;
+ $uv-loading-circle-border-top-left-radius: 100px !default;
+ $uv-loading-circle-border-bottom-left-radius: 100px !default;
+ $uv-loading-circle-border-bottom-right-radiu: 100px !default;
+ $uv-loading-circle-border-width:2px !default;
+ $uv-loading-circle-border-top-color:#e5e5e5 !default;
+ $uv-loading-circle-border-right-color:$uv-loading-circle-border-top-color !default;
+ $uv-loading-circle-border-bottom-color:$uv-loading-circle-border-top-color !default;
+ $uv-loading-circle-border-left-color:$uv-loading-circle-border-top-color !default;
+ $uv-loading-circle-border-style:solid !default;
+ $uv-loading-icon-host-font-size:0px !default;
+ $uv-loading-icon-host-line-height:1 !default;
+ $uv-loading-icon-vertical-margin:6px 0 0 !default;
+ $uv-loading-icon-dot-top:0 !default;
+ $uv-loading-icon-dot-left:0 !default;
+ $uv-loading-icon-dot-width:100% !default;
+ $uv-loading-icon-dot-height:100% !default;
+ $uv-loading-icon-dot-before-width:2px !default;
+ $uv-loading-icon-dot-before-height:25% !default;
+ $uv-loading-icon-dot-before-margin:0 auto !default;
+ $uv-loading-icon-dot-before-background-color:currentColor !default;
+ $uv-loading-icon-dot-before-border-radius:40% !default;
+
+ .uv-loading-icon {
+ /* #ifndef APP-NVUE */
+ // display: inline-flex;
+ /* #endif */
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ color: $uv-loading-icon-color;
+
+ &__text {
+ margin-left: $uv-loading-icon-text-margin-left;
+ color: $uv-loading-icon-text-color;
+ font-size: $uv-loading-icon-text-font-size;
+ line-height: $uv-loading-icon-text-line-height;
+ }
+
+ &__spinner {
+ width: $uv-loading-width;
+ height: $uv-loading-height;
+ position: relative;
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ max-width: $uv-loading-max-width;
+ max-height: $uv-loading-max-height;
+ animation: uv-rotate 1s linear infinite;
+ /* #endif */
+ }
+
+ &__spinner--semicircle {
+ border-width: $uv-loading-semicircle-border-width;
+ border-color: $uv-loading-semicircle-border-color;
+ border-top-right-radius: $uv-loading-semicircle-border-top-right-radius;
+ border-top-left-radius: $uv-loading-semicircle-border-top-left-radius;
+ border-bottom-left-radius: $uv-loading-semicircle-border-bottom-left-radius;
+ border-bottom-right-radius: $uv-loading-semicircle-border-bottom-right-radiu;
+ border-style: $uv-loading-semicircle-border-style;
+ }
+
+ &__spinner--circle {
+ border-top-right-radius: $uv-loading-circle-border-top-right-radius;
+ border-top-left-radius: $uv-loading-circle-border-top-left-radius;
+ border-bottom-left-radius: $uv-loading-circle-border-bottom-left-radius;
+ border-bottom-right-radius: $uv-loading-circle-border-bottom-right-radiu;
+ border-width: $uv-loading-circle-border-width;
+ border-top-color: $uv-loading-circle-border-top-color;
+ border-right-color: $uv-loading-circle-border-right-color;
+ border-bottom-color: $uv-loading-circle-border-bottom-color;
+ border-left-color: $uv-loading-circle-border-left-color;
+ border-style: $uv-loading-circle-border-style;
+ }
+
+ &--vertical {
+ flex-direction: column
+ }
+ }
+
+ /* #ifndef APP-NVUE */
+ :host {
+ font-size: $uv-loading-icon-host-font-size;
+ line-height: $uv-loading-icon-host-line-height;
+ }
+
+ .uv-loading-icon {
+ &__spinner--spinner {
+ animation-timing-function: steps(12)
+ }
+
+ &__text:empty {
+ display: none
+ }
+
+ &--vertical &__text {
+ margin: $uv-loading-icon-vertical-margin;
+ color: $uv-content-color;
+ }
+
+ &__dot {
+ position: absolute;
+ top: $uv-loading-icon-dot-top;
+ left: $uv-loading-icon-dot-left;
+ width: $uv-loading-icon-dot-width;
+ height: $uv-loading-icon-dot-height;
+
+ &:before {
+ display: block;
+ width: $uv-loading-icon-dot-before-width;
+ height: $uv-loading-icon-dot-before-height;
+ margin: $uv-loading-icon-dot-before-margin;
+ background-color: $uv-loading-icon-dot-before-background-color;
+ border-radius: $uv-loading-icon-dot-before-border-radius;
+ content: " "
+ }
+ }
+ }
+
+ @for $i from 1 through 12 {
+ .uv-loading-icon__dot:nth-of-type(#{$i}) {
+ transform: rotate($i * 30deg);
+ opacity: 1 - 0.0625 * ($i - 1);
+ }
+ }
+
+ @keyframes uv-rotate {
+ 0% {
+ transform: rotate(0deg)
+ }
+
+ to {
+ transform: rotate(1turn)
+ }
+ }
+
+ /* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-loading-icon/package.json b/ledApp/uni_modules/uv-loading-icon/package.json
new file mode 100644
index 0000000..1485897
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-icon/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-loading-icon",
+ "displayName": "uv-loading-icon 加载动画 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.3",
+ "description": "此组件为一个小动画,目前用在uv-ui的uv-load-more加载更多等组件,还可以运用在项目中正在加载状态场景。",
+ "keywords": [
+ "uv-loading-icon",
+ "uvui",
+ "uv-ui",
+ "loading",
+ "加载动画"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-loading-icon/readme.md b/ledApp/uni_modules/uv-loading-icon/readme.md
new file mode 100644
index 0000000..7b0cf0f
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-icon/readme.md
@@ -0,0 +1,19 @@
+## LoadingIcon 加载动画
+
+> **组件名:uv-loading-icon**
+
+此组件为一个小动画,目前用在 `uv-ui` 的 `uv-load-more` 加载更多等组件,还可以运用在项目中正在加载状态场景。
+
+# <a href="https://www.uvui.cn/components/loadingIcon.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-loading-page/changelog.md b/ledApp/uni_modules/uv-loading-page/changelog.md
new file mode 100644
index 0000000..4b7ef61
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-page/changelog.md
@@ -0,0 +1,11 @@
+## 1.0.4(2024-01-20)
+1. 修改上版本带出的问题
+## 1.0.3(2024-01-15)
+1. 重构,避免初始加载的时候先显示页面的问题
+## 1.0.2(2023-07-02)
+uv-loading-page 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-loading-page 加载页
diff --git a/ledApp/uni_modules/uv-loading-page/components/uv-loading-page/props.js b/ledApp/uni_modules/uv-loading-page/components/uv-loading-page/props.js
new file mode 100644
index 0000000..f1a8a06
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-page/components/uv-loading-page/props.js
@@ -0,0 +1,55 @@
+export default {
+ props: {
+ // 提示内容
+ loadingText: {
+ type: [String, Number],
+ default: ''
+ },
+ // 文字上方用于替换loading动画的图片
+ image: {
+ type: String,
+ default: ''
+ },
+ // 加载动画的模式,circle-圆形,spinner-花朵形,semicircle-半圆形
+ loadingMode: {
+ type: String,
+ default: 'circle'
+ },
+ // 是否加载中
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ // 背景色
+ bgColor: {
+ type: String,
+ default: '#fff'
+ },
+ // 文字颜色
+ color: {
+ type: String,
+ default: '#C8C8C8'
+ },
+ // 文字大小
+ fontSize: {
+ type: [String, Number],
+ default: 16
+ },
+ // 图标大小
+ iconSize: {
+ type: [String, Number],
+ default: 26
+ },
+ // 加载中图标的颜色,只能rgb或者十六进制颜色值
+ loadingColor: {
+ type: String,
+ default: '#C8C8C8'
+ },
+ // 过渡时间
+ duration: {
+ type: [String, Number],
+ default: 300
+ },
+ ...uni.$uv?.props?.loadingPage
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-loading-page/components/uv-loading-page/uv-loading-page.vue b/ledApp/uni_modules/uv-loading-page/components/uv-loading-page/uv-loading-page.vue
new file mode 100644
index 0000000..8fb26d1
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-page/components/uv-loading-page/uv-loading-page.vue
@@ -0,0 +1,96 @@
+<template>
+ <view class="uv-loading-page uv-flex-column" v-if="showLoading" :style="[loadingPageStyle]">
+ <image v-if="image!=''" :src="image" class="uv-loading-page__img" mode="widthFit" :style="[{
+ width: $uv.addUnit(iconSize),
+ height: $uv.addUnit(iconSize)
+ }]"></image>
+ <uv-loading-icon v-else :mode="loadingMode" :size="iconSize" :color="loadingColor"></uv-loading-icon>
+ <slot>
+ <text class="uv-loading-page__text" :style="[{
+ fontSize: $uv.addUnit(fontSize),
+ color: color
+ }]">{{ loadingText }}</text>
+ </slot>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from "./props.js";
+ /**
+ * loadingPage 加载动画
+ * @description 警此组件为一个小动画,目前用在uvui的loadmore加载更多和switch开关等组件的正在加载状态场景。
+ * @tutorial https://www.uvui.cn/components/loading.html
+ *
+ * @property {Boolean} loading 是否加载中 (默认 false )
+ * @property {String | Number} loadingText 提示内容 (默认 '正在加载' )
+ * @property {String} image 文字上方用于替换loading动画的图片
+ * @property {String} loadingMode 加载动画的模式,circle-圆形,spinner-花朵形,semicircle-半圆形 (默认 'circle' )
+ * @property {String} bgColor 背景色 (默认 '#ffffff' )
+ * @property {String} color 文字颜色 (默认 '#C8C8C8' )
+ * @property {String | Number} fontSize 文字大小 (默认 19 )
+ * @property {String | Number} iconSize 图标大小 (默认 28 )
+ * @property {String} loadingColor 加载中图标的颜色,只能rgb或者十六进制颜色值 (默认 '#C8C8C8' )
+ * @property {String | Number} duration 关闭加载时的过渡时间,单位ms (默认 300 )
+ *
+ * @example <uv-loading mode="circle"></uv-loading>
+ */
+ export default {
+ name: "uv-loading-page",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ showLoading: false,
+ opacity: 1
+ }
+ },
+ watch: {
+ loading: {
+ immediate: true,
+ handler(newVal) {
+ if (newVal) {
+ this.showLoading = true;
+ this.opacity = 1;
+ } else {
+ this.opacity = 0;
+ this.$nextTick(() => {
+ this.$uv.sleep(this.duration).then(res => {
+ this.showLoading = false;
+ })
+ })
+ }
+ }
+ }
+ },
+ computed: {
+ loadingPageStyle() {
+ const style = {
+ 'position': 'fixed',
+ 'top': '0',
+ 'left': '0',
+ 'right': '0',
+ 'bottom': '0',
+ 'zIndex': '999',
+ 'background-color': this.bgColor,
+ 'transition-duration': `${this.duration}ms`,
+ 'opacity': this.opacity
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ }
+ }
+</script>
+<style scoped lang="scss">
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ $uv-loading-icon-margin-bottom: 10px !default;
+ .uv-loading-page {
+ flex: 1;
+ justify-content: center;
+ align-items: center;
+ padding-bottom: 150px;
+ transition-property: opacity;
+ &__text {
+ margin-top: $uv-loading-icon-margin-bottom;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-loading-page/package.json b/ledApp/uni_modules/uv-loading-page/package.json
new file mode 100644
index 0000000..cc9b171
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-page/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-loading-page",
+ "displayName": "uv-loading-page 加载页 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.4",
+ "description": "uv-loading-page 该组件是一个页面级的加载效果,可以在页面初始化数据等场景使用,与骨架屏有相似之处。",
+ "keywords": [
+ "uv-loading-page",
+ "uvui",
+ "uv-ui",
+ "page",
+ "loading"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-transition",
+ "uv-loading-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-loading-page/readme.md b/ledApp/uni_modules/uv-loading-page/readme.md
new file mode 100644
index 0000000..3a5dd96
--- /dev/null
+++ b/ledApp/uni_modules/uv-loading-page/readme.md
@@ -0,0 +1,11 @@
+## LoadingPage 加载页
+
+> **组件名:uv-loading-page**
+
+该组件是一个页面级的加载效果,可以在页面初始化数据等场景使用,与骨架屏有相似之处。
+
+### <a href="https://www.uvui.cn/components/loadingPage.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-modal/changelog.md b/ledApp/uni_modules/uv-modal/changelog.md
new file mode 100644
index 0000000..d532286
--- /dev/null
+++ b/ledApp/uni_modules/uv-modal/changelog.md
@@ -0,0 +1,28 @@
+## 1.0.11(2023-12-20)
+1. 优化
+## 1.0.10(2023-12-19)
+1. 修复confirm中快速使用closeLoading关闭加载状态失效的BUG
+## 1.0.9(2023-11-28)
+1. 修复上版本引出的确认和取消按钮均不显示,还有高度的BUG
+## 1.0.8(2023-09-08)
+1. 修复两个按钮之间竖线不显示的问题
+2. uv-ui项目自定义按钮示例修改
+## 1.0.7(2023-08-30)
+1. 增加align参数,设置文本对齐方式,left center right
+2. 增加textStyle参数,设置文本样式
+## 1.0.6(2023-08-23)
+1. 修复异步loading时,确认回调还会一直触发
+## 1.0.5(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.4(2023-07-02)
+uv-modal 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/modal.html
+## 1.0.3(2023-06-29)
+1. 增加closeLoading方法,方便异步加载手动取消
+2. 更新文档
+## 1.0.2(2023-06-11)
+1. 新增zIndex参数
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-modal 模态框
diff --git a/ledApp/uni_modules/uv-modal/components/uv-modal/props.js b/ledApp/uni_modules/uv-modal/components/uv-modal/props.js
new file mode 100644
index 0000000..6cb1f41
--- /dev/null
+++ b/ledApp/uni_modules/uv-modal/components/uv-modal/props.js
@@ -0,0 +1,90 @@
+export default {
+ props: {
+ // 标题
+ title: {
+ type: [String],
+ default: ''
+ },
+ // 弹窗内容
+ content: {
+ type: String,
+ default: ''
+ },
+ // 确认文案
+ confirmText: {
+ type: String,
+ default: '确认'
+ },
+ // 取消文案
+ cancelText: {
+ type: String,
+ default: '取消'
+ },
+ // 是否显示确认按钮
+ showConfirmButton: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示取消按钮
+ showCancelButton: {
+ type: Boolean,
+ default: false
+ },
+ // 确认按钮颜色
+ confirmColor: {
+ type: String,
+ default: '#2979ff'
+ },
+ // 取消文字颜色
+ cancelColor: {
+ type: String,
+ default: '#606266'
+ },
+ // 对调确认和取消的位置
+ buttonReverse: {
+ type: Boolean,
+ default: false
+ },
+ // 是否开启缩放效果
+ zoom: {
+ type: Boolean,
+ default: true
+ },
+ // 层级
+ zIndex: {
+ type: [String, Number],
+ default: 10075
+ },
+ // 是否异步关闭,只对确定按钮有效
+ asyncClose: {
+ type: Boolean,
+ default: false
+ },
+ // 是否允许点击遮罩关闭modal
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: true
+ },
+ // 给一个负的margin-top,往上偏移,避免和键盘重合的情况
+ negativeTop: {
+ type: [String, Number],
+ default: 0
+ },
+ // modal宽度,不支持百分比,可以数值,px,rpx单位
+ width: {
+ type: [String, Number],
+ default: '650rpx'
+ },
+ // 文本对齐方式,默认left
+ align: {
+ type: String,
+ default: 'left'
+ },
+ // 文本自定义样式
+ textStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ ...uni.$uv?.props?.modal
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-modal/components/uv-modal/uv-modal.vue b/ledApp/uni_modules/uv-modal/components/uv-modal/uv-modal.vue
new file mode 100644
index 0000000..4577b99
--- /dev/null
+++ b/ledApp/uni_modules/uv-modal/components/uv-modal/uv-modal.vue
@@ -0,0 +1,245 @@
+<template>
+ <uv-popup
+ ref="modalPopup"
+ mode="center"
+ :zoom="zoom"
+ :zIndex="zIndex"
+ :customStyle="{
+ borderRadius: '6px',
+ overflow: 'hidden',
+ marginTop: `-${$uv.addUnit(negativeTop)}`
+ }"
+ :closeOnClickOverlay="closeOnClickOverlay"
+ :safeAreaInsetBottom="false"
+ :duration="400"
+ @change="popupChange"
+ >
+ <view
+ class="uv-modal"
+ :style="{
+ width: $uv.addUnit(width),
+ }"
+ >
+ <text
+ class="uv-modal__title"
+ v-if="title"
+ >{{ title }}</text>
+ <view
+ class="uv-modal__content"
+ :style="{
+ paddingTop: `${title ? 12 : 25}px`
+ }"
+ >
+ <slot>
+ <text
+ class="uv-modal__content__text"
+ :style="[
+ {
+ textAlign: align
+ },
+ nvueStyle,
+ $uv.addStyle(textStyle)
+ ]"
+ >{{ content }}</text>
+ </slot>
+ </view>
+ <slot name="confirmButton">
+ <uv-line></uv-line>
+ <view
+ v-if="showConfirmButton || showCancelButton"
+ class="uv-modal__button-group"
+ :style="{
+ flexDirection: buttonReverse ? 'row-reverse' : 'row'
+ }"
+ >
+ <view
+ class="uv-modal__button-group__wrapper uv-modal__button-group__wrapper--cancel"
+ :hover-stay-time="150"
+ hover-class="uv-modal__button-group__wrapper--hover"
+ :class="[showCancelButton && !showConfirmButton && 'uv-modal__button-group__wrapper--only-cancel']"
+ v-if="showCancelButton"
+ @tap="cancelHandler"
+ >
+ <text
+ class="uv-modal__button-group__wrapper__text"
+ :style="{
+ color: cancelColor
+ }"
+ >{{ cancelText }}</text>
+ </view>
+ <uv-line
+ direction="column"
+ v-if="showConfirmButton && showCancelButton"
+ ></uv-line>
+ <view
+ class="uv-modal__button-group__wrapper uv-modal__button-group__wrapper--confirm"
+ :hover-stay-time="150"
+ hover-class="uv-modal__button-group__wrapper--hover"
+ :class="[!showCancelButton && showConfirmButton && 'uv-modal__button-group__wrapper--only-confirm']"
+ v-if="showConfirmButton"
+ @tap="confirmHandler"
+ >
+ <uv-loading-icon v-if="loading"></uv-loading-icon>
+ <text
+ v-else
+ class="uv-modal__button-group__wrapper__text"
+ :style="{
+ color: confirmColor
+ }"
+ >{{ confirmText }}</text>
+ </view>
+ </view>
+ </slot>
+ </view>
+ </uv-popup>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Modal 模态框
+ * @description 弹出模态框,常用于消息提示、消息确认、在当前页面内完成特定的交互操作。
+ * @tutorial https://www.uvui.cn/components/modul.html
+ * @property {String} title 标题内容
+ * @property {String} content 模态框内容,如传入slot内容,则此参数无效
+ * @property {String} confirmText 确认按钮的文字 (默认 '确认' )
+ * @property {String} cancelText 取消按钮的文字 (默认 '取消' )
+ * @property {Boolean} showConfirmButton 是否显示确认按钮 (默认 true )
+ * @property {Boolean} showCancelButton 是否显示取消按钮 (默认 false )
+ * @property {String} confirmColor 确认按钮的颜色 (默认 '#2979ff' )
+ * @property {String} cancelColor 取消按钮的颜色 (默认 '#606266' )
+ * @property {Boolean} buttonReverse 对调确认和取消的位置 (默认 false )
+ * @property {Boolean} zoom 是否开启缩放模式 (默认 true )
+ * @property {Boolean} asyncClose 是否异步关闭,只对确定按钮有效,见上方说明 (默认 false )
+ * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭该组件 (默认 true )
+ * @property {String | Number} negativeTop 往上偏移的值,给一个负的margin-top,往上偏移,避免和键盘重合的情况,单位任意,数值则默认为px单位 (默认 0 )
+ * @property {String | Number} width modal宽度,不支持百分比,可以数值,px,rpx单位 (默认 '650rpx' )
+ * @property {String} align 文本对齐方式 (默认'left')
+ * @property {String | Object} textStyle 文本扩展样式
+ * @event {Function} confirm 点击确认按钮时触发
+ * @event {Function} cancel 点击取消按钮时触发
+ * @event {Function} close 点击遮罩关闭出发,closeOnClickOverlay为true有效
+ * @example <uv-modal ref="modalPopup" title="title" content="content"></uv-modal>
+ */
+ export default {
+ name: 'uv-modal',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ loading: false
+ }
+ },
+ computed: {
+ nvueStyle() {
+ const style = {};
+ // 避免nvue中不能换行的问题
+ // #ifdef APP-NVUE
+ style.width = this.$uv.addUnit(this.$uv.getPx(this.width) - 50,'px');
+ // #endif
+ return style;
+ }
+ },
+ methods: {
+ open() {
+ this.$refs.modalPopup.open();
+ if (this.loading) this.loading = false;
+ },
+ close() {
+ this.$refs.modalPopup.close();
+ },
+ popupChange(e) {
+ if(!e.show) this.$emit('close');
+ },
+ // 点击确定按钮
+ confirmHandler() {
+ if(!this.loading) {
+ this.$emit('confirm');
+ }
+ // 如果配置了异步关闭,将按钮值为loading状态
+ if (this.asyncClose) {
+ this.loading = true;
+ } else {
+ this.close();
+ }
+ },
+ // 点击取消按钮
+ cancelHandler() {
+ this.$emit('cancel');
+ this.close();
+ },
+ closeLoading() {
+ this.$nextTick(()=>{
+ this.loading = false;
+ })
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-modal-border-radius: 6px;
+
+ .uv-modal {
+ width: 650rpx;
+ border-radius: $uv-modal-border-radius;
+ overflow: hidden;
+
+ &__title {
+ font-size: 16px;
+ font-weight: bold;
+ color: $uv-content-color;
+ text-align: center;
+ padding-top: 25px;
+ }
+
+ &__content {
+ padding: 12px 25px 25px 25px;
+ @include flex;
+ justify-content: center;
+
+ &__text {
+ line-height: 48rpx;
+ font-size: 15px;
+ color: $uv-content-color;
+ flex: 1;
+ }
+ }
+
+ &__button-group {
+ @include flex;
+ height: 48px;
+
+ &__wrapper {
+ flex: 1;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ height: 48px;
+
+ &--confirm,
+ &--only-cancel {
+ border-bottom-right-radius: $uv-modal-border-radius;
+ }
+
+ &--cancel,
+ &--only-confirm {
+ border-bottom-left-radius: $uv-modal-border-radius;
+ }
+
+ &--hover {
+ background-color: $uv-bg-color;
+ }
+
+ &__text {
+ color: $uv-content-color;
+ font-size: 16px;
+ text-align: center;
+ }
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-modal/package.json b/ledApp/uni_modules/uv-modal/package.json
new file mode 100644
index 0000000..2a815c0
--- /dev/null
+++ b/ledApp/uni_modules/uv-modal/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-modal",
+ "displayName": "uv-modal 模态框 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.11",
+ "description": "支持自定义内容,与uniapp提供的API uni.showModal类似,但是功能更强大,更加灵活",
+ "keywords": [
+ "uv-modal",
+ "uvui",
+ "uv-ui",
+ "modal",
+ "模态框"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-popup",
+ "uv-line",
+ "uv-loading-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-modal/readme.md b/ledApp/uni_modules/uv-modal/readme.md
new file mode 100644
index 0000000..e1aec02
--- /dev/null
+++ b/ledApp/uni_modules/uv-modal/readme.md
@@ -0,0 +1,23 @@
+## Modal 模态框
+
+> **组件名:uv-modal**
+
+弹出模态框,常用于消息提示、消息确认、在当前页面内完成特定的交互操作。
+
+特性:支持自定义内容,与uniapp提供的API `uni.showModal` 类似,但是功能更强大,更加灵活。
+
+运用场景:弹窗验证码输入等场景
+
+# <a href="https://www.uvui.cn/components/modal.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-navbar/changelog.md b/ledApp/uni_modules/uv-navbar/changelog.md
new file mode 100644
index 0000000..01cc4fd
--- /dev/null
+++ b/ledApp/uni_modules/uv-navbar/changelog.md
@@ -0,0 +1,17 @@
+## 1.0.7(2023-08-16)
+1. 修复ios可能存在点击返回按钮点不到的情况
+## 1.0.6(2023-08-07)
+1. 修复nvue在ios端可能存在背景图样式错乱的BUG
+## 1.0.5(2023-08-04)
+1. bgColor设置背景图片,增加imgMode属性
+## 1.0.4(2023-08-01)
+1. bgColor属性支持背景图片,在线图片或base64图片都可以
+## 1.0.3(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.2(2023-06-05)
+1. 兼容渐变背景色
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-navbar 自定义导航栏
diff --git a/ledApp/uni_modules/uv-navbar/components/uv-navbar/props.js b/ledApp/uni_modules/uv-navbar/components/uv-navbar/props.js
new file mode 100644
index 0000000..47fe150
--- /dev/null
+++ b/ledApp/uni_modules/uv-navbar/components/uv-navbar/props.js
@@ -0,0 +1,89 @@
+export default {
+ props: {
+ // 是否开启顶部安全区适配
+ safeAreaInsetTop: {
+ type: Boolean,
+ default: true
+ },
+ // 固定在顶部时,是否生成一个等高元素,以防止塌陷
+ placeholder: {
+ type: Boolean,
+ default: false
+ },
+ // 是否固定在顶部
+ fixed: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示下边框
+ border: {
+ type: Boolean,
+ default: false
+ },
+ // 左边的图标
+ leftIcon: {
+ type: String,
+ default: 'arrow-left'
+ },
+ // 左边的提示文字
+ leftText: {
+ type: String,
+ default: ''
+ },
+ // 左右的提示文字
+ rightText: {
+ type: String,
+ default: ''
+ },
+ // 右边的图标
+ rightIcon: {
+ type: String,
+ default: ''
+ },
+ // 标题
+ title: {
+ type: [String, Number],
+ default: ''
+ },
+ // 背景颜色
+ bgColor: {
+ type: String,
+ default: '#ffffff'
+ },
+ imgMode: {
+ type: String,
+ default: 'aspectFill'
+ },
+ // 标题的宽度
+ titleWidth: {
+ type: [String, Number],
+ default: '400rpx'
+ },
+ // 导航栏高度
+ height: {
+ type: [String, Number],
+ default: '44px'
+ },
+ // 左侧返回图标的大小
+ leftIconSize: {
+ type: [String, Number],
+ default: 20
+ },
+ // 左侧返回图标的颜色
+ leftIconColor: {
+ type: String,
+ default: '#303133'
+ },
+ // 点击左侧区域(返回图标),是否自动返回上一页
+ autoBack: {
+ type: Boolean,
+ default: false
+ },
+ // 标题的样式,对象或字符串
+ titleStyle: {
+ type: [String, Object],
+ default: ''
+ },
+ ...uni.$uv?.props?.navbar
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-navbar/components/uv-navbar/uv-navbar.vue b/ledApp/uni_modules/uv-navbar/components/uv-navbar/uv-navbar.vue
new file mode 100644
index 0000000..a46ad7a
--- /dev/null
+++ b/ledApp/uni_modules/uv-navbar/components/uv-navbar/uv-navbar.vue
@@ -0,0 +1,245 @@
+<template>
+ <view class="uv-navbar">
+ <view
+ class="uv-navbar__placeholder"
+ v-if="fixed && placeholder"
+ :style="{
+ height: $uv.addUnit($uv.getPx(height) + $uv.sys().statusBarHeight,'px'),
+ }"
+ ></view>
+ <view :class="[fixed && 'uv-navbar--fixed']">
+ <image class="uv-navbar--bgimg" :src="bgColor" :mode="imgMode" v-if="isImg" :style="[bgImgStyle]"></image>
+ <uv-status-bar
+ v-if="safeAreaInsetTop"
+ :bgColor="getStatusbgColor"
+ ></uv-status-bar>
+ <view
+ class="uv-navbar__content"
+ :class="[border && 'uv-border-bottom']"
+ :style="[{
+ height: $uv.addUnit(height)
+ },getBgColor]"
+ >
+ <view
+ class="uv-navbar__content__left"
+ hover-class="uv-navbar__content__left--hover"
+ hover-start-time="150"
+ @tap="leftClick"
+ >
+ <slot name="left">
+ <uv-icon
+ v-if="leftIcon"
+ :name="leftIcon"
+ :size="leftIconSize"
+ :color="leftIconColor"
+ ></uv-icon>
+ <text
+ v-if="leftText"
+ :style="{
+ color: leftIconColor
+ }"
+ class="uv-navbar__content__left__text"
+ >{{ leftText }}</text>
+ </slot>
+ </view>
+ <slot name="center">
+ <text
+ class="uv-line-1 uv-navbar__content__title"
+ :style="[{
+ width: $uv.addUnit(titleWidth),
+ flex: '0 1 auto'
+ }, $uv.addStyle(titleStyle)]"
+ >{{ title }}</text>
+ </slot>
+ <view
+ class="uv-navbar__content__right"
+ @tap="rightClick"
+ >
+ <slot name="right">
+ <uv-icon
+ v-if="rightIcon"
+ :name="rightIcon"
+ size="20"
+ ></uv-icon>
+ <text
+ v-if="rightText"
+ class="uv-navbar__content__right__text"
+ >{{ rightText }}</text>
+ </slot>
+ </view>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Navbar 自定义导航栏
+ * @description 此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用uni-app带的导航栏。
+ * @tutorial https://www.uvui.cn/components/navbar.html
+ * @property {Boolean} safeAreaInsetTop 是否开启顶部安全区适配 (默认 true )
+ * @property {Boolean} placeholder 固定在顶部时,是否生成一个等高元素,以防止塌陷 (默认 false )
+ * @property {Boolean} fixed 导航栏是否固定在顶部 (默认 false )
+ * @property {Boolean} border 导航栏底部是否显示下边框 (默认 false )
+ * @property {String} leftIcon 左边返回图标的名称,只能为uvui自带的图标 (默认 'arrow-left' )
+ * @property {String} leftText 左边的提示文字
+ * @property {String} rightText 右边的提示文字
+ * @property {String} rightIcon 右边返回图标的名称,只能为uvui自带的图标
+ * @property {String} title 导航栏标题,如设置为空字符,将会隐藏标题占位区域
+ * @property {String} bgColor 导航栏背景设置 (默认 '#ffffff' )
+ * @property {String | Number} titleWidth 导航栏标题的最大宽度,内容超出会以省略号隐藏 (默认 '400rpx' )
+ * @property {String | Number} height 导航栏高度(不包括状态栏高度在内,内部自动加上)(默认 '44px' )
+ * @property {String | Number} leftIconSize 左侧返回图标的大小(默认 20px )
+ * @property {String | Number} leftIconColor 左侧返回图标的颜色(默认 #303133 )
+ * @property {Boolean} autoBack 点击左侧区域(返回图标),是否自动返回上一页(默认 false )
+ * @property {Object | String} titleStyle 标题的样式,对象或字符串
+ * @event {Function} leftClick 点击左侧区域
+ * @event {Function} rightClick 点击右侧区域
+ * @example <uv-navbar title="剑未配妥,出门已是江湖" left-text="返回" right-text="帮助" @click-left="onClickBack" @click-right="onClickRight"></uv-navbar>
+ */
+ export default {
+ name: 'uv-navbar',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+
+ }
+ },
+ computed: {
+ getBgColor(){
+ const style = {};
+ if(this.bgColor){
+ if (this.bgColor.indexOf("gradient") > -1) {// 渐变色
+ style.backgroundImage = this.bgColor;
+ }else if(this.isImg){
+ style.background = 'transparent';
+ }else {
+ style.background = this.bgColor;
+ }
+ }
+ return style;
+ },
+ getStatusbgColor() {
+ if(this.bgColor){
+ if(this.isImg){
+ return 'transparent';
+ }else {
+ return this.bgColor;
+ }
+ }
+ },
+ // 判断传入的bgColor属性,是否图片路径,只要带有"/"均认为是图片形式
+ isImg() {
+ const isBase64 = this.bgColor.indexOf('data:') > -1 && this.bgColor.indexOf('base64') > -1;
+ return this.bgColor.indexOf('/') !== -1 || isBase64;
+ },
+ bgImgStyle() {
+ const style = {};
+ if(this.safeAreaInsetTop) {
+ style.height = this.$uv.addUnit(this.$uv.sys().statusBarHeight + 44, 'px');
+ } else {
+ style.height = '44px';
+ }
+ return style;
+ }
+ },
+ methods: {
+ // 点击左侧区域
+ leftClick() {
+ // 如果配置了autoBack,自动返回上一页
+ this.$emit('leftClick')
+ if(this.autoBack) {
+ uni.navigateBack()
+ }
+ },
+ // 点击右侧区域
+ rightClick() {
+ this.$emit('rightClick')
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-border: 1;
+ $show-border-bottom: 1;
+ $show-lines: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-navbar {
+ position: relative;
+ &--fixed {
+ position: fixed;
+ left: 0;
+ right: 0;
+ top: 0;
+ z-index: 11;
+ }
+
+ &--bgimg {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ top: 0;
+ /* #ifndef APP-NVUE */
+ width: 100%;
+ height: 100%;
+ /* #endif */
+ /* #ifdef APP-NVUE */
+ width: 750rpx;
+ /* #endif */
+ }
+
+ &__content {
+ @include flex(row);
+ align-items: center;
+ height: 44px;
+ background-color: #9acafc;
+ position: relative;
+ justify-content: center;
+
+ &__left,
+ &__right {
+ padding: 0 13px;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ @include flex(row);
+ align-items: center;
+ }
+
+ &__left {
+ left: 0;
+
+ &--hover {
+ opacity: 0.7;
+ }
+
+ &__text {
+ font-size: 15px;
+ margin-left: 3px;
+ }
+ }
+
+ &__title {
+ text-align: center;
+ font-size: 16px;
+ color: $uv-main-color;
+ }
+
+ &__right {
+ right: 0;
+
+ &__text {
+ font-size: 15px;
+ margin-left: 3px;
+ }
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-navbar/package.json b/ledApp/uni_modules/uv-navbar/package.json
new file mode 100644
index 0000000..8d429de
--- /dev/null
+++ b/ledApp/uni_modules/uv-navbar/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-navbar",
+ "displayName": "uv-navbar 自定义导航栏 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.7",
+ "description": "uv-navbar 此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用自带的原生导航栏。",
+ "keywords": [
+ "uv-navbar",
+ "uvui",
+ "uv-ui",
+ "navbar",
+ "自定义导航栏"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-status-bar"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-navbar/readme.md b/ledApp/uni_modules/uv-navbar/readme.md
new file mode 100644
index 0000000..412ea7b
--- /dev/null
+++ b/ledApp/uni_modules/uv-navbar/readme.md
@@ -0,0 +1,19 @@
+## Navbar 自定义导航栏
+
+> **组件名:uv-navbar**
+
+此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用自带的原生导航栏,支持渐变色、透明色、图片背景。
+
+# <a href="https://www.uvui.cn/components/navbar.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-no-network/changelog.md b/ledApp/uni_modules/uv-no-network/changelog.md
new file mode 100644
index 0000000..b6fc0ed
--- /dev/null
+++ b/ledApp/uni_modules/uv-no-network/changelog.md
@@ -0,0 +1,5 @@
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-no-network 无网络提示
diff --git a/ledApp/uni_modules/uv-no-network/components/uv-no-network/props.js b/ledApp/uni_modules/uv-no-network/components/uv-no-network/props.js
new file mode 100644
index 0000000..595b1fc
--- /dev/null
+++ b/ledApp/uni_modules/uv-no-network/components/uv-no-network/props.js
@@ -0,0 +1,20 @@
+export default {
+ props: {
+ // 页面文字提示
+ tips: {
+ type: String,
+ default: '哎呀,网络信号丢失'
+ },
+ // 一个z-index值,用于设置没有网络这个组件的层次,因为页面可能会有其他定位的元素层级过高,导致此组件被覆盖
+ zIndex: {
+ type: [String, Number],
+ default: ''
+ },
+ // image 没有网络的图片提示
+ image: {
+ type: String,
+ default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABLKADAAQAAAABAAABLAAAAADYYILnAABAAElEQVR4Ae29CZhkV3kefNeq6m2W7tn3nl0aCbHIAgmQPGB+sLCNzSID9g9PYrAf57d/+4+DiW0cy8QBJ06c2In/PLFDHJ78+MGCGNsYgyxwIwktwEijAc1ohtmnZ+2Z7p5eq6vu9r/vuXWrq25VdVV1V3dXVX9Hmj73nv285963vvOd75yraeIEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQaD8E9PbrkvRopSMwMBBYRs+5O/yJS68cPnzYXel4tFP/jXbqjPRFEAiCQNe6Bw/6gdFn9Oy9Q90LLG2DgBBW2wyldIQIPPPCte2a5q3jtR+4ff/4wuBuXotrDwSEsNpjHKUXQODppy+udYJMEUEZgbd94DvnNwlA7YGAEFZ7jOOK78Xp06eTTkq7sxwQhmXuf/754VXl4iSstRAQwmqt8ZLWlkHg0UcD49qYfUjXfLtMtOZ7npExJu4iqZWLl7DWQUAIq3XGSlpaAYHD77q8xwuCOSUoXw8Sl0eMux977DGzQjES3AIICGG1wCBJEysj8PXnz230XXdr5RQFMYbRvWnv6w8UhMhliyGwYghr4Pjg3oEXL34ey9zyC9tiD2ml5h47dr1LN7S6CMjz/A3PvHh1Z6UyJby5EVgRhKUe7Kz/JU0LfvrJo5f+Y3MPibSuFgQGBgasYSd9l6GDsup0WS/T/9RTp9fXmU2SNwECdQ92E7S57iaMeJnPQLK6ixkDLfjlb7546RfrLkQyNBcC3dsP6oHWMd9G+V3JgwPHh7rnm1/yLQ8CbU9Y33zp0j+nZFUMb/DHmB7+SHGY3LUKAk8cObtD00xlHDrfNge+Z2ozU3c9dvx4Yr5lSL6lR6CtCWvg6OAPw9z538ZhhZRl6XrwhW8du1KX/iNejtwvPQIDR8+vSRqJ/obU7GupjdNdh2gW0ZDypJBFR6BtB2rg2OVtuub9JcmpHIpBoK1xfffLzx4f7C0XL2HNiYDp6bs9z23Ypn1fC1Y/9PCFDc3ZW2lVHIG2JKzTp4Ok7nv/G6Q054MIvda+bNb74pEgKGtwGAdL7pcfAa8vOKEZ2kyjWuLr7uDh+/qvN6o8KWdxEWhLwroyeek/g4zuqwU6kNrhyZcu/UktaSXN8iNwuL9/RuvVXtJ9PbPQ1vhmcP6t9+47u9ByJP/SIdB2hDVw9MJHQFYfrQdCph84evFX68kjaZcPAZJWwjMXRFpJ2zr91tfuvrh8vZCa54NA2xGWrunvmg8QWCJ/N4ir7fCYDxatkOeBB7an501agXbygVdvv9IK/ZQ2FiPQdi9osGbH+zRNf7y4m9Xu9Me7N9nv0HXdr5ZS4psHgXpJC9P/wDRTx0Vn1TxjWG9LGrbaUm/Fi5meSvcrkxf/Cg/ow9XqAUk91v3qHT97r6471dJKfHMi8Oyzgx1Z03t1YAQVT2MwgsC3u+yXHzi0faQ5eyGtqgWBtpOw2Ol9+/TM+sTOn8L08MtzgQCy+tOHXr3jA0JWc6HU/HF5Scssr4jXcYqfP6V/T8iq+ceyWgvbUsKKOn38eJAYyl56TAuCEr2WYei//9Crd/5GlFb81kdASVopSFrerKRlaoZj9HR+700H10+0fg+lB21NWBxe2lhNHsUpDZr27mi4dV379R9+za4/iO7Fbx8ECknLCPTsTDJ17O33bJpqnx6u7J60PWFxeAcCbMV56dJfQKf1bkMLfuGh1+76zMoe9vbuPUnLsb2DtmOe5HSxvXsrvWtLBEhaTx29+Ma27Jx0ShAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQaEsEVoQdVluO3BJ06ptHL34b1XRjp4Ch6Rq24+kmjG4Nwwg+9uA9u/73EjRBqhAEihAoe3xwUQq5WTYEzp0b3ZnV/Ncf6O/9AvY9wlh/6dy3X7ncN512Zw9BVLXjuAP4np44vnQtkZoEgVkEhLBmsWiKqwsXpjbPBOn3gRfenwnc+7GBe+zsjclvonFDS9nA9Iy/u3x9+vAP3735VPk4CRUEFhcBIazFxbfm0k9fHD7k+v4nQFaPQIrx8Gmyx/GJ0J/t7ez7mw0b9MmaC2pQQgh0/ZSm4g5TwueWWtqLt0HuVy4CQljLPPYnB0depTn+b3t+8B4t0AdBUv93h2H9xc6da0aXs2m+r1WQsLRnl7NdUvfKRkAIa5nG//r1oGtsZvjTgev/kqYHF/TA+AXoqv4npJemOEiQU1Eo2l+G0movBK1UBBPU7s9E1+ILAkuNgKwSLjXiqO/khVtvARH8dxDBRkMzPrF/V+9/BlG5y9CUqlXinHv9mRPXtvuus88L9H3JPv2zD2yXExCqAicJBIFWRwAvv3Xqwq0/Pnn+lv/K+ZvfPH3p9p5W75O0fxaBp793ce3AwIDMWmYhafiVgNtwSMsXeHp4eNXJC8Nf0PAdRCiuf/XgrnWUqsqotcvnl9DmRkCdweX4b9N7+m/ih+mbMraLM14yJVwcXItKpT1VRve+ArC3Qqn+3gM7132jKEGZm6tXg86J7OhDfuA/iHwPUpfUZSfu2L59tXxEoQxeyxkEgjKeOnLxHb4RqC+NY5H3+2953d4XlrNN7Vq3ENYij+yZwbG9jpt9GkBPQ5H9zgP9607OVeWp87cOQtn9zwJf+xDMNFfj+jryPqXpxj8c2Nn7P+SXey70lidu4IXzb0DNB4tr9751+HV7zxSHyd1CERDCWiiCc+QPjUCnsaqmZ62O5IN7N/VUNP48ee7mAZDTf4Tt049iUG4Guv4ZfNLos9UIbo7qJWoJEHjy+bP7fNsoOcnW0A0/aacef8PdG28sQTNWTBVCWIs01OfPj66BpfqTmq732UnjgT1bei+Vq4pTv7HM8Ceg2/o1qLQug7T+FaaM3IqTLZdewpoHgYEjV9fphvOj+OShWa5V+CxvZtpzv/LwG/aNl4uXsPoRwI+4uEYjAJ2GmdG8L0FK2mYa+tsrkdXZy+P7x2ZuHdW14P+BLdank9q6Qwd3rf+ckFWjR6Tx5Q2cP58K9Jm3VCIr1ogt48lO237r3//96YofeG18y9q7RFklXITxPXV+5DchKb3ZDMy37Nu5tuxG4R9cHH6b42QfAzlds+3EPXu2rfrBIjRFilwkBIIR7SHoJDurFU89ZOd680Gke6JaWomvjoBIWNUxqivFD87fej0e0n8Fwvr0/t1rnyqX+QfnRz7g+8FX8Rv8vL3auF/IqhxKzR2WCPxXqKeq3krDTdj2ierpJEUtCIgOqxaUakwzNBR0D09yiqePHOjveyOkpxLr9VMXb73V97S/h3nDXx7Y2fdPkAYbncW1IgIDxy5vM7LZt/hgrnLtxyaBrJNxv/72N+6tuNhSLp+EVUZACKsyNnXHvHL+1qcgNf2KbSXu2bt9dcmS9qlzo/fARgcmCtpzB3b1/Vg5QiuslLowENyDWDn8cSjl98PgdBviu03N+rl9/WufLEwr18uDwLdevLTF1YK3xnVZ2HI1bUxrT7z5zTuXdRP78qCyeLUKYTUI25OXbm4JPO00TBj+6I7+db8ZL3ZwMOiYdG4dA1lN9HWte2iuI2NAVPapC8O/CGPR34Ip/AZIbIMo7yX8G9QMbcS09P+2b1vf5XgdrXaPfiYns9oeLLEd8D1/B7Dp0E1jGP042pXQj7RKf546cmGzp+tv1TRf6YQD35/QO3seP3xow5IfC9QqmM23naJ0ny9ysXwgq98BWc0kVhv/Nhalbqe8kd/Fr8MOSEr3zEVWrwyO3I29hl+E9LUHGf+nAXI6sGPdd8uV2YphIKnE5IyL6bLxk7cn3bdkHHefrpvJAExMZ1uBZmqeNzXtfzUzk/m/ens7LjV7Px+8d9e1579/44l0duZtge+Np5zEEw8c2pBu9na3YvtEwmrAqNE8IZvNHsep5//yjl3r/0O8yFOXbv0QCO05gP0JGIL+fjw+uj91YeRh/Dp/PtCDM7Zpfmjvjt6Xo7hW9ycmJjaYduf7Hdf/8HTGfa3rG9rYxLSWnsloPg7fijZV8oFM2Ja2a9t6EJd7bCztvHP7us4rrdD/r3/7ct9I99jEI4cOiQ3dIg2YEFYDgOUJDFj1e8TqX7cT4kImXuQr5279A4DeBEX8ayvprU4N3rovcALot/TH13T0fXDTJn0qXk4r3k9OTm4y7a6PzjjORzOOvn1kbEqbnEprPhRzwAKzwFLHk05hv6Yd6N+o3R6beG50aPSdr3qV6IJKkVp5ITIlXOCYn4Yexr0w/DO6YXymHFlR0e5r7tsM3fxgJbI6fW1ivTeT+SsYmr54cFff+5Cu5X+hb94Merp6/J/PusGvTE6724eGJ7RpSFOkKPCUZvBPBccoHBet3Rwe13rX9tw/PjXzZ5hKvr8SfhWKkeA2REAIa4GD6p0feRdWBnvxjv2PckVhVfBf4A29uG/X2i+Ui2eYn8n8NryuDr3jPfWSFV5k44UT137eshIP2K7/64cObbheqZ6lCp+Ydt8TBO7vTM5od1+/NR4SFVhoLpKKt410lnE8LTMzo3V2dLznxLkhYgQ9obiVjEDln7mVjEodfYcpw+MAsftg/7qSDbAnb97sCSb0Yei2fqOcbovVqKNnNO8HmAE9Cv3Wp+uoWjt27HpXNqH9WTKR+kBHKqEFbvo5y3N/avfu4g23R45f3WGa1k9ZicTd0zPTf/f6O7f8dT311Jp2fHzmgJlI/N70jPPe4bEZ6Kg4qw0lqlrLiNKBiLWerpTW25PUbkPXZViW62ecHz+4d8PXojTirzwEyhq8rTwYFtRjvpX/rlwJ+iSXugPbMuyKBOHo3geRJtuT7PujcmVUCuPJlhnL/9NUqvMD2eyM5sxMaIlE4n7XML907tyNjcxHQjty4sZv66Z1xEok/xNW5n4uZSf+8sT5m++vVO58wkEu5sR09pd9w/rWyET2vReujiqygrSopn/zKZN5qMeirotKeTyolm7p/+X06Wvr51ue5Gt9BISwFjiGsLl6N6SrvylXDNTK70D4mX071pwtF88w6Jd/DG/1E1u26NOV0pQL71y3/8PJVOcHMzPTWkcCH2YGOaTTaS2RTN6f1fQvvvDK1bdnbO2JZCr1SeRfn05Pa1PTU0gXJBKW+ecnzlxvCGndhFQ1NRP8bcY1/vjS9bF1V26MwHwsVKiXa3etYVw1TNhYJ3TDjQCO42jJVMcez7J+t9YyJF37ISCEtahjGjxkGDr2DJZ31D8h5vUQJL5RPkXlUMM07u3qSGidICvkzzuSlmlZb0olrK9hD9v9JCrPC196JoPMAolFg6CV+PPj54YeyWecx8Vk2v1Q0rSfhFT18LnBmzBRyNalp5qrSuq7kiAsh4SFa7oZ9M0wzI+cPHOjZPo9V1kS1z4ICGEt4lhiCvZrSa2jol7qzPXJPk6nIGbVbWfUvcr7hO9MP97ZVXpggOu6ajplYStj7l1XvbRMXbPAbp6HzSSBlkraNknrvfVCcPt2sHYi7f3pTDb47KUbYxuvKqkKpYBXKBnV869c3WgbDEixAck0FGFFfEzJzbIsO9C1TyrcymWWsLZGIHoW2rqTzdo5dXyykz0NC8l779i5vu4zwM+eHVntGP5jqVTq/6AkVc5NZ3wNH2lVxNWZNIukMSjiNd9z0+CHp5DXAdX4SAg203w8GB5IATtODHzdK8C15kEjhXvNS9rWA11dnfcMDY9prscss48RySakrOLWqODCoIKAgkuVgsS0urtD60haeV1YYVbbtjUn6/74HXvW/11huFy3PwKzT1r797Upe3jq4sib9u9Y+wxe+vh7W1N7jx49v6ZzbffnQD4/Cj1Pfjx54XiBls6GVuTUc9mQsOIO9mPQFdkIRlz4fy5JLm2ZMOqTcJaXIqpcqnixVe+rdbZ3dbc2OT0D0wZIibHSksmklslknvx+//q3PiKnXcTQae/b+LPQ3r1t0969cOL6G7o6E09qgZegdMJBpVQ1DbKCpyUt6oPKz/4NEJalCAuZFIuEVBJd+jgLh4rvAiFqUVGkhJZMWFp3Z0obGSu/d5gSnWmavuO6h+/cvYHSobgVgoAYjrb4QPMUiGtj1/79jBMkLBwiTlMASlYzTkhWCJyTrGAyMOFkst/BoYMmuIIyGJYcMXMMdNwHPhYN1qWS1t6ZLGaKZL8yzFXTr15BooLLMugHMBRNKgW+It8y9TEcJGt4rvcRFCCEVQbFdg0Swmrxkb0+cf2XOzq73kgdFieEXF2jdEUJKQH6SVWQrNjtZDKlpTPp38U58iUbthk/Ph7sN6zg/xudSGvD4xkq6otcnnjyF0XRRTflkyC0IIJE1JG0QbqGNpMNp5xFhRTcZDNoj66988SFm5vv3LX+WkGUXLYxAuXnCW3c4XbqGs9hwjv+a9lsuN+ahOJSCoLjNDAFvVUll0p1aNPp6adTweSflEszPO48oFn+4yOTmR+6enOshKyYhzWpf/jDuuf6x2aV/qNRaPG/1d0gUXWCA0uu7GhMmkqmerEc8KOVU0lMuyFQ+Ylut562YX9Sncmf7Ojo3BDZWbGLtMkiUVXSWTFNuMqWuYG530f7+/tnGFboxsfdd9mm8XdDo9O7rg6NFq0CFqZr5DWlK9qV0fZqGvZchSuPlevB2VmG/hOV4yWm3RAQwmrhEcW64qu4ykfJho52Vp3J8quBYQooqWDKADftBd6HD+5efyoKj/zR8ew/hWXY56/cnFh7a3RCTTGjuMX0SVB9qzu1qfQM+jO3dBW1g6uVSHv/qVNX10Vh4rc3AkJYLTy+WA/8ou9kJjo7bOh+DLVFZ64TEbCyBktxI5PJZj56R//Gx+NdH5vM4vuI+p8NXh9LjU1iw3EZhXc8TyPuuV9wDaaCfBjTM06N0hVWQmHBDzvSDZ5tvqYR7ZAymh8BIazmH6OKLbzv0KZvJEz3ZzEFnEolaEtV2XEaCLKadrIz//TQnk1/EU85NuH8th8Yf4j9gMZUOrNkZEVZCnsbtTU9KW18GqcKFyjh420sd2+j33pg3F8uTsLaDwEhrBYf04O7N/2t7/o/C2FoGnsIy/YGlvAwSfCvZzLOe+8oR1ZT3u/5uvHJC9dGtJlMrfqjslXVHwjpat2aLi2rjFFLjUSrFUjlO0juddXSSXx7ICCE1QbjiHO0/hofbPgwpnDTOR2V6hWNQqGUx34890noet5yaO+Gko3Y45PO7/uB/lvnrwxrWdha1absbgxo1FWtwplXqYSJY5Nn5lU3bLHQmGA/yko0plVSSjMjIITVzKNTR9sO7dv8RSeb/T9BWmMkKv4D+YzBXuljV7yxd+zfte6VeHGKrHTz4+cv38JWmyUmKzSGG5z7VndoE7kz3uPtq+Welvhwm39weVjOyaoFsBZPI4TV4gNY2Pw79mz8KyebeRIH+VEZTaX0sf27+v794TKmCxNTzr/2NOPj5wZBVjjdYSklq6jN69dyKuhqmWztivYob+RTSkPbe/xMdlMUJn77IiCE1W5jq+s4dYEO6mzsYAmvi/+CrH7LDYxPcBq4HGTFVcG1ULLT5orS1ULIkoSFI2cMHKG8obiXcteOCAhhtdmo6gaOh4EWWlkyYU9gvHswXfgV19d/7+LVkSWfBrItJJhObL/p7elQR8fUZnEV70XxPc01sM+xrzhU7toRgZIHuh07uZL6xA3LBaYB+Ar8rBsfz34YX1j+D5eu317QNGy2xPquSE4mDuXb2IujY2AgytNE67RiKFshzuwCR5s9ZSMlsK0QEMJqq+GkBKOF5yFzRoidK5BoFCeMjM/8mG+a//Xy0Li55KYLBRiTrGjwOQ1br4VMBQuKVJeQKVPxMLlvPwSEsNpsTEECmBLSgbHUpwD1YGwse59l2p+9fmuig4fiNZIowrqq/6Xeqm9Vh9JbjcOKvqFtACX7gV8kTVZvkaRoRQSEsFpx1OZoM2iKxxuHLtDcsZlgLzYZfv7m7XSv+r7fIm234XSP/8o5ktWqzqSyZr89PoXPYDTYkZvziw0NLluKayoEyq4iNVULpTF1IaDjHHZmoAW4aep9geN8fiLt998cGYdtVp7K6iqzXGJFUCAi7jdkuapsBJKcPBwgyP8YRyV7B04Q3dDbpY3jg6gupoMNla5U41BbUN9n0sr1ScKaHwEhrOYfo7paCAW0WiWknihhW/0Tabf/6tDtxpIVSIhGnz1dSXUkDL8fSHKi4/lWPId9Kp3Vxqegp8J/m9f14D6DQ/nmb281FwgkZ1Dj7bnSSFx7ICCE1R7jmO8FJJr8jCvjeNrIxFjDJBpKVaSlXhwDw384MyucBoLAGEfHI5ptO6n1YAq4FjorH9IWjUOnFlF3pj62aui3whbI33ZGQAir/UY3XCVEvzgdw/8NcSyGUhSlpVWQrFg2p39xp0JYLyIohaXxdZ2FGofG6yi85/QS32F0Asu8URgu1+2JgCjd22xcsVElPC85169Gaa1YTkRWJKpSqooBiQQzONvq9sRULKKxtzzAEJw1api2EFZjoW3K0oSwmnJY5tcoSD09HanEDztubnfO/IopyUWC6sUmZUpW5aSqkgwgK04DxxaZrFivacCaIdAuH9zaM1rSDgloOwSEsNpoSMenvU93dXb+EE5taFivKElRqd67qrNmsqIF+yjMF/i56MV2JqadYKxXMDXM6+4Wu04pf/kQEMJaPuwbWvPticwj4Il/NnTrdl7JrqaDC5wTUle1GmdWWVCw1+JotjA6PgnThsIdQrXknF8arkJi/+R355dbcrUaArU9ha3WqxXW3tHR9C5dN//T9eEJ3aGdUwP7T0V7F86Mr0VW4mF6o2NTS/ilaB2HDmb8wA2+08AuS1FNjIAQVhMPTi1NgwRkGKbxRxMz3uaJSRzVUkumOtLwo6Zc7aOkVdEhynN9NQ1cyuNqeEqD67mX9TXGyxXbJhFthYAQVosP58S0909czfqJqzdGODVqaG/IUbCWr2p0yukfp4FUtDfeir1yl8IPUGjPHFy/fqJyKolpJwSEsFp4NEfT6Z3YBvOp8MvMc0hAi9hHNQ1cBrJil5TUZxhfXsTuSdFNhoAQVpMNSD3NMTzzU1PZYAM/ProYkg3UV5rHT8lXmA7SwnwEq4FLLVkRI04HM+n0LdvzvlEPZpK2tREQwmrR8ZucCd7hePr7rw2N5PfxLUZXON1zHKz4kb0KnIttP6Njk8tyaimbwXPrsW/yq3v3bhoqaJZctjkCQlgtOMCYCnU4GedTI+NpQ32XbxH7QOmKG5nzdIWZJz8HNkKygqI9TmSL2JSiovGVn0A39c8WBcpN2yMghNWCQ4zPc0HRbr6GEs6chJFnmfl3knZO4/hmII1B6fiFG9br0s6qAeXPp2WUrhzHeXH/jr6n5pNf8rQuAkJYLTZ2kK7Wul7w6zeGx9DyUsZovOodOizosTg1TM9k1Wogpa7lIisOF+w48E/7E5B1Y/cgtdizsBKbK6c1tNioT6X9n3MDcyePOo7OoJqrC6S0+ZIYV+GSOHxvc18PJCxXG4ed13I727axqTp9yk9rX1jutkj9S4+ASFhLj/m8axwdDdbgELxfGsLpoZyqVXPVU1QugVJUV0dC27p+FaaBWWxknq6ceAljTNMiAf/BoUMbJpewWqmqSRAQCatJBqKWZpgJ731Zx9pJM4aK0hXe5vlKVFEbKFlxs3PvqpSSqpbzKztRm+gnEkktnU6/2GFMfa4wXK5XDgJCWC0y1iAR6/Z49iOjY7C5qkG6mk+3SFQGlEP8FFdnygrNFqBsn1OxP5+K5pGHbcBhqhT8fqu/v39mHkVIljZAQAirRQYx7Wj3Zj3tddQjVVJ4l50CMjHe8mqOTJCCvmoTyIrENXx7Uinbm4Gs2PZUqkObnp76i0N7N36tWl8kvn0RaGnCGhgILKPn3B3+xKVXDh8+nPseX3sOlpt13+P4uonv71WeDqLr1ampFB8S1JrulNaHc9rTMxltcpofOeWns0rTLkeIZUHRnpm5YibMf7kc9UudzYNAyyrd8ZLpWvfgQT8w+oyevXeo++bBtaEtQd9s1/ffRsV3I6eDJCp+nourgH04UZQnhIYfWm1o8xdUGCU8/E/bil89sH3dlQUVJplbHoGWJaxnXri2HTvd1nEEcCBS3z++MLi75UejQgcmJjL92ax/gNJPo6QekhVXAbdvXI3D+XQ1Bcxiu02zTAEjKFIdHTQS/S8Hd2/4YhQm/spFoCUJ6+mnL651gkwRQRmBt33gO+c3teNQYin/oG6aKX5rcKEukqqoWN+Ij5vy81v8UATDG0WGC21jlJ96K6wKPpWd8H8jChN/ZSPQcoR1+vTppJPS7iw3bIZl7n/++eFV5eJaOczX9Z2YvM1LPxWpocBHKv8qHHdMqSphGUqqahaThfj40ITBcbLnsDj6oXvu2bS4n96JVy73TYtASxHWo48GxrUx+5Cu+XY5RH3PMzLGxF0ktXLxrRoGNVPPfNtOolIrgElLGYH2wbZqcipdIFVFlDbfGhqfj9bskCaHHS/7gTt3r73Y+BqkxFZFoKUI6/C7Lu/Bl1jmlKB8PUhcHjHufuyxx/g5lbZw+BL7bX4EoiZqyS0T0uM0j1+82QSl+ua+bhxj7GjD2LicwWkLzaarigbKsmDJ7gcTmezMBw/t3ixntUfAiK8QaBmzhq8/f26j77pbaxo3w+jetPf1B5D2RE3pmzyR4/nH+Mti4Wx1dUrCHO0lSVGqskFUnakkpn6mhu086jgYHkWTW3Wbo4Tli6L5gqYHE47vfeDufVv+YflaIjU3KwItIWEdO3a9Szc0ElDNDqcLbHjmxas7a87QxAnX9ljfxcr+Mzs29ykpi1O8iJjoR/cm5o7dnUl89LRLW93dyWmVIip+Kp7pmlWqIvQ8Mga9Gslm3Efu3LX+K008HNK0ZUSgplnGMrZPGxgYsIKeXa/TA61jPu0w0+7xBx/cd3M+eZspD0wbDgWm+RXP13cODY/jWGKuGAb48jG+agNpilbqlKZoWDqDY2AyjtNUlupzYZlKpXgaxIVMNv0zd+/d+uxcaSVuZSPQ/IT13TN34QRvZW81n6HSDdMLUqmjh9tgd//Fi8OHEl3JL3Z2dh3MzGA7XU664llVWRz/QhLjNYmsmaWp/DjCjqIDdlaZTOZZ1/A+fGj7hjP5OLkQBMog0NSE9cSRszuswNhdpt31BRnazM3U9IuPHDrUuG+419eChqU+cvzqjp7u5P9KJpMPpqc51Zv9QntLkFQBEqZluVCw/7nhaP9i376+8YIouRQEyiLQtIQ1cPT8GjOw7vE8tyFtxBrb2MBXdh579FF99g0vC0nzB548ebNHT2l/aFmJj1BPBYyav9EFLaQ+jdPAVNL8/pZ13a8qiJLLOhAAjvrTRy/d0enbF+69d0tzHFhWR/vnk7Rple6mp+9uFFkRGF8LVj/08IUN8wGp2fIcPLh+4sCu9R+F3ucj0MLf4vaVVnChqYWmdaQS2jpY2vd0djh86Vqh7c3Yxm8dudTPxaW0lrn7yJEjZW0Tm7HdC2lT0xKW1xecgHE3FDWNcb7uDh6+r/96Y0prjlIO7ur7TOD5b3ayzt9ylY0Gl83qKFXZsCXrXdOlrV3djf2LBr556JOshLDmMWhPPXV6vav5O5jVxYLUhNl3iIbV8yiqpbI0bQcP85C2Xu0l3dczC0XUN4Pzb71339mFltOM+Q/0rzu5f2fvu1zH+QDOt3uZ0pbVRMRFouJK5qqeTkhVqyBdtdUmhGV5JI4cudrpd5kHiyp3tTU/8s6r+4rC2vCmaQmLWJO0Ep65INJK2tbpt75298U2HLuiLh3oX/95L+0/kHUyvwTieiUJHVEimVzy1UKeWMqv2pCoKEVFRNXT1aHawnBx80eAZj7TwcxdAc5Gi5fiaNnNT37nCk4xaV/X1IRF2B94YHt63qQVaCcfePX2K+07fMU9U7qtHev+xE/7r3cc70O+6w1gxuV0dHZiusgvJS/O7IskRXLs6KCxqj+B26t9a3uUREWi4plbQlTFYzXvu+7tB3EIUGel/L6e3TNw5NS8zYAqldss4YvzBC9C7559drAja3qvDoyg6pwCP+KBZaVOPPjazS1vMLpQKE9fuPnawDB+EqehPwzWuAuSl8LPg90WVxhJJPWQCUmPBAWTBEz1TFUGpqO3wYYvIPgr2az35a2b1/50V6f1e1NTlVcvEzB0xRekj67usu5FmS2/crvQcaol/zeeObfTSOj91dIq28PxiaOHDx9quy8LtQxhcZBqIS0Dhkl2l/3yA4e2j1Qb2JUUD1Iyz1waOQib0vsxKXsAFvH3wMB0JySwtZC+DBPTN5BOCEnhrI1BuKe9l6tIzsVCiD6E0DOabrwI2elZ09aP7N3aNxjheXvK+a1OENa0EFYEyYL9rz072Ju03ZpNQKj7Xd899cKhNrA9LASvZTY/s9GcHoK0XsrakLS8UklLxyl+/rj+/Qfu2367sJNyTS7SuZfneO7ffweBGScu3NwAqWgrTvTc5jjBZmw87tMCfRXYKQWOgula4OiBOQUZ7DZuhrAGdQXxV0zPuCaGnkv3VPGHOpPw7+QPR62OM5HhdNddGOeX2kmCbSnC4mDlSStVTFr4eLljdHV+702vWz9R66Cu5HS5h5hmHvz3QiOxwJTRo2BGgY06dm7OVhewYGAY6s75oD+ZDs4JPY9JyqSCQ7ABqftd5VFM3/j2Ja4mtsWpJQSq6ZXu5UZTKeJnsHpohiYPRqBn04nkS2+CQWW59BK2dAjwS0Y4IHDz2ERWG8Gnwm7iK9W3sFmbvrqGPzw6gW8eTmvTM07XmTPX28KYd7EQ3rjnvv1QFHbPt3zT9DcMPHd+13zzN1s+/hC2rKOo7NjeQdsxT5LEWrYjbdLw05eHtwWe9jl0542u62HZHZIVpalY/yIlP5X3MHYddLLZfy4fmYiBhNuB509vw+rG3tKY+kOwGHLi7W/cS91jS7v4s9TSnZHGLx8CICH9lXNDX+zpWfXuycnaBV2e3e567nAm4973qv0bzy1fD5qr5oEB7KXt0u7B3Loh7yhWVfypbOalh9+wr6U3mbfklLC5Hi1pDRE4ef7Wj+EEiZ+amqpvJT2bzWjJRLIPR3n9riA5i4DZg720DSIrlsrvHXSZ9p7ZGlrzSgirNcetqVp9/vz5FJTqj6JRejTdq6eBMzNpHP9s//QrF4bvrydfO6f1JrCX1mvcXlo98Kembjotr3wXwmrnp36J+pYNeh5JdqRem83O77gxkpxtW3bgOZ/g1HKJmt3U1Rw+3D+zrc89aunagnWzpq6PdxujLz388L4F78tdbtCEsJZ7BFq8/sHBoMPX/I9hyrGgnuDUUZzrnnz7yQu3HlxQQW2Ued++fZmJ1e5LoPB5k5ZpWCPXz+08du+99zrtAI0QVjuM4jL2YcIZeh+2+9wF49MFtYJSlgmHE0g/JlLWLJQPg7RmhtyXsJ18eja0tivsXhj6xy9ve/mRR5TRcG2ZmjyViN9NPkDN3Dz1FW5z9XM4i+s1ME1YcFNpUIrVLHzJzHnwjl0bn1twgW1UwPHjxxPXpztejR0HFTc+F3YXRwxdfdM9W08D0zrs4wtLaM5rkbCac1xaolWOvurhZIPIih0OdVm2haNTfqUlAFjCRnJP4HBn+iUqz6tVa2nGpTe/etsP2o2s2G8hrGqjL/FlEQC5GHghfplSUSMdvwaEA/9+4vjpa3c2stx2KIsfUek2dr+EuXNF2xEjSJx98w/tbFt7NiGsdniSl6EPp84O3W/Z1oPzXRms1GRKWdCJdeCIlJ+vlGYlh997r+70+EPH8NHJEtLCauCph+7bmj81ox1xEsJqx1Fdij4Zxi9AT2KSYBrtslgxhOD2gWOyz7AstFzx6zFHj1mGobYUYAgC9cHge3ddK5uhjQKFsNpoMJeqK6+8cm0X6noXiWUxHA8WxAdWNyQM45HFKL8dyiRpueM7jllmMGpnjO+1w9fNaxmXxiogaqlR0jQdAkeOBPjczrnOiQ6jw88ESSOA6KT7iQzOHEvavu1pZsLQg4QPP/DdZG9Xx/vWrOr+mfR03SvtNffdxleAQIgvTzjBT0w409Mpu2faufZy+vDhw5WPMa25dEnYqggIYbXqyNXY7i/jCyvdfmaVb5hdVsLp9LJGp43j1/1A7/RdvdMwPRzEboRnLVHe9vEvL3eXBOB4ZMta22H+TiqV2LJQ26u5u6Bju44Z3J7O/Lvp6cwPmBanOwQ4uNHRTWMK21bSvh1Mm642nTWCtKkH07rnTE72aOO0XZq7bIltVQSEsFp15HLthg5J/+aJE12m3tVjOPYq1/dW4cTjHnwMYhXOce8xDd3y/PJW6OpMdsTRVy4iK/rKMR/jwvz825VIHFzT3fkx13UW/dnhRy3GJyeeHEs7n1XNibUPFvY6vtGDw5vV9w0Vofn81qGhZfDhi3HX8SfQ/3HPMse9CWcCX0gel2OIFJIt+2fRH7qWRaYJG85NxldGzV4tGayFSLQ24+q9ULyu9gJfMU5ELTn6wUISTl03NHz1KzyiJLqmX657OLLdSJgoXTO7cBxyN172blier4YCvBsFdSNXV2dC35tKJrbzfPfFdjwvC/qs9MSMxxNRsSqmT6LhUDQHE+jUBE7UnATXTuLsrRn01K2l/x6+qItiR3TNG8V59KNB0DGSfNXGUXwJY2Gm+osNhpSvEBDCasIHgVLTt75/aQ0MnXpBNb2QgNYEntfr4wu/nBYpKQLtxtdwAh0SBX3VDe7nM/Ha5vf1Fb/CURS2bCTAWWuxR229qRsbQQQbUed61LfW14JVKKsTJ5sk8WUcHbtlNANyTOhgcmAGKH7p3m1FWpqtuZCu+LByVdKHVMjpKEQrBwIW9tnpXOIH+QTDSH/D9f0bmCLewDn1I4HmwtAypPDZ/oe9oXKf/aMPsWxSs/RR13FHrURiZE1gDR86tKHEdCDMKX+XCwEhrOVCvqBeHNaW6ui11/mWDtLQ1kEiWodXE4rwYgepAPssTPCMOjIdAk94TZ8pMZjch8HjDorGFUTUAwlkh64be0A9/ZCatiDZWtOyE7ClQmIdJICJFYhA+TRV4Fo5/QIHiUvrTEbkVRCxiJfsSBbfYk87OTExXxdazY5yUgiRKfpHQ1YSkONmAZY+gV4NIeVFfCXoLNA5h/Plb5LzWAyzF+IVXdNnvO/6GcsyhjC1vmWZ7s2pO3fdOqzriy9asnJxZREoerDLppDAhiIAEtCfO3F5rW0a6z1PX4/nf53nG5RqqrpieSnULEVh8cx4E7ugH78H8tG9eP/24oVezY+pkpA8b/abhPF8le75BqdsXUtaFeaTlTI2IByEoU1l8oq1mkokcZHElIRoWmpejMMCMyCvQXyy7JjjuUcgOl4tLCzCMpTHgFpcgkViX/dH/ax2Szf8m2Yqc/MN+1r7BM/C/rfCtRDWEozSkbMjq7NTY5t13dqE6dhG3wsSqlp+C9DDi0ifLrqmT1f6BgUaPjiHN0lJAGAfvpWcI4XjiHIMF6ocO/EjmMa9HeelQ1LT1PRpoce/sJwOTCQtc+kfGQp6Uxl+9JWtmL+jNEaJ0gKBgbsygR58B4sHfwV5aliVWg3vCHv6ymHcdG868IzrVsK6pnd71+/dsmXxbD3m3/W2ybn0T1/bQFe5I8euX+9ybuqbXMPbDA7ZCKV4uMOecyz+9OfmWvj9x9zEw6JW+JuOX298WhE6qtwLEV3TL1tb/AWj7sqwfqaro/sdmcyM+vBp2XzzDEzaBiQsNH+e+eeTjQ+ohwqnG0BYhfVzNYKrkOmpyauYYH8KvD8G6RPBszrC6Jq+ystl0ghzXEZjR5+O4+iZwTh+eG7Yqa5rq/3hGzzTSkXKn4YgIITVABjBP+ZzP7i8ydasrZCetuCHvIvFRs92SEdlpnCYE2LOQi12OA7RNf1yjrphHIyE9yOXPnfNMDg70DpdTf8DWDKs5rRvMVwChAWrUgh21HzllD0NrigqlxKVC7bKQuOOWeGiuI7OTkhb6T8C/Xw3xkel9cXxj6eIxiY3Hhx3X9dHsWJwDaa3l1+zd9Mt/F4tUk/ijWnP+/DBb8++LWqvnh0c7NDGta0pO7kl6zpb8AJzEUr91kYEFdeBRCt69Nm4+AsSl6jwjVGckY6VwPwUpLhLURx9xliWvxFHi/w+zB0SWCnLsVpxnoXesSI2ngp4zmRJXPgf/0IleGH51R6uwjeX5MR76qtITh7+8N9Cp4GF7Sm8Zl1s35pVXVomm/5c1vG+Wm284njHJeJq44/FjixUAld8w7uijW6+xo3MhW2S6+oIVHumqpewglJ87+LFtcFUcqur+1vxwPcZJqYPMOyhXw6GKI4+4/GwQpjCBhe+6XDIpFb06PM+np5hhS5eXzw9bLJ2pBLGv4Fe36BU4kA6IQGw8MUY6MJywVeqDs54Z69zrWdY7jI3G1ZtUiSV6zzDI3IqLLew/wu9jspl+yywrA1pEed5QceXPT3jBb/DLrA5ua5UHZ/4eMTbFx+fwvE3DJO8fANrjlctL7giJhRx9MrfR89R+VgJ1Y6currONuwd0FNsxwtV02mPlWGLy1TxlPHf6Hh8PH9xesvw9yRM+5PIRT2ZIgVKKZxWUY/PT8aTFPji0i3m4Ed1hDWV/7uY9bNGtiGqAyorJRWSqCgdkrQiR5KddrwPlsq8xfhG6efvx8dvtiQczDdmmPaldDBxSVYeZ3GJXxUMWzxq5d4fPz7Ym7X1HTAL2A7NqtJHEQ3qtCPjw3LoxB/v+OMZ5VVzR5aHWRuErYA+y4uu6fM+Xl9J/lh7bFvbY+vmv0bWos9tsXAWSLIiaSnyApHxJz6SbFSFuXTw8i86r5vVRW1m+6IHmUREAuI0lcREP5q2ztWPrO9/YK54xsXHI56+cePvj3qBfimZNS+J5FWMcrjptThsRd4dPX9+DcwEd5iQphwozfkCwJKaLv9ewHYKeicfSudwShcnJDBBOD3MTwGRO0cqLIj73jQTaejDBYaPHTBgJ/i5+HyYijd95sFhRzkzB7yL2IrCtGwezj9nOQVTUlfPwiicifnu5J0qHHd8mXHIG6ZD7JQqIk9kJK6QwAokMWRUhMaSeJ0vcfaiXNhs7PyuwpYV51Vh+EM/Pu2M9GckpyiOuZm2Wvtom+Y4me8xPbvIIujzPu6Wbvyt1ejL3U7Sv/v754ZHsORwaX3KGdwiJhO5pzY+Mivk/urVq52jTnIXlEc78LKu8qAMx/G8kHhyOicosz0ovM3IrIDKb15HSvDoOoqv+hMLYCOWI8ash0vmufryZVcqLz4u8fym3ov1xT/EVp4UDUTn4/iS0xW+sZTMojASmLqGp64iH4FRXJQ2TKj+lv7JVRTVxwQkm9APyaboGnGMzSVR6VR87ipsVT645ovOzi5tamb6zzB1/nqzjz+s9YetwLioZW5C8jq08K9+1IxS8yQsfF6ap1WL2BK8VOaJc6NbPcPrx7wJ++hmHQUPvOaQgMJ3ETtVlERDP0wVsQ19uPgcLQyt/Dc+p4jlL6k/1xa2qVyh5ApEzEoErm/DsPOTXV3de6anq36roFyRdYWVbVSshHJEMt98saIXfIu9koplYZL6m/hUz7kS/Jt0/PE8+Jj6X/Y6k+fv2tA1BKIvB/OC8WnGAmp5dpqx3XW36fjgYK/upXbhFd+BrRlqn16MfkrspkoC4hnirYjbUVWzs4rHx8uL3cerjwt0TA4RcBcsuX8Rn97q54okVsCKJJ9YkSvy1gJR4aOtnAr6OJP+L13d+BKBKMEzHhAfgDh6yzD+vqHjTDDvYpAxLqwEfVdbE9bpIEi6V27tdLP+LnzPrWS/XrRTnz5d4e79+LNY7r4kP+Z7Jv7z1LyPL0B4Tb+ci9cXLy+eJ54e8Rw//rqqcUR+HOrgYVprJbBl5E2w63oI64J7k8mUDZLGhmAXs19ucVkxP8gKQu4ptCxbMy2TW3KAGI4u1P207ztH3CDx/7bL+Cdse8h1Zy5ev7Dp8uHD7blJuy0J69TV8XW6l92Dl3cbLG6g98idbhDgdANcY1ZY9o2N4mpNr96GRf1Da3Wui0RW69F1bWslvp81LD2xDTOGu9DhQzBc7AcYfYlkAqo6A6ozqHNBYJTESGitTGShsp0qQSxT4AcoPJQw0LBlEPhBFakHDjoLvY+XgVIyg7WK77tG8n9pvpHXBbXL+OMBd7FN6KLu+uf27esbX9RHdIkLbxvCGhgYsDb3v2a7obt7YHakpKmYiqgE2ioqJbzIOszXcSov/DAzRRNehyJKvPx4+igv/ZLKEaCkoZxUFMYXE1I8f7Xyq/UHp9CkAlfbCF3NdlhS7IQguA0N2wiJYy1ktC5IISb1Okr5jSYruy2SGlYkIkKLSC3yy/WrUWGzSnjaTUX/QEhYQuNewLCdwBFKRkpOuAfr4sBnwwfDg6B0MHagORhBHNqHw5WxTwYav6lAt/42MBLfrYZXHO9w3Ftr/B0Hp0pY+tkD29ddAz5ln8NGjddSlNPyhHV8aKjbzAS7Dd3egRcvgRHJWyrHASw9Pyp+vlSxEluH0jWAGQF9VVZMpxHVRZ/xSKQU4PR5Xy0+/sLQZCFS9DN/XKtSeh5WrL2x+sMyZv+W67+vwz5eC7oDx12rm9pakNg639B68XL3Qh+2Bm94DySxHhg0daBHSQhiCbyyyMS9SDi8RhEHyYP1qD9qak0S4VGn5VYrSTRKEkKHWYYiHuQmCYb/YKYLqS+3H5LYckxJmz6qhSYJ5yNgzgtuclESpncBfN8Fj3lgJdCSGpHcGECoxrouMoHjzO+4evLLMB1VKxJV8Wyj8Q80Ix043jnTu32hlTdkh08Yn7UWcnio9Qs3pzZm0lN7LCOxIdIZxbuQ1+lAVFFxJB7aMeUIiPkiPRPjo2v6dPF4FVjHnxi/oQK0Az/bymf5uI7ayGLj6eM63nrbF5VNXzV7nv3HViQL3JAEaSV1z0iBNJIgJBCYkSKJYbdjEiSHw7a0BI5s6QBBbINUswMUsQ6E11UojZGccA9dcZDBdQY+TgyFTgkiEKYyIBvstAQzIRk8cBJ+A2j4gZFDFWAqjAp3V5IhQYYwwUJ57ByS0QINzMYK8FyrRxt3KNbXb2qG/UVNT5wDyCt6/A0boGbdqzPA4tD21SPquWihPy1FWHjQzYs3xnZkM95ePIZd8RccBx1xez/UPowp46I4+uVcLD9/8Plq0Gfy6Jp+uez5uqPyY+UtNN5DuVQc06drpv4bIDXsjtsMpdkOSC79QK4Xog3PzwF4IBNCBiIhpBSpoE8jioqWaM2KCRuOqwLXgIQItKIe0lCYD/lZjoqgGIo0+J++SsmMKA8eqQ21qHuUh2PfzQHN6vgG6vVK8GfmQhcbr3Yff+AEi3rtdCtNF8u/eIWD2ATXx4Mg0XH1Vr/hm7sDQw8PvyvTrriKWocEE0C6oM/kJRJHrAykgj6WGlq+JUifu6YfS6pu4/UVa6AgQcXKi78ApekhcWFBwMstEkTX9MvVHw+Lt2ex+4+Pg62CxgsHEwZbAdgWIJfA+ICkfDRYtyAwWWB7Ay8F8VT/KB0bOJ4Gx/CQfUKSwZGrJJs8iZHYgB0zMB+zk8hopQ8hEcEog2ERASIBAOL5fIrVIKLxXKtzKPZLgZUckvGf+/nH5HsK0+Uz3316zeAjj3D23Lwu90w0ZwNpiZ72UnvwfO/AXIFnXfLBxLOsHn6yiLqmr3oQ04LHX9hq6TFHI6txrlYWkHj98UT1lh8vryR/rIKq6aO204drdP8hRWF3itmLUw42QnW1CSTSA2IAIXkWOBYKLWw8wjVqNkEaFqjFwLQNJhWI4ZiFoiq6QX0SbsEo6HMoWVFCYprwjw6FP65BXCSoXJwiOwpnFK9A6yiWkQhRDwA9XAfpwLS/AqnqSKP7jwapquiznXFXMn6x8Yg/X/HySvLHKqiaPlZfvf0H6BloAM/v3tpzHkJwUx59Uxb4GE5Lfnt2ZGS16SX3+F5mq4llfegtwnaSR6J5EC8hPUV6IDaS6aDnoZ5DpYe6AtdgOr4pyhXLNPH0KKCo/DDP7N+S+mI6qHzbQr7AbdgW+iylWn0l5cf6E29ftfSN6L9lGl04x30tOtMHklmLhxpClW9BL4S1T+i2uNPRp+0FflD0AN9A9LHnmHGBBfJCE3QL9ALiguoJqiu+64gDzWGIIAlhzhaSDsMV/yjJi3BxyY9khP9BXBSzEMY/AFORGMmM1yyKZfmm+ZKuJf4uMHV1THEj+o+S864E7zYd/8Dliqp2MamvPbt9uw4dY/M4DnXTuMuXx/scK9iHLcbryzfKwvOJBSGNPl10Tb8WV0xYyMFymDdXXv46Kq+ueChJQI4WlSUqf8StOf5CNdXqr9afxe8/Gm6AoLAqGKyCGLSG350ACFzKM2FvaeOseEhFOsjItdQ2S6wYYmkOdl2+CfLBvmpIV55vYY2Qn6uAxAWC40zbhxSmWArcQj0TSIiSU37mx0kgVesgLereOSz8E5EWJa6Qzyh1hZEcO7xY4Ct9WLfNvwa+5xA2h6uGP6vMPxMsZ8WNf0Gf+cOCw9usq51a5+kNG9Sn1IjJsjoO0LI7EpVra/vxhPdFs7JyjYriohlbTAKGxO1C6oJEljseOLqmTxfPX66OucJK66OUNzuDjK7p05UIbGwX25I/vrj4BYrnD0uZ/Rtvfzz9fPsPIkgkbL0DZNMFRVEHFEY2ZCBTcwMLdfCsCCVN4SwpE9YG+ARNgD24IDHYSYB1yNCYDkLRFoC8oOUG40AKQx5IYyAmlQ6SF7dDoSof0hbJiApzqLs43aPc5UG+AvVQ/4T7nGQFQiJ5kdbAkmgH2Sz0FaWB4gLrad22v4nmuvPt/yzCc1+V4t0e4z93r8PYwDCvNANxLSthkai0jmCf5+jq6y6Y4SkjTfoKprgWufj9Dg3AozBmiK7pl3H8WDH3u0YfLY6u6c/HVS2vSvsxoygyTF2q/qNenEyjJ5NJPYGPRidME1M1/JYqwyoNq32Ihu4J0z5M+WA2DoqwEI9wfmEaEhQJzPNsKNOh0jJwrfRVJqbnNOrC6IGwQFzgHiKrpCuq2kE+FizrMXWE7IWCEKemg7hSiimOQchNIC3EchqpHlBO95TshQThkwF5TL9k+Mm/MZLGzVo3AlQdLzagDle1vCYd/wU9/5Z5ZcyZPnNow/J8ZHZZCGtsbKw3rdn7nIzTx42o0WfP1cPKuYJ6XPFs5q7p8zmKx5v8cdcxDeMPOR1fj+gh4X10TV/dukiC+nJPeLy8eH1hrtm/UVvpKxcrP2oL/dlcs1eQ9PCeo73wGcp+R2Xyvlp74vH19B9EkoA2CYKUlcQqJCQj6vkoyBjh/IurcJiy4Zxy2FMptRBO7sK3kClR0UYUZAX+wMqfC1ICiYHMYBsKSQsSFKaAUEqZLoiK00ASFsgpN0UEUWE6yOkiiArE6NmUb91OWwAAEuNJREFUszCNxA0c/uBoF04W86YOarWQAYjGmHBBEIkUiXEqib025hNmInWknv6zKo77Sh3/RvcfSx5Xl4O4yr5Y7NxiuEEQFT4uvs8yrF5VvosX28LLS185vsiRHkc9YPiJtrCbJIzHyx3gJdfpl80flZWPR6qIxJghus7xjSqj4E9UNn2VvN76Csqq6XIR+48OYEeGlcAaXhLfQwxNQcgQEI9IErOOxBUuCuDLz9Arm5iyOTaYy7Jty8hAb2VCm43ZmwnwQTbgFpAWyA4SGEKhaMdgYNpngKAcpeMCAfFjYGE4yAqco3RZ0LorUqOkxVkf6AgzvFBPFbISSsOUD+WRrWijpcwbmI4Gomj4yxAIv4bPVU+q9sfxk/EP36UlfP49N3vNWr/m9CZdX/zzjDDofAoW3XHVr9NPHdB8p2+uORl/mjFLUktMbBTtkSJbpLCRxYyD5OpJps/4+DJuvq5IIgoLqfi3pLzcRuloM7QSzKImsBSWG80LVKkxkSvOkFHaCjL5QvrPN9rwvaSVtEg2ICmQCNRQkGjwnlOpNktMxdds+GxcRFrIyCmhTQMEUJjl4qwtzPbAOVC8o0DUZroGiMmBpEUfRBZ4DvRUJC4/1GOpij1ML9XU0PJdFxIZGsOpJkkOQ0YdFh5CPodKl0WfRqQkVUhTIEf1iN4GkdJU4Rx/xsJfHkpfMv4cd+IAUJb1+YdkfSU7NXp6+/bti7qquKiEdfVq0Gl2TO2DonYzAcUTCv0slCB8FuGia/q8j7iAPl30aNIPHVKq55w+00MvjFLo05WmV8H5P9XLzydVF/H0xbGl9UGfjm226B98po2u6fO+0f3H9M7SbT1h+FoS00ybSmm+5/RZHxzbwWvVHtSvNuLRR4BKl0vPtHRhWh1SESUsNBkH0qjvNiAx4MA1JDBc4yBmTPmwJArJCFM+dA1SE5XsmFIqRTzKUrZYkMio78IUkauFoW6Mcbin1GWrOR8nqOEUEUQFmuK3ZdEw6NFg92s9j3XLp0CIsAuS8VdPkcKhCZ9/KAc81x/c3NdzFjy6KHZc0YPNh7VhDg9jYnh4co9n2dvx1nLalys7Rimx2xLGigfEJBQ0Xr149FkBVb04BQiTlPAFbTiDxRGKM1pJf5AgarPKG0sQu413N07hkCANO5m0fSebtCwziW5DqMISHTRMJCDF23inYbmsauNCHq+Vn1ta5dErzKN8psP/RiIXVpAegKJQ30Y06AQSEXdAIpdL0wbTNsLpoSIeCwRJHZYBpTusIFAIlPC0iqL5AxoCcmLPQkkLdITRCc0dSFqQD1A51g4pLOXmhZCwDMO2BpH9q6ZtDoU4oKQIy5yEynFnv+mzw+0+/q3Sf5yT4aYs89zq1alLIK7wYeQANcCpgW5AOaqIARzxcudrXrMTz+cuFAxBI1Rw06eLKz3xsnDikt+Mmr9mWBlXrbySeJAlTt8MXJImXHRNv0zx2GpWZ3r0KKqzXHlRHH26+fQf+mkbg56ADjppUuihMJl7BEhGtmnj+4Phj1lEUAzjaQcgJkzcqPPmlI/yjdJV8Trf/+hbeYyP0uMS0zSVF8SEaSELxkhR6a7IC1IVHkNMBWEkCljxYQ7YXgWKrDCHw2ohJDDKSkr5Tst3TANBp7DdgkTFKSOpxYMtV2i3hXQoJjwbBo3L4oibAajdXmSbCl01PEvi6x3PetMvwfi3cv+xHpPRk8GZvo6Oq5y5FvZlvtfqQZ5v5igfH7iRdHqrn/H24McyEb6ejCUxkCwqEATi8JDNKtWRIxI6wrLj+aOyQgIqLT/KTZ+OLYnCFGHE60PdSgzIgVmcfrbt5evjYkB97VeNyv8plx/UYoChElhYgB7KtD3PAUWRpejIVNzNAjNzyDuYRqnrMF5dIx4CkTrlAJQRps2FhZIX5lqYwfFLOygTBeSmkUhDEgNvIC7MR5ML6JhozoCpn+858G1utbH4j7BRT0Z9VlZzbTyOKJCKeCjkqYbkFBJh+DXCPVcKuXKIFURlm8WBoZSFOBCYmk6i33ioT+Kw1CegEMspcFfe+M8+rRySNum/YUwm9I7TPT04NWOBDg/nwtz16xMbEp3mPswIOuI6G7wBSlynz1pQWZEIP0smIcEEWN3QsfJDn+nj9FFSPh73wilgdE2f+eOumo4pPqWI2kI/LKu4RVXLq7H/kJopRUFhnkj4joNT9KC/BlZgAIVD1I+cwASVUBgCIsF1KEQxJLpGPKHGP5LYrAs5ikREnmJ61KF4K5cG1+REVS6HC1JauGroYYcOrLWUEp6MSF0UpoZgK5hV2dgEzeNLYbMBnRQZEUPnOwGMT6GOp57Kg/0WTCMYjnsQHpDmlJFTR5IcNt/alvV1PdF5NsKcLSpGG03L6QcjnWDpeIXqgFYb//A9wGi1+fMPDeqY7nae6uvT530KKp+JebkhHJyX6Fqz33X83tCgRr1d6gXBH+XnFtEwDmEVMBfAtbK7UvHxVTb1gGLQokbFVBZMDtUJHmT+dsPxmqSRU2nkrxkWxhfbOfEVwLov4sIaonSRr1qZy6vy8xliPbn+qPjYHxSm6mJwdB357DfaVtJ/BMLeW0/ayVQSR6TA5AB7h8kwmFeRrFBUSFYkJk7GsM+F5SuiCQmFBEriCskHYcxfEM9ozBjBS/yaKD//rBzndjD3BHswAcmqwFdhOWGugCw5owwpEt9sxMlVGWQEK4GlcAOi1XAcL6eLICfdcMFmNDnH7xdO/YTCHTkxM2B6EiSPbuXmHrZO5eJy4Iu6lfo2Gu8orFfA+PM9UMjnHpBIx9v+/Q9Wm8nMfcMTE1d7u7vP4Ec6fzy1wqOGP3xI63JHjgT2/rsy/boTbMP0pe78dVUWS5wjK0VUjIqNN3kA62ZYeIcfxofXDFNFUZBTT4W6m71mWBlXrb4yWSoEYWh0jVIUdJEmzA6o18mRDN7dCplCEkK8IiP4WRAU9OO8j5wimZB3SAhKYlJEphLkJCaSEP7PEdxsfVG5UWFxP6qPPngTlvBED6IWLN8dTPmg8ocFPPRXWBdlFWqqCEmLlhAgLRtKdLaAkpQNfRUM6DUQGOUiTimNEaT7FvRVw/F6K91XG4/mHf9KPaovvJ36jzfSS1mpc6mUdhnvhZL4a0GjZsKBKK+n0+kt0AHvztCAsIzjeeAeUKVPF1l101cBWCICxcGmcPalUeHRnyguIsJYej79fFnpKxdjrKhu+spVK69Ke+OW6SXlh7Xk/8b7D5umJKY6nUiQAEmp5ZKoD5Ay8kTFzcAsJIrL+ZREYCWAaU4ubXRNP8wfpuSuGubHMwCJhSuGPCiYJIMw5GV6xkfY0Wd+WoPiBAlEhvnzNluw3SKZYTkQHIQ5J1RQDg7Lw/QQGUIdFp4wcC9KgQ/7KkxjucEHROVmc3ZaCFfEjMxUvlPvBZ0WhT1Q1zG06hQKyGPA9qEh4bPRJuO/0p//WvoPyXpa77BPr9L1mn64QiJRT0vlP3jg1oyn0/th1dnN6VOkQyh8wVRuPpLUH9GHi+sckD4vLaj43NSHLwfv8cKjbGxdgc97JUpFpIRbpovKYHTUltkpHYkyEqNYf1gWfZU+Vn+JiMZERS4qKyTAMv1hmwoItLT/aL6OL9cn8A4mknhDkR5CUuh43ExhAXjnIQVxRQ9UwnU1JM73meHISINzlY/1Ir3jwNQBtui5IpU3K2mFZbEUEhgJiHlZhkqI8rws7hPFxBHlZ5romu1CGRSv2HyQEQiLPkwefJcSk2o0mU+F8Z46KswbKd8qvRUWiq7BsuoYlF/q+Jd839p4/KNnFHhw+Fbc819r/y3dHO7qsk9D2lLPBvEq59SLXC6CYSCq1OTk5F48g+FxLyQSvvyzhFK8taaYL1ACiYdkkSOg/HVO4irmAySLlR8+yHy5wnaWysTF7YmnRxdyecMXFDcxx3KjNCUEGUtb2r4Iixwh5qebxEG58v2Hkh0ERqlLp5kClNLkngLSyF8XExrZi089SYbFm9DRg1FCbEKyoxQE8sqFkTOgTwrDVIPCP/k8qpRcGrxMEXmxnpwjUeXbhjpgA2bBNsp0HPQWOiwNOnddw5YcNIdSFyzTlUKehEbrLDxDNn7osjCXPw5FO22qgPfKHn/pf8XxxxetvSvYlX8BxBVKCdGDmPPDhz0W+Oijjxof//jHt+Hh2oko/qKqFx4l0BJQmQIwS3RNn/fxZXqGFbq4nQzimI9tKFs+S1S1KJ9XoQkEfUQwtKg98fSzefMMwmx5F28/IqK2RLjM2b54/gX0H0v6+IiDZSVgHJogfYWNzDMUpCtsUkKg4pKIUJAsnNTlkjNWzfBCPMOhi8JAiCSqPBmyMFVQ1OdctQwLywNZ5cPCpDl80D6IhjzBASQF0sUeREpSJCyE4ceSpJXbEO2612AHepaTSRn/YrtEAD3n8xV/ntv4+S96nyGRO9gccQZmEPiBK3bRi5kPHcG+v2T32n2+53bxNY8oQyWIB0SR9OmqxMeTh5lm/8azx8srEbCQNSqTpUTX+eagwCiPqiWeQAXO/olHV2tPaYUFjWCxsQJjt7MV564K6iOB2Xj1adNGa3PqDMFl4XwSSnAQCUIibqFPlwtTwbiOkoSR+JvLx3KYv9BXaSrlLyifSegQBNMFTAWhiIeFArRZnoX+8Y2EzKhbnuNlYO9wFpZXkwoH5Kmj/6qOFTz+0n8+Y4Y/2pVIcJqY35+YJ6wjEN33ZzL9kPY3hWjx6Sv+RcByLIQAZZYQJSn2C944FRF/QkvjQ31XZDcV04GVPOGl+WdJEhVGbaNPV3d7Va7ZP83U/1ACgzTjkg4gjUFvHhGWkrPAPnnBLNeFSEKKfAbzOu9yBAUdVj6cZURpZuU3XOUILioD93x2IEnxxFGc9c6M+M93cHSNZVzHquBQDeMn4x898wQ2us7pgGvAbyU8/z5e5EupVEqtJirCgp4KHxVI7sbrQIYKHyKF3+yvIvEEX8FsQNk9qXwgBpgQwNo7p9OKrukzfdzF08+WTmYrV35YF+tU8bEpYImInGtLVH+8PkzZ8iQcVpjrawXCLOHH5uo/9JmWjbXHJMQcNhVW8bOklbsumnJw7Q+cgtVK2mJxAUNNKKncp54KHuzAwnjCE01B1UIHA1A80ik/IkdIfTj6mE8MXh2sSKZhdHUd+IcDykwFLj4eMv7Fv+il75c8/xEmeHaojD+jZ4LgbsPVVvO5iutg4oSAFCCiAqVp/jrUKRU8mzVexsube05ff3tiD0Q1wkP/ojrYgeiaftiheHsjLKL4GrudTxYvb0H9h94bpzeAwCD4cAqJf5SmlBjFH5D8ChVC1Q8KyIkrjtgbE64y4lqtINJHel5Hq4q4ZdsYzsWBWaU+rkFWtFzQbiNNnWciNbT/qD4+Hitq/FdE/3mWzmvQU+W4hZZPenQuRHRNfylcvfVjpUqz0Tj6dNE1/fm4euufTx1z5am3/hr6z6lj9A9ElneKwPJ3IYEVEpqKys0YFeUhoDBP4TV/+bjVIkfqKuu8/ixC/+tqR73111V4DYnrrb+G8a+h1tkk9dY/m7MxV7XUzwdP3ApBgCYG6Co+L6/+kcB4X0g0ERFFzwXjojBc5q8ZhqOKtWEoROmLEwSWBIHowVySyqSS5kIABEYhisRFEov8SgRWGD6K9OMgq8IwBIkTBBYXASGsxcW3pUoHgfF5iIiLPv9x+03kuLxMqaqsUj1KJL4gsFgICGEtFrJtUG6OwDhtJHHhqLOl+dBAG0AnXRAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBIGVhMD/D0fV/fpMMM+gAAAAAElFTkSuQmCC'
+ },
+ ...uni.$uv?.props?.noNetwork
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-no-network/components/uv-no-network/uv-no-network.vue b/ledApp/uni_modules/uv-no-network/components/uv-no-network/uv-no-network.vue
new file mode 100644
index 0000000..5f134fa
--- /dev/null
+++ b/ledApp/uni_modules/uv-no-network/components/uv-no-network/uv-no-network.vue
@@ -0,0 +1,222 @@
+<template>
+ <uv-overlay
+ :show="!isConnected"
+ :zIndex="zIndex"
+ @touchmove.stop.prevent="noop"
+ :customStyle="{
+ backgroundColor: '#fff',
+ display: 'flex',
+ justifyContent: 'center',
+ }"
+ >
+ <view
+ class="uv-no-network"
+ >
+ <uv-icon
+ :name="image"
+ size="150"
+ imgMode="widthFit"
+ class="uv-no-network__error-icon"
+ ></uv-icon>
+ <text class="uv-no-network__tips">{{tips}}</text>
+ <!-- 只有APP平台,才能跳转设置页,因为需要调用plus环境 -->
+ <!-- #ifdef APP-PLUS -->
+ <view class="uv-no-network__app">
+ <text class="uv-no-network__app__setting">请检查网络,或前往</text>
+ <text
+ class="uv-no-network__app__to-setting"
+ @tap="openSettings"
+ >设置</text>
+ </view>
+ <!-- #endif -->
+ <view class="uv-no-network__retry">
+ <uv-button
+ size="mini"
+ text="重试"
+ type="primary"
+ plain
+ @click="retry"
+ ></uv-button>
+ </view>
+ </view>
+ </uv-overlay>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+
+ /**
+ * noNetwork 无网络提示
+ * @description 该组件无需任何配置,引入即可,内部自动处理所有功能和事件。
+ * @tutorial https://www.uvui.cn/components/noNetwork.html
+ * @property {String} tips 没有网络时的提示语 (默认:'哎呀,网络信号丢失' )
+ * @property {String | Number} zIndex 组件的z-index值
+ * @property {String} image 无网络的图片提示,可用的src地址或base64图片
+ * @event {Function} retry 用户点击页面的"重试"按钮时触发
+ * @example <uv-no-network></uv-no-network>
+ */
+ export default {
+ name: "uv-no-network",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ isConnected: true, // 是否有网络连接
+ networkType: "none", // 网络类型
+ }
+ },
+ mounted() {
+ this.isIOS = (uni.getSystemInfoSync().platform === 'ios')
+ uni.onNetworkStatusChange((res) => {
+ this.isConnected = res.isConnected
+ this.networkType = res.networkType
+ this.emitEvent(this.networkType)
+ })
+ uni.getNetworkType({
+ success: (res) => {
+ this.networkType = res.networkType
+ this.emitEvent(this.networkType)
+ if (res.networkType == 'none') {
+ this.isConnected = false
+ } else {
+ this.isConnected = true
+ }
+ }
+ })
+ },
+ methods: {
+ retry() {
+ // 重新检查网络
+ uni.getNetworkType({
+ success: (res) => {
+ this.networkType = res.networkType
+ this.emitEvent(this.networkType)
+ if (res.networkType == 'none') {
+ this.$uv.toast('无网络连接')
+ this.isConnected = false
+ } else {
+ this.$uv.toast('网络已连接')
+ this.isConnected = true
+ }
+ }
+ })
+ this.$emit('retry')
+ },
+ // 发出事件给父组件
+ emitEvent(networkType) {
+ this.$emit(networkType === 'none' ? 'disconnected' : 'connected')
+ },
+ async openSettings() {
+ if (this.networkType == "none") {
+ this.openSystemSettings()
+ return
+ }
+ },
+ openAppSettings() {
+ this.gotoAppSetting()
+ },
+ openSystemSettings() {
+ // 以下方法来自5+范畴,如需深究,请自行查阅相关文档
+ // https://ask.dcloud.net.cn/docs/
+ if (this.isIOS) {
+ this.gotoiOSSetting()
+ } else {
+ this.gotoAndroidSetting()
+ }
+ },
+ network() {
+ var result = null
+ var cellularData = plus.ios.newObject("CTCellularData")
+ var state = cellularData.plusGetAttribute("restrictedState")
+ if (state == 0) {
+ result = null
+ } else if (state == 2) {
+ result = 1
+ } else if (state == 1) {
+ result = 2
+ }
+ plus.ios.deleteObject(cellularData)
+ return result
+ },
+ gotoAppSetting() {
+ if (this.isIOS) {
+ var UIApplication = plus.ios.import("UIApplication")
+ var application2 = UIApplication.sharedApplication()
+ var NSURL2 = plus.ios.import("NSURL")
+ var setting2 = NSURL2.URLWithString("app-settings:")
+ application2.openURL(setting2)
+ plus.ios.deleteObject(setting2)
+ plus.ios.deleteObject(NSURL2)
+ plus.ios.deleteObject(application2)
+ } else {
+ var Intent = plus.android.importClass("android.content.Intent")
+ var Settings = plus.android.importClass("android.provider.Settings")
+ var Uri = plus.android.importClass("android.net.Uri")
+ var mainActivity = plus.android.runtimeMainActivity()
+ var intent = new Intent()
+ intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ var uri = Uri.fromParts("package", mainActivity.getPackageName(), null)
+ intent.setData(uri)
+ mainActivity.startActivity(intent)
+ }
+ },
+ gotoiOSSetting() {
+ var UIApplication = plus.ios.import("UIApplication")
+ var application2 = UIApplication.sharedApplication()
+ var NSURL2 = plus.ios.import("NSURL")
+ var setting2 = NSURL2.URLWithString("App-prefs:root=General")
+ application2.openURL(setting2)
+ plus.ios.deleteObject(setting2)
+ plus.ios.deleteObject(NSURL2)
+ plus.ios.deleteObject(application2)
+ },
+ gotoAndroidSetting() {
+ var Intent = plus.android.importClass("android.content.Intent")
+ var Settings = plus.android.importClass("android.provider.Settings")
+ var mainActivity = plus.android.runtimeMainActivity()
+ var intent = new Intent(Settings.ACTION_SETTINGS)
+ mainActivity.startActivity(intent)
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-no-network {
+ @include flex(column);
+ justify-content: center;
+ align-items: center;
+ margin-top: -100px;
+
+ &__tips {
+ color: $uv-tips-color;
+ font-size: 14px;
+ margin-top: 15px;
+ }
+
+ &__app {
+ @include flex(row);
+ margin-top: 6px;
+
+ &__setting {
+ color: $uv-light-color;
+ font-size: 13px;
+ }
+
+ &__to-setting {
+ font-size: 13px;
+ color: $uv-primary;
+ margin-left: 3px;
+ }
+ }
+
+ &__retry {
+ @include flex(row);
+ justify-content: center;
+ margin-top: 15px;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-no-network/package.json b/ledApp/uni_modules/uv-no-network/package.json
new file mode 100644
index 0000000..cb48aa9
--- /dev/null
+++ b/ledApp/uni_modules/uv-no-network/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-no-network",
+ "displayName": "uv-no-network 无网络提示 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.1",
+ "description": "uv-no-network 该组件在没有任何网络的情况下,显示在内容上方,无需任何配置,引入即可,内部自动处理所有功能和事件。",
+ "keywords": [
+ "uv-no-network",
+ "uvui",
+ "uv-ui",
+ "network",
+ "无网络"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-overlay",
+ "uv-icon",
+ "uv-button"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-no-network/readme.md b/ledApp/uni_modules/uv-no-network/readme.md
new file mode 100644
index 0000000..0e4a1a2
--- /dev/null
+++ b/ledApp/uni_modules/uv-no-network/readme.md
@@ -0,0 +1,11 @@
+## NoNetwork 无网络提示
+
+> **组件名:uv-no-network**
+
+该组件在没有任何网络的情况下,显示在内容上方,无需任何配置,引入即可,内部自动处理所有功能和事件。
+
+### <a href="https://www.uvui.cn/components/noNetwork.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-notice-bar/changelog.md b/ledApp/uni_modules/uv-notice-bar/changelog.md
new file mode 100644
index 0000000..cdfec7d
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/changelog.md
@@ -0,0 +1,19 @@
+## 1.0.7(2023-10-13)
+1. unmounted兼容vue3
+## 1.0.6(2023-08-03)
+1. 竖向滚动时候增加change回调
+## 1.0.5(2023-07-21)
+1. 增加icon类型,支持设置false不显示图标
+2. 优化文档
+## 1.0.4(2023-07-03)
+1. 增加disableScroll 属性,禁止自动滚动
+2. 优化文档
+## 1.0.3(2023-06-04)
+1. 修复text传值为null报错的问题
+## 1.0.2(2023-05-30)
+1. 修复error报错的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-notice-bar 滚动通知
diff --git a/ledApp/uni_modules/uv-notice-bar/components/uv-column-notice/props.js b/ledApp/uni_modules/uv-notice-bar/components/uv-column-notice/props.js
new file mode 100644
index 0000000..d93917c
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/components/uv-column-notice/props.js
@@ -0,0 +1,61 @@
+export default {
+ props: {
+ // 显示的内容,字符串
+ text: {
+ type: [Array],
+ default: ''
+ },
+ // 是否显示左侧的音量图标
+ icon: {
+ type: [String, Boolean, null],
+ default: 'volume'
+ },
+ // 通告模式,link-显示右箭头,closable-显示右侧关闭图标
+ mode: {
+ type: String,
+ default: ''
+ },
+ // 文字颜色,各图标也会使用文字颜色
+ color: {
+ type: String,
+ default: '#f9ae3d'
+ },
+ // 背景颜色
+ bgColor: {
+ type: String,
+ default: '#fdf6ec'
+ },
+ // 字体大小,单位px
+ fontSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度
+ speed: {
+ type: [String, Number],
+ default: 80
+ },
+ // direction = row时,是否使用步进形式滚动
+ step: {
+ type: Boolean,
+ default: false
+ },
+ // 滚动一个周期的时间长,单位ms
+ duration: {
+ type: [String, Number],
+ default: 1500
+ },
+ // 是否禁止用手滑动切换,仅`direction="column"生效`
+ // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
+ disableTouch: {
+ type: Boolean,
+ default: true
+ },
+ // 是否禁止滚动,仅`direction="column"生效`
+ disableScroll: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.columnNotice
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-notice-bar/components/uv-column-notice/uv-column-notice.vue b/ledApp/uni_modules/uv-notice-bar/components/uv-column-notice/uv-column-notice.vue
new file mode 100644
index 0000000..73c6bfc
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/components/uv-column-notice/uv-column-notice.vue
@@ -0,0 +1,176 @@
+<template>
+ <view
+ class="uv-notice"
+ @tap="clickHandler"
+ >
+ <slot name="icon">
+ <view
+ class="uv-notice__left-icon"
+ v-if="icon"
+ >
+ <uv-icon
+ :name="icon"
+ :color="color"
+ size="19"
+ ></uv-icon>
+ </view>
+ </slot>
+ <swiper
+ :disable-touch="disableTouch"
+ :vertical="step ? false : true"
+ circular
+ :interval="duration"
+ :autoplay="!disableScroll"
+ class="uv-notice__swiper"
+ :style="[swiperStyle]"
+ @change="noticeChange"
+ >
+ <swiper-item
+ v-for="(item, index) in text"
+ :key="index"
+ class="uv-notice__swiper__item"
+ >
+ <text
+ class="uv-notice__swiper__item__text uv-line-1"
+ :style="[textStyle]"
+ >{{ item }}</text>
+ </swiper-item>
+ </swiper>
+ <view
+ class="uv-notice__right-icon"
+ v-if="['link', 'closable'].includes(mode)"
+ >
+ <uv-icon
+ v-if="mode === 'link'"
+ name="arrow-right"
+ :size="17"
+ :color="color"
+ ></uv-icon>
+ <uv-icon
+ v-if="mode === 'closable'"
+ name="close"
+ :size="16"
+ :color="color"
+ @click="close"
+ ></uv-icon>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * ColumnNotice 滚动通知中的垂直滚动 内部组件
+ * @description 该组件用于滚动通告场景,是其中的垂直滚动方式
+ * @tutorial https://www.uvui.cn/components/noticeBar.html
+ * @property {Array} text 显示的内容,字符串
+ * @property {String} icon 是否显示左侧的音量图标 ( 默认 'volume' )
+ * @property {String} mode 通告模式,link-显示右箭头,closable-显示右侧关闭图标
+ * @property {String} color 文字颜色,各图标也会使用文字颜色 ( 默认 '#f9ae3d' )
+ * @property {String} bgColor 背景颜色 ( 默认 '#fdf6ec' )
+ * @property {String | Number} fontSize 字体大小,单位px ( 默认 14 )
+ * @property {String | Number} speed 水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度 ( 默认 80 )
+ * @property {Boolean} step direction = row时,是否使用步进形式滚动 ( 默认 false )
+ * @property {String | Number} duration 滚动一个周期的时间长,单位ms ( 默认 1500 )
+ * @property {Boolean} disableTouch 是否禁止用手滑动切换 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 ( 默认 true )
+ * @example
+ */
+ export default {
+ emits: ['click','close','change'],
+ mixins: [mpMixin, mixin, props],
+ watch: {
+ text: {
+ immediate: true,
+ handler(newValue, oldValue) {
+ if(!this.$uv.test.array(newValue)) {
+ this.$uv.error('noticebar组件direction为column时,要求text参数为数组形式')
+ }
+ }
+ }
+ },
+ computed: {
+ // 文字内容的样式
+ textStyle() {
+ let style = {}
+ style.color = this.color
+ style.fontSize = this.$uv.addUnit(this.fontSize)
+ return style
+ },
+ // 垂直或者水平滚动
+ vertical() {
+ if (this.mode == 'horizontal') return false
+ else return true
+ },
+ // NVUE中的swiper在css中样式不生效
+ swiperStyle(){
+ const style = {};
+ // #ifdef APP-NVUE
+ style.flex = 1;
+ style.height = '16px';
+ // #endif
+ return style;
+ }
+ },
+ data() {
+ return {
+ index:0
+ }
+ },
+ methods: {
+ noticeChange(e){
+ this.index = e.detail.current
+ this.$emit('change', this.index);
+ },
+ // 点击通告栏
+ clickHandler() {
+ this.$emit('click', this.index)
+ },
+ // 点击关闭按钮
+ close() {
+ this.$emit('close')
+ }
+ }
+ };
+</script>
+
+<style lang="scss" scoped>
+ $show-lines: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-notice {
+ @include flex;
+ align-items: center;
+ justify-content: space-between;
+
+ &__left-icon {
+ align-items: center;
+ margin-right: 5px;
+ }
+
+ &__right-icon {
+ margin-left: 5px;
+ align-items: center;
+ }
+
+ &__swiper {
+ height: 16px;
+ @include flex;
+ align-items: center;
+ flex: 1;
+
+ &__item {
+ @include flex;
+ align-items: center;
+ overflow: hidden;
+
+ &__text {
+ font-size: 14px;
+ color: $uv-warning;
+ }
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-notice-bar/components/uv-notice-bar/props.js b/ledApp/uni_modules/uv-notice-bar/components/uv-notice-bar/props.js
new file mode 100644
index 0000000..3fe0b44
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/components/uv-notice-bar/props.js
@@ -0,0 +1,76 @@
+export default {
+ props: {
+ // 显示的内容,数组
+ text: {
+ type: [Array, String],
+ default: () => []
+ },
+ // 通告滚动模式,row-横向滚动,column-竖向滚动
+ direction: {
+ type: String,
+ default: 'row'
+ },
+ // direction = row时,是否使用步进形式滚动
+ step: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示左侧的音量图标
+ icon: {
+ type: [String, Boolean, null],
+ default: 'volume'
+ },
+ // 通告模式,link-显示右箭头,closable-显示右侧关闭图标
+ mode: {
+ type: String,
+ default: ''
+ },
+ // 文字颜色,各图标也会使用文字颜色
+ color: {
+ type: String,
+ default: '#f9ae3d'
+ },
+ // 背景颜色
+ bgColor: {
+ type: String,
+ default: '#fdf6ec'
+ },
+ // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度
+ speed: {
+ type: [String, Number],
+ default: 80
+ },
+ // 字体大小
+ fontSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 滚动一个周期的时间长,单位ms
+ duration: {
+ type: [String, Number],
+ default: 2000
+ },
+ // 跳转的页面路径
+ url: {
+ type: String,
+ default: ''
+ },
+ // 页面跳转的类型
+ linkType: {
+ type: String,
+ default: 'navigateTo'
+ },
+ // 是否禁止用手滑动切换
+ // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
+ disableTouch: {
+ type: Boolean,
+ default: true
+ },
+ // 是否禁止滚动,仅`direction="column"生效`
+ disableScroll: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.noticeBar
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-notice-bar/components/uv-notice-bar/uv-notice-bar.vue b/ledApp/uni_modules/uv-notice-bar/components/uv-notice-bar/uv-notice-bar.vue
new file mode 100644
index 0000000..14dd7c5
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/components/uv-notice-bar/uv-notice-bar.vue
@@ -0,0 +1,110 @@
+<template>
+ <view
+ class="uv-notice-bar"
+ v-if="show"
+ :style="[{
+ backgroundColor: bgColor
+ }, $uv.addStyle(customStyle)]"
+ >
+ <template v-if="direction === 'column' || (direction === 'row' && step)">
+ <uv-column-notice
+ :color="color"
+ :bgColor="bgColor"
+ :text="text"
+ :mode="mode"
+ :step="step"
+ :icon="icon"
+ :disable-touch="disableTouch"
+ :disable-scroll="disableScroll"
+ :fontSize="fontSize"
+ :duration="duration"
+ @close="close"
+ @click="click"
+ @change="change"
+ ></uv-column-notice>
+ </template>
+ <template v-else>
+ <uv-row-notice
+ :color="color"
+ :bgColor="bgColor"
+ :text="text"
+ :mode="mode"
+ :fontSize="fontSize"
+ :speed="speed"
+ :url="url"
+ :linkType="linkType"
+ :icon="icon"
+ @close="close"
+ @click="click"
+ ></uv-row-notice>
+ </template>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+
+ /**
+ * noticeBar 滚动通知
+ * @description 该组件用于滚动通告场景,有多种模式可供选择
+ * @tutorial https://www.uvui.cn/components/noticeBar.html
+ * @property {Array | String} text 显示的内容,数组
+ * @property {String} direction 通告滚动模式,row-横向滚动,column-竖向滚动 ( 默认 'row' )
+ * @property {Boolean} step direction = row时,是否使用步进形式滚动 ( 默认 false )
+ * @property {String} icon 是否显示左侧的音量图标 ( 默认 'volume' )
+ * @property {String} mode 通告模式,link-显示右箭头,closable-显示右侧关闭图标
+ * @property {String} color 文字颜色,各图标也会使用文字颜色 ( 默认 '#f9ae3d' )
+ * @property {String} bgColor 背景颜色 ( 默认 '#fdf6ec' )
+ * @property {String | Number} speed 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度 ( 默认 80 )
+ * @property {String | Number} fontSize 字体大小 ( 默认 14 )
+ * @property {String | Number} duration 滚动一个周期的时间长,单位ms ( 默认 2000 )
+ * @property {Boolean} disableTouch 是否禁止用手滑动切换 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序(默认34) ( 默认 true )
+ * @property {String} url 跳转的页面路径
+ * @property {String} linkType 页面跳转的类型 ( 默认 navigateTo )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} click 点击通告文字触发
+ * @event {Function} close 点击右侧关闭图标触发
+ * @example <uv-notice-bar :more-icon="true" :list="list"></uv-notice-bar>
+ */
+ export default {
+ name: "uv-notice-bar",
+ emits: ['click','close','change'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ show: true
+ }
+ },
+ methods: {
+ // 点击通告栏
+ click(index) {
+ this.$emit('click', index)
+ if (this.url && this.linkType) {
+ // 此方法写在mixin中,另外跳转的url和linkType参数也在mixin的props中
+ this.openPage()
+ }
+ },
+ // 点击关闭按钮
+ close() {
+ this.show = false
+ this.$emit('close')
+ },
+ // 竖向滚动时触发
+ change(index){
+ this.$emit('change',index)
+ }
+ }
+ };
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-notice-bar {
+ overflow: hidden;
+ padding: 9px 12px;
+ flex: 1;
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-notice-bar/components/uv-row-notice/props.js b/ledApp/uni_modules/uv-notice-bar/components/uv-row-notice/props.js
new file mode 100644
index 0000000..cba1823
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/components/uv-row-notice/props.js
@@ -0,0 +1,40 @@
+export default {
+ props: {
+ // 显示的内容,字符串
+ text: {
+ type: String,
+ default: ''
+ },
+ // 是否显示左侧的音量图标
+ icon: {
+ type: [String, Boolean, null],
+ default: 'volume'
+ },
+ // 通告模式,link-显示右箭头,closable-显示右侧关闭图标
+ mode: {
+ type: String,
+ default: ''
+ },
+ // 文字颜色,各图标也会使用文字颜色
+ color: {
+ type: String,
+ default: '#f9ae3d'
+ },
+ // 背景颜色
+ bgColor: {
+ type: String,
+ default: '#fdf6ec'
+ },
+ // 字体大小,单位px
+ fontSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度
+ speed: {
+ type: [String, Number],
+ default: 80
+ },
+ ...uni.$uv?.props?.rowNotice
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-notice-bar/components/uv-row-notice/uv-row-notice.vue b/ledApp/uni_modules/uv-notice-bar/components/uv-row-notice/uv-row-notice.vue
new file mode 100644
index 0000000..16117dd
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/components/uv-row-notice/uv-row-notice.vue
@@ -0,0 +1,341 @@
+<template>
+ <view
+ class="uv-notice"
+ @tap="clickHandler"
+ >
+ <slot name="icon">
+ <view
+ class="uv-notice__left-icon"
+ v-if="icon"
+ >
+ <uv-icon
+ :name="icon"
+ :color="color"
+ size="19"
+ ></uv-icon>
+ </view>
+ </slot>
+ <view
+ class="uv-notice__content"
+ ref="uv-notice__content"
+ >
+ <view
+ ref="uv-notice__content__text"
+ class="uv-notice__content__text"
+ :style="[animationStyle]"
+ >
+ <text
+ v-for="(item, index) in innerText"
+ :key="index"
+ :style="[textStyle]"
+ >{{item}}</text>
+ </view>
+ </view>
+ <view
+ class="uv-notice__right-icon"
+ v-if="['link', 'closable'].includes(mode)"
+ >
+ <uv-icon
+ v-if="mode === 'link'"
+ name="arrow-right"
+ :size="17"
+ :color="color"
+ ></uv-icon>
+ <uv-icon
+ v-if="mode === 'closable'"
+ @click="close"
+ name="close"
+ :size="16"
+ :color="color"
+ ></uv-icon>
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const animation = uni.requireNativePlugin('animation')
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * RowNotice 滚动通知中的水平滚动模式
+ * @description 水平滚动
+ * @tutorial https://www.uvui.cn/components/noticeBar.html
+ * @property {String | Number} text 显示的内容,字符串
+ * @property {String} icon 是否显示左侧的音量图标 (默认 'volume' )
+ * @property {String} mode 通告模式,link-显示右箭头,closable-显示右侧关闭图标
+ * @property {String} color 文字颜色,各图标也会使用文字颜色 (默认 '#f9ae3d' )
+ * @property {String} bgColor 背景颜色 (默认 ''#fdf6ec' )
+ * @property {String | Number} fontSize 字体大小,单位px (默认 14 )
+ * @property {String | Number} speed 水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度 (默认 80 )
+ *
+ * @event {Function} click 点击通告文字触发
+ * @event {Function} close 点击右侧关闭图标触发
+ * @example
+ */
+ export default {
+ name: 'uv-row-notice',
+ emits: ['click','close'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ animationDuration: '0', // 动画执行时间
+ animationPlayState: 'paused', // 动画的开始和结束执行
+ // nvue下,内容发生变化,导致滚动宽度也变化,需要标志为是否需要重新计算宽度
+ // 不能在内容变化时直接重新计算,因为nvue的animation模块上一次的滚动不是刚好结束,会有影响
+ nvueInit: true,
+ show: true
+ };
+ },
+ watch: {
+ text: {
+ immediate: true,
+ handler(newValue, oldValue) {
+ // #ifdef APP-NVUE
+ this.nvueInit = true
+ // #endif
+ // #ifndef APP-NVUE
+ this.vue()
+ // #endif
+
+ if(!this.$uv.test.string(newValue)) {
+ this.$uv.error('noticebar组件direction为row时,要求text参数为字符串形式')
+ }
+ }
+ },
+ fontSize() {
+ // #ifdef APP-NVUE
+ this.nvueInit = true
+ // #endif
+ // #ifndef APP-NVUE
+ this.vue()
+ // #endif
+ },
+ speed() {
+ // #ifdef APP-NVUE
+ this.nvueInit = true
+ // #endif
+ // #ifndef APP-NVUE
+ this.vue()
+ // #endif
+ }
+ },
+ computed: {
+ // 文字内容的样式
+ textStyle() {
+ let style = {}
+ style.color = this.color
+ style.fontSize = this.$uv.addUnit(this.fontSize)
+ return style
+ },
+ animationStyle() {
+ let style = {}
+ style.animationDuration = this.animationDuration
+ style.animationPlayState = this.animationPlayState
+ return style
+ },
+ // 内部对用户传入的数据进一步分割,放到多个text标签循环,否则如果用户传入的字符串很长(100个字符以上)
+ // 放在一个text标签中进行滚动,在低端安卓机上,动画可能会出现抖动现象,需要分割到多个text中可解决此问题
+ innerText() {
+ let result = [],
+ // 每组text标签的字符长度
+ len = 20
+ const textArr = this.text? this.text.split(''):[]
+ for (let i = 0; i < textArr.length; i += len) {
+ // 对拆分的后的text进行slice分割,得到的为数组再进行join拼接为字符串
+ result.push(textArr.slice(i, i + len).join(''))
+ }
+ return result
+ }
+ },
+ mounted() {
+ // #ifdef APP-PLUS
+ // 在APP上(含nvue),监听当前webview是否处于隐藏状态(进入下一页时即为hide状态)
+ // 如果webivew隐藏了,为了节省性能的损耗,应停止动画的执行,同时也是为了保持进入下一页返回后,滚动位置保持不变
+ var pages = getCurrentPages()
+ var page = pages[pages.length - 1]
+ var currentWebview = page.$getAppWebview()
+ currentWebview.addEventListener('hide', () => {
+ this.webviewHide = true
+ })
+ currentWebview.addEventListener('show', () => {
+ this.webviewHide = false
+ })
+ // #endif
+
+ this.init()
+ },
+ methods: {
+ init() {
+ // #ifdef APP-NVUE
+ this.nvue()
+ // #endif
+
+ // #ifndef APP-NVUE
+ this.vue()
+ // #endif
+
+ if(!this.$uv.test.string(this.text)) {
+ this.$uv.error('noticebar组件direction为row时,要求text参数为字符串形式')
+ }
+ },
+ // vue版处理
+ async vue() {
+ // #ifndef APP-NVUE
+ let boxWidth = 0,
+ textWidth = 0
+ // 进行一定的延时
+ await this.$uv.sleep()
+ // 查询盒子和文字的宽度
+ textWidth = (await this.$uvGetRect('.uv-notice__content__text')).width
+ boxWidth = (await this.$uvGetRect('.uv-notice__content')).width
+ // 根据t=s/v(时间=路程/速度),这里为何不需要加上#uv-notice-box的宽度,因为中设置了.uv-notice-content样式中设置了padding-left: 100%
+ // 恰巧计算出来的结果中已经包含了#uv-notice-box的宽度
+ this.animationDuration = `${textWidth / this.$uv.getPx(this.speed)}s`
+ // 这里必须这样开始动画,否则在APP上动画速度不会改变
+ this.animationPlayState = 'paused'
+ setTimeout(() => {
+ this.animationPlayState = 'running'
+ }, 10)
+ // #endif
+ },
+ // nvue版处理
+ async nvue() {
+ // #ifdef APP-NVUE
+ this.nvueInit = false
+ let boxWidth = 0,
+ textWidth = 0
+ // 进行一定的延时
+ await this.$uv.sleep()
+ // 查询盒子和文字的宽度
+ textWidth = (await this.getNvueRect('uv-notice__content__text')).width
+ boxWidth = (await this.getNvueRect('uv-notice__content')).width
+ // 将文字移动到盒子的右边沿,之所以需要这么做,是因为nvue不支持100%单位,否则可以通过css设置
+ animation.transition(this.$refs['uv-notice__content__text'], {
+ styles: {
+ transform: `translateX(${boxWidth}px)`
+ },
+ }, () => {
+ // 如果非禁止动画,则开始滚动
+ !this.stopAnimation && this.loopAnimation(textWidth, boxWidth)
+ });
+ // #endif
+ },
+ loopAnimation(textWidth, boxWidth) {
+ // #ifdef APP-NVUE
+ animation.transition(this.$refs['uv-notice__content__text'], {
+ styles: {
+ // 目标移动终点为-textWidth,也即当文字的最右边贴到盒子的左边框的位置
+ transform: `translateX(-${textWidth}px)`
+ },
+ // 滚动时间的计算为,时间 = 路程(boxWidth + textWidth) / 速度,最后转为毫秒
+ duration: (boxWidth + textWidth) / this.$uv.getPx(this.speed) * 1000,
+ delay: 10
+ }, () => {
+ animation.transition(this.$refs['uv-notice__content__text'], {
+ styles: {
+ // 重新将文字移动到盒子的右边沿
+ transform: `translateX(${this.stopAnimation ? 0 : boxWidth}px)`
+ },
+ }, () => {
+ // 如果非禁止动画,则继续下一轮滚动
+ if (!this.stopAnimation) {
+ // 判断是否需要初始化计算尺寸
+ if (this.nvueInit) {
+ this.nvue()
+ } else {
+ this.loopAnimation(textWidth, boxWidth)
+ }
+ }
+ });
+ })
+ // #endif
+ },
+ getNvueRect(el) {
+ // #ifdef APP-NVUE
+ // 返回一个promise
+ return new Promise(resolve => {
+ dom.getComponentRect(this.$refs[el], (res) => {
+ resolve(res.size)
+ })
+ })
+ // #endif
+ },
+ // 点击通告栏
+ clickHandler(index) {
+ this.$emit('click')
+ },
+ // 点击右侧按钮,需要判断点击的是关闭图标还是箭头图标
+ close() {
+ this.$emit('close')
+ }
+ },
+ // #ifdef APP-NVUE
+ // #ifdef VUE2
+ beforeDestroy() {
+ this.stopAnimation = true
+ },
+ // #endif
+ // #ifdef VUE3
+ unmounted() {
+ this.stopAnimation = true
+ }
+ // #endif
+ // #endif
+ };
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-notice {
+ @include flex;
+ align-items: center;
+ justify-content: space-between;
+
+ &__left-icon {
+ align-items: center;
+ margin-right: 5px;
+ }
+
+ &__right-icon {
+ margin-left: 5px;
+ align-items: center;
+ }
+
+ &__content {
+ text-align: right;
+ flex: 1;
+ @include flex;
+ flex-wrap: nowrap;
+ overflow: hidden;
+
+ &__text {
+ font-size: 14px;
+ color: $uv-warning;
+ /* #ifndef APP-NVUE */
+ // 这一句很重要,为了能让滚动左右连接起来
+ padding-left: 100%;
+ word-break: keep-all;
+ white-space: nowrap;
+ animation: uv-loop-animation 10s linear infinite both;
+ /* #endif */
+ @include flex(row);
+ }
+ }
+
+ }
+ /* #ifndef APP-NVUE */
+ @keyframes uv-loop-animation {
+ 0% {
+ transform: translate3d(0, 0, 0);
+ }
+
+ 100% {
+ transform: translate3d(-100%, 0, 0);
+ }
+ }
+ /* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-notice-bar/package.json b/ledApp/uni_modules/uv-notice-bar/package.json
new file mode 100644
index 0000000..f1bca29
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-notice-bar",
+ "displayName": "uv-notice-bar 滚动通知 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.7",
+ "description": "uv-notice-bar 该组件用于滚动通告场景,有多种模式可供选择。",
+ "keywords": [
+ "uv-notice-bar",
+ "uvui",
+ "uv-ui",
+ "notice",
+ "滚动公告"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-notice-bar/readme.md b/ledApp/uni_modules/uv-notice-bar/readme.md
new file mode 100644
index 0000000..61eb131
--- /dev/null
+++ b/ledApp/uni_modules/uv-notice-bar/readme.md
@@ -0,0 +1,19 @@
+## NoticeBar 滚动通知
+
+> **组件名:uv-notice-bar**
+
+该组件用于滚动通告场景,有多种模式可供选择。灵活配置,开箱即用。
+
+# <a href="https://www.uvui.cn/components/noticeBar.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small style="font-size:14px;font-weight:700;">(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-notify/changelog.md b/ledApp/uni_modules/uv-notify/changelog.md
new file mode 100644
index 0000000..5d818ba
--- /dev/null
+++ b/ledApp/uni_modules/uv-notify/changelog.md
@@ -0,0 +1,9 @@
+## 1.0.3(2023-10-13)
+1. unmounted兼容vue3
+## 1.0.2(2023-07-02)
+uv-notify 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/notify.html
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-notify 消息提示
diff --git a/ledApp/uni_modules/uv-notify/components/uv-notify/props.js b/ledApp/uni_modules/uv-notify/components/uv-notify/props.js
new file mode 100644
index 0000000..5728276
--- /dev/null
+++ b/ledApp/uni_modules/uv-notify/components/uv-notify/props.js
@@ -0,0 +1,45 @@
+export default {
+ props: {
+ // 到顶部的距离
+ top: {
+ type: [String, Number],
+ default: 0
+ },
+ // type主题,primary,success,warning,error
+ type: {
+ type: String,
+ default: 'primary'
+ },
+ // 字体颜色
+ color: {
+ type: String,
+ default: '#ffffff'
+ },
+ // 背景颜色
+ bgColor: {
+ type: String,
+ default: ''
+ },
+ // 展示的文字内容
+ message: {
+ type: String,
+ default: ''
+ },
+ // 展示时长,为0时不消失,单位ms
+ duration: {
+ type: [String, Number],
+ default: 3000
+ },
+ // 字体大小
+ fontSize: {
+ type: [String, Number],
+ default: 15
+ },
+ // 是否留出顶部安全距离(状态栏高度)
+ safeAreaInsetTop: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.notify
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-notify/components/uv-notify/uv-notify.vue b/ledApp/uni_modules/uv-notify/components/uv-notify/uv-notify.vue
new file mode 100644
index 0000000..96c1566
--- /dev/null
+++ b/ledApp/uni_modules/uv-notify/components/uv-notify/uv-notify.vue
@@ -0,0 +1,220 @@
+<template>
+ <uv-transition
+ mode="slide-top"
+ :customStyle="containerStyle"
+ :show="open"
+ >
+ <view
+ class="uv-notify"
+ :class="[`uv-notify--${tmpConfig.type}`]"
+ :style="[backgroundColor, $uv.addStyle(customStyle)]"
+ >
+ <uv-status-bar v-if="tmpConfig.safeAreaInsetTop"></uv-status-bar>
+ <view class="uv-notify__warpper">
+ <slot name="icon">
+ <uv-icon
+ v-if="['success', 'warning', 'error'].includes(tmpConfig.type)"
+ :name="tmpConfig.icon"
+ :color="tmpConfig.color"
+ :size="1.3 * tmpConfig.fontSize"
+ :customStyle="{marginRight: '4px'}"
+ ></uv-icon>
+ </slot>
+ <text
+ class="uv-notify__warpper__text"
+ :style="{
+ fontSize: $uv.addUnit(tmpConfig.fontSize),
+ color: tmpConfig.color
+ }"
+ >{{ tmpConfig.message }}</text>
+ </view>
+ </view>
+ </uv-transition>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * notify 顶部提示
+ * @description 该组件一般用于页面顶部向下滑出一个提示,尔后自动收起的场景
+ * @tutorial
+ * @property {String | Number} top 到顶部的距离 ( 默认 0 )
+ * @property {String} type 主题,primary,success,warning,error ( 默认 'primary' )
+ * @property {String} color 字体颜色 ( 默认 '#ffffff' )
+ * @property {String} bgColor 背景颜色
+ * @property {String} message 展示的文字内容
+ * @property {String | Number} duration 展示时长,为0时不消失,单位ms ( 默认 3000 )
+ * @property {String | Number} fontSize 字体大小 ( 默认 15 )
+ * @property {Boolean} safeAreaInsetTop 是否留出顶部安全距离(状态栏高度) ( 默认 false )
+ * @property {Object} customStyle 组件的样式,对象形式
+ * @event {Function} open 开启组件时调用的函数
+ * @event {Function} close 关闭组件式调用的函数
+ * @example <uv-notify message="Hi uvui"></uv-notify>
+ */
+ export default {
+ name: 'uv-notify',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 是否展示组件
+ open: false,
+ timer: null,
+ config: {
+ // 到顶部的距离
+ top: this.top,
+ // type主题,primary,success,warning,error
+ type: this.type,
+ // 字体颜色
+ color: this.color,
+ // 背景颜色
+ bgColor: this.bgColor,
+ // 展示的文字内容
+ message: this.message,
+ // 展示时长,为0时不消失,单位ms
+ duration: this.duration,
+ // 字体大小
+ fontSize: this.fontSize,
+ // 是否留出顶部安全距离(状态栏高度)
+ safeAreaInsetTop: this.safeAreaInsetTop
+ },
+ // 合并后的配置,避免多次调用组件后,可能会复用之前使用的配置参数
+ tmpConfig: {}
+ }
+ },
+ computed: {
+ containerStyle() {
+ let top = 0
+ if (this.tmpConfig.top === 0) {
+ // #ifdef H5
+ // H5端,导航栏为普通元素,需要将组件移动到导航栏的下边沿
+ // H5的导航栏高度为44px
+ top = 44
+ // #endif
+ }
+ const style = {
+ top: this.$uv.addUnit(this.tmpConfig.top === 0 ? top : this.tmpConfig.top),
+ // 因为组件底层为uv-transition组件,必须将其设置为fixed定位
+ // 让其出现在导航栏底部
+ position: 'fixed',
+ left: 0,
+ right: 0,
+ zIndex: 10076
+ }
+ return style
+ },
+ // 组件背景颜色
+ backgroundColor() {
+ const style = {}
+ if (this.tmpConfig.bgColor) {
+ style.backgroundColor = this.tmpConfig.bgColor
+ }
+ return style
+ },
+ // 默认主题下的图标
+ icon() {
+ let icon
+ if (this.tmpConfig.type === 'success') {
+ icon = 'checkmark-circle'
+ } else if (this.tmpConfig.type === 'error') {
+ icon = 'close-circle'
+ } else if (this.tmpConfig.type === 'warning') {
+ icon = 'error-circle'
+ }
+ return icon
+ }
+ },
+ created() {
+ // 通过主题的形式调用toast,批量生成方法函数
+ ['primary', 'success', 'error', 'warning'].map(item => {
+ this[item] = message => this.show({
+ type: item,
+ message
+ })
+ })
+ },
+ methods: {
+ show(options) {
+ // 不将结果合并到this.config变量,避免多次调用uv-toast,前后的配置造成混乱
+ this.tmpConfig = this.$uv.deepMerge(this.config, options)
+ // 任何定时器初始化之前,都要执行清除操作,否则可能会造成混乱
+ this.clearTimer()
+ this.open = true
+ if (this.tmpConfig.duration > 0) {
+ this.timer = setTimeout(() => {
+ this.open = false
+ // 倒计时结束,清除定时器,隐藏toast组件
+ this.clearTimer()
+ // 判断是否存在callback方法,如果存在就执行
+ typeof(this.tmpConfig.complete) === 'function' && this.tmpConfig.complete()
+ }, this.tmpConfig.duration)
+ }
+ },
+ // 关闭notify
+ close() {
+ this.clearTimer()
+ },
+ clearTimer() {
+ this.open = false
+ // 清除定时器
+ clearTimeout(this.timer)
+ this.timer = null
+ }
+ },
+ // #ifdef VUE2
+ beforeDestroy() {
+ this.clearTimer()
+ },
+ // #endif
+ // #ifdef VUE3
+ unmounted() {
+ this.clearTimer()
+ }
+ // #endif
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-notify-padding: 8px 10px !default;
+ $uv-notify-text-font-size: 15px !default;
+ $uv-notify-primary-bgColor: $uv-primary !default;
+ $uv-notify-success-bgColor: $uv-success !default;
+ $uv-notify-error-bgColor: $uv-error !default;
+ $uv-notify-warning-bgColor: $uv-warning !default;
+
+
+ .uv-notify {
+ padding: $uv-notify-padding;
+
+ &__warpper {
+ @include flex;
+ align-items: center;
+ text-align: center;
+ justify-content: center;
+
+ &__text {
+ font-size: $uv-notify-text-font-size;
+ text-align: center;
+ }
+ }
+
+ &--primary {
+ background-color: $uv-notify-primary-bgColor;
+ }
+
+ &--success {
+ background-color: $uv-notify-success-bgColor;
+ }
+
+ &--error {
+ background-color: $uv-notify-error-bgColor;
+ }
+
+ &--warning {
+ background-color: $uv-notify-warning-bgColor;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-notify/package.json b/ledApp/uni_modules/uv-notify/package.json
new file mode 100644
index 0000000..921949b
--- /dev/null
+++ b/ledApp/uni_modules/uv-notify/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-notify",
+ "displayName": "uv-notify 消息提示 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.3",
+ "description": "uv-notify 该组件一般用于页面顶部向下滑出一个提示,后自动收起的场景。",
+ "keywords": [
+ "uv-notify",
+ "uvui",
+ "uv-ui",
+ "notify",
+ "消息提示"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-status-bar",
+ "uv-overlay",
+ "uv-transition"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-notify/readme.md b/ledApp/uni_modules/uv-notify/readme.md
new file mode 100644
index 0000000..482344a
--- /dev/null
+++ b/ledApp/uni_modules/uv-notify/readme.md
@@ -0,0 +1,11 @@
+## Notify 消息提示
+
+> **组件名:uv-notify**
+
+该组件一般用于页面顶部向下滑出一个提示,后自动收起的场景。
+
+### <a href="https://www.uvui.cn/components/notify.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-number-box/changelog.md b/ledApp/uni_modules/uv-number-box/changelog.md
new file mode 100644
index 0000000..3f07995
--- /dev/null
+++ b/ledApp/uni_modules/uv-number-box/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-07-13)
+1. 修复 uv-number-box设置value属性不生效的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-number-box 步进器
diff --git a/ledApp/uni_modules/uv-number-box/components/uv-number-box/props.js b/ledApp/uni_modules/uv-number-box/components/uv-number-box/props.js
new file mode 100644
index 0000000..64b633b
--- /dev/null
+++ b/ledApp/uni_modules/uv-number-box/components/uv-number-box/props.js
@@ -0,0 +1,113 @@
+export default {
+ props: {
+ value: {
+ type: [String, Number],
+ default: 0
+ },
+ modelValue: {
+ type: [String, Number],
+ default: 0
+ },
+ // 步进器标识符,在change回调返回
+ name: {
+ type: [String, Number],
+ default: ''
+ },
+ // 最小值
+ min: {
+ type: [String, Number],
+ default: 1
+ },
+ // 最大值
+ max: {
+ type: [String, Number],
+ default: Number.MAX_SAFE_INTEGER
+ },
+ // 加减的步长,可为小数
+ step: {
+ type: [String, Number],
+ default: 1
+ },
+ // 是否只允许输入整数
+ integer: {
+ type: Boolean,
+ default: false
+ },
+ // 是否禁用,包括输入框,加减按钮
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否禁用输入框
+ disabledInput: {
+ type: Boolean,
+ default: false
+ },
+ // 是否开启异步变更,开启后需要手动控制输入值
+ asyncChange: {
+ type: Boolean,
+ default: false
+ },
+ // 输入框宽度,单位为px
+ inputWidth: {
+ type: [String, Number],
+ default: 35
+ },
+ // 是否显示减少按钮
+ showMinus: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示增加按钮
+ showPlus: {
+ type: Boolean,
+ default: true
+ },
+ // 显示的小数位数
+ decimalLength: {
+ type: [String, Number, null],
+ default: null
+ },
+ // 是否开启长按加减手势
+ longPress: {
+ type: Boolean,
+ default: true
+ },
+ // 输入框文字和加减按钮图标的颜色
+ color: {
+ type: String,
+ default: '#323233'
+ },
+ // 按钮大小,宽高等于此值,单位px,输入框高度和此值保持一致
+ buttonSize: {
+ type: [String, Number],
+ default: 30
+ },
+ // 输入框和按钮的背景颜色
+ bgColor: {
+ type: String,
+ default: '#EBECEE'
+ },
+ // 指定光标于键盘的距离,避免键盘遮挡输入框,单位px
+ cursorSpacing: {
+ type: [String, Number],
+ default: 100
+ },
+ // 是否禁用增加按钮
+ disablePlus: {
+ type: Boolean,
+ default: false
+ },
+ // 是否禁用减少按钮
+ disableMinus: {
+ type: Boolean,
+ default: false
+ },
+ // 加减按钮图标的样式
+ iconStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ ...uni.$uv?.props?.numberBox
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-number-box/components/uv-number-box/uv-number-box.vue b/ledApp/uni_modules/uv-number-box/components/uv-number-box/uv-number-box.vue
new file mode 100644
index 0000000..6937e89
--- /dev/null
+++ b/ledApp/uni_modules/uv-number-box/components/uv-number-box/uv-number-box.vue
@@ -0,0 +1,395 @@
+<template>
+ <view class="uv-number-box">
+ <view
+ class="uv-number-box__slot"
+ @tap.stop="clickHandler('minus')"
+ @touchstart="onTouchStart('minus')"
+ @touchend.stop="clearTimeout"
+ v-if="showMinus && $slots.minus"
+ >
+ <slot name="minus" />
+ </view>
+ <view
+ v-else-if="showMinus"
+ class="uv-number-box__minus"
+ @tap.stop="clickHandler('minus')"
+ @touchstart="onTouchStart('minus')"
+ @touchend.stop="clearTimeout"
+ hover-class="uv-number-box__minus--hover"
+ hover-stay-time="150"
+ :class="{ 'uv-number-box__minus--disabled': isDisabled('minus') }"
+ :style="[buttonStyle('minus')]"
+ >
+ <uv-icon
+ name="minus"
+ :color="isDisabled('minus') ? '#c8c9cc' : '#323233'"
+ size="15"
+ bold
+ :customStyle="iconStyle"
+ ></uv-icon>
+ </view>
+ <slot name="input">
+ <input
+ :disabled="disabledInput || disabled"
+ :cursor-spacing="getCursorSpacing"
+ :class="{ 'uv-number-box__input--disabled': disabled || disabledInput }"
+ v-model="currentValue"
+ class="uv-number-box__input"
+ @blur="onBlur"
+ @focus="onFocus"
+ @input="onInput"
+ type="number"
+ :style="[inputStyle]"
+ />
+ </slot>
+ <view
+ class="uv-number-box__slot"
+ @tap.stop="clickHandler('plus')"
+ @touchstart="onTouchStart('plus')"
+ @touchend.stop="clearTimeout"
+ v-if="showPlus && $slots.plus"
+ >
+ <slot name="plus" />
+ </view>
+ <view
+ v-else-if="showPlus"
+ class="uv-number-box__plus"
+ @tap.stop="clickHandler('plus')"
+ @touchstart="onTouchStart('plus')"
+ @touchend.stop="clearTimeout"
+ hover-class="uv-number-box__plus--hover"
+ hover-stay-time="150"
+ :class="{ 'uv-number-box__minus--disabled': isDisabled('plus') }"
+ :style="[buttonStyle('plus')]"
+ >
+ <uv-icon
+ name="plus"
+ :color="isDisabled('plus') ? '#c8c9cc' : '#323233'"
+ size="15"
+ bold
+ :customStyle="iconStyle"
+ ></uv-icon>
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * numberBox 步进器
+ * @description 该组件一般用于商城购物选择物品数量的场景。
+ * @tutorial https://www.uvui.cn/components/numberBox.html
+ * @property {String | Number} value / v-model 用于双向绑定的值,初始化时设置设为默认min值(最小值) (默认 0 )
+ * @property {String | Number} name 步进器标识符,在change回调返回
+ * @property {String | Number} min 最小值 (默认 1 )
+ * @property {String | Number} max 最大值 (默认 Number.MAX_SAFE_INTEGER )
+ * @property {String | Number} step 加减的步长,可为小数 (默认 1 )
+ * @property {Boolean} integer 是否只允许输入整数 (默认 false )
+ * @property {Boolean} disabled 是否禁用,包括输入框,加减按钮 (默认 false )
+ * @property {Boolean} disabledInput 是否禁用输入框 (默认 false )
+ * @property {Boolean} asyncChange 是否开启异步变更,开启后需要手动控制输入值 (默认 false )
+ * @property {String | Number} inputWidth 输入框宽度,单位为px (默认 35 )
+ * @property {Boolean} showMinus 是否显示减少按钮 (默认 true )
+ * @property {Boolean} showPlus 是否显示增加按钮 (默认 true )
+ * @property {String | Number} decimalLength 显示的小数位数
+ * @property {Boolean} longPress 是否开启长按加减手势 (默认 true )
+ * @property {String} color 输入框文字和加减按钮图标的颜色 (默认 '#323233' )
+ * @property {String | Number} buttonSize 按钮大小,宽高等于此值,单位px,输入框高度和此值保持一致 (默认 30 )
+ * @property {String} bgColor 输入框和按钮的背景颜色 (默认 '#EBECEE' )
+ * @property {String | Number} cursorSpacing 指定光标于键盘的距离,避免键盘遮挡输入框,单位px (默认 100 )
+ * @property {Boolean} disablePlus 是否禁用增加按钮 (默认 false )
+ * @property {Boolean} disableMinus 是否禁用减少按钮 (默认 false )
+ * @property {Object | String} iconStyle 加减按钮图标的样式
+ * @event {Function} onFocus 输入框活动焦点
+ * @event {Function} onBlur 输入框失去焦点
+ * @event {Function} onInput 输入框值发生变化
+ * @event {Function} onChange
+ * @example <uv-number-box v-model="value" @change="valChange"></uv-number-box>
+ */
+ export default {
+ name: 'uv-number-box',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 输入框实际操作的值
+ currentValue: '',
+ // 定时器
+ longPressTimer: null
+ }
+ },
+ watch: {
+ // 多个值之间,只要一个值发生变化,都要重新检查check()函数
+ watchChange(n) {
+ this.check()
+ },
+ value(newVal) {
+ if (newVal !== this.currentValue) {
+ this.currentValue = this.format(this.value)
+ }
+ },
+ modelValue(newVal) {
+ if (newVal !== this.currentValue) {
+ this.currentValue = this.format(this.modelValue)
+ }
+ }
+ },
+ computed: {
+ getCursorSpacing() {
+ // 判断传入的单位,如果为px单位,需要转成px
+ return this.$uv.getPx(this.cursorSpacing)
+ },
+ // 按钮的样式
+ buttonStyle() {
+ return (type) => {
+ const style = {
+ backgroundColor: this.bgColor,
+ height: this.$uv.addUnit(this.buttonSize),
+ color: this.color
+ }
+ if (this.isDisabled(type)) {
+ style.backgroundColor = '#f7f8fa'
+ }
+ return style
+ }
+ },
+ // 输入框的样式
+ inputStyle() {
+ const disabled = this.disabled || this.disabledInput
+ const style = {
+ color: this.color,
+ backgroundColor: this.bgColor,
+ height: this.$uv.addUnit(this.buttonSize),
+ width: this.$uv.addUnit(this.inputWidth)
+ }
+ return style
+ },
+ // 用于监听多个值发生变化
+ watchChange() {
+ return [this.integer, this.decimalLength, this.min, this.max]
+ },
+ isDisabled() {
+ return (type) => {
+ if (type === 'plus') {
+ // 在点击增加按钮情况下,判断整体的disabled,是否单独禁用增加按钮,以及当前值是否大于最大的允许值
+ return (
+ this.disabled ||
+ this.disablePlus ||
+ this.currentValue >= this.max
+ )
+ }
+ // 点击减少按钮同理
+ return (
+ this.disabled ||
+ this.disableMinus ||
+ this.currentValue <= this.min
+ )
+ }
+ },
+ },
+ created() {
+ this.init()
+ },
+ methods: {
+ init() {
+ const value = this.value || this.modelValue;
+ this.currentValue = this.format(value)
+ },
+ // 格式化整理数据,限制范围
+ format(value) {
+ value = this.filter(value)
+ // 如果为空字符串,那么设置为0,同时将值转为Number类型
+ value = value === '' ? 0 : +value
+ // 对比最大最小值,取在min和max之间的值
+ value = Math.max(Math.min(this.max, value), this.min)
+ // 如果设定了最大的小数位数,使用toFixed去进行格式化
+ if (this.decimalLength !== null) {
+ value = value.toFixed(this.decimalLength)
+ }
+ return value
+ },
+ // 过滤非法的字符
+ filter(value) {
+ // 只允许0-9之间的数字,"."为小数点,"-"为负数时候使用
+ value = String(value).replace(/[^0-9.-]/g, '')
+ // 如果只允许输入整数,则过滤掉小数点后的部分
+ if (this.integer && value.indexOf('.') !== -1) {
+ value = value.split('.')[0]
+ }
+ return value;
+ },
+ check() {
+ // 格式化了之后,如果前后的值不相等,那么设置为格式化后的值
+ const val = this.format(this.currentValue);
+ if (val !== this.currentValue) {
+ this.currentValue = val
+ }
+ },
+ // 输入框活动焦点
+ onFocus(event) {
+ this.$emit('focus', {
+ ...event.detail,
+ name: this.name,
+ })
+ },
+ // 输入框失去焦点
+ onBlur(event) {
+ // 对输入值进行格式化
+ const value = this.format(event.detail.value)
+ // 发出blur事件
+ this.$emit(
+ 'blur', {
+ ...event.detail,
+ name: this.name,
+ }
+ )
+ },
+ // 输入框值发生变化
+ onInput(e) {
+ const {
+ value = ''
+ } = e.detail || {}
+ // 为空返回
+ if (value === '') return
+ let formatted = this.filter(value)
+ // 最大允许的小数长度
+ if (this.decimalLength !== null && formatted.indexOf('.') !== -1) {
+ const pair = formatted.split('.');
+ formatted = `${pair[0]}.${pair[1].slice(0, this.decimalLength)}`
+ }
+ formatted = this.format(formatted)
+ this.emitChange(formatted);
+ },
+ // 发出change事件
+ emitChange(value) {
+ // 如果开启了异步变更值,则不修改内部的值,需要用户手动在外部通过v-model变更
+ if (!this.asyncChange) {
+ this.$nextTick(() => {
+ this.$emit('input', value)
+ this.$emit('update:modelValue', value)
+ this.currentValue = value
+ this.$forceUpdate()
+ })
+ }
+ this.$emit('change', {
+ value,
+ name: this.name,
+ });
+ },
+ onChange() {
+ const {
+ type
+ } = this
+ if (this.isDisabled(type)) {
+ return this.$emit('overlimit', type)
+ }
+ const diff = type === 'minus' ? -this.step : +this.step
+ const value = this.format(this.add(+this.currentValue, diff))
+ this.emitChange(value)
+ this.$emit(type)
+ },
+ // 对值扩大后进行四舍五入,再除以扩大因子,避免出现浮点数操作的精度问题
+ add(num1, num2) {
+ const cardinal = Math.pow(10, 10);
+ return Math.round((num1 + num2) * cardinal) / cardinal
+ },
+ // 点击加减按钮
+ clickHandler(type) {
+ this.type = type
+ this.onChange()
+ },
+ longPressStep() {
+ // 每隔一段时间,重新调用longPressStep方法,实现长按加减
+ this.clearTimeout()
+ this.longPressTimer = setTimeout(() => {
+ this.onChange()
+ this.longPressStep()
+ }, 250);
+ },
+ onTouchStart(type) {
+ if (!this.longPress) return
+ this.clearTimeout()
+ this.type = type
+ // 一定时间后,默认达到长按状态
+ this.longPressTimer = setTimeout(() => {
+ this.onChange()
+ this.longPressStep()
+ }, 600)
+ },
+ // 触摸结束,清除定时器,停止长按加减
+ onTouchEnd() {
+ if (!this.longPress) return
+ this.clearTimeout()
+ },
+ // 清除定时器
+ clearTimeout() {
+ clearTimeout(this.longPressTimer)
+ this.longPressTimer = null
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-numberBox-hover-bgColor: #E6E6E6 !default;
+ $uv-numberBox-disabled-color: #c8c9cc !default;
+ $uv-numberBox-disabled-bgColor: #f7f8fa !default;
+ $uv-numberBox-plus-radius: 4px !default;
+ $uv-numberBox-minus-radius: 4px !default;
+ $uv-numberBox-input-text-align: center !default;
+ $uv-numberBox-input-font-size: 15px !default;
+ $uv-numberBox-input-padding: 0 !default;
+ $uv-numberBox-input-margin: 0 2px !default;
+ $uv-numberBox-input-disabled-color: #c8c9cc !default;
+ $uv-numberBox-input-disabled-bgColor: #f2f3f5 !default;
+ .uv-number-box {
+ @include flex(row);
+ align-items: center;
+ &__slot {
+ /* #ifndef APP-NVUE */
+ touch-action: none;
+ /* #endif */
+ }
+ &__plus,
+ &__minus {
+ width: 35px;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ /* #ifndef APP-NVUE */
+ touch-action: none;
+ /* #endif */
+ &--hover {
+ background-color: $uv-numberBox-hover-bgColor !important;
+ }
+ &--disabled {
+ color: $uv-numberBox-disabled-color;
+ background-color: $uv-numberBox-disabled-bgColor;
+ }
+ }
+ &__plus {
+ border-top-right-radius: $uv-numberBox-plus-radius;
+ border-bottom-right-radius: $uv-numberBox-plus-radius;
+ }
+ &__minus {
+ border-top-left-radius: $uv-numberBox-minus-radius;
+ border-bottom-left-radius: $uv-numberBox-minus-radius;
+ }
+ &__input {
+ position: relative;
+ text-align: $uv-numberBox-input-text-align;
+ font-size: $uv-numberBox-input-font-size;
+ padding: $uv-numberBox-input-padding;
+ margin: $uv-numberBox-input-margin;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ &--disabled {
+ color: $uv-numberBox-input-disabled-color;
+ background-color: $uv-numberBox-input-disabled-bgColor;
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-number-box/package.json b/ledApp/uni_modules/uv-number-box/package.json
new file mode 100644
index 0000000..771e6eb
--- /dev/null
+++ b/ledApp/uni_modules/uv-number-box/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-number-box",
+ "displayName": "uv-number-box 步进器 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "uv-number-box 该组件一般用于商城购物选择物品数量的场景。",
+ "keywords": [
+ "uv-number-box",
+ "uvui",
+ "uv-ui",
+ "number",
+ "步进器"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-number-box/readme.md b/ledApp/uni_modules/uv-number-box/readme.md
new file mode 100644
index 0000000..6b83da9
--- /dev/null
+++ b/ledApp/uni_modules/uv-number-box/readme.md
@@ -0,0 +1,11 @@
+## NumberBox 步进器
+
+> **组件名:uv-number-box**
+
+该组件一般用于商城购物选择物品数量的场景。
+
+### <a href="https://www.uvui.cn/components/numberBox.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-overlay/changelog.md b/ledApp/uni_modules/uv-overlay/changelog.md
new file mode 100644
index 0000000..ff14713
--- /dev/null
+++ b/ledApp/uni_modules/uv-overlay/changelog.md
@@ -0,0 +1,9 @@
+## 1.0.3(2023-07-02)
+uv-overlay 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+## 1.0.2(2023-06-29)
+1. 优化,H5端禁止穿透滚动
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-overlay组件
diff --git a/ledApp/uni_modules/uv-overlay/components/uv-overlay/props.js b/ledApp/uni_modules/uv-overlay/components/uv-overlay/props.js
new file mode 100644
index 0000000..267c613
--- /dev/null
+++ b/ledApp/uni_modules/uv-overlay/components/uv-overlay/props.js
@@ -0,0 +1,25 @@
+export default {
+ props: {
+ // 是否显示遮罩
+ show: {
+ type: Boolean,
+ default: false
+ },
+ // 层级z-index
+ zIndex: {
+ type: [String, Number],
+ default: 10070
+ },
+ // 遮罩的过渡时间,单位为ms
+ duration: {
+ type: [String, Number],
+ default: 300
+ },
+ // 不透明度值,当做rgba的第四个参数
+ opacity: {
+ type: [String, Number],
+ default: 0.5
+ },
+ ...uni.$uv?.props?.overlay
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue b/ledApp/uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue
new file mode 100644
index 0000000..53568c0
--- /dev/null
+++ b/ledApp/uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue
@@ -0,0 +1,85 @@
+<template>
+ <uv-transition
+ :show="show"
+ mode="fade"
+ custom-class="uv-overlay"
+ :duration="duration"
+ :custom-style="overlayStyle"
+ @click="clickHandler"
+ @touchmove.stop.prevent="clear"
+ >
+ <slot />
+ </uv-transition>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+
+ /**
+ * overlay 遮罩
+ * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
+ * @tutorial https://www.uvui.cn/components/overlay.html
+ * @property {Boolean} show 是否显示遮罩(默认 false )
+ * @property {String | Number} zIndex zIndex 层级(默认 10070 )
+ * @property {String | Number} duration 动画时长,单位毫秒(默认 300 )
+ * @property {String | Number} opacity 不透明度值,当做rgba的第四个参数 (默认 0.5 )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @event {Function} click 点击遮罩发送事件
+ * @example <uv-overlay :show="show" @click="show = false"></uv-overlay>
+ */
+ export default {
+ name: "uv-overlay",
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ watch: {
+ show(newVal){
+ // #ifdef H5
+ if(newVal){
+ document.querySelector('body').style.overflow = 'hidden';
+ }else{
+ document.querySelector('body').style.overflow = '';
+ }
+ // #endif
+ }
+ },
+ computed: {
+ overlayStyle() {
+ const style = {
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ zIndex: this.zIndex,
+ bottom: 0,
+ 'background-color': `rgba(0, 0, 0, ${this.opacity})`
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ methods: {
+ clickHandler() {
+ this.$emit('click')
+ },
+ clear() {}
+ }
+ }
+</script>
+<style lang="scss" scoped>
+/* #ifndef APP-NVUE */
+$uv-overlay-top:0 !default;
+$uv-overlay-left:0 !default;
+$uv-overlay-width:100% !default;
+$uv-overlay-height:100% !default;
+$uv-overlay-background-color:rgba(0, 0, 0, .7) !default;
+.uv-overlay {
+ position: fixed;
+ top:$uv-overlay-top;
+ left:$uv-overlay-left;
+ width: $uv-overlay-width;
+ height:$uv-overlay-height;
+ background-color:$uv-overlay-background-color;
+}
+/* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-overlay/package.json b/ledApp/uni_modules/uv-overlay/package.json
new file mode 100644
index 0000000..a664f3a
--- /dev/null
+++ b/ledApp/uni_modules/uv-overlay/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-overlay",
+ "displayName": "uv-overlay 遮罩层 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.3",
+ "description": "uv-overlay 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景,uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。",
+ "keywords": [
+ "uv-overlay",
+ "uvui",
+ "uv-ui",
+ "overlay",
+ "遮罩层"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-transition"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-overlay/readme.md b/ledApp/uni_modules/uv-overlay/readme.md
new file mode 100644
index 0000000..4e8af4b
--- /dev/null
+++ b/ledApp/uni_modules/uv-overlay/readme.md
@@ -0,0 +1,11 @@
+## Overlay 遮罩层
+
+> **组件名:uv-overlay**
+
+创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景,uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。
+
+### <a href="https://www.uvui.cn/components/overlay.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-parse/changelog.md b/ledApp/uni_modules/uv-parse/changelog.md
new file mode 100644
index 0000000..4575e07
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.4(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.3(2023-06-19)
+1. 修复nvue模式下不显示的BUG
+## 1.0.2(2023-06-02)
+1. 修复可能存在的BUG
+2. 优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-parse 富文本解析器
diff --git a/ledApp/uni_modules/uv-parse/components/uv-parse/node/node.vue b/ledApp/uni_modules/uv-parse/components/uv-parse/node/node.vue
new file mode 100644
index 0000000..5857077
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/components/uv-parse/node/node.vue
@@ -0,0 +1,576 @@
+<template>
+ <view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
+ <block v-for="(n, i) in childs" v-bind:key="i">
+ <!-- 图片 -->
+ <!-- 占位图 -->
+ <image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
+ <!-- 显示图片 -->
+ <!-- #ifdef H5 || (APP-PLUS && VUE2) -->
+ <img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+ <!-- #endif -->
+ <!-- #ifndef H5 || (APP-PLUS && VUE2) -->
+ <!-- 表格中的图片,使用 rich-text 防止大小不正确 -->
+ <rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style,src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
+ <!-- #endif -->
+ <!-- #ifndef H5 || APP-PLUS -->
+ <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+ <!-- #endif -->
+ <!-- #ifdef APP-PLUS && VUE3 -->
+ <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+ <!-- #endif -->
+ <!-- 文本 -->
+ <!-- #ifdef MP-WEIXIN -->
+ <text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text>
+ <!-- #endif -->
+ <!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
+ <text v-else-if="n.text" decode>{{n.text}}</text>
+ <!-- #endif -->
+ <text v-else-if="n.name==='br'">\n</text>
+ <!-- 链接 -->
+ <view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
+ <node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
+ </view>
+ <!-- 视频 -->
+ <!-- #ifdef APP-PLUS -->
+ <view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
+ <!-- #endif -->
+ <!-- #ifndef APP-PLUS -->
+ <video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+ <!-- #endif -->
+ <!-- #ifdef H5 || APP-PLUS -->
+ <iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
+ <embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
+ <!-- #endif -->
+ <!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
+ <!-- 音频 -->
+ <audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+ <!-- #endif -->
+ <view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
+ <node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
+ <view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
+ <node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
+ <block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
+ <view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
+ <node :childs="tr.children" :opts="opts" />
+ </view>
+ <view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
+ <view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
+ <node :childs="td.children" :opts="opts" />
+ </view>
+ </view>
+ </block>
+ </view>
+ </view>
+
+ <!-- 富文本 -->
+ <!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
+ <rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
+ <!-- #endif -->
+ <!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
+ <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
+ <!-- #endif -->
+ <!-- 继续递归 -->
+ <view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
+ <node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
+ </view>
+ <node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
+ </block>
+ </view>
+</template>
+<script module="handler" lang="wxs">
+// 行内标签列表
+var inlineTags = {
+ abbr: true,
+ b: true,
+ big: true,
+ code: true,
+ del: true,
+ em: true,
+ i: true,
+ ins: true,
+ label: true,
+ q: true,
+ small: true,
+ span: true,
+ strong: true,
+ sub: true,
+ sup: true
+}
+/**
+ * @description 判断是否为行内标签
+ */
+module.exports = {
+ isInline: function (tagName, style) {
+ return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
+ }
+}
+</script>
+<script>
+
+import node from './node'
+export default {
+ name: 'node',
+ options: {
+ // #ifdef MP-WEIXIN
+ virtualHost: true,
+ // #endif
+ // #ifdef MP-TOUTIAO
+ addGlobalClass: false
+ // #endif
+ },
+ data () {
+ return {
+ ctrl: {},
+ // #ifdef MP-WEIXIN
+ isiOS: uni.getSystemInfoSync().system.includes('iOS')
+ // #endif
+ }
+ },
+ props: {
+ name: String,
+ attrs: {
+ type: Object,
+ default () {
+ return {}
+ }
+ },
+ childs: Array,
+ opts: Array
+ },
+ components: {
+
+ // #ifndef (H5 || APP-PLUS) && VUE3
+ node
+ // #endif
+ },
+ mounted () {
+ this.$nextTick(() => {
+ for (this.root = this.$parent; this.root.$options.name !== 'uv-parse'; this.root = this.root.$parent);
+ })
+ // #ifdef H5 || APP-PLUS
+ if (this.opts[0]) {
+ let i
+ for (i = this.childs.length; i--;) {
+ if (this.childs[i].name === 'img') break
+ }
+ if (i !== -1) {
+ this.observer = uni.createIntersectionObserver(this).relativeToViewport({
+ top: 500,
+ bottom: 500
+ })
+ this.observer.observe('._img', res => {
+ if (res.intersectionRatio) {
+ this.$set(this.ctrl, 'load', 1)
+ this.observer.disconnect()
+ }
+ })
+ }
+ }
+ // #endif
+ },
+ beforeDestroy () {
+ // #ifdef H5 || APP-PLUS
+ if (this.observer) {
+ this.observer.disconnect()
+ }
+ // #endif
+ },
+ methods:{
+ // #ifdef MP-WEIXIN
+ toJSON () { return this },
+ // #endif
+ /**
+ * @description 播放视频事件
+ * @param {Event} e
+ */
+ play (e) {
+ this.root.$emit('play')
+ // #ifndef APP-PLUS
+ if (this.root.pauseVideo) {
+ let flag = false
+ const id = e.target.id
+ for (let i = this.root._videos.length; i--;) {
+ if (this.root._videos[i].id === id) {
+ flag = true
+ } else {
+ this.root._videos[i].pause() // 自动暂停其他视频
+ }
+ }
+ // 将自己加入列表
+ if (!flag) {
+ const ctx = uni.createVideoContext(id
+ // #ifndef MP-BAIDU
+ , this
+ // #endif
+ )
+ ctx.id = id
+ if (this.root.playbackRate) {
+ ctx.playbackRate(this.root.playbackRate)
+ }
+ this.root._videos.push(ctx)
+ }
+ }
+ // #endif
+ },
+
+ /**
+ * @description 图片点击事件
+ * @param {Event} e
+ */
+ imgTap (e) {
+ const node = this.childs[e.currentTarget.dataset.i]
+ if (node.a) {
+ this.linkTap(node.a)
+ return
+ }
+ if (node.attrs.ignore) return
+ // #ifdef H5 || APP-PLUS
+ node.attrs.src = node.attrs.src || node.attrs['data-src']
+ // #endif
+ this.root.$emit('imgtap', node.attrs)
+ // 自动预览图片
+ if (this.root.previewImg) {
+ uni.previewImage({
+ // #ifdef MP-WEIXIN
+ showmenu: this.root.showImgMenu,
+ // #endif
+ // #ifdef MP-ALIPAY
+ enablesavephoto: this.root.showImgMenu,
+ enableShowPhotoDownload: this.root.showImgMenu,
+ // #endif
+ current: parseInt(node.attrs.i),
+ urls: this.root.imgList
+ })
+ }
+ },
+
+ /**
+ * @description 图片长按
+ */
+ imgLongTap (e) {
+ // #ifdef APP-PLUS
+ const attrs = this.childs[e.currentTarget.dataset.i].attrs
+ if (this.opts[3] && !attrs.ignore) {
+ uni.showActionSheet({
+ itemList: ['保存图片'],
+ success: () => {
+ const save = path => {
+ uni.saveImageToPhotosAlbum({
+ filePath: path,
+ success () {
+ uni.showToast({
+ title: '保存成功'
+ })
+ }
+ })
+ }
+ if (this.root.imgList[attrs.i].startsWith('http')) {
+ uni.downloadFile({
+ url: this.root.imgList[attrs.i],
+ success: res => save(res.tempFilePath)
+ })
+ } else {
+ save(this.root.imgList[attrs.i])
+ }
+ }
+ })
+ }
+ // #endif
+ },
+
+ /**
+ * @description 图片加载完成事件
+ * @param {Event} e
+ */
+ imgLoad (e) {
+ const i = e.currentTarget.dataset.i
+ /* #ifndef H5 || (APP-PLUS && VUE2) */
+ if (!this.childs[i].w) {
+ // 设置原宽度
+ this.$set(this.ctrl, i, e.detail.width)
+ } else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
+ // 加载完毕,取消加载中占位图
+ this.$set(this.ctrl, i, 1)
+ }
+ this.checkReady()
+ },
+
+ /**
+ * @description 检查是否所有图片加载完毕
+ */
+ checkReady () {
+ if (this.root && !this.root.lazyLoad) {
+ this.root._unloadimgs -= 1
+ if (!this.root._unloadimgs) {
+ setTimeout(() => {
+ this.root.getRect().then(rect => {
+ this.root.$emit('ready', rect)
+ }).catch(() => {
+ this.root.$emit('ready', {})
+ })
+ }, 350)
+ }
+ }
+ },
+
+ /**
+ * @description 链接点击事件
+ * @param {Event} e
+ */
+ linkTap (e) {
+ const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
+ const attrs = node.attrs || e
+ const href = attrs.href
+ this.root.$emit('linktap', Object.assign({
+ innerText: this.root.getText(node.children || []) // 链接内的文本内容
+ }, attrs))
+ if (href) {
+ if (href[0] === '#') {
+ // 跳转锚点
+ this.root.navigateTo(href.substring(1)).catch(() => { })
+ } else if (href.split('?')[0].includes('://')) {
+ // 复制外部链接
+ if (this.root.copyLink) {
+ // #ifdef H5
+ window.open(href)
+ // #endif
+ // #ifdef MP
+ uni.setClipboardData({
+ data: href,
+ success: () =>
+ uni.showToast({
+ title: '链接已复制'
+ })
+ })
+ // #endif
+ // #ifdef APP-PLUS
+ plus.runtime.openWeb(href)
+ // #endif
+ }
+ } else {
+ // 跳转页面
+ uni.navigateTo({
+ url: href,
+ fail () {
+ uni.switchTab({
+ url: href,
+ fail () { }
+ })
+ }
+ })
+ }
+ }
+ },
+
+ /**
+ * @description 错误事件
+ * @param {Event} e
+ */
+ mediaError (e) {
+ const i = e.currentTarget.dataset.i
+ const node = this.childs[i]
+ // 加载其他源
+ if (node.name === 'video' || node.name === 'audio') {
+ let index = (this.ctrl[i] || 0) + 1
+ if (index > node.src.length) {
+ index = 0
+ }
+ if (index < node.src.length) {
+ this.$set(this.ctrl, i, index)
+ return
+ }
+ } else if (node.name === 'img') {
+ // #ifdef H5 && VUE3
+ if (this.opts[0] && !this.ctrl.load) return
+ // #endif
+ // 显示错误占位图
+ if (this.opts[2]) {
+ this.$set(this.ctrl, i, -1)
+ }
+ this.checkReady()
+ }
+ if (this.root) {
+ this.root.$emit('error', {
+ source: node.name,
+ attrs: node.attrs,
+ // #ifndef H5 && VUE3
+ errMsg: e.detail.errMsg
+ // #endif
+ })
+ }
+ }
+ }
+}
+</script>
+<style>
+/* a 标签默认效果 */
+._a {
+ padding: 1.5px 0 1.5px 0;
+ color: #366092;
+ word-break: break-all;
+}
+
+/* a 标签点击态效果 */
+._hover {
+ text-decoration: underline;
+ opacity: 0.7;
+}
+
+/* 图片默认效果 */
+._img {
+ max-width: 100%;
+ -webkit-touch-callout: none;
+}
+
+/* 内部样式 */
+
+._block {
+ display: block;
+}
+
+._b,
+._strong {
+ font-weight: bold;
+}
+
+._code {
+ font-family: monospace;
+}
+
+._del {
+ text-decoration: line-through;
+}
+
+._em,
+._i {
+ font-style: italic;
+}
+
+._h1 {
+ font-size: 2em;
+}
+
+._h2 {
+ font-size: 1.5em;
+}
+
+._h3 {
+ font-size: 1.17em;
+}
+
+._h5 {
+ font-size: 0.83em;
+}
+
+._h6 {
+ font-size: 0.67em;
+}
+
+._h1,
+._h2,
+._h3,
+._h4,
+._h5,
+._h6 {
+ display: block;
+ font-weight: bold;
+}
+
+._image {
+ height: 1px;
+}
+
+._ins {
+ text-decoration: underline;
+}
+
+._li {
+ display: list-item;
+}
+
+._ol {
+ list-style-type: decimal;
+}
+
+._ol,
+._ul {
+ display: block;
+ padding-left: 40px;
+ margin: 1em 0;
+}
+
+._q::before {
+ content: '"';
+}
+
+._q::after {
+ content: '"';
+}
+
+._sub {
+ font-size: smaller;
+ vertical-align: sub;
+}
+
+._sup {
+ font-size: smaller;
+ vertical-align: super;
+}
+
+._thead,
+._tbody,
+._tfoot {
+ display: table-row-group;
+}
+
+._tr {
+ display: table-row;
+}
+
+._td,
+._th {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+._th {
+ font-weight: bold;
+ text-align: center;
+}
+
+._ul {
+ list-style-type: disc;
+}
+
+._ul ._ul {
+ margin: 0;
+ list-style-type: circle;
+}
+
+._ul ._ul ._ul {
+ list-style-type: square;
+}
+
+._abbr,
+._b,
+._code,
+._del,
+._em,
+._i,
+._ins,
+._label,
+._q,
+._span,
+._strong,
+._sub,
+._sup {
+ display: inline;
+}
+
+/* #ifdef APP-PLUS */
+._video {
+ width: 300px;
+ height: 225px;
+}
+/* #endif */
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-parse/components/uv-parse/parser.js b/ledApp/uni_modules/uv-parse/components/uv-parse/parser.js
new file mode 100644
index 0000000..e2e7a87
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/components/uv-parse/parser.js
@@ -0,0 +1,1335 @@
+/**
+ * @fileoverview html 解析器
+ */
+
+// 配置
+const config = {
+ // 信任的标签(保持标签名不变)
+ trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
+
+ // 块级标签(转为 div,其他的非信任标签转为 span)
+ blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
+
+ // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
+ // 行内标签
+ inlineTags: makeMap('abbr,b,big,code,del,em,i,ins,label,q,small,span,strong,sub,sup'),
+ // #endif
+
+ // 要移除的标签
+ ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),
+
+ // 自闭合的标签
+ voidTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
+
+ // html 实体
+ entities: {
+ lt: '<',
+ gt: '>',
+ quot: '"',
+ apos: "'",
+ ensp: '\u2002',
+ emsp: '\u2003',
+ nbsp: '\xA0',
+ semi: ';',
+ ndash: '–',
+ mdash: '—',
+ middot: '·',
+ lsquo: '‘',
+ rsquo: '’',
+ ldquo: '“',
+ rdquo: '”',
+ bull: '•',
+ hellip: '…',
+ larr: '←',
+ uarr: '↑',
+ rarr: '→',
+ darr: '↓'
+ },
+
+ // 默认的标签样式
+ tagStyle: {
+ // #ifndef APP-PLUS-NVUE
+ address: 'font-style:italic',
+ big: 'display:inline;font-size:1.2em',
+ caption: 'display:table-caption;text-align:center',
+ center: 'text-align:center',
+ cite: 'font-style:italic',
+ dd: 'margin-left:40px',
+ mark: 'background-color:yellow',
+ pre: 'font-family:monospace;white-space:pre',
+ s: 'text-decoration:line-through',
+ small: 'display:inline;font-size:0.8em',
+ strike: 'text-decoration:line-through',
+ u: 'text-decoration:underline'
+ // #endif
+ },
+
+ // svg 大小写对照表
+ svgDict: {
+ animatetransform: 'animateTransform',
+ lineargradient: 'linearGradient',
+ viewbox: 'viewBox',
+ attributename: 'attributeName',
+ repeatcount: 'repeatCount',
+ repeatdur: 'repeatDur'
+ }
+}
+const tagSelector={}
+const {
+ windowWidth,
+ // #ifdef MP-WEIXIN
+ system
+ // #endif
+} = uni.getSystemInfoSync()
+const blankChar = makeMap(' ,\r,\n,\t,\f')
+let idIndex = 0
+
+// #ifdef H5 || APP-PLUS
+config.ignoreTags.iframe = undefined
+config.trustTags.iframe = true
+config.ignoreTags.embed = undefined
+config.trustTags.embed = true
+// #endif
+// #ifdef APP-PLUS-NVUE
+config.ignoreTags.source = undefined
+config.ignoreTags.style = undefined
+// #endif
+
+/**
+ * @description 创建 map
+ * @param {String} str 逗号分隔
+ */
+function makeMap (str) {
+ const map = Object.create(null)
+ const list = str.split(',')
+ for (let i = list.length; i--;) {
+ map[list[i]] = true
+ }
+ return map
+}
+
+/**
+ * @description 解码 html 实体
+ * @param {String} str 要解码的字符串
+ * @param {Boolean} amp 要不要解码 &
+ * @returns {String} 解码后的字符串
+ */
+function decodeEntity (str, amp) {
+ let i = str.indexOf('&')
+ while (i !== -1) {
+ const j = str.indexOf(';', i + 3)
+ let code
+ if (j === -1) break
+ if (str[i + 1] === '#') {
+ // { 形式的实体
+ code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))
+ if (!isNaN(code)) {
+ str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)
+ }
+ } else {
+ // 形式的实体
+ code = str.substring(i + 1, j)
+ if (config.entities[code] || (code === 'amp' && amp)) {
+ str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)
+ }
+ }
+ i = str.indexOf('&', i + 1)
+ }
+ return str
+}
+
+/**
+ * @description 合并多个块级标签,加快长内容渲染
+ * @param {Array} nodes 要合并的标签数组
+ */
+function mergeNodes (nodes) {
+ let i = nodes.length - 1
+ for (let j = i; j >= -1; j--) {
+ if (j === -1 || nodes[j].c || !nodes[j].name || (nodes[j].name !== 'div' && nodes[j].name !== 'p' && nodes[j].name[0] !== 'h') || (nodes[j].attrs.style || '').includes('inline')) {
+ if (i - j >= 5) {
+ nodes.splice(j + 1, i - j, {
+ name: 'div',
+ attrs: {},
+ children: nodes.slice(j + 1, i + 1)
+ })
+ }
+ i = j - 1
+ }
+ }
+}
+
+/**
+ * @description html 解析器
+ * @param {Object} vm 组件实例
+ */
+function Parser (vm) {
+ this.options = vm || {}
+ this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)
+ this.imgList = vm.imgList || []
+ this.imgList._unloadimgs = 0
+ this.plugins = vm.plugins || []
+ this.attrs = Object.create(null)
+ this.stack = []
+ this.nodes = []
+ this.pre = (this.options.containerStyle || '').includes('white-space') && this.options.containerStyle.includes('pre') ? 2 : 0
+}
+
+/**
+ * @description 执行解析
+ * @param {String} content 要解析的文本
+ */
+Parser.prototype.parse = function (content) {
+ // 插件处理
+ for (let i = this.plugins.length; i--;) {
+ if (this.plugins[i].onUpdate) {
+ content = this.plugins[i].onUpdate(content, config) || content
+ }
+ }
+
+ new Lexer(this).parse(content)
+ // 出栈未闭合的标签
+ while (this.stack.length) {
+ this.popNode()
+ }
+ if (this.nodes.length > 50) {
+ mergeNodes(this.nodes)
+ }
+ return this.nodes
+}
+
+/**
+ * @description 将标签暴露出来(不被 rich-text 包含)
+ */
+Parser.prototype.expose = function () {
+ // #ifndef APP-PLUS-NVUE
+ for (let i = this.stack.length; i--;) {
+ const item = this.stack[i]
+ if (item.c || item.name === 'a' || item.name === 'video' || item.name === 'audio') return
+ item.c = 1
+ }
+ // #endif
+}
+
+/**
+ * @description 处理插件
+ * @param {Object} node 要处理的标签
+ * @returns {Boolean} 是否要移除此标签
+ */
+Parser.prototype.hook = function (node) {
+ for (let i = this.plugins.length; i--;) {
+ if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) === false) {
+ return false
+ }
+ }
+ return true
+}
+
+/**
+ * @description 将链接拼接上主域名
+ * @param {String} url 需要拼接的链接
+ * @returns {String} 拼接后的链接
+ */
+Parser.prototype.getUrl = function (url) {
+ const domain = this.options.domain
+ if (url[0] === '/') {
+ if (url[1] === '/') {
+ // // 开头的补充协议名
+ url = (domain ? domain.split('://')[0] : 'http') + ':' + url
+ } else if (domain) {
+ // 否则补充整个域名
+ url = domain + url
+ } /* #ifdef APP-PLUS */ else {
+ url = plus.io.convertLocalFileSystemURL(url)
+ } /* #endif */
+ } else if (!url.includes('data:') && !url.includes('://')) {
+ if (domain) {
+ url = domain + '/' + url
+ } /* #ifdef APP-PLUS */ else {
+ url = plus.io.convertLocalFileSystemURL(url)
+ } /* #endif */
+ }
+ return url
+}
+
+/**
+ * @description 解析样式表
+ * @param {Object} node 标签
+ * @returns {Object}
+ */
+Parser.prototype.parseStyle = function (node) {
+ const attrs = node.attrs
+ const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))
+ const styleObj = {}
+ let tmp = ''
+
+ if (attrs.id && !this.xml) {
+ // 暴露锚点
+ if (this.options.useAnchor) {
+ this.expose()
+ } else if (node.name !== 'img' && node.name !== 'a' && node.name !== 'video' && node.name !== 'audio') {
+ attrs.id = undefined
+ }
+ }
+
+ // 转换 width 和 height 属性
+ if (attrs.width) {
+ styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')
+ attrs.width = undefined
+ }
+ if (attrs.height) {
+ styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')
+ attrs.height = undefined
+ }
+
+ for (let i = 0, len = list.length; i < len; i++) {
+ const info = list[i].split(':')
+ if (info.length < 2) continue
+ const key = info.shift().trim().toLowerCase()
+ let value = info.join(':').trim()
+ if ((value[0] === '-' && value.lastIndexOf('-') > 0) || value.includes('safe')) {
+ // 兼容性的 css 不压缩
+ tmp += `;${key}:${value}`
+ } else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {
+ // 重复的样式进行覆盖
+ if (value.includes('url')) {
+ // 填充链接
+ let j = value.indexOf('(') + 1
+ if (j) {
+ while (value[j] === '"' || value[j] === "'" || blankChar[value[j]]) {
+ j++
+ }
+ value = value.substr(0, j) + this.getUrl(value.substr(j))
+ }
+ } else if (value.includes('rpx')) {
+ // 转换 rpx(rich-text 内部不支持 rpx)
+ value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px')
+ }
+ styleObj[key] = value
+ }
+ }
+
+ node.attrs.style = tmp
+ return styleObj
+}
+
+/**
+ * @description 解析到标签名
+ * @param {String} name 标签名
+ * @private
+ */
+Parser.prototype.onTagName = function (name) {
+ this.tagName = this.xml ? name : name.toLowerCase()
+ if (this.tagName === 'svg') {
+ this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
+ config.ignoreTags.style = undefined // svg 标签内 style 可用
+ }
+}
+
+/**
+ * @description 解析到属性名
+ * @param {String} name 属性名
+ * @private
+ */
+Parser.prototype.onAttrName = function (name) {
+ name = this.xml ? name : name.toLowerCase()
+ if (name.substr(0, 5) === 'data-') {
+ if (name === 'data-src' && !this.attrs.src) {
+ // data-src 自动转为 src
+ this.attrName = 'src'
+ } else if (this.tagName === 'img' || this.tagName === 'a') {
+ // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用
+ this.attrName = name
+ } else {
+ // 剩余的移除以减小大小
+ this.attrName = undefined
+ }
+ } else {
+ this.attrName = name
+ this.attrs[name] = 'T' // boolean 型属性缺省设置
+ }
+}
+
+/**
+ * @description 解析到属性值
+ * @param {String} val 属性值
+ * @private
+ */
+Parser.prototype.onAttrVal = function (val) {
+ const name = this.attrName || ''
+ if (name === 'style' || name === 'href') {
+ // 部分属性进行实体解码
+ this.attrs[name] = decodeEntity(val, true)
+ } else if (name.includes('src')) {
+ // 拼接主域名
+ this.attrs[name] = this.getUrl(decodeEntity(val, true))
+ } else if (name) {
+ this.attrs[name] = val
+ }
+}
+
+/**
+ * @description 解析到标签开始
+ * @param {Boolean} selfClose 是否有自闭合标识 />
+ * @private
+ */
+Parser.prototype.onOpenTag = function (selfClose) {
+ // 拼装 node
+ const node = Object.create(null)
+ node.name = this.tagName
+ node.attrs = this.attrs
+ // 避免因为自动 diff 使得 type 被设置为 null 导致部分内容不显示
+ if (this.options.nodes.length) {
+ node.type = 'node'
+ }
+ this.attrs = Object.create(null)
+
+ const attrs = node.attrs
+ const parent = this.stack[this.stack.length - 1]
+ const siblings = parent ? parent.children : this.nodes
+ const close = this.xml ? selfClose : config.voidTags[node.name]
+
+ // 替换标签名选择器
+ if (tagSelector[node.name]) {
+ attrs.class = tagSelector[node.name] + (attrs.class ? ' ' + attrs.class : '')
+ }
+
+ // 转换 embed 标签
+ if (node.name === 'embed') {
+ // #ifndef H5 || APP-PLUS
+ const src = attrs.src || ''
+ // 按照后缀名和 type 将 embed 转为 video 或 audio
+ if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) {
+ node.name = 'video'
+ } else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) {
+ node.name = 'audio'
+ }
+ if (attrs.autostart) {
+ attrs.autoplay = 'T'
+ }
+ attrs.controls = 'T'
+ // #endif
+ // #ifdef H5 || APP-PLUS
+ this.expose()
+ // #endif
+ }
+
+ // #ifndef APP-PLUS-NVUE
+ // 处理音视频
+ if (node.name === 'video' || node.name === 'audio') {
+ // 设置 id 以便获取 context
+ if (node.name === 'video' && !attrs.id) {
+ attrs.id = 'v' + idIndex++
+ }
+ // 没有设置 controls 也没有设置 autoplay 的自动设置 controls
+ if (!attrs.controls && !attrs.autoplay) {
+ attrs.controls = 'T'
+ }
+ // 用数组存储所有可用的 source
+ node.src = []
+ if (attrs.src) {
+ node.src.push(attrs.src)
+ attrs.src = undefined
+ }
+ this.expose()
+ }
+ // #endif
+
+ // 处理自闭合标签
+ if (close) {
+ if (!this.hook(node) || config.ignoreTags[node.name]) {
+ // 通过 base 标签设置主域名
+ if (node.name === 'base' && !this.options.domain) {
+ this.options.domain = attrs.href
+ } /* #ifndef APP-PLUS-NVUE */ else if (node.name === 'source' && parent && (parent.name === 'video' || parent.name === 'audio') && attrs.src) {
+ // 设置 source 标签(仅父节点为 video 或 audio 时有效)
+ parent.src.push(attrs.src)
+ } /* #endif */
+ return
+ }
+
+ // 解析 style
+ const styleObj = this.parseStyle(node)
+
+ // 处理图片
+ if (node.name === 'img') {
+ if (attrs.src) {
+ // 标记 webp
+ if (attrs.src.includes('webp')) {
+ node.webp = 'T'
+ }
+ // data url 图片如果没有设置 original-src 默认为不可预览的小图片
+ if (attrs.src.includes('data:') && !attrs['original-src']) {
+ attrs.ignore = 'T'
+ }
+ if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
+ for (let i = this.stack.length; i--;) {
+ const item = this.stack[i]
+ if (item.name === 'a') {
+ node.a = item.attrs
+ }
+ if (item.name === 'table' && !node.webp && !attrs.src.includes('cloud://')) {
+ if (!styleObj.display || styleObj.display.includes('inline')) {
+ node.t = 'inline-block'
+ } else {
+ node.t = styleObj.display
+ }
+ styleObj.display = undefined
+ }
+ // #ifndef H5 || APP-PLUS
+ const style = item.attrs.style || ''
+ if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || parseInt(styleObj.width) > 100)) {
+ styleObj.width = '100% !important'
+ styleObj.height = ''
+ for (let j = i + 1; j < this.stack.length; j++) {
+ this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')
+ }
+ } else if (style.includes('flex') && styleObj.width === '100%') {
+ for (let j = i + 1; j < this.stack.length; j++) {
+ const style = this.stack[j].attrs.style || ''
+ if (!style.includes(';width') && !style.includes(' width') && style.indexOf('width') !== 0) {
+ styleObj.width = ''
+ break
+ }
+ }
+ } else if (style.includes('inline-block')) {
+ if (styleObj.width && styleObj.width[styleObj.width.length - 1] === '%') {
+ item.attrs.style += ';max-width:' + styleObj.width
+ styleObj.width = ''
+ } else {
+ item.attrs.style += ';max-width:100%'
+ }
+ }
+ // #endif
+ item.c = 1
+ }
+ attrs.i = this.imgList.length.toString()
+ let src = attrs['original-src'] || attrs.src
+ // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360
+ if (this.imgList.includes(src)) {
+ // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位
+ let i = src.indexOf('://')
+ if (i !== -1) {
+ i += 3
+ let newSrc = src.substr(0, i)
+ for (; i < src.length; i++) {
+ if (src[i] === '/') break
+ newSrc += Math.random() > 0.5 ? src[i].toUpperCase() : src[i]
+ }
+ newSrc += src.substr(i)
+ src = newSrc
+ }
+ }
+ // #endif
+ this.imgList.push(src)
+ if (!node.t) {
+ this.imgList._unloadimgs += 1
+ }
+ // #ifdef H5 || APP-PLUS
+ if (this.options.lazyLoad) {
+ attrs['data-src'] = attrs.src
+ attrs.src = undefined
+ }
+ // #endif
+ }
+ }
+ if (styleObj.display === 'inline') {
+ styleObj.display = ''
+ }
+ // #ifndef APP-PLUS-NVUE
+ if (attrs.ignore) {
+ styleObj['max-width'] = styleObj['max-width'] || '100%'
+ attrs.style += ';-webkit-touch-callout:none'
+ }
+ // #endif
+ // 设置的宽度超出屏幕,为避免变形,高度转为自动
+ if (parseInt(styleObj.width) > windowWidth) {
+ styleObj.height = undefined
+ }
+ // 记录是否设置了宽高
+ if (!isNaN(parseInt(styleObj.width))) {
+ node.w = 'T'
+ }
+ if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {
+ node.h = 'T'
+ }
+ } else if (node.name === 'svg') {
+ siblings.push(node)
+ this.stack.push(node)
+ this.popNode()
+ return
+ }
+ for (const key in styleObj) {
+ if (styleObj[key]) {
+ attrs.style += `;${key}:${styleObj[key].replace(' !important', '')}`
+ }
+ }
+ attrs.style = attrs.style.substr(1) || undefined
+ // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
+ if (!attrs.style) {
+ delete attrs.style
+ }
+ // #endif
+ } else {
+ if ((node.name === 'pre' || ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) && this.pre !== 2) {
+ this.pre = node.pre = 1
+ }
+ node.children = []
+ this.stack.push(node)
+ }
+
+ // 加入节点树
+ siblings.push(node)
+}
+
+/**
+ * @description 解析到标签结束
+ * @param {String} name 标签名
+ * @private
+ */
+Parser.prototype.onCloseTag = function (name) {
+ // 依次出栈到匹配为止
+ name = this.xml ? name : name.toLowerCase()
+ let i
+ for (i = this.stack.length; i--;) {
+ if (this.stack[i].name === name) break
+ }
+ if (i !== -1) {
+ while (this.stack.length > i) {
+ this.popNode()
+ }
+ } else if (name === 'p' || name === 'br') {
+ const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
+ siblings.push({
+ name,
+ attrs: {
+ class: tagSelector[name] || '',
+ style: this.tagStyle[name] || ''
+ }
+ })
+ }
+}
+
+/**
+ * @description 处理标签出栈
+ * @private
+ */
+Parser.prototype.popNode = function () {
+ const node = this.stack.pop()
+ let attrs = node.attrs
+ const children = node.children
+ const parent = this.stack[this.stack.length - 1]
+ const siblings = parent ? parent.children : this.nodes
+
+ if (!this.hook(node) || config.ignoreTags[node.name]) {
+ // 获取标题
+ if (node.name === 'title' && children.length && children[0].type === 'text' && this.options.setTitle) {
+ uni.setNavigationBarTitle({
+ title: children[0].text
+ })
+ }
+ siblings.pop()
+ return
+ }
+
+ if (node.pre && this.pre !== 2) {
+ // 是否合并空白符标识
+ this.pre = node.pre = undefined
+ for (let i = this.stack.length; i--;) {
+ if (this.stack[i].pre) {
+ this.pre = 1
+ }
+ }
+ }
+
+ const styleObj = {}
+
+ // 转换 svg
+ if (node.name === 'svg') {
+ if (this.xml > 1) {
+ // 多层 svg 嵌套
+ this.xml--
+ return
+ }
+ // #ifdef APP-PLUS-NVUE
+ (function traversal (node) {
+ if (node.name) {
+ // 调整 svg 的大小写
+ node.name = config.svgDict[node.name] || node.name
+ for (const item in node.attrs) {
+ if (config.svgDict[item]) {
+ node.attrs[config.svgDict[item]] = node.attrs[item]
+ node.attrs[item] = undefined
+ }
+ }
+ for (let i = 0; i < (node.children || []).length; i++) {
+ traversal(node.children[i])
+ }
+ }
+ })(node)
+ // #endif
+ // #ifndef APP-PLUS-NVUE
+ let src = ''
+ const style = attrs.style
+ attrs.style = ''
+ attrs.xmlns = 'http://www.w3.org/2000/svg';
+ (function traversal (node) {
+ if (node.type === 'text') {
+ src += node.text
+ return
+ }
+ const name = config.svgDict[node.name] || node.name
+ src += '<' + name
+ for (const item in node.attrs) {
+ const val = node.attrs[item]
+ if (val) {
+ src += ` ${config.svgDict[item] || item}="${val}"`
+ }
+ }
+ if (!node.children) {
+ src += '/>'
+ } else {
+ src += '>'
+ for (let i = 0; i < node.children.length; i++) {
+ traversal(node.children[i])
+ }
+ src += '</' + name + '>'
+ }
+ })(node)
+ node.name = 'img'
+ node.attrs = {
+ src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
+ style,
+ ignore: 'T'
+ }
+ node.children = undefined
+ // #endif
+ this.xml = false
+ config.ignoreTags.style = true
+ return
+ }
+
+ // #ifndef APP-PLUS-NVUE
+ // 转换 align 属性
+ if (attrs.align) {
+ if (node.name === 'table') {
+ if (attrs.align === 'center') {
+ styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'
+ } else {
+ styleObj.float = attrs.align
+ }
+ } else {
+ styleObj['text-align'] = attrs.align
+ }
+ attrs.align = undefined
+ }
+
+ // 转换 dir 属性
+ if (attrs.dir) {
+ styleObj.direction = attrs.dir
+ attrs.dir = undefined
+ }
+
+ // 转换 font 标签的属性
+ if (node.name === 'font') {
+ if (attrs.color) {
+ styleObj.color = attrs.color
+ attrs.color = undefined
+ }
+ if (attrs.face) {
+ styleObj['font-family'] = attrs.face
+ attrs.face = undefined
+ }
+ if (attrs.size) {
+ let size = parseInt(attrs.size)
+ if (!isNaN(size)) {
+ if (size < 1) {
+ size = 1
+ } else if (size > 7) {
+ size = 7
+ }
+ styleObj['font-size'] = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'xxx-large'][size - 1]
+ }
+ attrs.size = undefined
+ }
+ }
+ // #endif
+
+ // 一些编辑器的自带 class
+ if ((attrs.class || '').includes('align-center')) {
+ styleObj['text-align'] = 'center'
+ }
+
+ Object.assign(styleObj, this.parseStyle(node))
+
+ if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {
+ styleObj['max-width'] = '100%'
+ styleObj['box-sizing'] = 'border-box'
+ }
+
+ // #ifndef APP-PLUS-NVUE
+ if (config.blockTags[node.name]) {
+ node.name = 'div'
+ } else if (!config.trustTags[node.name] && !this.xml) {
+ // 未知标签转为 span,避免无法显示
+ node.name = 'span'
+ }
+
+ if (node.name === 'a' || node.name === 'ad'
+ // #ifdef H5 || APP-PLUS
+ || node.name === 'iframe' // eslint-disable-line
+ // #endif
+ ) {
+ this.expose()
+ } else if (node.name === 'video') {
+ if ((styleObj.height || '').includes('auto')) {
+ styleObj.height = undefined
+ }
+ /* #ifdef APP-PLUS */
+ let str = '<video style="width:100%;height:100%"'
+ for (const item in attrs) {
+ if (attrs[item]) {
+ str += ' ' + item + '="' + attrs[item] + '"'
+ }
+ }
+ if (this.options.pauseVideo) {
+ str += ' onplay="this.dispatchEvent(new CustomEvent(\'vplay\',{bubbles:!0}));for(var e=document.getElementsByTagName(\'video\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()"'
+ }
+ str += '>'
+ for (let i = 0; i < node.src.length; i++) {
+ str += '<source src="' + node.src[i] + '">'
+ }
+ str += '</video>'
+ node.html = str
+ /* #endif */
+ } else if ((node.name === 'ul' || node.name === 'ol') && node.c) {
+ // 列表处理
+ const types = {
+ a: 'lower-alpha',
+ A: 'upper-alpha',
+ i: 'lower-roman',
+ I: 'upper-roman'
+ }
+ if (types[attrs.type]) {
+ attrs.style += ';list-style-type:' + types[attrs.type]
+ attrs.type = undefined
+ }
+ for (let i = children.length; i--;) {
+ if (children[i].name === 'li') {
+ children[i].c = 1
+ }
+ }
+ } else if (node.name === 'table') {
+ // 表格处理
+ // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现
+ let padding = parseFloat(attrs.cellpadding)
+ let spacing = parseFloat(attrs.cellspacing)
+ const border = parseFloat(attrs.border)
+ const bordercolor = styleObj['border-color']
+ const borderstyle = styleObj['border-style']
+ if (node.c) {
+ // padding 和 spacing 默认 2
+ if (isNaN(padding)) {
+ padding = 2
+ }
+ if (isNaN(spacing)) {
+ spacing = 2
+ }
+ }
+ if (border) {
+ attrs.style += `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}`
+ }
+ if (node.flag && node.c) {
+ // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
+ styleObj.display = 'grid'
+ if (spacing) {
+ styleObj['grid-gap'] = spacing + 'px'
+ styleObj.padding = spacing + 'px'
+ } else if (border) {
+ // 无间隔的情况下避免边框重叠
+ attrs.style += ';border-left:0;border-top:0'
+ }
+
+ const width = [] // 表格的列宽
+ const trList = [] // tr 列表
+ const cells = [] // 保存新的单元格
+ const map = {}; // 被合并单元格占用的格子
+
+ (function traversal (nodes) {
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].name === 'tr') {
+ trList.push(nodes[i])
+ } else {
+ traversal(nodes[i].children || [])
+ }
+ }
+ })(children)
+
+ for (let row = 1; row <= trList.length; row++) {
+ let col = 1
+ for (let j = 0; j < trList[row - 1].children.length; j++) {
+ const td = trList[row - 1].children[j]
+ if (td.name === 'td' || td.name === 'th') {
+ // 这个格子被上面的单元格占用,则列号++
+ while (map[row + '.' + col]) {
+ col++
+ }
+ let style = td.attrs.style || ''
+ let start = style.indexOf('width') ? style.indexOf(';width') : 0
+ // 提取出 td 的宽度
+ if (start !== -1) {
+ let end = style.indexOf(';', start + 6)
+ if (end === -1) {
+ end = style.length
+ }
+ if (!td.attrs.colspan) {
+ width[col] = style.substring(start ? start + 7 : 6, end)
+ }
+ style = style.substr(0, start) + style.substr(end)
+ }
+ // 设置竖直对齐
+ style += ';display:flex'
+ start = style.indexOf('vertical-align')
+ if (start !== -1) {
+ const val = style.substr(start + 15, 10)
+ if (val.includes('middle')) {
+ style += ';align-items:center'
+ } else if (val.includes('bottom')) {
+ style += ';align-items:flex-end'
+ }
+ } else {
+ style += ';align-items:center'
+ }
+ // 设置水平对齐
+ start = style.indexOf('text-align')
+ if (start !== -1) {
+ const val = style.substr(start + 11, 10)
+ if (val.includes('center')) {
+ style += ';justify-content: center'
+ } else if (val.includes('right')) {
+ style += ';justify-content: right'
+ }
+ }
+ style = (border ? `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '') + ';' + style
+ // 处理列合并
+ if (td.attrs.colspan) {
+ style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`
+ if (!td.attrs.rowspan) {
+ style += `;grid-row-start:${row};grid-row-end:${row + 1}`
+ }
+ col += parseInt(td.attrs.colspan) - 1
+ }
+ // 处理行合并
+ if (td.attrs.rowspan) {
+ style += `;grid-row-start:${row};grid-row-end:${row + parseInt(td.attrs.rowspan)}`
+ if (!td.attrs.colspan) {
+ style += `;grid-column-start:${col};grid-column-end:${col + 1}`
+ }
+ // 记录下方单元格被占用
+ for (let rowspan = 1; rowspan < td.attrs.rowspan; rowspan++) {
+ for (let colspan = 0; colspan < (td.attrs.colspan || 1); colspan++) {
+ map[(row + rowspan) + '.' + (col - colspan)] = 1
+ }
+ }
+ }
+ if (style) {
+ td.attrs.style = style
+ }
+ cells.push(td)
+ col++
+ }
+ }
+ if (row === 1) {
+ let temp = ''
+ for (let i = 1; i < col; i++) {
+ temp += (width[i] ? width[i] : 'auto') + ' '
+ }
+ styleObj['grid-template-columns'] = temp
+ }
+ }
+ node.children = cells
+ } else {
+ // 没有使用合并单元格的表格通过 table 布局实现
+ if (node.c) {
+ styleObj.display = 'table'
+ }
+ if (!isNaN(spacing)) {
+ styleObj['border-spacing'] = spacing + 'px'
+ }
+ if (border || padding) {
+ // 遍历
+ (function traversal (nodes) {
+ for (let i = 0; i < nodes.length; i++) {
+ const td = nodes[i]
+ if (td.name === 'th' || td.name === 'td') {
+ if (border) {
+ td.attrs.style = `border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'};${td.attrs.style || ''}`
+ }
+ if (padding) {
+ td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`
+ }
+ } else if (td.children) {
+ traversal(td.children)
+ }
+ }
+ })(children)
+ }
+ }
+ // 给表格添加一个单独的横向滚动层
+ if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {
+ const table = Object.assign({}, node)
+ node.name = 'div'
+ node.attrs = {
+ style: 'overflow:auto'
+ }
+ node.children = [table]
+ attrs = table.attrs
+ }
+ } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
+ for (let i = this.stack.length; i--;) {
+ if (this.stack[i].name === 'table') {
+ this.stack[i].flag = 1 // 指示含有合并单元格
+ break
+ }
+ }
+ } else if (node.name === 'ruby') {
+ // 转换 ruby
+ node.name = 'span'
+ for (let i = 0; i < children.length - 1; i++) {
+ if (children[i].type === 'text' && children[i + 1].name === 'rt') {
+ children[i] = {
+ name: 'div',
+ attrs: {
+ style: 'display:inline-block;text-align:center'
+ },
+ children: [{
+ name: 'div',
+ attrs: {
+ style: 'font-size:50%;' + (children[i + 1].attrs.style || '')
+ },
+ children: children[i + 1].children
+ }, children[i]]
+ }
+ children.splice(i + 1, 1)
+ }
+ }
+ } else if (node.c) {
+ (function traversal (node) {
+ node.c = 2
+ for (let i = node.children.length; i--;) {
+ const child = node.children[i]
+ // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
+ if (child.name && (config.inlineTags[child.name] || ((child.attrs.style || '').includes('inline') && child.children)) && !child.c) {
+ traversal(child)
+ }
+ // #endif
+ if (!child.c || child.name === 'table') {
+ node.c = 1
+ }
+ }
+ })(node)
+ }
+
+ if ((styleObj.display || '').includes('flex') && !node.c) {
+ for (let i = children.length; i--;) {
+ const item = children[i]
+ if (item.f) {
+ item.attrs.style = (item.attrs.style || '') + item.f
+ item.f = undefined
+ }
+ }
+ }
+ // flex 布局时部分样式需要提取到 rich-text 外层
+ const flex = parent && ((parent.attrs.style || '').includes('flex') || (parent.attrs.style || '').includes('grid'))
+ // #ifdef MP-WEIXIN
+ // 检查基础库版本 virtualHost 是否可用
+ && !(node.c && wx.getNFCAdapter) // eslint-disable-line
+ // #endif
+ // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
+ && !node.c // eslint-disable-line
+ // #endif
+ if (flex) {
+ node.f = ';max-width:100%'
+ }
+
+ if (children.length >= 50 && node.c && !(styleObj.display || '').includes('flex')) {
+ mergeNodes(children)
+ }
+ // #endif
+
+ for (const key in styleObj) {
+ if (styleObj[key]) {
+ const val = `;${key}:${styleObj[key].replace(' !important', '')}`
+ /* #ifndef APP-PLUS-NVUE */
+ if (flex && ((key.includes('flex') && key !== 'flex-direction') || key === 'align-self' || key.includes('grid') || styleObj[key][0] === '-' || (key.includes('width') && val.includes('%')))) {
+ node.f += val
+ if (key === 'width') {
+ attrs.style += ';width:100%'
+ }
+ } else /* #endif */ {
+ attrs.style += val
+ }
+ }
+ }
+ attrs.style = attrs.style.substr(1) || undefined
+ // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
+ for (const key in attrs) {
+ if (!attrs[key]) {
+ delete attrs[key]
+ }
+ }
+ // #endif
+}
+
+/**
+ * @description 解析到文本
+ * @param {String} text 文本内容
+ */
+Parser.prototype.onText = function (text) {
+ if (!this.pre) {
+ // 合并空白符
+ let trim = ''
+ let flag
+ for (let i = 0, len = text.length; i < len; i++) {
+ if (!blankChar[text[i]]) {
+ trim += text[i]
+ } else {
+ if (trim[trim.length - 1] !== ' ') {
+ trim += ' '
+ }
+ if (text[i] === '\n' && !flag) {
+ flag = true
+ }
+ }
+ }
+ // 去除含有换行符的空串
+ if (trim === ' ') {
+ if (flag) return
+ // #ifdef VUE3
+ else {
+ const parent = this.stack[this.stack.length - 1]
+ if (parent && parent.name[0] === 't') return
+ }
+ // #endif
+ }
+ text = trim
+ }
+ const node = Object.create(null)
+ node.type = 'text'
+ // #ifdef (MP-BAIDU || MP-ALIPAY || MP-TOUTIAO) && VUE3
+ node.attrs = {}
+ // #endif
+ node.text = decodeEntity(text)
+ if (this.hook(node)) {
+ // #ifdef MP-WEIXIN
+ if (this.options.selectable === 'force' && system.includes('iOS') && !uni.canIUse('rich-text.user-select')) {
+ this.expose()
+ }
+ // #endif
+ const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
+ siblings.push(node)
+ }
+}
+
+/**
+ * @description html 词法分析器
+ * @param {Object} handler 高层处理器
+ */
+function Lexer (handler) {
+ this.handler = handler
+}
+
+/**
+ * @description 执行解析
+ * @param {String} content 要解析的文本
+ */
+Lexer.prototype.parse = function (content) {
+ this.content = content || ''
+ this.i = 0 // 标记解析位置
+ this.start = 0 // 标记一个单词的开始位置
+ this.state = this.text // 当前状态
+ for (let len = this.content.length; this.i !== -1 && this.i < len;) {
+ this.state()
+ }
+}
+
+/**
+ * @description 检查标签是否闭合
+ * @param {String} method 如果闭合要进行的操作
+ * @returns {Boolean} 是否闭合
+ * @private
+ */
+Lexer.prototype.checkClose = function (method) {
+ const selfClose = this.content[this.i] === '/'
+ if (this.content[this.i] === '>' || (selfClose && this.content[this.i + 1] === '>')) {
+ if (method) {
+ this.handler[method](this.content.substring(this.start, this.i))
+ }
+ this.i += selfClose ? 2 : 1
+ this.start = this.i
+ this.handler.onOpenTag(selfClose)
+ if (this.handler.tagName === 'script') {
+ this.i = this.content.indexOf('</', this.i)
+ if (this.i !== -1) {
+ this.i += 2
+ this.start = this.i
+ }
+ this.state = this.endTag
+ } else {
+ this.state = this.text
+ }
+ return true
+ }
+ return false
+}
+
+/**
+ * @description 文本状态
+ * @private
+ */
+Lexer.prototype.text = function () {
+ this.i = this.content.indexOf('<', this.i) // 查找最近的标签
+ if (this.i === -1) {
+ // 没有标签了
+ if (this.start < this.content.length) {
+ this.handler.onText(this.content.substring(this.start, this.content.length))
+ }
+ return
+ }
+ const c = this.content[this.i + 1]
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ // 标签开头
+ if (this.start !== this.i) {
+ this.handler.onText(this.content.substring(this.start, this.i))
+ }
+ this.start = ++this.i
+ this.state = this.tagName
+ } else if (c === '/' || c === '!' || c === '?') {
+ if (this.start !== this.i) {
+ this.handler.onText(this.content.substring(this.start, this.i))
+ }
+ const next = this.content[this.i + 2]
+ if (c === '/' && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
+ // 标签结尾
+ this.i += 2
+ this.start = this.i
+ this.state = this.endTag
+ return
+ }
+ // 处理注释
+ let end = '-->'
+ if (c !== '!' || this.content[this.i + 2] !== '-' || this.content[this.i + 3] !== '-') {
+ end = '>'
+ }
+ this.i = this.content.indexOf(end, this.i)
+ if (this.i !== -1) {
+ this.i += end.length
+ this.start = this.i
+ }
+ } else {
+ this.i++
+ }
+}
+
+/**
+ * @description 标签名状态
+ * @private
+ */
+Lexer.prototype.tagName = function () {
+ if (blankChar[this.content[this.i]]) {
+ // 解析到标签名
+ this.handler.onTagName(this.content.substring(this.start, this.i))
+ while (blankChar[this.content[++this.i]]);
+ if (this.i < this.content.length && !this.checkClose()) {
+ this.start = this.i
+ this.state = this.attrName
+ }
+ } else if (!this.checkClose('onTagName')) {
+ this.i++
+ }
+}
+
+/**
+ * @description 属性名状态
+ * @private
+ */
+Lexer.prototype.attrName = function () {
+ let c = this.content[this.i]
+ if (blankChar[c] || c === '=') {
+ // 解析到属性名
+ this.handler.onAttrName(this.content.substring(this.start, this.i))
+ let needVal = c === '='
+ const len = this.content.length
+ while (++this.i < len) {
+ c = this.content[this.i]
+ if (!blankChar[c]) {
+ if (this.checkClose()) return
+ if (needVal) {
+ // 等号后遇到第一个非空字符
+ this.start = this.i
+ this.state = this.attrVal
+ return
+ }
+ if (this.content[this.i] === '=') {
+ needVal = true
+ } else {
+ this.start = this.i
+ this.state = this.attrName
+ return
+ }
+ }
+ }
+ } else if (!this.checkClose('onAttrName')) {
+ this.i++
+ }
+}
+
+/**
+ * @description 属性值状态
+ * @private
+ */
+Lexer.prototype.attrVal = function () {
+ const c = this.content[this.i]
+ const len = this.content.length
+ if (c === '"' || c === "'") {
+ // 有冒号的属性
+ this.start = ++this.i
+ this.i = this.content.indexOf(c, this.i)
+ if (this.i === -1) return
+ this.handler.onAttrVal(this.content.substring(this.start, this.i))
+ } else {
+ // 没有冒号的属性
+ for (; this.i < len; this.i++) {
+ if (blankChar[this.content[this.i]]) {
+ this.handler.onAttrVal(this.content.substring(this.start, this.i))
+ break
+ } else if (this.checkClose('onAttrVal')) return
+ }
+ }
+ while (blankChar[this.content[++this.i]]);
+ if (this.i < len && !this.checkClose()) {
+ this.start = this.i
+ this.state = this.attrName
+ }
+}
+
+/**
+ * @description 结束标签状态
+ * @returns {String} 结束的标签名
+ * @private
+ */
+Lexer.prototype.endTag = function () {
+ const c = this.content[this.i]
+ if (blankChar[c] || c === '>' || c === '/') {
+ this.handler.onCloseTag(this.content.substring(this.start, this.i))
+ if (c !== '>') {
+ this.i = this.content.indexOf('>', this.i)
+ if (this.i === -1) return
+ }
+ this.start = ++this.i
+ this.state = this.text
+ } else {
+ this.i++
+ }
+}
+
+export default Parser
diff --git a/ledApp/uni_modules/uv-parse/components/uv-parse/uv-parse.vue b/ledApp/uni_modules/uv-parse/components/uv-parse/uv-parse.vue
new file mode 100644
index 0000000..2aa9837
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/components/uv-parse/uv-parse.vue
@@ -0,0 +1,498 @@
+<template>
+ <view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
+ <slot v-if="!nodes[0]" />
+ <!-- #ifndef APP-PLUS-NVUE -->
+ <node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
+ <!-- #endif -->
+ <!-- #ifdef APP-PLUS-NVUE -->
+ <web-view ref="web" src="/uni_modules/uv-parse/static/app-plus/uv-parse/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
+ <!-- #endif -->
+ </view>
+</template>
+
+<script>
+/**
+ * uv-parse v1.0.3
+ * @description 富文本组件
+ * @tutorial https://www.uvui.cn/components/parse.html
+ * @property {String} container-style 容器的样式
+ * @property {String} content 用于渲染的 html 字符串
+ * @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
+ * @property {String} domain 主域名,用于拼接链接
+ * @property {String} error-img 图片出错时的占位图链接
+ * @property {Boolean} lazy-load 是否开启图片懒加载
+ * @property {string} loading-img 图片加载过程中的占位图链接
+ * @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
+ * @property {Boolean} preview-img 是否允许图片被点击时自动预览
+ * @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
+ * @property {Boolean | String} selectable 是否开启长按复制
+ * @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
+ * @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
+ * @property {Object} tag-style 标签的默认样式
+ * @property {Boolean | Number} use-anchor 是否使用锚点链接
+ * @event {Function} load dom 结构加载完毕时触发
+ * @event {Function} ready 所有图片加载完毕时触发
+ * @event {Function} imgtap 图片被点击时触发
+ * @event {Function} linktap 链接被点击时触发
+ * @event {Function} play 音视频播放时触发
+ * @event {Function} error 媒体加载出错时触发
+ */
+// #ifndef APP-PLUS-NVUE
+import node from './node/node'
+// #endif
+import Parser from './parser'
+const plugins=[]
+// #ifdef APP-PLUS-NVUE
+const dom = weex.requireModule('dom')
+// #endif
+export default {
+ name: 'uv-parse',
+ data () {
+ return {
+ nodes: [],
+ // #ifdef APP-PLUS-NVUE
+ height: 3
+ // #endif
+ }
+ },
+ props: {
+ containerStyle: {
+ type: String,
+ default: ''
+ },
+ content: {
+ type: String,
+ default: ''
+ },
+ copyLink: {
+ type: [Boolean, String],
+ default: true
+ },
+ domain: String,
+ errorImg: {
+ type: String,
+ default: ''
+ },
+ lazyLoad: {
+ type: [Boolean, String],
+ default: false
+ },
+ loadingImg: {
+ type: String,
+ default: ''
+ },
+ pauseVideo: {
+ type: [Boolean, String],
+ default: true
+ },
+ previewImg: {
+ type: [Boolean, String],
+ default: true
+ },
+ scrollTable: [Boolean, String],
+ selectable: [Boolean, String],
+ setTitle: {
+ type: [Boolean, String],
+ default: true
+ },
+ showImgMenu: {
+ type: [Boolean, String],
+ default: true
+ },
+ tagStyle: Object,
+ useAnchor: [Boolean, Number]
+ },
+ // #ifdef VUE3
+ emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
+ // #endif
+ // #ifndef APP-PLUS-NVUE
+ components: {
+ node
+ },
+ // #endif
+ watch: {
+ content (content) {
+ this.setContent(content)
+ }
+ },
+ created () {
+ this.plugins = []
+ for (let i = plugins.length; i--;) {
+ this.plugins.push(new plugins[i](this))
+ }
+ },
+ mounted () {
+ if (this.content && !this.nodes.length) {
+ this.setContent(this.content)
+ }
+ },
+ beforeDestroy () {
+ this._hook('onDetached')
+ },
+ methods: {
+ /**
+ * @description 将锚点跳转的范围限定在一个 scroll-view 内
+ * @param {Object} page scroll-view 所在页面的示例
+ * @param {String} selector scroll-view 的选择器
+ * @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
+ */
+ in (page, selector, scrollTop) {
+ // #ifndef APP-PLUS-NVUE
+ if (page && selector && scrollTop) {
+ this._in = {
+ page,
+ selector,
+ scrollTop
+ }
+ }
+ // #endif
+ },
+
+ /**
+ * @description 锚点跳转
+ * @param {String} id 要跳转的锚点 id
+ * @param {Number} offset 跳转位置的偏移量
+ * @returns {Promise}
+ */
+ navigateTo (id, offset) {
+ return new Promise((resolve, reject) => {
+ if (!this.useAnchor) {
+ reject(Error('Anchor is disabled'))
+ return
+ }
+ offset = offset || parseInt(this.useAnchor) || 0
+ // #ifdef APP-PLUS-NVUE
+ if (!id) {
+ dom.scrollToElement(this.$refs.web, {
+ offset
+ })
+ resolve()
+ } else {
+ this._navigateTo = {
+ resolve,
+ reject,
+ offset
+ }
+ this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
+ }
+ // #endif
+ // #ifndef APP-PLUS-NVUE
+ let deep = ' '
+ // #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
+ deep = '>>>'
+ // #endif
+ const selector = uni.createSelectorQuery()
+ // #ifndef MP-ALIPAY
+ .in(this._in ? this._in.page : this)
+ // #endif
+ .select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
+ if (this._in) {
+ selector.select(this._in.selector).scrollOffset()
+ .select(this._in.selector).boundingClientRect()
+ } else {
+ // 获取 scroll-view 的位置和滚动距离
+ selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
+ }
+ selector.exec(res => {
+ if (!res[0]) {
+ reject(Error('Label not found'))
+ return
+ }
+ const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
+ if (this._in) {
+ // scroll-view 跳转
+ this._in.page[this._in.scrollTop] = scrollTop
+ } else {
+ // 页面跳转
+ uni.pageScrollTo({
+ scrollTop,
+ duration: 300
+ })
+ }
+ resolve()
+ })
+ // #endif
+ })
+ },
+
+ /**
+ * @description 获取文本内容
+ * @return {String}
+ */
+ getText (nodes) {
+ let text = '';
+ (function traversal (nodes) {
+ for (let i = 0; i < nodes.length; i++) {
+ const node = nodes[i]
+ if (node.type === 'text') {
+ text += node.text.replace(/&/g, '&')
+ } else if (node.name === 'br') {
+ text += '\n'
+ } else {
+ // 块级标签前后加换行
+ const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
+ if (isBlock && text && text[text.length - 1] !== '\n') {
+ text += '\n'
+ }
+ // 递归获取子节点的文本
+ if (node.children) {
+ traversal(node.children)
+ }
+ if (isBlock && text[text.length - 1] !== '\n') {
+ text += '\n'
+ } else if (node.name === 'td' || node.name === 'th') {
+ text += '\t'
+ }
+ }
+ }
+ })(nodes || this.nodes)
+ return text
+ },
+
+ /**
+ * @description 获取内容大小和位置
+ * @return {Promise}
+ */
+ getRect () {
+ return new Promise((resolve, reject) => {
+ uni.createSelectorQuery()
+ // #ifndef MP-ALIPAY
+ .in(this)
+ // #endif
+ .select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
+ })
+ },
+
+ /**
+ * @description 暂停播放媒体
+ */
+ pauseMedia () {
+ for (let i = (this._videos || []).length; i--;) {
+ this._videos[i].pause()
+ }
+ // #ifdef APP-PLUS
+ const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
+ // #ifndef APP-PLUS-NVUE
+ let page = this.$parent
+ while (!page.$scope) page = page.$parent
+ page.$scope.$getAppWebview().evalJS(command)
+ // #endif
+ // #ifdef APP-PLUS-NVUE
+ this.$refs.web.evalJs(command)
+ // #endif
+ // #endif
+ },
+
+ /**
+ * @description 设置媒体播放速率
+ * @param {Number} rate 播放速率
+ */
+ setPlaybackRate (rate) {
+ this.playbackRate = rate
+ for (let i = (this._videos || []).length; i--;) {
+ this._videos[i].playbackRate(rate)
+ }
+ // #ifdef APP-PLUS
+ const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
+ // #ifndef APP-PLUS-NVUE
+ let page = this.$parent
+ while (!page.$scope) page = page.$parent
+ page.$scope.$getAppWebview().evalJS(command)
+ // #endif
+ // #ifdef APP-PLUS-NVUE
+ this.$refs.web.evalJs(command)
+ // #endif
+ // #endif
+ },
+
+ /**
+ * @description 设置内容
+ * @param {String} content html 内容
+ * @param {Boolean} append 是否在尾部追加
+ */
+ setContent (content, append) {
+ if (!append || !this.imgList) {
+ this.imgList = []
+ }
+ const nodes = new Parser(this).parse(content)
+ // #ifdef APP-PLUS-NVUE
+ if (this._ready) {
+ this._set(nodes, append)
+ }
+ // #endif
+ this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
+
+ // #ifndef APP-PLUS-NVUE
+ this._videos = []
+ this.$nextTick(() => {
+ this._hook('onLoad')
+ this.$emit('load')
+ })
+
+ if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
+ // 设置懒加载,每 350ms 获取高度,不变则认为加载完毕
+ let height = 0
+ const callback = rect => {
+ if (!rect || !rect.height) rect = {}
+ // 350ms 总高度无变化就触发 ready 事件
+ if (rect.height === height) {
+ this.$emit('ready', rect)
+ } else {
+ height = rect.height
+ setTimeout(() => {
+ this.getRect().then(callback).catch(callback)
+ }, 350)
+ }
+ }
+ this.getRect().then(callback).catch(callback)
+ } else {
+ // 未设置懒加载,等待所有图片加载完毕
+ if (!this.imgList._unloadimgs) {
+ this.getRect().then(rect => {
+ this.$emit('ready', rect)
+ }).catch(() => {
+ this.$emit('ready', {})
+ })
+ }
+ }
+ // #endif
+ },
+
+ /**
+ * @description 调用插件钩子函数
+ */
+ _hook (name) {
+ for (let i = plugins.length; i--;) {
+ if (this.plugins[i][name]) {
+ this.plugins[i][name]()
+ }
+ }
+ },
+
+ // #ifdef APP-PLUS-NVUE
+ /**
+ * @description 设置内容
+ */
+ _set (nodes, append) {
+ this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
+ },
+
+ /**
+ * @description 接收到 web-view 消息
+ */
+ _onMessage (e) {
+ const message = e.detail.data[0]
+ switch (message.action) {
+ // web-view 初始化完毕
+ case 'onJSBridgeReady':
+ this._ready = true
+ if (this.nodes) {
+ this._set(this.nodes)
+ }
+ break
+ // 内容 dom 加载完毕
+ case 'onLoad':
+ this.height = message.height
+ this._hook('onLoad')
+ this.$emit('load')
+ break
+ // 所有图片加载完毕
+ case 'onReady':
+ this.getRect().then(res => {
+ this.$emit('ready', res)
+ }).catch(() => {
+ this.$emit('ready', {})
+ })
+ break
+ // 总高度发生变化
+ case 'onHeightChange':
+ this.height = message.height
+ break
+ // 图片点击
+ case 'onImgTap':
+ this.$emit('imgtap', message.attrs)
+ if (this.previewImg) {
+ uni.previewImage({
+ current: parseInt(message.attrs.i),
+ urls: this.imgList
+ })
+ }
+ break
+ // 链接点击
+ case 'onLinkTap': {
+ const href = message.attrs.href
+ this.$emit('linktap', message.attrs)
+ if (href) {
+ // 锚点跳转
+ if (href[0] === '#') {
+ if (this.useAnchor) {
+ dom.scrollToElement(this.$refs.web, {
+ offset: message.offset
+ })
+ }
+ } else if (href.includes('://')) {
+ // 打开外链
+ if (this.copyLink) {
+ plus.runtime.openWeb(href)
+ }
+ } else {
+ uni.navigateTo({
+ url: href,
+ fail () {
+ uni.switchTab({
+ url: href
+ })
+ }
+ })
+ }
+ }
+ break
+ }
+ case 'onPlay':
+ this.$emit('play')
+ break
+ // 获取到锚点的偏移量
+ case 'getOffset':
+ if (typeof message.offset === 'number') {
+ dom.scrollToElement(this.$refs.web, {
+ offset: message.offset + this._navigateTo.offset
+ })
+ this._navigateTo.resolve()
+ } else {
+ this._navigateTo.reject(Error('Label not found'))
+ }
+ break
+ // 点击
+ case 'onClick':
+ this.$emit('tap')
+ this.$emit('click')
+ break
+ // 出错
+ case 'onError':
+ this.$emit('error', {
+ source: message.source,
+ attrs: message.attrs
+ })
+ }
+ }
+ // #endif
+ }
+}
+</script>
+
+<style>
+/* #ifndef APP-PLUS-NVUE */
+/* 根节点样式 */
+._root {
+ padding: 1px 0;
+ overflow-x: auto;
+ overflow-y: hidden;
+ -webkit-overflow-scrolling: touch;
+}
+
+/* 长按复制 */
+._select {
+ user-select: text;
+}
+/* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-parse/package.json b/ledApp/uni_modules/uv-parse/package.json
new file mode 100644
index 0000000..834499e
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-parse",
+ "displayName": "uv-parse 富文本解析器 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.4",
+ "description": "uv-parse 该组件一般用于富文本解析场景,比如解析文章内容,商品详情,带原生HTML标签的各类字符串等,此组件和uni-app官方的rich-text组件功能有重合之处,但是也有不同的地方。",
+ "keywords": [
+ "uv-parse",
+ "uvui",
+ "uv-ui",
+ "parse",
+ "富文本"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-parse/readme.md b/ledApp/uni_modules/uv-parse/readme.md
new file mode 100644
index 0000000..78737b1
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/readme.md
@@ -0,0 +1,21 @@
+## Parse 富文本解析器
+
+> **组件名:uv-parse**
+
+该组件一般用于富文本解析场景,比如解析文章内容,商品详情,带原生`HTML`标签的各类字符串等,此组件和`uni-app`官方的`rich-text`组件功能有重合之处,但是也有不同的地方。
+
+该插件只提供富文本的解析,该功能已经足够丰富。如果需要富文本的编辑,可使用`uniapp`官方提供的组件。
+
+# <a href="https://www.uvui.cn/components/parse.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/js/handler.js b/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/js/handler.js
new file mode 100644
index 0000000..9283832
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/js/handler.js
@@ -0,0 +1,224 @@
+'use strict'
+
+// 等待初始化完毕
+document.addEventListener('UniAppJSBridgeReady', () => {
+ document.body.onclick = function () {
+ return uni.postMessage({
+ data: {
+ action: 'onClick'
+ }
+ })
+ }
+
+ uni.postMessage({
+ data: {
+ action: 'onJSBridgeReady'
+ }
+ })
+})
+let options
+let medias = []
+/**
+ * @description 获取标签的所有属性
+ * @param {Element} ele
+ */
+
+function getAttrs(ele) {
+ const attrs = Object.create(null)
+
+ for (let i = ele.attributes.length; i--;) {
+ attrs[ele.attributes[i].name] = ele.attributes[i].value
+ }
+
+ return attrs
+}
+/**
+ * @description 图片加载出错
+ */
+
+function onImgError() {
+ if (options[1]) {
+ this.src = options[1]
+ this.onerror = null
+ } // 取消监听点击
+
+ this.onclick = null
+ this.ontouchstart = null
+ uni.postMessage({
+ data: {
+ action: 'onError',
+ source: 'img',
+ attrs: getAttrs(this)
+ }
+ })
+}
+/**
+ * @description 创建 dom 结构
+ * @param {object[]} nodes 节点数组
+ * @param {Element} parent 父节点
+ * @param {string} namespace 命名空间
+ */
+
+function createDom(nodes, parent, namespace) {
+ const _loop = function _loop(i) {
+ const node = nodes[i]
+ let ele = void 0
+
+ if (!node.type || node.type == 'node') {
+ let { name } = node // svg 需要设置 namespace
+
+ if (name == 'svg') namespace = 'http://www.w3.org/2000/svg'
+ if (name == 'html' || name == 'body') name = 'div' // 创建标签
+
+ if (!namespace) ele = document.createElement(name); else ele = document.createElementNS(namespace, name) // 设置属性
+
+ for (const item in node.attrs) {
+ ele.setAttribute(item, node.attrs[item])
+ } // 递归创建子节点
+
+ if (node.children) createDom(node.children, ele, namespace) // 处理图片
+
+ if (name == 'img') {
+ if (!ele.src && ele.getAttribute('data-src')) ele.src = ele.getAttribute('data-src')
+
+ if (!node.attrs.ignore) {
+ // 监听图片点击事件
+ ele.onclick = function (e) {
+ e.stopPropagation()
+ uni.postMessage({
+ data: {
+ action: 'onImgTap',
+ attrs: getAttrs(this)
+ }
+ })
+ }
+ }
+
+ if (options[2]) {
+ image = new Image()
+ image.src = ele.src
+ ele.src = options[2]
+
+ image.onload = function () {
+ ele.src = this.src
+ }
+
+ image.onerror = function () {
+ ele.onerror()
+ }
+ }
+
+ ele.onerror = onImgError
+ } // 处理链接
+ else if (name == 'a') {
+ ele.addEventListener('click', function (e) {
+ e.stopPropagation()
+ e.preventDefault() // 阻止默认跳转
+
+ const href = this.getAttribute('href')
+ let offset
+ if (href && href[0] == '#') offset = (document.getElementById(href.substr(1)) || {}).offsetTop
+ uni.postMessage({
+ data: {
+ action: 'onLinkTap',
+ attrs: getAttrs(this),
+ offset
+ }
+ })
+ }, true)
+ } // 处理音视频
+ else if (name == 'video' || name == 'audio') {
+ medias.push(ele)
+
+ if (!node.attrs.autoplay) {
+ if (!node.attrs.controls) ele.setAttribute('controls', 'true') // 空白图占位
+
+ if (!node.attrs.poster) ele.setAttribute('poster', "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>")
+ }
+
+ if (options[3]) {
+ ele.onplay = function () {
+ for (let _i = 0; _i < medias.length; _i++) {
+ if (medias[_i] != this) medias[_i].pause()
+ }
+ }
+ }
+
+ ele.onerror = function () {
+ uni.postMessage({
+ data: {
+ action: 'onError',
+ source: name,
+ attrs: getAttrs(this)
+ }
+ })
+ }
+ } // 处理表格
+ else if (name == 'table' && options[4] && !ele.style.cssText.includes('inline')) {
+ const div = document.createElement('div')
+ div.style.overflow = 'auto'
+ div.appendChild(ele)
+ ele = div
+ } else if (name == 'svg') namespace = void 0
+ } else ele = document.createTextNode(node.text.replace(/&/g, '&'))
+
+ parent.appendChild(ele)
+ }
+
+ for (let i = 0; i < nodes.length; i++) {
+ var image
+
+ _loop(i)
+ }
+} // 设置 html 内容
+
+window.setContent = function (nodes, opts, append) {
+ const ele = document.getElementById('content') // 背景颜色
+
+ if (opts[0]) document.body.bgColor = opts[0] // 长按复制
+
+ if (!opts[5]) ele.style.userSelect = 'none'
+
+ if (!append) {
+ ele.innerHTML = '' // 不追加则先清空
+
+ medias = []
+ }
+
+ options = opts
+ const fragment = document.createDocumentFragment()
+ createDom(nodes, fragment)
+ ele.appendChild(fragment) // 触发事件
+
+ let height = ele.scrollHeight
+ uni.postMessage({
+ data: {
+ action: 'onLoad',
+ height
+ }
+ })
+ clearInterval(window.timer)
+ let ready = false
+ window.timer = setInterval(() => {
+ if (ele.scrollHeight != height) {
+ height = ele.scrollHeight
+ uni.postMessage({
+ data: {
+ action: 'onHeightChange',
+ height
+ }
+ })
+ } else if (!ready) {
+ ready = true
+ uni.postMessage({
+ data: {
+ action: 'onReady'
+ }
+ })
+ }
+ }, 350)
+} // 回收计时器
+
+window.onunload = function () {
+ clearInterval(window.timer)
+}
diff --git a/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/js/uni.webview.min.js b/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/js/uni.webview.min.js
new file mode 100644
index 0000000..518332d
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/js/uni.webview.min.js
@@ -0,0 +1,19 @@
+!(function (e, n) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = n() : typeof define === 'function' && define.amd ? define(n) : (e = e || self).uni = n() }(this, (() => {
+ 'use strict'
+
+ try { const e = {}; Object.defineProperty(e, 'passive', { get() { !0 } }), window.addEventListener('test-passive', null, e) } catch (e) {} const n = Object.prototype.hasOwnProperty; function t(e, t) { return n.call(e, t) } const i = []; const a = function (e, n) { const t = { options: { timestamp: +new Date() }, name: e, arg: n }; if (window.__dcloud_weex_postMessage || window.__dcloud_weex_) { if (e === 'postMessage') { const a = { data: [n] }; return window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessage(a) : window.__dcloud_weex_.postMessage(JSON.stringify(a)) } const o = { type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }; window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessageToService(o) : window.__dcloud_weex_.postMessageToService(JSON.stringify(o)) } if (!window.plus) return window.parent.postMessage({ type: 'WEB_INVOKE_APPSERVICE', data: t, pageId: '' }, '*'); if (i.length === 0) { const r = plus.webview.currentWebview(); if (!r) throw new Error('plus.webview.currentWebview() is undefined'); const d = r.parent(); let s = ''; s = d ? d.id : r.id, i.push(s) } if (plus.webview.getWebviewById('__uniapp__service'))plus.webview.postMessageToUniNView({ type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }, '__uniapp__service'); else { const w = JSON.stringify(t); plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat('WEB_INVOKE_APPSERVICE', '",').concat(w, ',').concat(JSON.stringify(i), ');')) } }; const o = {
+ navigateTo() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('navigateTo', { url: encodeURI(n) }) }, navigateBack() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.delta; a('navigateBack', { delta: parseInt(n) || 1 }) }, switchTab() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('switchTab', { url: encodeURI(n) }) }, reLaunch() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('reLaunch', { url: encodeURI(n) }) }, redirectTo() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('redirectTo', { url: encodeURI(n) }) }, getEnv(e) { window.plus ? e({ plus: !0 }) : e({ h5: !0 }) }, postMessage() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; a('postMessage', e.data || {}) }
+ }; const r = /uni-app/i.test(navigator.userAgent); const d = /Html5Plus/i.test(navigator.userAgent); const s = /complete|loaded|interactive/; const w = window.my && navigator.userAgent.indexOf('AlipayClient') > -1; const u = window.swan && window.swan.webView && /swan/i.test(navigator.userAgent); const c = window.qq && window.qq.miniProgram && /QQ/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent); const g = window.tt && window.tt.miniProgram && /toutiaomicroapp/i.test(navigator.userAgent); const v = window.wx && window.wx.miniProgram && /micromessenger/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent); const p = window.qa && /quickapp/i.test(navigator.userAgent); for (var l, _ = function () { window.UniAppJSBridge = !0, document.dispatchEvent(new CustomEvent('UniAppJSBridgeReady', { bubbles: !0, cancelable: !0 })) }, f = [function (e) { if (r || d) return window.__dcloud_weex_postMessage || window.__dcloud_weex_ ? document.addEventListener('DOMContentLoaded', e) : window.plus && s.test(document.readyState) ? setTimeout(e, 0) : document.addEventListener('plusready', e), o }, function (e) { if (v) return window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('WeixinJSBridgeReady', e), window.wx.miniProgram }, function (e) { if (c) return window.QQJSBridge && window.QQJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QQJSBridgeReady', e), window.qq.miniProgram }, function (e) {
+ if (w) {
+ document.addEventListener('DOMContentLoaded', e); const n = window.my; return {
+ navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv
+ }
+ }
+ }, function (e) { if (u) return document.addEventListener('DOMContentLoaded', e), window.swan.webView }, function (e) { if (g) return document.addEventListener('DOMContentLoaded', e), window.tt.miniProgram }, function (e) {
+ if (p) {
+ window.QaJSBridge && window.QaJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QaJSBridgeReady', e); const n = window.qa; return {
+ navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv
+ }
+ }
+ }, function (e) { return document.addEventListener('DOMContentLoaded', e), o }], m = 0; m < f.length && !(l = f[m](_)); m++);l || (l = {}); const E = typeof uni !== 'undefined' ? uni : {}; if (!E.navigateTo) for (const b in l)t(l, b) && (E[b] = l[b]); return E.webView = l, E
+})))
diff --git a/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/local.html b/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/local.html
new file mode 100644
index 0000000..f78d703
--- /dev/null
+++ b/ledApp/uni_modules/uv-parse/static/app-plus/uv-parse/local.html
@@ -0,0 +1 @@
+<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}@keyframes show{0%{opacity:0}100%{opacity:1}}</style></head><body><div id="content"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-pick-color/changelog.md b/ledApp/uni_modules/uv-pick-color/changelog.md
new file mode 100644
index 0000000..7788c12
--- /dev/null
+++ b/ledApp/uni_modules/uv-pick-color/changelog.md
@@ -0,0 +1,18 @@
+## 1.0.8(2023-08-14)
+1. 取消scrollTop参数,使用clientY
+## 1.0.7(2023-08-13)
+1. 增加scrollTop参数,设置滚动条的位置。不设置如果页面出现滚动就需要传该值,会出现颜色面板无法进行选颜色的情况。
+## 1.0.6(2023-08-04)
+1. 修复颜色值错误的BUG
+## 1.0.5(2023-08-02)
+1. 更新依赖
+## 1.0.4(2023-07-02)
+uv-pick-color 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/pickColor.html
+## 1.0.3(2023-06-12)
+1. 修复在选择颜色时候百度小程序报错的BUG
+## 1.0.2(2023-05-27)
+1. 兼容非移动h5端不能选择的BUG
+## 1.0.1(2023-05-24)
+1. 去掉template中存在的this.导致头条小程序编译警告
+## 1.0.0(2023-05-23)
+uv-pick-color 新增颜色选择器
diff --git a/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/colors.js b/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/colors.js
new file mode 100644
index 0000000..cf804b2
--- /dev/null
+++ b/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/colors.js
@@ -0,0 +1,217 @@
+/**
+ * hsb 转 rgb
+ * @param {Object} hsb 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
+ */
+export function hsbToRgb(hsb) {
+ let rgb = {};
+ let h = hsb.h;
+ let s = hsb.s * 255 / 100;
+ let v = hsb.b * 255 / 100;
+ if (s == 0) {
+ rgb.r = rgb.g = rgb.b = v;
+ } else {
+ let t1 = v;
+ let t2 = ((255 - s) * v) / 255;
+ let t3 = ((t1 - t2) * (h % 60)) / 60;
+ if (h == 360) h = 0;
+ if (h < 60) {
+ rgb.r = t1;
+ rgb.b = t2;
+ rgb.g = t2 + t3;
+ } else if (h < 120) {
+ rgb.g = t1;
+ rgb.b = t2;
+ rgb.r = t1 - t3;
+ } else if (h < 180) {
+ rgb.g = t1;
+ rgb.r = t2;
+ rgb.b = t2 + t3;
+ } else if (h < 240) {
+ rgb.b = t1;
+ rgb.r = t2;
+ rgb.g = t1 - t3;
+ } else if (h < 300) {
+ rgb.b = t1;
+ rgb.g = t2;
+ rgb.r = t2 + t3;
+ } else if (h < 360) {
+ rgb.r = t1;
+ rgb.g = t2;
+ rgb.b = t1 - t3;
+ } else {
+ rgb.r = 0;
+ rgb.g = 0;
+ rgb.b = 0;
+ }
+ }
+ return {
+ r: Math.round(rgb.r),
+ g: Math.round(rgb.g),
+ b: Math.round(rgb.b)
+ };
+}
+/**
+ * rgb转hsb
+ * @param {Object} rgb 颜色rgb值
+ */
+export function rgbToHsb(rgb) {
+ let hsb = {
+ h: 0,
+ s: 0,
+ b: 0
+ };
+ let h = 0,
+ s = 0,
+ v = 0;
+ let r = rgb.r,
+ g = rgb.g,
+ b = rgb.b;
+ let min = Math.min(rgb.r, rgb.g, rgb.b);
+ let max = Math.max(rgb.r, rgb.g, rgb.b);
+ v = max / 255;
+ if (max === 0) {
+ s = 0;
+ } else {
+ s = 1 - (min / max);
+ }
+ if (max === min) {
+ h = 0; //事实上,max===min的时候,h无论为多少都无所谓
+ } else if (max === r && g >= b) {
+ h = 60 * ((g - b) / (max - min)) + 0;
+ } else if (max === r && g < b) {
+ h = 60 * ((g - b) / (max - min)) + 360
+ } else if (max === g) {
+ h = 60 * ((b - r) / (max - min)) + 120
+ } else if (max === b) {
+ h = 60 * ((r - g) / (max - min)) + 240
+ }
+ hsb.h = parseInt(h);
+ hsb.s = parseInt(s * 100);
+ hsb.b = parseInt(v * 100);
+ return hsb;
+}
+/**
+ * rgb 转 二进制 hex
+ * @param {Object} rgb
+ */
+export function rgbToHex(rgb) {
+ let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
+ hex.map(function(str, i) {
+ if (str.length == 1) {
+ hex[i] = '0' + str;
+ }
+ });
+ return hex.join('');
+}
+//预制颜色
+export const colorList = [{
+ r: 60,
+ g: 156,
+ b: 255,
+ a: 1
+}, {
+ r: 245,
+ g: 108,
+ b: 108,
+ a: 1
+}, {
+ r: 249,
+ g: 174,
+ b: 61,
+ a: 1
+}, {
+ r: 90,
+ g: 199,
+ b: 37,
+ a: 1
+}, {
+ r: 144,
+ g: 147,
+ b: 153,
+ a: 1
+}, {
+ r: 48,
+ g: 49,
+ b: 51,
+ a: 1
+}, {
+ r: 233,
+ g: 30,
+ b: 99,
+ a: 1
+}, {
+ r: 156,
+ g: 39,
+ b: 176,
+ a: 1
+}, {
+ r: 103,
+ g: 58,
+ b: 183,
+ a: 1
+}, {
+ r: 63,
+ g: 81,
+ b: 181,
+ a: 1
+}, {
+ r: 0,
+ g: 188,
+ b: 212,
+ a: 1
+}, {
+ r: 0,
+ g: 150,
+ b: 136,
+ a: 1
+}, {
+ r: 139,
+ g: 195,
+ b: 74,
+ a: 1
+}, {
+ r: 205,
+ g: 220,
+ b: 57,
+ a: 1
+}, {
+ r: 255,
+ g: 235,
+ b: 59,
+ a: 1
+}, {
+ r: 255,
+ g: 193,
+ b: 7,
+ a: 1
+}, {
+ r: 255,
+ g: 152,
+ b: 0,
+ a: 1
+}, {
+ r: 255,
+ g: 87,
+ b: 34,
+ a: 1
+}, {
+ r: 121,
+ g: 85,
+ b: 72,
+ a: 1
+}, {
+ r: 158,
+ g: 158,
+ b: 158,
+ a: 1
+}, {
+ r: 0,
+ g: 0,
+ b: 0,
+ a: 0.5
+}, {
+ r: 0,
+ g: 0,
+ b: 0,
+ a: 0
+}]
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/props.js b/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/props.js
new file mode 100644
index 0000000..a57f063
--- /dev/null
+++ b/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/props.js
@@ -0,0 +1,47 @@
+export default {
+ props: {
+ // 颜色选择器初始颜色
+ color: {
+ type: Object,
+ default: () => {
+ return { r: 0, g: 0, b: 0, a: 0 }
+ }
+ },
+ // 预制颜色
+ prefabColor: {
+ type: Array,
+ default: () => []
+ },
+ // 是否允许点击遮罩关闭
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: true
+ },
+ // 顶部标题
+ title: {
+ type: String,
+ default: ''
+ },
+ // 取消按钮的文字
+ cancelText: {
+ type: String,
+ default: '取消'
+ },
+ // 确认按钮的文字
+ confirmText: {
+ type: String,
+ default: '确定'
+ },
+ // 取消按钮的颜色
+ cancelColor: {
+ type: String,
+ default: '#909193'
+ },
+ // 确认按钮的颜色
+ confirmColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ ...uni.$uv?.props?.pickColor
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/uv-pick-color.vue b/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/uv-pick-color.vue
new file mode 100644
index 0000000..f283a92
--- /dev/null
+++ b/ledApp/uni_modules/uv-pick-color/components/uv-pick-color/uv-pick-color.vue
@@ -0,0 +1,551 @@
+<template>
+ <!-- #ifndef APP-NVUE -->
+ <uv-popup
+ ref="pickerColorPopup"
+ mode="bottom"
+ :close-on-click-overlay="closeOnClickOverlay"
+ @change="popupChange">
+ <view class="uv-pick-color">
+ <uv-toolbar
+ :show="showToolbar"
+ :cancelColor="cancelColor"
+ :confirmColor="confirmColor"
+ :cancelText="cancelText"
+ :confirmText="confirmText"
+ :title="title"
+ :show-border="true"
+ @cancel="cancelHandler"
+ @confirm="confirmHandler"></uv-toolbar>
+ <view
+ class="uv-pick-color__box"
+ :style="{
+ background:`rgb(${bgcolor.r},${bgcolor.g},${bgcolor.b})`
+ }"
+ >
+ <!-- #ifdef H5 -->
+ <view
+ class="uv-pick-color__box__bg drag-box"
+ @tap.stop.prevent="touchstart($event, 0)"
+ @touchstart.stop.prevent="touchstart($event, 0)"
+ @touchmove.stop.prevent="touchmove($event, 0)"
+ @touchend.stop.prevent="touchend($event, 0)"
+ >
+ <!-- #endif -->
+ <!-- #ifndef H5 -->
+ <view
+ class="uv-pick-color__box__bg drag-box"
+ @touchstart.stop.prevent="touchstart($event, 0)"
+ @touchmove.stop.prevent="touchmove($event, 0)"
+ @touchend.stop.prevent="touchend($event, 0)"
+ >
+ <!-- #endif -->
+ <view class="uv-pick-color__box__bg-mask"></view>
+ <view
+ class="uv-pick-color__box__bg-pointer"
+ :style="[pointerStyle]"
+ >
+ </view>
+ </view>
+ </view>
+ <view class="uv-pick-color__control">
+ <view class="uv-pick-color__control__alpha">
+ <view
+ class="uv-pick-color__control__alpha--color"
+ :style="{ background:`rgba(${rgba.r},${rgba.g},${rgba.b},${rgba.a})` }"
+ ></view>
+ </view>
+ <view class="uv-pick-color__control__item">
+ <!-- #ifdef H5 -->
+ <view
+ class="uv-pick-color__control__item__drag drag-box"
+ @tap.stop="touchstart($event, 1)"
+ @touchstart.stop="touchstart($event, 1)"
+ @touchmove.stop="touchmove($event, 1)"
+ @touchend.stop="touchend($event, 1)"
+ >
+ <!-- #endif -->
+ <!-- #ifndef H5 -->
+ <view
+ class="uv-pick-color__control__item__drag drag-box"
+ @touchstart.stop="touchstart($event, 1)"
+ @touchmove.stop="touchmove($event, 1)"
+ @touchend.stop="touchend($event, 1)"
+ >
+ <!-- #endif -->
+ <view class="uv-pick-color__control__item__drag--hue"></view>
+ <view
+ class="uv-pick-color__control__item__drag--circle"
+ :style="{
+ left: $uv.getPx(site[1].left - 10,true)
+ }"
+ ></view>
+ </view>
+ <!-- #ifdef H5 -->
+ <view
+ class="uv-pick-color__control__item__drag drag-box"
+ @tap.stop="touchstart($event, 2)"
+ @touchstart.stop="touchstart($event, 2)"
+ @touchmove.stop="touchmove($event, 2)"
+ @touchend.stop="touchend($event, 2)"
+ >
+ <!-- #endif -->
+ <!-- #ifndef H5 -->
+ <view
+ class="uv-pick-color__control__item__drag drag-box"
+ @touchstart.stop="touchstart($event, 2)"
+ @touchmove.stop="touchmove($event, 2)"
+ @touchend.stop="touchend($event, 2)"
+ >
+ <!-- #endif -->
+ <view class="uv-pick-color__control__item__drag--alpha"></view>
+ <view
+ class="uv-pick-color__control__item__drag--circle"
+ :style="{
+ left: $uv.getPx(site[2].left - 10,true)
+ }"
+ ></view>
+ </view>
+ </view>
+ </view>
+ <view class="uv-pick-color__result">
+ <view
+ class="uv-pick-color__result__select"
+ hover-class="uv-hover-class"
+ @click.stop="select"
+ >
+ <text class="text">切换</text>
+ <text class="text">模式</text>
+ </view>
+ <view class="uv-pick-color__result__item" v-if="mode">
+ <view class="uv-pick-color__result__item--value uv-border">
+ <text>{{hex}}</text>
+ </view>
+ <view class="uv-pick-color__result__item--hex">
+ <text>HEX</text>
+ </view>
+ </view>
+ <template v-else>
+ <view class="uv-pick-color__result__item">
+ <view class="uv-pick-color__result__item--value uv-border">
+ <text>{{rgba.r}}</text>
+ </view>
+ <view class="uv-pick-color__result__item--rgba">
+ <text>R</text>
+ </view>
+ </view>
+ <view class="uv-pick-color__result__gap"></view>
+ <view class="uv-pick-color__result__item">
+ <view class="uv-pick-color__result__item--value uv-border">
+ <text>{{rgba.g}}</text>
+ </view>
+ <view class="uv-pick-color__result__item--rgba">
+ <text>G</text>
+ </view>
+ </view>
+ <view class="uv-pick-color__result__gap"></view>
+ <view class="uv-pick-color__result__item">
+ <view class="uv-pick-color__result__item--value uv-border">
+ <text>{{rgba.b}}</text>
+ </view>
+ <view class="uv-pick-color__result__item--rgba">
+ <text>B</text>
+ </view>
+ </view>
+ <view class="uv-pick-color__result__gap"></view>
+ <view class="uv-pick-color__result__item">
+ <view class="uv-pick-color__result__item--value uv-border">
+ <text>{{rgba.a}}</text>
+ </view>
+ <view class="uv-pick-color__result__item--rgba">
+ <text>A</text>
+ </view>
+ </view>
+ </template>
+ </view>
+ <view class="uv-pick-color__prefab">
+ <view
+ class="uv-pick-color__prefab__item"
+ v-for="(item,index) in colorList"
+ :key="index"
+ >
+ <view
+ class="uv-pick-color__prefab__item--color"
+ :style="{ background:`rgba(${item.r},${item.g},${item.b},${item.a})` }"
+ @click.stop="setColorBySelect(item)"
+ ></view>
+ </view>
+ </view>
+ </view>
+ </uv-popup>
+ <!-- #endif -->
+ <!-- #ifdef APP-NVUE -->
+ <view>
+ <text>nvue暂不支持uv-pick-color组件</text>
+ </view>
+ <!-- #endif -->
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';
+ import { rgbToHsb, hsbToRgb, rgbToHex, colorList } from './colors.js';
+ import props from './props.js';
+ export default {
+ name: 'uv-pick-color',
+ emits: ['confirm', 'cancel', 'close', 'change'],
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ pointerStyle() {
+ const style = {};
+ style.top = this.$uv.addUnit(this.site[0].top - 8);
+ style.left = this.$uv.addUnit(this.site[0].left - 8);
+ return style;
+ }
+ },
+ data() {
+ return {
+ showToolbar: false,
+ // rgba 颜色
+ rgba: {
+ r: 0,
+ g: 0,
+ b: 0,
+ a: 1
+ },
+ // hsb 颜色
+ hsb: {
+ h: 0,
+ s: 0,
+ b: 0
+ },
+ site: [{
+ top: 0,
+ left: 0
+ }, {
+ left: 0
+ }, {
+ left: 0
+ }],
+ index: 0,
+ bgcolor: {
+ r: 255,
+ g: 0,
+ b: 0,
+ a: 1
+ },
+ hex: '#000000',
+ mode: true,
+ colorList: colorList
+ };
+ },
+ watch: {
+ prefabColor(newVal) {
+ this.colorList = newVal;
+ }
+ },
+ created() {
+ // #ifdef APP-NVUE
+ return this.$uv.error('nvue暂不支持uv-pick-color组件');
+ // #endif
+ this.rgba = this.color;
+ if (this.prefabColor.length) this.colorList = this.prefabColor;
+ },
+ methods: {
+ open() {
+ this.$refs.pickerColorPopup.open();
+ this.showToolbar = true;
+ this.$nextTick(async () => {
+ await this.$uv.sleep(350);
+ this.getSelectorQuery();
+ })
+ },
+ close() {
+ this.$refs.pickerColorPopup.close();
+ },
+ popupChange(e) {
+ if(!e.show) this.$emit('close');
+ },
+ // 点击工具栏的取消按钮
+ cancelHandler() {
+ this.$emit('cancel');
+ this.close();
+ },
+ // 点击工具栏的确定按钮
+ confirmHandler() {
+ this.$emit('confirm', {
+ rgba: this.rgba,
+ hex: this.hex
+ })
+ this.close();
+ },
+ // 初始化
+ init() {
+ // hsb 颜色
+ this.hsb = rgbToHsb(this.rgba);
+ this.setValue(this.rgba);
+ },
+ async getSelectorQuery() {
+ const data = await this.$uvGetRect('.drag-box',true);
+ this.position = data;
+ this.setColorBySelect(this.rgba);
+ },
+ // 选择模式
+ select() {
+ this.mode = !this.mode;
+ },
+ touchstart(e, index) {
+ const { clientX, clientY } = e.touches[0];
+ this.pageX = clientX;
+ this.pageY = clientY;
+ this.setPosition(clientX, clientY, index);
+ },
+ touchmove(e, index) {
+ const { clientX, clientY } = e.touches[0];
+ this.moveX = clientX;
+ this.moveY = clientY;
+ this.setPosition(clientX, clientY, index);
+ },
+ touchend(e, index) {},
+ /**
+ * 设置位置
+ */
+ setPosition(x, y, index) {
+ this.index = index;
+ const { top, left, width, height } = this.position[index];
+ // 设置最大最小值
+ this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));
+ if (index === 0) {
+ this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));
+ // 设置颜色
+ this.hsb.s = parseInt((100 * this.site[index].left) / width);
+ this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);
+ this.setColor();
+ this.setValue(this.rgba);
+ } else {
+ this.setControl(index, this.site[index].left);
+ }
+ },
+ /**
+ * 设置 rgb 颜色
+ */
+ setColor() {
+ const rgb = hsbToRgb(this.hsb);
+ this.rgba.r = rgb.r;
+ this.rgba.g = rgb.g;
+ this.rgba.b = rgb.b;
+ },
+ /**
+ * 设置二进制颜色
+ * @param {Object} rgb
+ */
+ setValue(rgb) {
+ this.hex = `#${(rgbToHex(rgb))}`;
+ },
+ setControl(index, x) {
+ const {
+ top,
+ left,
+ width,
+ height
+ } = this.position[index];
+
+ if (index === 1) {
+ this.hsb.h = parseInt((360 * x) / width);
+ this.bgcolor = hsbToRgb({
+ h: this.hsb.h,
+ s: 100,
+ b: 100
+ });
+ this.setColor()
+ } else {
+ this.rgba.a = +(x / width).toFixed(1);
+ }
+ this.setValue(this.rgba);
+ },
+ setColorBySelect(getrgb) {
+ const { r, g, b, a } = getrgb;
+ let rgb = {};
+ rgb = {
+ r: r ? parseInt(r) : 0,
+ g: g ? parseInt(g) : 0,
+ b: b ? parseInt(b) : 0,
+ a: a ? a : 0
+ };
+ this.rgba = rgb;
+ this.hsb = rgbToHsb(rgb);
+ this.changeViewByHsb();
+ },
+ changeViewByHsb() {
+ const [a, b, c] = this.position;
+ this.site[0].left = parseInt(this.hsb.s * a.width / 100);
+ this.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);
+ this.setColor(this.hsb.h);
+ this.setValue(this.rgba);
+ this.bgcolor = hsbToRgb({
+ h: this.hsb.h,
+ s: 100,
+ b: 100
+ });
+ this.site[1].left = this.hsb.h / 360 * b.width;
+ this.site[2].left = this.rgba.a * c.width;
+ }
+ }
+ };
+</script>
+<style scoped lang="scss">
+ $show-border: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ $height: 400rpx;
+ /* #ifndef APP-NVUE */
+ .uv-pick-color {
+ &__box {
+ position: relative;
+ height: $height;
+ background: rgb(255, 0, 0);
+ margin: 20rpx;
+ &__bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
+ &-mask {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: $height;
+ background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
+ }
+ &-pointer {
+ position: absolute;
+ top: -8px;
+ left: -8px;
+ z-index: 2;
+ width: 16px;
+ height: 16px;
+ border: 1px #fff solid;
+ border-radius: 8px;
+ }
+ }
+ }
+ &__control {
+ @include flex;
+ padding: 10rpx 20rpx;
+ &__alpha {
+ width: 100rpx;
+ height: 100rpx;
+ border-radius: 50rpx;
+ background-color: #fff;
+ background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+ background-size: 36rpx 36rpx;
+ background-position: 0 0, 18rpx 18rpx;
+ border: 1px #eee solid;
+ overflow: hidden;
+ &--color {
+ width: 100%;
+ height: 100%;
+ }
+ }
+ &__item {
+ flex: 1;
+ @include flex(column);
+ justify-content: space-between;
+ height: 100rpx;
+ padding: 6rpx 0 6rpx 30rpx;
+ }
+ &__item__drag {
+ position: relative;
+ height: 16px;
+ background-color: #fff;
+ background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
+ linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+ background-size: 32rpx 32rpx;
+ background-position: 0 0, 16rpx 16rpx;
+ &--hue {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
+ }
+ &--alpha {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
+ }
+ &--circle {
+ position: absolute;
+ top: -2px;
+ width: 20px;
+ height: 20px;
+ box-sizing: border-box;
+ border-radius: 10px;
+ background: #fff;
+ box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
+ }
+ }
+ }
+ &__result {
+ @include flex;
+ padding: 20rpx;
+ text-align: center;
+ &__select {
+ @include flex(column);
+ justify-content: center;
+ width: 100rpx;
+ height: 100rpx;
+ margin-right: 10px;
+ border-radius: 10rpx;
+ box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
+ font-size: 14px;
+ color: #999;
+ }
+ &__item {
+ flex: 1;
+ height: 100rpx;
+ &--value {
+ height: 50rpx;
+ line-height: 50rpx;
+ border-radius: 4rpx;
+ font-size: 28rpx;
+ color: #999;
+ }
+ &--hex,
+ &--rgba {
+ height: 50rpx;
+ line-height: 50rpx;
+ font-size: 30rpx;
+ }
+ }
+ &__gap {
+ width: 10px;
+ height: 10px;
+ }
+ }
+ &__prefab {
+ @include flex;
+ margin: 0 -8rpx;
+ padding: 0 20rpx 20rpx;
+ flex-wrap: wrap;
+ &__item {
+ width: 50rpx;
+ height: 50rpx;
+ margin: 8rpx;
+ border-radius: 6rpx;
+ background-color: #fff;
+ background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
+ linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
+ background-size: 36rpx 36rpx;
+ background-position: 0 0, 18rpx 18rpx;
+ border: 1px #eee solid;
+ &--color {
+ width: 100%;
+ height: 100%;
+ border-radius: 6rpx;
+ }
+ }
+ }
+ }
+ /* #endif */
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-pick-color/package.json b/ledApp/uni_modules/uv-pick-color/package.json
new file mode 100644
index 0000000..21a6b9a
--- /dev/null
+++ b/ledApp/uni_modules/uv-pick-color/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-pick-color",
+ "displayName": "uv-pick-color 颜色选择器 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.8",
+ "description": "该组件为颜色选择器,支持预制颜色,初始化颜色,简单配置,开箱即用",
+ "keywords": [
+ "uv-pick-color",
+ "uvui",
+ "uv-ui",
+ "颜色",
+ "color"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-popup",
+ "uv-toolbar"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "u"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-pick-color/readme.md b/ledApp/uni_modules/uv-pick-color/readme.md
new file mode 100644
index 0000000..c4accf5
--- /dev/null
+++ b/ledApp/uni_modules/uv-pick-color/readme.md
@@ -0,0 +1,19 @@
+## PickColor 颜色选择器
+
+> **组件名:uv-pick-color**
+
+该组件为颜色选择器,支持预制颜色,初始化颜色,简单配置,开箱即用。
+
+# <a href="https://www.uvui.cn/components/pickColor.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-picker/changelog.md b/ledApp/uni_modules/uv-picker/changelog.md
new file mode 100644
index 0000000..532be9b
--- /dev/null
+++ b/ledApp/uni_modules/uv-picker/changelog.md
@@ -0,0 +1,33 @@
+## 1.0.14(2023-12-29)
+1. 修复上个版本引出的BUG
+## 1.0.13(2023-12-26)
+1. 修复抖音小程序滚到底不触发change的BUG
+## 1.0.12(2023-11-20)
+1. 修复issues反馈的问题uv-picker在组合式API的自定义组件中,columns动态赋值无法显示选项:https://gitee.com/climblee/uv-ui/issues/I8H0GQ
+## 1.0.11(2023-10-11)
+1. 将immediate-change默认值改为true,该值在于change回调的及时性,微信小程序生效
+## 1.0.10(2023-08-25)
+1. 增加round属性设置弹窗圆角,默认为0
+## 1.0.9(2023-08-24)
+1. 修复cli项目不返回值的问题
+## 1.0.8(2023-08-04)
+1. 优化
+## 1.0.7(2023-08-02)
+1. 改组件中删除uv-toolbar组件,请单独下载uv-toolbar组件
+## 1.0.6(2023-07-02)
+uv-picker 由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/picker.html
+## 1.0.5(2023-06-26)
+1. 增加color参数
+2. 增加activeColor参数
+## 1.0.4(2023-06-15)
+1. 修改支付宝报错的BUG
+## 1.0.3(2023-06-12)
+1. setColumnValues的使用统一化,避免某些平台报错
+2. 取消change回调回传的组件实例,直接统一通过ref的方式调取setColumnValues方法
+## 1.0.2(2023-05-23)
+1. uv-toolbar组件新增下边框属性
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-picker 选择器
diff --git a/ledApp/uni_modules/uv-picker/components/uv-picker/props.js b/ledApp/uni_modules/uv-picker/components/uv-picker/props.js
new file mode 100644
index 0000000..19549a6
--- /dev/null
+++ b/ledApp/uni_modules/uv-picker/components/uv-picker/props.js
@@ -0,0 +1,95 @@
+export default {
+ props: {
+ // 是否展示顶部的操作栏
+ showToolbar: {
+ type: Boolean,
+ default: true
+ },
+ // 顶部标题
+ title: {
+ type: String,
+ default: ''
+ },
+ // 弹窗圆角
+ round: {
+ type: [String, Number],
+ default: 0
+ },
+ // 对象数组,设置每一列的数据
+ columns: {
+ type: Array,
+ default: () => []
+ },
+ // 是否显示加载中状态
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ // 各列中,单个选项的高度
+ itemHeight: {
+ type: [String, Number],
+ default: 44
+ },
+ // 取消按钮的文字
+ cancelText: {
+ type: String,
+ default: '取消'
+ },
+ // 确认按钮的文字
+ confirmText: {
+ type: String,
+ default: '确定'
+ },
+ // 取消按钮的颜色
+ cancelColor: {
+ type: String,
+ default: '#909193'
+ },
+ // 确认按钮的颜色
+ confirmColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 文字颜色
+ color: {
+ type: String,
+ default: ''
+ },
+ // 选中文字的颜色
+ activeColor: {
+ type: String,
+ default: ''
+ },
+ // 每列中可见选项的数量
+ visibleItemCount: {
+ type: [String, Number],
+ default: 5
+ },
+ // 选项对象中,需要展示的属性键名
+ keyName: {
+ type: String,
+ default: 'text'
+ },
+ // 是否允许点击遮罩关闭选择器
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: true
+ },
+ // 是否允许点击确认关闭选择器
+ closeOnClickConfirm: {
+ type: Boolean,
+ default: true
+ },
+ // 各列的默认索引
+ defaultIndex: {
+ type: Array,
+ default: () => [],
+ },
+ // 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件,只在微信2.21.1及以上有效
+ immediateChange: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.picker
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-picker/components/uv-picker/uv-picker.vue b/ledApp/uni_modules/uv-picker/components/uv-picker/uv-picker.vue
new file mode 100644
index 0000000..c5e7c8a
--- /dev/null
+++ b/ledApp/uni_modules/uv-picker/components/uv-picker/uv-picker.vue
@@ -0,0 +1,330 @@
+<template>
+ <uv-popup
+ ref="pickerPopup"
+ mode="bottom"
+ :round="round"
+ :close-on-click-overlay="closeOnClickOverlay"
+ @change="popupChange"
+ >
+ <view class="uv-picker">
+ <uv-toolbar
+ v-if="showToolbar"
+ :cancelColor="cancelColor"
+ :confirmColor="confirmColor"
+ :cancelText="cancelText"
+ :confirmText="confirmText"
+ :title="title"
+ @cancel="cancel"
+ @confirm="confirm"
+ ></uv-toolbar>
+ <!-- #ifdef MP-TOUTIAO -->
+ <picker-view
+ class="uv-picker__view"
+ :indicatorStyle="`height: ${$uv.addUnit(itemHeight)}`"
+ :value="innerIndex"
+ :immediateChange="immediateChange"
+ :style="{
+ height: `${$uv.addUnit(visibleItemCount * itemHeight)}`
+ }"
+ @pickend="changeHandler"
+ >
+ <!-- #endif -->
+ <!-- #ifndef MP-TOUTIAO -->
+ <picker-view
+ class="uv-picker__view"
+ :indicatorStyle="`height: ${$uv.addUnit(itemHeight)}`"
+ :value="innerIndex"
+ :immediateChange="immediateChange"
+ :style="{
+ height: `${$uv.addUnit(visibleItemCount * itemHeight)}`
+ }"
+ @change="changeHandler"
+ >
+ <!-- #endif -->
+ <!-- @pickend在这里为了解决抖音等滚到底不触发change兼容性问题 -->
+ <picker-view-column
+ v-for="(item, index) in innerColumns"
+ :key="index"
+ class="uv-picker__view__column"
+ >
+ <text
+ v-if="$uv.test.array(item)"
+ class="uv-picker__view__column__item uv-line-1"
+ v-for="(item1, index1) in item"
+ :key="index1"
+ :style="[{
+ height: $uv.addUnit(itemHeight),
+ lineHeight: $uv.addUnit(itemHeight),
+ fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal'
+ },textStyle(index,index1)]"
+ >{{ getItemText(item1) }}</text>
+ </picker-view-column>
+ </picker-view>
+ <view
+ v-if="loading"
+ class="uv-picker--loading"
+ >
+ <uv-loading-icon mode="circle"></uv-loading-icon>
+ </view>
+ </view>
+ </uv-popup>
+</template>
+
+<script>
+/**
+ * uv-picker
+ * @description 选择器
+ * @property {Boolean} showToolbar 是否显示顶部的操作栏(默认 true )
+ * @property {String} title 顶部标题
+ * @property {Array} columns 对象数组,设置每一列的数据
+ * @property {Boolean} loading 是否显示加载中状态(默认 false )
+ * @property {String | Number} itemHeight 各列中,单个选项的高度(默认 44 )
+ * @property {String} cancelText 取消按钮的文字(默认 '取消' )
+ * @property {String} confirmText 确认按钮的文字(默认 '确定' )
+ * @property {String} cancelColor 取消按钮的颜色(默认 '#909193' )
+ * @property {String} confirmColor 确认按钮的颜色(默认 '#3c9cff' )
+ * @property {String} color 文字颜色(默认 '' )
+ * @property {String} activeColor 选中文字的颜色(默认 '' )
+ * @property {String | Number} visibleItemCount 每列中可见选项的数量(默认 5 )
+ * @property {String} keyName 选项对象中,需要展示的属性键名(默认 'text' )
+ * @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭选择器(默认 false )
+ * @property {Array} defaultIndex 各列的默认索引
+ * @property {Boolean} immediateChange 是否在手指松开时立即触发change事件(默认 false )
+ * @event {Function} close 关闭选择器时触发
+ * @event {Function} cancel 点击取消按钮触发
+ * @event {Function} change 当选择值变化时触发
+ * @event {Function} confirm 点击确定按钮,返回当前选择的值
+ */
+import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+import props from './props.js';
+export default {
+ name: 'uv-picker',
+ emits: ['confirm','cancel','close','change'],
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ // 为了解决支付宝不生效
+ textStyle(){
+ return (index,index1) => {
+ const style = {};
+ // #ifndef APP-NVUE
+ style.display = 'block';
+ // #endif
+ if(this.color) {
+ style.color = this.color;
+ }
+ if(this.activeColor && index1 === this.innerIndex[index]) {
+ style.color = this.activeColor;
+ }
+ return style;
+ }
+ }
+ },
+ data() {
+ return {
+ // 上一次选择的列索引
+ lastIndex: [],
+ // 索引值 ,对应picker-view的value
+ innerIndex: [],
+ // 各列的值
+ innerColumns: [],
+ // 上一次的变化列索引
+ columnIndex: 0,
+ }
+ },
+ watch: {
+ // 监听默认索引的变化,重新设置对应的值
+ defaultIndex: {
+ immediate: true,
+ handler(n) {
+ this.setIndexs(n, true)
+ }
+ },
+ // 监听columns参数的变化
+ columns: {
+ deep: true,
+ immediate: true,
+ handler(n) {
+ this.setColumns(n)
+ }
+ },
+ },
+ methods: {
+ open() {
+ this.$refs.pickerPopup.open();
+ },
+ close() {
+ this.$refs.pickerPopup.close();
+ },
+ popupChange(e) {
+ if(!e.show) this.$emit('close');
+ },
+ // 获取item需要显示的文字,判别为对象还是文本
+ getItemText(item) {
+ if (this.$uv.test.object(item)) {
+ return item[this.keyName]
+ } else {
+ return item
+ }
+ },
+ // 点击工具栏的取消按钮
+ cancel() {
+ this.$emit('cancel');
+ this.close();
+ },
+ // 点击工具栏的确定按钮
+ confirm() {
+ // 在这里使用deepClone拷贝后,vue3会自动转换成原始对象,这样处理是因为cli项目可能出现不返回值的情况
+ this.$emit('confirm', this.$uv.deepClone({
+ indexs: this.innerIndex,
+ value: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),
+ values: this.innerColumns
+ }));
+ if(this.closeOnClickConfirm) {
+ this.close();
+ }
+ },
+ // 选择器某一列的数据发生变化时触发
+ changeHandler(e) {
+ const {
+ value
+ } = e.detail
+ let index = 0,
+ columnIndex = 0
+ // 通过对比前后两次的列索引,得出当前变化的是哪一列
+ for (let i = 0; i < value.length; i++) {
+ let item = value[i]
+ if (item !== (this.lastIndex[i] || 0)) { // 把undefined转为合法假值0
+ // 设置columnIndex为当前变化列的索引
+ columnIndex = i
+ // index则为变化列中的变化项的索引
+ index = item
+ break // 终止循环,即使少一次循环,也是性能的提升
+ }
+ }
+ this.columnIndex = columnIndex
+ const values = this.innerColumns
+ // 将当前的各项变化索引,设置为"上一次"的索引变化值
+ this.setLastIndex(value)
+ this.setIndexs(value)
+
+ this.$emit('change', {
+ value: this.innerColumns.map((item, index) => item[value[index]]),
+ index,
+ indexs: value,
+ // values为当前变化列的数组内容
+ values,
+ columnIndex
+ })
+ },
+ // 设置index索引,此方法可被外部调用设置
+ setIndexs(index, setLastIndex) {
+ this.innerIndex = this.$uv.deepClone(index)
+ if (setLastIndex) {
+ this.setLastIndex(index)
+ }
+ },
+ // 记录上一次的各列索引位置
+ setLastIndex(index) {
+ // 当能进入此方法,意味着当前设置的各列默认索引,即为“上一次”的选中值,需要记录,是因为changeHandler中
+ // 需要拿前后的变化值进行对比,得出当前发生改变的是哪一列
+ this.lastIndex = this.$uv.deepClone(index)
+ },
+ // 设置对应列选项的所有值
+ setColumnValues(columnIndex, values) {
+ // 替换innerColumns数组中columnIndex索引的值为values,使用的是数组的splice方法
+ this.innerColumns.splice(columnIndex, 1, values)
+ // 拷贝一份原有的innerIndex做临时变量,将大于当前变化列的所有的列的默认索引设置为0
+ let tmpIndex = this.$uv.deepClone(this.innerIndex)
+ for (let i = 0; i < this.innerColumns.length; i++) {
+ if (i > this.columnIndex) {
+ tmpIndex[i] = 0
+ }
+ }
+ // 一次性赋值,不能单个修改,否则无效
+ this.setIndexs(tmpIndex)
+ },
+ // 获取对应列的所有选项
+ getColumnValues(columnIndex) {
+ // 进行同步阻塞,因为外部得到change事件之后,可能需要执行setColumnValues更新列的值
+ // 索引如果在外部change的回调中调用getColumnValues的话,可能无法得到变更后的列值,这里进行一定延时,保证值的准确性
+ (async () => {
+ await this.$uv.sleep()
+ })()
+ return this.innerColumns[columnIndex]
+ },
+ // 设置整体各列的columns的值
+ setColumns(columns) {
+ this.innerColumns = this.$uv.deepClone(columns)
+ // 如果在设置各列数据时,没有被设置默认的各列索引defaultIndex,那么用0去填充它,数组长度为列的数量
+ if (this.innerIndex.length === 0) {
+ this.innerIndex = new Array(columns.length).fill(0)
+ }
+ },
+ // 获取各列选中值对应的索引
+ getIndexs() {
+ return this.innerIndex
+ },
+ // 获取各列选中的值
+ getValues() {
+ // 进行同步阻塞,因为外部得到change事件之后,可能需要执行setColumnValues更新列的值
+ // 索引如果在外部change的回调中调用getValues的话,可能无法得到变更后的列值,这里进行一定延时,保证值的准确性
+ (async () => {
+ await this.$uv.sleep()
+ })()
+ return this.innerColumns.map((item, index) => item[this.innerIndex[index]])
+ }
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+ $show-lines: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-picker {
+ position: relative;
+
+ &__view {
+
+ &__column {
+ @include flex;
+ flex: 1;
+ justify-content: center;
+
+ &__item {
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 16px;
+ text-align: center;
+ /* #ifndef APP-NVUE */
+ display: block;
+ /* #endif */
+ color: $uv-main-color;
+
+ &--disabled {
+ /* #ifndef APP-NVUE */
+ cursor: not-allowed;
+ /* #endif */
+ opacity: 0.35;
+ }
+ }
+ }
+ }
+
+ &--loading {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ bottom: 0;
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba(255, 255, 255, 0.87);
+ z-index: 1000;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-picker/package.json b/ledApp/uni_modules/uv-picker/package.json
new file mode 100644
index 0000000..0d62d7e
--- /dev/null
+++ b/ledApp/uni_modules/uv-picker/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-picker",
+ "displayName": "uv-picker 选择器 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.14",
+ "description": "uv-picker 此选择器用于单列,多列,多列联动的选择场景...",
+ "keywords": [
+ "uv-picker",
+ "uvui",
+ "uv-ui",
+ "picker",
+ "联动选择"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-popup",
+ "uv-loading-icon",
+ "uv-toolbar"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-picker/readme.md b/ledApp/uni_modules/uv-picker/readme.md
new file mode 100644
index 0000000..c9dd0b9
--- /dev/null
+++ b/ledApp/uni_modules/uv-picker/readme.md
@@ -0,0 +1,21 @@
+## Picker 选择器
+
+> **组件名:uv-picker**
+
+此选择器用于单列,多列,多列联动的选择场景。
+
+`uv-datetime-picker`等组件也用到了该组件,功能完善,需要特别注意的是`columns`参数的形式是数组嵌套。
+
+# <a href="https://www.uvui.cn/components/picker.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-popup/changelog.md b/ledApp/uni_modules/uv-popup/changelog.md
new file mode 100644
index 0000000..8bb3992
--- /dev/null
+++ b/ledApp/uni_modules/uv-popup/changelog.md
@@ -0,0 +1,18 @@
+## 1.0.7(2023-11-20)
+修复issues问题:https://gitee.com/climblee/uv-ui/issues/I8HDLO
+## 1.0.6(2023-10-13)
+1. 优化vue,内容有背景色,设置圆角被遮挡的情况
+## 1.0.5(2023-09-10)
+1. 修复H5默认层级过高的问题
+2. 修复全局设置prop无效的问题
+## 1.0.4(2023-08-08)
+1. 修复修改zIndex不生效的BUG
+## 1.0.3(2023-07-02)
+uv-popup 弹出层,代码重构优化,性能翻倍,小程序体验性能更加,避免卡顿。打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/popup.html
+## 1.0.2(2023-06-11)
+1. 修复zIndex层级问题
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-popup组件
diff --git a/ledApp/uni_modules/uv-popup/components/uv-popup/keypress.js b/ledApp/uni_modules/uv-popup/components/uv-popup/keypress.js
new file mode 100644
index 0000000..62dda46
--- /dev/null
+++ b/ledApp/uni_modules/uv-popup/components/uv-popup/keypress.js
@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+ name: 'Keypress',
+ props: {
+ disable: {
+ type: Boolean,
+ default: false
+ }
+ },
+ mounted () {
+ const keyNames = {
+ esc: ['Esc', 'Escape'],
+ tab: 'Tab',
+ enter: 'Enter',
+ space: [' ', 'Spacebar'],
+ up: ['Up', 'ArrowUp'],
+ left: ['Left', 'ArrowLeft'],
+ right: ['Right', 'ArrowRight'],
+ down: ['Down', 'ArrowDown'],
+ delete: ['Backspace', 'Delete', 'Del']
+ }
+ const listener = ($event) => {
+ if (this.disable) {
+ return
+ }
+ const keyName = Object.keys(keyNames).find(key => {
+ const keyName = $event.key
+ const value = keyNames[key]
+ return value === keyName || (Array.isArray(value) && value.includes(keyName))
+ })
+ if (keyName) {
+ // 避免和其他按键事件冲突
+ setTimeout(() => {
+ this.$emit(keyName, {})
+ }, 0)
+ }
+ }
+ document.addEventListener('keyup', listener)
+ // this.$once('hook:beforeDestroy', () => {
+ // document.removeEventListener('keyup', listener)
+ // })
+ },
+ render: () => {}
+}
+// #endif
diff --git a/ledApp/uni_modules/uv-popup/components/uv-popup/uv-popup.vue b/ledApp/uni_modules/uv-popup/components/uv-popup/uv-popup.vue
new file mode 100644
index 0000000..a63ff9d
--- /dev/null
+++ b/ledApp/uni_modules/uv-popup/components/uv-popup/uv-popup.vue
@@ -0,0 +1,539 @@
+<template>
+ <view
+ v-if="showPopup"
+ class="uv-popup"
+ :class="[popupClass, isDesktop ? 'fixforpc-z-index' : '']"
+ :style="[{zIndex: zIndex}]"
+ >
+ <view @touchstart="touchstart">
+ <!-- 遮罩层 -->
+ <uv-overlay
+ key="1"
+ v-if="maskShow && overlay"
+ :show="showTrans"
+ :duration="duration"
+ :custom-style="overlayStyle"
+ :opacity="overlayOpacity"
+ :zIndex="zIndex"
+ @click="onTap"
+ ></uv-overlay>
+ <uv-transition
+ key="2"
+ :mode="ani"
+ name="content"
+ :custom-style="transitionStyle"
+ :duration="duration"
+ :show="showTrans"
+ @click="onTap"
+ >
+ <view
+ class="uv-popup__content"
+ :style="[contentStyle]"
+ :class="[popupClass]"
+ @click="clear"
+ >
+ <uv-status-bar v-if="safeAreaInsetTop"></uv-status-bar>
+ <slot />
+ <uv-safe-bottom v-if="safeAreaInsetBottom"></uv-safe-bottom>
+ <view
+ v-if="closeable"
+ @tap.stop="close"
+ class="uv-popup__content__close"
+ :class="['uv-popup__content__close--' + closeIconPos]"
+ hover-class="uv-popup__content__close--hover"
+ hover-stay-time="150"
+ >
+ <uv-icon
+ name="close"
+ color="#909399"
+ size="18"
+ bold
+ ></uv-icon>
+ </view>
+ </view>
+ </uv-transition>
+ </view>
+ <!-- #ifdef H5 -->
+ <keypress v-if="maskShow" @esc="onTap" />
+ <!-- #endif -->
+ </view>
+</template>
+
+<script>
+ // #ifdef H5
+ import keypress from './keypress.js'
+ // #endif
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ /**
+ * PopUp 弹出层
+ * @description 弹出层组件,为了解决遮罩弹层的问题
+ * @tutorial https://www.uvui.cn/components/popup.html
+ * @property {String} mode = [top|center|bottom|left|right] 弹出方式
+ * @value top 顶部弹出
+ * @value center 中间弹出
+ * @value bottom 底部弹出
+ * @value left 左侧弹出
+ * @value right 右侧弹出
+ * @property {Number} duration 动画时长,默认300
+ * @property {Boolean} overlay 是否显示遮罩,默认true
+ * @property {Boolean} overlayOpacity 遮罩透明度,默认0.5
+ * @property {Object} overlayStyle 遮罩自定义样式
+ * @property {Boolean} closeOnClickOverlay = [true|false] 蒙版点击是否关闭弹窗,默认true
+ * @property {Number | String} zIndex 弹出层的层级
+ * @property {Boolean} safeAreaInsetTop 是否留出顶部安全区(状态栏高度),默认false
+ * @property {Boolean} safeAreaInsetBottom 是否为留出底部安全区适配,默认true
+ * @property {Boolean} closeable 是否显示关闭图标,默认false
+ * @property {Boolean} closeIconPos 自定义关闭图标位置,`top-left`-左上角,`top-right`-右上角,`bottom-left`-左下角,`bottom-right`-右下角,默认top-right
+ * @property {String} bgColor 主窗口背景色
+ * @property {String} maskBackgroundColor 蒙版颜色
+ * @property {Boolean} customStyle 自定义样式
+ * @event {Function} change 打开关闭弹窗触发,e={show: false}
+ * @event {Function} maskClick 点击遮罩触发
+ */
+ export default {
+ name: 'uv-popup',
+ components: {
+ // #ifdef H5
+ keypress
+ // #endif
+ },
+ mixins: [mpMixin, mixin],
+ emits: ['change', 'maskClick'],
+ props: {
+ // 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+ // message: 消息提示 ; dialog : 对话框
+ mode: {
+ type: String,
+ default: 'center'
+ },
+ // 动画时长,单位ms
+ duration: {
+ type: [String, Number],
+ default: 300
+ },
+ // 层级
+ zIndex: {
+ type: [String, Number],
+ // #ifdef H5
+ default: 997
+ // #endif
+ // #ifndef H5
+ default: 10075
+ // #endif
+ },
+ bgColor: {
+ type: String,
+ default: '#ffffff'
+ },
+ safeArea: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示遮罩
+ overlay: {
+ type: Boolean,
+ default: true
+ },
+ // 点击遮罩是否关闭弹窗
+ closeOnClickOverlay: {
+ type: Boolean,
+ default: true
+ },
+ // 遮罩的透明度,0-1之间
+ overlayOpacity: {
+ type: [Number, String],
+ default: 0.4
+ },
+ // 自定义遮罩的样式
+ overlayStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ // 是否为iPhoneX留出底部安全距离
+ safeAreaInsetBottom: {
+ type: Boolean,
+ default: true
+ },
+ // 是否留出顶部安全距离(状态栏高度)
+ safeAreaInsetTop: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示关闭图标
+ closeable: {
+ type: Boolean,
+ default: false
+ },
+ // 自定义关闭图标位置,top-left为左上角,top-right为右上角,bottom-left为左下角,bottom-right为右下角
+ closeIconPos: {
+ type: String,
+ default: 'top-right'
+ },
+ // mode=center,也即中部弹出时,是否使用缩放模式
+ zoom: {
+ type: Boolean,
+ default: true
+ },
+ round: {
+ type: [Number, String],
+ default: 0
+ },
+ ...uni.$uv?.props?.popup
+ },
+ watch: {
+ /**
+ * 监听type类型
+ */
+ type: {
+ handler: function(type) {
+ if (!this.config[type]) return
+ this[this.config[type]](true)
+ },
+ immediate: true
+ },
+ isDesktop: {
+ handler: function(newVal) {
+ if (!this.config[newVal]) return
+ this[this.config[this.mode]](true)
+ },
+ immediate: true
+ },
+ // H5 下禁止底部滚动
+ showPopup(show) {
+ // #ifdef H5
+ // fix by mehaotian 处理 h5 滚动穿透的问题
+ document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
+ // #endif
+ }
+ },
+ data() {
+ return {
+ ani: [],
+ showPopup: false,
+ showTrans: false,
+ popupWidth: 0,
+ popupHeight: 0,
+ config: {
+ top: 'top',
+ bottom: 'bottom',
+ center: 'center',
+ left: 'left',
+ right: 'right',
+ message: 'top',
+ dialog: 'center',
+ share: 'bottom'
+ },
+ transitionStyle: {
+ position: 'fixed',
+ left: 0,
+ right: 0
+ },
+ maskShow: true,
+ mkclick: true,
+ popupClass: this.isDesktop ? 'fixforpc-top' : 'top',
+ direction: ''
+ }
+ },
+ computed: {
+ isDesktop() {
+ return this.popupWidth >= 500 && this.popupHeight >= 500
+ },
+ bg() {
+ if (this.bgColor === '' || this.bgColor === 'none' || this.$uv.getPx(this.round)>0) {
+ return 'transparent'
+ }
+ return this.bgColor
+ },
+ contentStyle() {
+ const style = {};
+ if (this.bgColor) {
+ style.backgroundColor = this.bg
+ }
+ if(this.round) {
+ const value = this.$uv.addUnit(this.round)
+ const mode = this.direction?this.direction:this.mode
+ style.backgroundColor = this.bgColor
+ if(mode === 'top') {
+ style.borderBottomLeftRadius = value
+ style.borderBottomRightRadius = value
+ } else if(mode === 'bottom') {
+ style.borderTopLeftRadius = value
+ style.borderTopRightRadius = value
+ } else if(mode === 'center') {
+ style.borderRadius = value
+ }
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ // #ifndef VUE3
+ // TODO vue2
+ destroyed() {
+ this.setH5Visible()
+ },
+ // #endif
+ // #ifdef VUE3
+ // TODO vue3
+ unmounted() {
+ this.setH5Visible()
+ },
+ // #endif
+ created() {
+ // TODO 处理 message 组件生命周期异常的问题
+ this.messageChild = null
+ // TODO 解决头条冒泡的问题
+ this.clearPropagation = false
+ },
+ methods: {
+ setH5Visible() {
+ // #ifdef H5
+ // fix by mehaotian 处理 h5 滚动穿透的问题
+ document.getElementsByTagName('body')[0].style.overflow = 'visible'
+ // #endif
+ },
+ /**
+ * 公用方法,不显示遮罩层
+ */
+ closeMask() {
+ this.maskShow = false
+ },
+ // TODO nvue 取消冒泡
+ clear(e) {
+ // #ifndef APP-NVUE
+ e.stopPropagation()
+ // #endif
+ this.clearPropagation = true
+ },
+
+ open(direction) {
+ // fix by mehaotian 处理快速打开关闭的情况
+ if (this.showPopup) {
+ return
+ }
+ let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
+ if (!(direction && innerType.indexOf(direction) !== -1)) {
+ direction = this.mode
+ }else {
+ this.direction = direction;
+ }
+ if (!this.config[direction]) {
+ return this.$uv.error(`缺少类型:${direction}`);
+ }
+ this[this.config[direction]]()
+ this.$emit('change', {
+ show: true,
+ type: direction
+ })
+ },
+ close(type) {
+ this.showTrans = false
+ this.$emit('change', {
+ show: false,
+ type: this.mode
+ })
+ clearTimeout(this.timer)
+ // // 自定义关闭事件
+ this.timer = setTimeout(() => {
+ this.showPopup = false
+ }, 300)
+ },
+ // TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
+ touchstart() {
+ this.clearPropagation = false
+ },
+ onTap() {
+ if (this.clearPropagation) {
+ // fix by mehaotian 兼容 nvue
+ this.clearPropagation = false
+ return
+ }
+ this.$emit('maskClick')
+ if (!this.closeOnClickOverlay) return
+ this.close()
+ },
+ /**
+ * 顶部弹出样式处理
+ */
+ top(type) {
+ this.popupClass = this.isDesktop ? 'fixforpc-top' : 'top'
+ this.ani = ['slide-top']
+ this.transitionStyle = {
+ position: 'fixed',
+ zIndex: this.zIndex,
+ left: 0,
+ right: 0,
+ backgroundColor: this.bg
+ }
+ // TODO 兼容 type 属性 ,后续会废弃
+ if (type) return
+ this.showPopup = true
+ this.showTrans = true
+ this.$nextTick(() => {
+ if (this.messageChild && this.mode === 'message') {
+ this.messageChild.timerClose()
+ }
+ })
+ },
+ /**
+ * 底部弹出样式处理
+ */
+ bottom(type) {
+ this.popupClass = 'bottom'
+ this.ani = ['slide-bottom']
+ this.transitionStyle = {
+ position: 'fixed',
+ zIndex: this.zIndex,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: this.bg
+ }
+ // TODO 兼容 type 属性 ,后续会废弃
+ if (type) return
+ this.showPopup = true
+ this.showTrans = true
+ },
+ /**
+ * 中间弹出样式处理
+ */
+ center(type) {
+ this.popupClass = 'center'
+ this.ani = this.zoom?['zoom-in', 'fade']:['fade'];
+ this.transitionStyle = {
+ position: 'fixed',
+ zIndex: this.zIndex,
+ /* #ifndef APP-NVUE */
+ display: 'flex',
+ flexDirection: 'column',
+ /* #endif */
+ bottom: 0,
+ left: 0,
+ right: 0,
+ top: 0,
+ justifyContent: 'center',
+ alignItems: 'center'
+ }
+ // TODO 兼容 type 属性 ,后续会废弃
+ if (type) return
+ this.showPopup = true
+ this.showTrans = true
+ },
+ left(type) {
+ this.popupClass = 'left'
+ this.ani = ['slide-left']
+ this.transitionStyle = {
+ position: 'fixed',
+ zIndex: this.zIndex,
+ left: 0,
+ bottom: 0,
+ top: 0,
+ backgroundColor: this.bg,
+ /* #ifndef APP-NVUE */
+ display: 'flex',
+ flexDirection: 'column'
+ /* #endif */
+ }
+ // TODO 兼容 type 属性 ,后续会废弃
+ if (type) return
+ this.showPopup = true
+ this.showTrans = true
+ },
+ right(type) {
+ this.popupClass = 'right'
+ this.ani = ['slide-right']
+ this.transitionStyle = {
+ position: 'fixed',
+ zIndex: this.zIndex,
+ bottom: 0,
+ right: 0,
+ top: 0,
+ backgroundColor: this.bg,
+ /* #ifndef APP-NVUE */
+ display: 'flex',
+ flexDirection: 'column'
+ /* #endif */
+ }
+ // TODO 兼容 type 属性 ,后续会废弃
+ if (type) return
+ this.showPopup = true
+ this.showTrans = true
+ }
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ .uv-popup {
+ position: fixed;
+ /* #ifndef APP-NVUE */
+ z-index: 99;
+
+ /* #endif */
+ &.top,
+ &.left,
+ &.right {
+ /* #ifdef H5 */
+ top: var(--window-top);
+ /* #endif */
+ /* #ifndef H5 */
+ top: 0;
+ /* #endif */
+ }
+
+ .uv-popup__content {
+ /* #ifndef APP-NVUE */
+ display: block;
+ overflow: hidden;
+ /* #endif */
+ position: relative;
+
+ &.left,
+ &.right {
+ /* #ifdef H5 */
+ padding-top: var(--window-top);
+ /* #endif */
+ /* #ifndef H5 */
+ padding-top: 0;
+ /* #endif */
+ flex: 1;
+ }
+ &__close {
+ position: absolute;
+
+ &--hover {
+ opacity: 0.4;
+ }
+ }
+
+ &__close--top-left {
+ top: 15px;
+ left: 15px;
+ }
+
+ &__close--top-right {
+ top: 15px;
+ right: 15px;
+ }
+
+ &__close--bottom-left {
+ bottom: 15px;
+ left: 15px;
+ }
+
+ &__close--bottom-right {
+ right: 15px;
+ bottom: 15px;
+ }
+ }
+ }
+
+ .fixforpc-z-index {
+ /* #ifndef APP-NVUE */
+ z-index: 999;
+ /* #endif */
+ }
+
+ .fixforpc-top {
+ top: 0;
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-popup/package.json b/ledApp/uni_modules/uv-popup/package.json
new file mode 100644
index 0000000..53c112c
--- /dev/null
+++ b/ledApp/uni_modules/uv-popup/package.json
@@ -0,0 +1,92 @@
+{
+ "id": "uv-popup",
+ "displayName": "uv-popup 弹出层 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.7",
+ "description": "uv-popup 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。",
+ "keywords": [
+ "uv-popup",
+ "uvui",
+ "uv-ui",
+ "popup",
+ "弹出层"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-overlay",
+ "uv-transition",
+ "uv-icon",
+ "uv-status-bar",
+ "uv-safe-bottom"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-popup/readme.md b/ledApp/uni_modules/uv-popup/readme.md
new file mode 100644
index 0000000..92229a8
--- /dev/null
+++ b/ledApp/uni_modules/uv-popup/readme.md
@@ -0,0 +1,21 @@
+## Popup 弹出层
+
+> **组件名:uv-popup**
+
+弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。
+
+该组件已经放弃原来`uview2.x`的写法,参照了官方`uni-popup`的写法进行重构。在小程序端的性能大大提升,打开和关闭避免延迟,调用方法与之前相比也有所差异,具体请查看文档。
+
+# <a href="https://www.uvui.cn/components/popup.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/changelog.md b/ledApp/uni_modules/uv-qrcode/changelog.md
new file mode 100644
index 0000000..70d8ba1
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.5(2023-12-19)
+1. 优化
+## 1.0.4(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.3(2023-06-26)
+1. H5增加属性h5SaveTip 保存二维码时候是否显示提示
+## 1.0.2(2023-06-01)
+1. 修复点击触发两次事件的BUG
+## 1.0.1(2023-05-23)
+1. 修复在部分平台不显示加载效果的BUG
+## 1.0.0(2023-05-17)
+1. 新增uv-qrcode组件
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/cache.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/cache.js
new file mode 100644
index 0000000..d897d26
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/cache.js
@@ -0,0 +1 @@
+export const cacheImageList = [];
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/bridge/bridge-weex.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/bridge/bridge-weex.js
new file mode 100644
index 0000000..27086ec
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/bridge/bridge-weex.js
@@ -0,0 +1,241 @@
+const isWeex = typeof WXEnvironment !== 'undefined';
+const isWeexIOS = isWeex && /ios/i.test(WXEnvironment.platform);
+const isWeexAndroid = isWeex && !isWeexIOS;
+
+import GLmethod from '../context-webgl/GLmethod';
+
+const GCanvasModule =
+ (typeof weex !== 'undefined' && weex.requireModule) ? (weex.requireModule('gcanvas')) :
+ (typeof __weex_require__ !== 'undefined') ? (__weex_require__('@weex-module/gcanvas')) : {};
+
+let isDebugging = false;
+
+let isComboDisabled = false;
+
+const logCommand = (function () {
+ const methodQuery = [];
+ Object.keys(GLmethod).forEach(key => {
+ methodQuery[GLmethod[key]] = key;
+ })
+ const queryMethod = (id) => {
+ return methodQuery[parseInt(id)] || 'NotFoundMethod';
+ }
+ const logCommand = (id, cmds) => {
+ const mId = cmds.split(',')[0];
+ const mName = queryMethod(mId);
+ console.log(`=== callNative - componentId:${id}; method: ${mName}; cmds: ${cmds}`);
+ }
+ return logCommand;
+})();
+
+function joinArray(arr, sep) {
+ let res = '';
+ for (let i = 0; i < arr.length; i++) {
+ if (i !== 0) {
+ res += sep;
+ }
+ res += arr[i];
+ }
+ return res;
+}
+
+const commandsCache = {}
+
+const GBridge = {
+
+ callEnable: (ref, configArray) => {
+
+ commandsCache[ref] = [];
+
+ return GCanvasModule.enable({
+ componentId: ref,
+ config: configArray
+ });
+ },
+
+ callEnableDebug: () => {
+ isDebugging = true;
+ },
+
+ callEnableDisableCombo: () => {
+ isComboDisabled = true;
+ },
+
+ callSetContextType: function (componentId, context_type) {
+ GCanvasModule.setContextType(context_type, componentId);
+ },
+
+ callReset: function(id){
+ GCanvasModule.resetComponent && canvasModule.resetComponent(componentId);
+ },
+
+ render: isWeexIOS ? function (componentId) {
+ return GCanvasModule.extendCallNative({
+ contextId: componentId,
+ type: 0x60000001
+ });
+ } : function (componentId) {
+ return callGCanvasLinkNative(componentId, 0x60000001, 'render');
+ },
+
+ render2d: isWeexIOS ? function (componentId, commands, callback) {
+
+ if (isDebugging) {
+ console.log('>>> >>> render2d ===');
+ console.log('>>> commands: ' + commands);
+ }
+
+ GCanvasModule.render([commands, callback?true:false], componentId, callback);
+
+ } : function (componentId, commands,callback) {
+
+ if (isDebugging) {
+ console.log('>>> >>> render2d ===');
+ console.log('>>> commands: ' + commands);
+ }
+
+ callGCanvasLinkNative(componentId, 0x20000001, commands);
+ if(callback){
+ callback();
+ }
+ },
+
+ callExtendCallNative: isWeexIOS ? function (componentId, cmdArgs) {
+
+ throw 'should not be here anymore ' + cmdArgs;
+
+ } : function (componentId, cmdArgs) {
+
+ throw 'should not be here anymore ' + cmdArgs;
+
+ },
+
+
+ flushNative: isWeexIOS ? function (componentId) {
+
+ const cmdArgs = joinArray(commandsCache[componentId], ';');
+ commandsCache[componentId] = [];
+
+ if (isDebugging) {
+ console.log('>>> >>> flush native ===');
+ console.log('>>> commands: ' + cmdArgs);
+ }
+
+ const result = GCanvasModule.extendCallNative({
+ "contextId": componentId,
+ "type": 0x60000000,
+ "args": cmdArgs
+ });
+
+ const res = result && result.result;
+
+ if (isDebugging) {
+ console.log('>>> result: ' + res);
+ }
+
+ return res;
+
+ } : function (componentId) {
+
+ const cmdArgs = joinArray(commandsCache[componentId], ';');
+ commandsCache[componentId] = [];
+
+ if (isDebugging) {
+ console.log('>>> >>> flush native ===');
+ console.log('>>> commands: ' + cmdArgs);
+ }
+
+ const result = callGCanvasLinkNative(componentId, 0x60000000, cmdArgs);
+
+ if (isDebugging) {
+ console.log('>>> result: ' + result);
+ }
+
+ return result;
+ },
+
+ callNative: function (componentId, cmdArgs, cache) {
+
+ if (isDebugging) {
+ logCommand(componentId, cmdArgs);
+ }
+
+ commandsCache[componentId].push(cmdArgs);
+
+ if (!cache || isComboDisabled) {
+ return GBridge.flushNative(componentId);
+ } else {
+ return undefined;
+ }
+ },
+
+ texImage2D(componentId, ...args) {
+ if (isWeexIOS) {
+ if (args.length === 6) {
+ const [target, level, internalformat, format, type, image] = args;
+ GBridge.callNative(
+ componentId,
+ GLmethod.texImage2D + ',' + 6 + ',' + target + ',' + level + ',' + internalformat + ',' + format + ',' + type + ',' + image.src
+ )
+ } else if (args.length === 9) {
+ const [target, level, internalformat, width, height, border, format, type, image] = args;
+ GBridge.callNative(
+ componentId,
+ GLmethod.texImage2D + ',' + 9 + ',' + target + ',' + level + ',' + internalformat + ',' + width + ',' + height + ',' + border + ',' +
+ + format + ',' + type + ',' + (image ? image.src : 0)
+ )
+ }
+ } else if (isWeexAndroid) {
+ if (args.length === 6) {
+ const [target, level, internalformat, format, type, image] = args;
+ GCanvasModule.texImage2D(componentId, target, level, internalformat, format, type, image.src);
+ } else if (args.length === 9) {
+ const [target, level, internalformat, width, height, border, format, type, image] = args;
+ GCanvasModule.texImage2D(componentId, target, level, internalformat, width, height, border, format, type, (image ? image.src : 0));
+ }
+ }
+ },
+
+ texSubImage2D(componentId, target, level, xoffset, yoffset, format, type, image) {
+ if (isWeexIOS) {
+ if (arguments.length === 8) {
+ GBridge.callNative(
+ componentId,
+ GLmethod.texSubImage2D + ',' + 6 + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset, + ',' + format + ',' + type + ',' + image.src
+ )
+ }
+ } else if (isWeexAndroid) {
+ GCanvasModule.texSubImage2D(componentId, target, level, xoffset, yoffset, format, type, image.src);
+ }
+ },
+
+ bindImageTexture(componentId, src, imageId) {
+ GCanvasModule.bindImageTexture([src, imageId], componentId);
+ },
+
+ perloadImage([url, id], callback) {
+ GCanvasModule.preLoadImage([url, id], function (image) {
+ image.url = url;
+ image.id = id;
+ callback(image);
+ });
+ },
+
+ measureText(text, fontStyle, componentId) {
+ return GCanvasModule.measureText([text, fontStyle], componentId);
+ },
+
+ getImageData (componentId, x, y, w, h, callback) {
+ GCanvasModule.getImageData([x, y,w,h],componentId,callback);
+ },
+
+ putImageData (componentId, data, x, y, w, h, callback) {
+ GCanvasModule.putImageData([x, y,w,h,data],componentId,callback);
+ },
+
+ toTempFilePath(componentId, x, y, width, height, destWidth, destHeight, fileType, quality, callback){
+ GCanvasModule.toTempFilePath([x, y, width,height, destWidth, destHeight, fileType, quality], componentId, callback);
+ }
+}
+
+export default GBridge;
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleLinearGradient.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleLinearGradient.js
new file mode 100644
index 0000000..3e7f03a
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleLinearGradient.js
@@ -0,0 +1,18 @@
+class FillStyleLinearGradient {
+
+ constructor(x0, y0, x1, y1) {
+ this._start_pos = { _x: x0, _y: y0 };
+ this._end_pos = { _x: x1, _y: y1 };
+ this._stop_count = 0;
+ this._stops = [0, 0, 0, 0, 0];
+ }
+
+ addColorStop = function (pos, color) {
+ if (this._stop_count < 5 && 0.0 <= pos && pos <= 1.0) {
+ this._stops[this._stop_count] = { _pos: pos, _color: color };
+ this._stop_count++;
+ }
+ }
+}
+
+export default FillStyleLinearGradient;
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStylePattern.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStylePattern.js
new file mode 100644
index 0000000..6e4f646
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStylePattern.js
@@ -0,0 +1,8 @@
+class FillStylePattern {
+ constructor(img, pattern) {
+ this._style = pattern;
+ this._img = img;
+ }
+}
+
+export default FillStylePattern;
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleRadialGradient.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleRadialGradient.js
new file mode 100644
index 0000000..7790596
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleRadialGradient.js
@@ -0,0 +1,17 @@
+class FillStyleRadialGradient {
+ constructor(x0, y0, r0, x1, y1, r1) {
+ this._start_pos = { _x: x0, _y: y0, _r: r0 };
+ this._end_pos = { _x: x1, _y: y1, _r: r1 };
+ this._stop_count = 0;
+ this._stops = [0, 0, 0, 0, 0];
+ }
+
+ addColorStop(pos, color) {
+ if (this._stop_count < 5 && 0.0 <= pos && pos <= 1.0) {
+ this._stops[this._stop_count] = { _pos: pos, _color: color };
+ this._stop_count++;
+ }
+ }
+}
+
+export default FillStyleRadialGradient;
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/RenderingContext.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/RenderingContext.js
new file mode 100644
index 0000000..e6b8f48
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/RenderingContext.js
@@ -0,0 +1,666 @@
+import FillStylePattern from './FillStylePattern';
+import FillStyleLinearGradient from './FillStyleLinearGradient';
+import FillStyleRadialGradient from './FillStyleRadialGradient';
+import GImage from '../env/image.js';
+import {
+ ArrayBufferToBase64,
+ Base64ToUint8ClampedArray
+} from '../env/tool.js';
+
+export default class CanvasRenderingContext2D {
+
+ _drawCommands = '';
+
+ _globalAlpha = 1.0;
+
+ _fillStyle = 'rgb(0,0,0)';
+ _strokeStyle = 'rgb(0,0,0)';
+
+ _lineWidth = 1;
+ _lineCap = 'butt';
+ _lineJoin = 'miter';
+
+ _miterLimit = 10;
+
+ _globalCompositeOperation = 'source-over';
+
+ _textAlign = 'start';
+ _textBaseline = 'alphabetic';
+
+ _font = '10px sans-serif';
+
+ _savedGlobalAlpha = [];
+
+ timer = null;
+ componentId = null;
+
+ _notCommitDrawImageCache = [];
+ _needRedrawImageCache = [];
+ _redrawCommands = '';
+ _autoSaveContext = true;
+ // _imageMap = new GHashMap();
+ // _textureMap = new GHashMap();
+
+ constructor() {
+ this.className = 'CanvasRenderingContext2D';
+ //this.save()
+ }
+
+ setFillStyle(value) {
+ this.fillStyle = value;
+ }
+
+ set fillStyle(value) {
+ this._fillStyle = value;
+
+ if (typeof(value) == 'string') {
+ this._drawCommands = this._drawCommands.concat("F" + value + ";");
+ } else if (value instanceof FillStylePattern) {
+ const image = value._img;
+ if (!image.complete) {
+ image.onload = () => {
+ var index = this._needRedrawImageCache.indexOf(image);
+ if (index > -1) {
+ this._needRedrawImageCache.splice(index, 1);
+ CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+ this._redrawflush(true);
+ }
+ }
+ this._notCommitDrawImageCache.push(image);
+ } else {
+ CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+ }
+
+ //CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+ this._drawCommands = this._drawCommands.concat("G" + image._id + "," + value._style + ";");
+ } else if (value instanceof FillStyleLinearGradient) {
+ var command = "D" + value._start_pos._x.toFixed(2) + "," + value._start_pos._y.toFixed(2) + "," +
+ value._end_pos._x.toFixed(2) + "," + value._end_pos._y.toFixed(2) + "," +
+ value._stop_count;
+ for (var i = 0; i < value._stop_count; ++i) {
+ command += ("," + value._stops[i]._pos + "," + value._stops[i]._color);
+ }
+ this._drawCommands = this._drawCommands.concat(command + ";");
+ } else if (value instanceof FillStyleRadialGradient) {
+ var command = "H" + value._start_pos._x.toFixed(2) + "," + value._start_pos._y.toFixed(2) + "," + value._start_pos._r
+ .toFixed(2) + "," +
+ value._end_pos._x.toFixed(2) + "," + value._end_pos._y.toFixed(2) + "," + value._end_pos._r.toFixed(2) + "," +
+ value._stop_count;
+ for (var i = 0; i < value._stop_count; ++i) {
+ command += ("," + value._stops[i]._pos + "," + value._stops[i]._color);
+ }
+ this._drawCommands = this._drawCommands.concat(command + ";");
+ }
+ }
+
+ get fillStyle() {
+ return this._fillStyle;
+ }
+
+ get globalAlpha() {
+ return this._globalAlpha;
+ }
+
+ setGlobalAlpha(value) {
+ this.globalAlpha = value;
+ }
+
+ set globalAlpha(value) {
+ this._globalAlpha = value;
+ this._drawCommands = this._drawCommands.concat("a" + value.toFixed(2) + ";");
+ }
+
+
+ get strokeStyle() {
+ return this._strokeStyle;
+ }
+
+ setStrokeStyle(value) {
+ this.strokeStyle = value;
+ }
+
+ set strokeStyle(value) {
+
+ this._strokeStyle = value;
+
+ if (typeof(value) == 'string') {
+ this._drawCommands = this._drawCommands.concat("S" + value + ";");
+ } else if (value instanceof FillStylePattern) {
+ CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+ this._drawCommands = this._drawCommands.concat("G" + image._id + "," + value._style + ";");
+ } else if (value instanceof FillStyleLinearGradient) {
+ var command = "D" + value._start_pos._x.toFixed(2) + "," + value._start_pos._y.toFixed(2) + "," +
+ value._end_pos._x.toFixed(2) + "," + value._end_pos._y.toFixed(2) + "," +
+ value._stop_count;
+
+ for (var i = 0; i < value._stop_count; ++i) {
+ command += ("," + value._stops[i]._pos + "," + value._stops[i]._color);
+ }
+ this._drawCommands = this._drawCommands.concat(command + ";");
+ } else if (value instanceof FillStyleRadialGradient) {
+ var command = "H" + value._start_pos._x.toFixed(2) + "," + value._start_pos._y.toFixed(2) + "," + value._start_pos._r
+ .toFixed(2) + "," +
+ value._end_pos._x.toFixed(2) + "," + value._end_pos._y + ",".toFixed(2) + value._end_pos._r.toFixed(2) + "," +
+ value._stop_count;
+
+ for (var i = 0; i < value._stop_count; ++i) {
+ command += ("," + value._stops[i]._pos + "," + value._stops[i]._color);
+ }
+ this._drawCommands = this._drawCommands.concat(command + ";");
+ }
+ }
+
+ get lineWidth() {
+ return this._lineWidth;
+ }
+
+ setLineWidth(value) {
+ this.lineWidth = value;
+ }
+
+ set lineWidth(value) {
+ this._lineWidth = value;
+ this._drawCommands = this._drawCommands.concat("W" + value + ";");
+ }
+
+ get lineCap() {
+ return this._lineCap;
+ }
+
+ setLineCap(value) {
+ this.lineCap = value;
+ }
+
+ set lineCap(value) {
+ this._lineCap = value;
+ this._drawCommands = this._drawCommands.concat("C" + value + ";");
+ }
+
+ get lineJoin() {
+ return this._lineJoin;
+ }
+
+ setLineJoin(value) {
+ this.lineJoin = value
+ }
+
+ set lineJoin(value) {
+ this._lineJoin = value;
+ this._drawCommands = this._drawCommands.concat("J" + value + ";");
+ }
+
+ get miterLimit() {
+ return this._miterLimit;
+ }
+
+ setMiterLimit(value) {
+ this.miterLimit = value
+ }
+
+ set miterLimit(value) {
+ this._miterLimit = value;
+ this._drawCommands = this._drawCommands.concat("M" + value + ";");
+ }
+
+ get globalCompositeOperation() {
+ return this._globalCompositeOperation;
+ }
+
+ set globalCompositeOperation(value) {
+
+ this._globalCompositeOperation = value;
+ let mode = 0;
+ switch (value) {
+ case "source-over":
+ mode = 0;
+ break;
+ case "source-atop":
+ mode = 5;
+ break;
+ case "source-in":
+ mode = 0;
+ break;
+ case "source-out":
+ mode = 2;
+ break;
+ case "destination-over":
+ mode = 4;
+ break;
+ case "destination-atop":
+ mode = 4;
+ break;
+ case "destination-in":
+ mode = 4;
+ break;
+ case "destination-out":
+ mode = 3;
+ break;
+ case "lighter":
+ mode = 1;
+ break;
+ case "copy":
+ mode = 2;
+ break;
+ case "xor":
+ mode = 6;
+ break;
+ default:
+ mode = 0;
+ }
+
+ this._drawCommands = this._drawCommands.concat("B" + mode + ";");
+ }
+
+ get textAlign() {
+ return this._textAlign;
+ }
+
+ setTextAlign(value) {
+ this.textAlign = value
+ }
+
+ set textAlign(value) {
+
+ this._textAlign = value;
+ let Align = 0;
+ switch (value) {
+ case "start":
+ Align = 0;
+ break;
+ case "end":
+ Align = 1;
+ break;
+ case "left":
+ Align = 2;
+ break;
+ case "center":
+ Align = 3;
+ break;
+ case "right":
+ Align = 4;
+ break;
+ default:
+ Align = 0;
+ }
+
+ this._drawCommands = this._drawCommands.concat("A" + Align + ";");
+ }
+
+ get textBaseline() {
+ return this._textBaseline;
+ }
+
+ setTextBaseline(value) {
+ this.textBaseline = value
+ }
+
+ set textBaseline(value) {
+ this._textBaseline = value;
+ let baseline = 0;
+ switch (value) {
+ case "alphabetic":
+ baseline = 0;
+ break;
+ case "middle":
+ baseline = 1;
+ break;
+ case "top":
+ baseline = 2;
+ break;
+ case "hanging":
+ baseline = 3;
+ break;
+ case "bottom":
+ baseline = 4;
+ break;
+ case "ideographic":
+ baseline = 5;
+ break;
+ default:
+ baseline = 0;
+ break;
+ }
+
+ this._drawCommands = this._drawCommands.concat("E" + baseline + ";");
+ }
+
+ get font() {
+ return this._font;
+ }
+
+ setFontSize(size) {
+ var str = this._font;
+ var strs = str.trim().split(/\s+/);
+ for (var i = 0; i < strs.length; i++) {
+ var values = ["normal", "italic", "oblique", "normal", "small-caps", "normal", "bold",
+ "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900",
+ "normal", "ultra-condensed", "extra-condensed", "condensed", "semi-condensed",
+ "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"
+ ];
+
+ if (-1 == values.indexOf(strs[i].trim())) {
+ if (typeof size === 'string') {
+ strs[i] = size;
+ } else if (typeof size === 'number') {
+ strs[i] = String(size) + 'px';
+ }
+ break;
+ }
+ }
+ this.font = strs.join(" ");
+ }
+
+ set font(value) {
+ this._font = value;
+ this._drawCommands = this._drawCommands.concat("j" + value + ";");
+ }
+
+ setTransform(a, b, c, d, tx, ty) {
+ this._drawCommands = this._drawCommands.concat("t" +
+ (a === 1 ? "1" : a.toFixed(2)) + "," +
+ (b === 0 ? "0" : b.toFixed(2)) + "," +
+ (c === 0 ? "0" : c.toFixed(2)) + "," +
+ (d === 1 ? "1" : d.toFixed(2)) + "," + tx.toFixed(2) + "," + ty.toFixed(2) + ";");
+ }
+
+ transform(a, b, c, d, tx, ty) {
+ this._drawCommands = this._drawCommands.concat("f" +
+ (a === 1 ? "1" : a.toFixed(2)) + "," +
+ (b === 0 ? "0" : b.toFixed(2)) + "," +
+ (c === 0 ? "0" : c.toFixed(2)) + "," +
+ (d === 1 ? "1" : d.toFixed(2)) + "," + tx + "," + ty + ";");
+ }
+
+ resetTransform() {
+ this._drawCommands = this._drawCommands.concat("m;");
+ }
+
+ scale(a, d) {
+ this._drawCommands = this._drawCommands.concat("k" + a.toFixed(2) + "," +
+ d.toFixed(2) + ";");
+ }
+
+ rotate(angle) {
+ this._drawCommands = this._drawCommands
+ .concat("r" + angle.toFixed(6) + ";");
+ }
+
+ translate(tx, ty) {
+ this._drawCommands = this._drawCommands.concat("l" + tx.toFixed(2) + "," + ty.toFixed(2) + ";");
+ }
+
+ save() {
+ this._savedGlobalAlpha.push(this._globalAlpha);
+ this._drawCommands = this._drawCommands.concat("v;");
+ }
+
+ restore() {
+ this._drawCommands = this._drawCommands.concat("e;");
+ this._globalAlpha = this._savedGlobalAlpha.pop();
+ }
+
+ createPattern(img, pattern) {
+ if (typeof img === 'string') {
+ var imgObj = new GImage();
+ imgObj.src = img;
+ img = imgObj;
+ }
+ return new FillStylePattern(img, pattern);
+ }
+
+ createLinearGradient(x0, y0, x1, y1) {
+ return new FillStyleLinearGradient(x0, y0, x1, y1);
+ }
+
+ createRadialGradient = function(x0, y0, r0, x1, y1, r1) {
+ return new FillStyleRadialGradient(x0, y0, r0, x1, y1, r1);
+ };
+
+ createCircularGradient = function(x0, y0, r0) {
+ return new FillStyleRadialGradient(x0, y0, 0, x0, y0, r0);
+ };
+
+ strokeRect(x, y, w, h) {
+ this._drawCommands = this._drawCommands.concat("s" + x + "," + y + "," + w + "," + h + ";");
+ }
+
+
+ clearRect(x, y, w, h) {
+ this._drawCommands = this._drawCommands.concat("c" + x + "," + y + "," + w +
+ "," + h + ";");
+ }
+
+ clip() {
+ this._drawCommands = this._drawCommands.concat("p;");
+ }
+
+ resetClip() {
+ this._drawCommands = this._drawCommands.concat("q;");
+ }
+
+ closePath() {
+ this._drawCommands = this._drawCommands.concat("o;");
+ }
+
+ moveTo(x, y) {
+ this._drawCommands = this._drawCommands.concat("g" + x.toFixed(2) + "," + y.toFixed(2) + ";");
+ }
+
+ lineTo(x, y) {
+ this._drawCommands = this._drawCommands.concat("i" + x.toFixed(2) + "," + y.toFixed(2) + ";");
+ }
+
+ quadraticCurveTo = function(cpx, cpy, x, y) {
+ this._drawCommands = this._drawCommands.concat("u" + cpx + "," + cpy + "," + x + "," + y + ";");
+ }
+
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, ) {
+ this._drawCommands = this._drawCommands.concat(
+ "z" + cp1x.toFixed(2) + "," + cp1y.toFixed(2) + "," + cp2x.toFixed(2) + "," + cp2y.toFixed(2) + "," +
+ x.toFixed(2) + "," + y.toFixed(2) + ";");
+ }
+
+ arcTo(x1, y1, x2, y2, radius) {
+ this._drawCommands = this._drawCommands.concat("h" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + radius + ";");
+ }
+
+ beginPath() {
+ this._drawCommands = this._drawCommands.concat("b;");
+ }
+
+
+ fillRect(x, y, w, h) {
+ this._drawCommands = this._drawCommands.concat("n" + x + "," + y + "," + w +
+ "," + h + ";");
+ }
+
+ rect(x, y, w, h) {
+ this._drawCommands = this._drawCommands.concat("w" + x + "," + y + "," + w + "," + h + ";");
+ }
+
+ fill() {
+ this._drawCommands = this._drawCommands.concat("L;");
+ }
+
+ stroke(path) {
+ this._drawCommands = this._drawCommands.concat("x;");
+ }
+
+ arc(x, y, radius, startAngle, endAngle, anticlockwise) {
+
+ let ianticlockwise = 0;
+ if (anticlockwise) {
+ ianticlockwise = 1;
+ }
+
+ this._drawCommands = this._drawCommands.concat(
+ "y" + x.toFixed(2) + "," + y.toFixed(2) + "," +
+ radius.toFixed(2) + "," + startAngle + "," + endAngle + "," + ianticlockwise +
+ ";"
+ );
+ }
+
+ fillText(text, x, y) {
+ let tmptext = text.replace(/!/g, "!!");
+ tmptext = tmptext.replace(/,/g, "!,");
+ tmptext = tmptext.replace(/;/g, "!;");
+ this._drawCommands = this._drawCommands.concat("T" + tmptext + "," + x + "," + y + ",0.0;");
+ }
+
+ strokeText = function(text, x, y) {
+ let tmptext = text.replace(/!/g, "!!");
+ tmptext = tmptext.replace(/,/g, "!,");
+ tmptext = tmptext.replace(/;/g, "!;");
+ this._drawCommands = this._drawCommands.concat("U" + tmptext + "," + x + "," + y + ",0.0;");
+ }
+
+ measureText(text) {
+ return CanvasRenderingContext2D.GBridge.measureText(text, this.font, this.componentId);
+ }
+
+ isPointInPath = function(x, y) {
+ throw new Error('GCanvas not supported yet');
+ }
+
+ drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
+ if (typeof image === 'string') {
+ var imgObj = new GImage();
+ imgObj.src = image;
+ image = imgObj;
+ }
+ if (image instanceof GImage) {
+ if (!image.complete) {
+ imgObj.onload = () => {
+ var index = this._needRedrawImageCache.indexOf(image);
+ if (index > -1) {
+ this._needRedrawImageCache.splice(index, 1);
+ CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+ this._redrawflush(true);
+ }
+ }
+ this._notCommitDrawImageCache.push(image);
+ } else {
+ CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+ }
+ var srcArgs = [image, sx, sy, sw, sh, dx, dy, dw, dh];
+ var args = [];
+ for (var arg in srcArgs) {
+ if (typeof(srcArgs[arg]) != 'undefined') {
+ args.push(srcArgs[arg]);
+ }
+ }
+ this.__drawImage.apply(this, args);
+ //this.__drawImage(image,sx, sy, sw, sh, dx, dy, dw, dh);
+ }
+ }
+
+ __drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
+ const numArgs = arguments.length;
+
+ function drawImageCommands() {
+
+ if (numArgs === 3) {
+ const x = parseFloat(sx) || 0.0;
+ const y = parseFloat(sy) || 0.0;
+
+ return ("d" + image._id + ",0,0," +
+ image.width + "," + image.height + "," +
+ x + "," + y + "," + image.width + "," + image.height + ";");
+ } else if (numArgs === 5) {
+ const x = parseFloat(sx) || 0.0;
+ const y = parseFloat(sy) || 0.0;
+ const width = parseInt(sw) || image.width;
+ const height = parseInt(sh) || image.height;
+
+ return ("d" + image._id + ",0,0," +
+ image.width + "," + image.height + "," +
+ x + "," + y + "," + width + "," + height + ";");
+ } else if (numArgs === 9) {
+ sx = parseFloat(sx) || 0.0;
+ sy = parseFloat(sy) || 0.0;
+ sw = parseInt(sw) || image.width;
+ sh = parseInt(sh) || image.height;
+ dx = parseFloat(dx) || 0.0;
+ dy = parseFloat(dy) || 0.0;
+ dw = parseInt(dw) || image.width;
+ dh = parseInt(dh) || image.height;
+
+ return ("d" + image._id + "," +
+ sx + "," + sy + "," + sw + "," + sh + "," +
+ dx + "," + dy + "," + dw + "," + dh + ";");
+ }
+ }
+ this._drawCommands += drawImageCommands();
+ }
+
+ _flush(reserve, callback) {
+ const commands = this._drawCommands;
+ this._drawCommands = '';
+ CanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback);
+ this._needRender = false;
+ }
+
+ _redrawflush(reserve, callback) {
+ const commands = this._redrawCommands;
+ CanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback);
+ if (this._needRedrawImageCache.length == 0) {
+ this._redrawCommands = '';
+ }
+ }
+
+ draw(reserve, callback) {
+ if (!reserve) {
+ this._globalAlpha = this._savedGlobalAlpha.pop();
+ this._savedGlobalAlpha.push(this._globalAlpha);
+ this._redrawCommands = this._drawCommands;
+ this._needRedrawImageCache = this._notCommitDrawImageCache;
+ if (this._autoSaveContext) {
+ this._drawCommands = ("v;" + this._drawCommands);
+ this._autoSaveContext = false;
+ } else {
+ this._drawCommands = ("e;X;v;" + this._drawCommands);
+ }
+ } else {
+ this._needRedrawImageCache = this._needRedrawImageCache.concat(this._notCommitDrawImageCache);
+ this._redrawCommands += this._drawCommands;
+ if (this._autoSaveContext) {
+ this._drawCommands = ("v;" + this._drawCommands);
+ this._autoSaveContext = false;
+ }
+ }
+ this._notCommitDrawImageCache = [];
+ if (this._flush) {
+ this._flush(reserve, callback);
+ }
+ }
+
+ getImageData(x, y, w, h, callback) {
+ CanvasRenderingContext2D.GBridge.getImageData(this.componentId, x, y, w, h, function(res) {
+ res.data = Base64ToUint8ClampedArray(res.data);
+ if (typeof(callback) == 'function') {
+ callback(res);
+ }
+ });
+ }
+
+ putImageData(data, x, y, w, h, callback) {
+ if (data instanceof Uint8ClampedArray) {
+ data = ArrayBufferToBase64(data);
+ CanvasRenderingContext2D.GBridge.putImageData(this.componentId, data, x, y, w, h, function(res) {
+ if (typeof(callback) == 'function') {
+ callback(res);
+ }
+ });
+ }
+ }
+
+ toTempFilePath(x, y, width, height, destWidth, destHeight, fileType, quality, callback) {
+ CanvasRenderingContext2D.GBridge.toTempFilePath(this.componentId, x, y, width, height, destWidth, destHeight,
+ fileType, quality,
+ function(res) {
+ if (typeof(callback) == 'function') {
+ callback(res);
+ }
+ });
+ }
+}
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ActiveInfo.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ActiveInfo.js
new file mode 100644
index 0000000..b495129
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ActiveInfo.js
@@ -0,0 +1,11 @@
+export default class WebGLActiveInfo {
+ className = 'WebGLActiveInfo';
+
+ constructor({
+ type, name, size
+ }) {
+ this.type = type;
+ this.name = name;
+ this.size = size;
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Buffer.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Buffer.js
new file mode 100644
index 0000000..4800f67
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Buffer.js
@@ -0,0 +1,21 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLBuffer';
+
+function uuid(id) {
+ return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLBuffer {
+ className = name;
+
+ constructor(id) {
+ this.id = id;
+ }
+
+ static uuid = uuid;
+
+ uuid() {
+ return uuid(this.id);
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Framebuffer.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Framebuffer.js
new file mode 100644
index 0000000..28b46d3
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Framebuffer.js
@@ -0,0 +1,21 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLFrameBuffer';
+
+function uuid(id) {
+ return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLFramebuffer {
+ className = name;
+
+ constructor(id) {
+ this.id = id;
+ }
+
+ static uuid = uuid;
+
+ uuid() {
+ return uuid(this.id);
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLenum.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLenum.js
new file mode 100644
index 0000000..ac5544d
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLenum.js
@@ -0,0 +1,298 @@
+export default {
+ "DEPTH_BUFFER_BIT": 256,
+ "STENCIL_BUFFER_BIT": 1024,
+ "COLOR_BUFFER_BIT": 16384,
+ "POINTS": 0,
+ "LINES": 1,
+ "LINE_LOOP": 2,
+ "LINE_STRIP": 3,
+ "TRIANGLES": 4,
+ "TRIANGLE_STRIP": 5,
+ "TRIANGLE_FAN": 6,
+ "ZERO": 0,
+ "ONE": 1,
+ "SRC_COLOR": 768,
+ "ONE_MINUS_SRC_COLOR": 769,
+ "SRC_ALPHA": 770,
+ "ONE_MINUS_SRC_ALPHA": 771,
+ "DST_ALPHA": 772,
+ "ONE_MINUS_DST_ALPHA": 773,
+ "DST_COLOR": 774,
+ "ONE_MINUS_DST_COLOR": 775,
+ "SRC_ALPHA_SATURATE": 776,
+ "FUNC_ADD": 32774,
+ "BLEND_EQUATION": 32777,
+ "BLEND_EQUATION_RGB": 32777,
+ "BLEND_EQUATION_ALPHA": 34877,
+ "FUNC_SUBTRACT": 32778,
+ "FUNC_REVERSE_SUBTRACT": 32779,
+ "BLEND_DST_RGB": 32968,
+ "BLEND_SRC_RGB": 32969,
+ "BLEND_DST_ALPHA": 32970,
+ "BLEND_SRC_ALPHA": 32971,
+ "CONSTANT_COLOR": 32769,
+ "ONE_MINUS_CONSTANT_COLOR": 32770,
+ "CONSTANT_ALPHA": 32771,
+ "ONE_MINUS_CONSTANT_ALPHA": 32772,
+ "BLEND_COLOR": 32773,
+ "ARRAY_BUFFER": 34962,
+ "ELEMENT_ARRAY_BUFFER": 34963,
+ "ARRAY_BUFFER_BINDING": 34964,
+ "ELEMENT_ARRAY_BUFFER_BINDING": 34965,
+ "STREAM_DRAW": 35040,
+ "STATIC_DRAW": 35044,
+ "DYNAMIC_DRAW": 35048,
+ "BUFFER_SIZE": 34660,
+ "BUFFER_USAGE": 34661,
+ "CURRENT_VERTEX_ATTRIB": 34342,
+ "FRONT": 1028,
+ "BACK": 1029,
+ "FRONT_AND_BACK": 1032,
+ "TEXTURE_2D": 3553,
+ "CULL_FACE": 2884,
+ "BLEND": 3042,
+ "DITHER": 3024,
+ "STENCIL_TEST": 2960,
+ "DEPTH_TEST": 2929,
+ "SCISSOR_TEST": 3089,
+ "POLYGON_OFFSET_FILL": 32823,
+ "SAMPLE_ALPHA_TO_COVERAGE": 32926,
+ "SAMPLE_COVERAGE": 32928,
+ "NO_ERROR": 0,
+ "INVALID_ENUM": 1280,
+ "INVALID_VALUE": 1281,
+ "INVALID_OPERATION": 1282,
+ "OUT_OF_MEMORY": 1285,
+ "CW": 2304,
+ "CCW": 2305,
+ "LINE_WIDTH": 2849,
+ "ALIASED_POINT_SIZE_RANGE": 33901,
+ "ALIASED_LINE_WIDTH_RANGE": 33902,
+ "CULL_FACE_MODE": 2885,
+ "FRONT_FACE": 2886,
+ "DEPTH_RANGE": 2928,
+ "DEPTH_WRITEMASK": 2930,
+ "DEPTH_CLEAR_VALUE": 2931,
+ "DEPTH_FUNC": 2932,
+ "STENCIL_CLEAR_VALUE": 2961,
+ "STENCIL_FUNC": 2962,
+ "STENCIL_FAIL": 2964,
+ "STENCIL_PASS_DEPTH_FAIL": 2965,
+ "STENCIL_PASS_DEPTH_PASS": 2966,
+ "STENCIL_REF": 2967,
+ "STENCIL_VALUE_MASK": 2963,
+ "STENCIL_WRITEMASK": 2968,
+ "STENCIL_BACK_FUNC": 34816,
+ "STENCIL_BACK_FAIL": 34817,
+ "STENCIL_BACK_PASS_DEPTH_FAIL": 34818,
+ "STENCIL_BACK_PASS_DEPTH_PASS": 34819,
+ "STENCIL_BACK_REF": 36003,
+ "STENCIL_BACK_VALUE_MASK": 36004,
+ "STENCIL_BACK_WRITEMASK": 36005,
+ "VIEWPORT": 2978,
+ "SCISSOR_BOX": 3088,
+ "COLOR_CLEAR_VALUE": 3106,
+ "COLOR_WRITEMASK": 3107,
+ "UNPACK_ALIGNMENT": 3317,
+ "PACK_ALIGNMENT": 3333,
+ "MAX_TEXTURE_SIZE": 3379,
+ "MAX_VIEWPORT_DIMS": 3386,
+ "SUBPIXEL_BITS": 3408,
+ "RED_BITS": 3410,
+ "GREEN_BITS": 3411,
+ "BLUE_BITS": 3412,
+ "ALPHA_BITS": 3413,
+ "DEPTH_BITS": 3414,
+ "STENCIL_BITS": 3415,
+ "POLYGON_OFFSET_UNITS": 10752,
+ "POLYGON_OFFSET_FACTOR": 32824,
+ "TEXTURE_BINDING_2D": 32873,
+ "SAMPLE_BUFFERS": 32936,
+ "SAMPLES": 32937,
+ "SAMPLE_COVERAGE_VALUE": 32938,
+ "SAMPLE_COVERAGE_INVERT": 32939,
+ "COMPRESSED_TEXTURE_FORMATS": 34467,
+ "DONT_CARE": 4352,
+ "FASTEST": 4353,
+ "NICEST": 4354,
+ "GENERATE_MIPMAP_HINT": 33170,
+ "BYTE": 5120,
+ "UNSIGNED_BYTE": 5121,
+ "SHORT": 5122,
+ "UNSIGNED_SHORT": 5123,
+ "INT": 5124,
+ "UNSIGNED_INT": 5125,
+ "FLOAT": 5126,
+ "DEPTH_COMPONENT": 6402,
+ "ALPHA": 6406,
+ "RGB": 6407,
+ "RGBA": 6408,
+ "LUMINANCE": 6409,
+ "LUMINANCE_ALPHA": 6410,
+ "UNSIGNED_SHORT_4_4_4_4": 32819,
+ "UNSIGNED_SHORT_5_5_5_1": 32820,
+ "UNSIGNED_SHORT_5_6_5": 33635,
+ "FRAGMENT_SHADER": 35632,
+ "VERTEX_SHADER": 35633,
+ "MAX_VERTEX_ATTRIBS": 34921,
+ "MAX_VERTEX_UNIFORM_VECTORS": 36347,
+ "MAX_VARYING_VECTORS": 36348,
+ "MAX_COMBINED_TEXTURE_IMAGE_UNITS": 35661,
+ "MAX_VERTEX_TEXTURE_IMAGE_UNITS": 35660,
+ "MAX_TEXTURE_IMAGE_UNITS": 34930,
+ "MAX_FRAGMENT_UNIFORM_VECTORS": 36349,
+ "SHADER_TYPE": 35663,
+ "DELETE_STATUS": 35712,
+ "LINK_STATUS": 35714,
+ "VALIDATE_STATUS": 35715,
+ "ATTACHED_SHADERS": 35717,
+ "ACTIVE_UNIFORMS": 35718,
+ "ACTIVE_ATTRIBUTES": 35721,
+ "SHADING_LANGUAGE_VERSION": 35724,
+ "CURRENT_PROGRAM": 35725,
+ "NEVER": 512,
+ "LESS": 513,
+ "EQUAL": 514,
+ "LEQUAL": 515,
+ "GREATER": 516,
+ "NOTEQUAL": 517,
+ "GEQUAL": 518,
+ "ALWAYS": 519,
+ "KEEP": 7680,
+ "REPLACE": 7681,
+ "INCR": 7682,
+ "DECR": 7683,
+ "INVERT": 5386,
+ "INCR_WRAP": 34055,
+ "DECR_WRAP": 34056,
+ "VENDOR": 7936,
+ "RENDERER": 7937,
+ "VERSION": 7938,
+ "NEAREST": 9728,
+ "LINEAR": 9729,
+ "NEAREST_MIPMAP_NEAREST": 9984,
+ "LINEAR_MIPMAP_NEAREST": 9985,
+ "NEAREST_MIPMAP_LINEAR": 9986,
+ "LINEAR_MIPMAP_LINEAR": 9987,
+ "TEXTURE_MAG_FILTER": 10240,
+ "TEXTURE_MIN_FILTER": 10241,
+ "TEXTURE_WRAP_S": 10242,
+ "TEXTURE_WRAP_T": 10243,
+ "TEXTURE": 5890,
+ "TEXTURE_CUBE_MAP": 34067,
+ "TEXTURE_BINDING_CUBE_MAP": 34068,
+ "TEXTURE_CUBE_MAP_POSITIVE_X": 34069,
+ "TEXTURE_CUBE_MAP_NEGATIVE_X": 34070,
+ "TEXTURE_CUBE_MAP_POSITIVE_Y": 34071,
+ "TEXTURE_CUBE_MAP_NEGATIVE_Y": 34072,
+ "TEXTURE_CUBE_MAP_POSITIVE_Z": 34073,
+ "TEXTURE_CUBE_MAP_NEGATIVE_Z": 34074,
+ "MAX_CUBE_MAP_TEXTURE_SIZE": 34076,
+ "TEXTURE0": 33984,
+ "TEXTURE1": 33985,
+ "TEXTURE2": 33986,
+ "TEXTURE3": 33987,
+ "TEXTURE4": 33988,
+ "TEXTURE5": 33989,
+ "TEXTURE6": 33990,
+ "TEXTURE7": 33991,
+ "TEXTURE8": 33992,
+ "TEXTURE9": 33993,
+ "TEXTURE10": 33994,
+ "TEXTURE11": 33995,
+ "TEXTURE12": 33996,
+ "TEXTURE13": 33997,
+ "TEXTURE14": 33998,
+ "TEXTURE15": 33999,
+ "TEXTURE16": 34000,
+ "TEXTURE17": 34001,
+ "TEXTURE18": 34002,
+ "TEXTURE19": 34003,
+ "TEXTURE20": 34004,
+ "TEXTURE21": 34005,
+ "TEXTURE22": 34006,
+ "TEXTURE23": 34007,
+ "TEXTURE24": 34008,
+ "TEXTURE25": 34009,
+ "TEXTURE26": 34010,
+ "TEXTURE27": 34011,
+ "TEXTURE28": 34012,
+ "TEXTURE29": 34013,
+ "TEXTURE30": 34014,
+ "TEXTURE31": 34015,
+ "ACTIVE_TEXTURE": 34016,
+ "REPEAT": 10497,
+ "CLAMP_TO_EDGE": 33071,
+ "MIRRORED_REPEAT": 33648,
+ "FLOAT_VEC2": 35664,
+ "FLOAT_VEC3": 35665,
+ "FLOAT_VEC4": 35666,
+ "INT_VEC2": 35667,
+ "INT_VEC3": 35668,
+ "INT_VEC4": 35669,
+ "BOOL": 35670,
+ "BOOL_VEC2": 35671,
+ "BOOL_VEC3": 35672,
+ "BOOL_VEC4": 35673,
+ "FLOAT_MAT2": 35674,
+ "FLOAT_MAT3": 35675,
+ "FLOAT_MAT4": 35676,
+ "SAMPLER_2D": 35678,
+ "SAMPLER_CUBE": 35680,
+ "VERTEX_ATTRIB_ARRAY_ENABLED": 34338,
+ "VERTEX_ATTRIB_ARRAY_SIZE": 34339,
+ "VERTEX_ATTRIB_ARRAY_STRIDE": 34340,
+ "VERTEX_ATTRIB_ARRAY_TYPE": 34341,
+ "VERTEX_ATTRIB_ARRAY_NORMALIZED": 34922,
+ "VERTEX_ATTRIB_ARRAY_POINTER": 34373,
+ "VERTEX_ATTRIB_ARRAY_BUFFER_BINDING": 34975,
+ "IMPLEMENTATION_COLOR_READ_TYPE": 35738,
+ "IMPLEMENTATION_COLOR_READ_FORMAT": 35739,
+ "COMPILE_STATUS": 35713,
+ "LOW_FLOAT": 36336,
+ "MEDIUM_FLOAT": 36337,
+ "HIGH_FLOAT": 36338,
+ "LOW_INT": 36339,
+ "MEDIUM_INT": 36340,
+ "HIGH_INT": 36341,
+ "FRAMEBUFFER": 36160,
+ "RENDERBUFFER": 36161,
+ "RGBA4": 32854,
+ "RGB5_A1": 32855,
+ "RGB565": 36194,
+ "DEPTH_COMPONENT16": 33189,
+ "STENCIL_INDEX8": 36168,
+ "DEPTH_STENCIL": 34041,
+ "RENDERBUFFER_WIDTH": 36162,
+ "RENDERBUFFER_HEIGHT": 36163,
+ "RENDERBUFFER_INTERNAL_FORMAT": 36164,
+ "RENDERBUFFER_RED_SIZE": 36176,
+ "RENDERBUFFER_GREEN_SIZE": 36177,
+ "RENDERBUFFER_BLUE_SIZE": 36178,
+ "RENDERBUFFER_ALPHA_SIZE": 36179,
+ "RENDERBUFFER_DEPTH_SIZE": 36180,
+ "RENDERBUFFER_STENCIL_SIZE": 36181,
+ "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE": 36048,
+ "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME": 36049,
+ "FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL": 36050,
+ "FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE": 36051,
+ "COLOR_ATTACHMENT0": 36064,
+ "DEPTH_ATTACHMENT": 36096,
+ "STENCIL_ATTACHMENT": 36128,
+ "DEPTH_STENCIL_ATTACHMENT": 33306,
+ "NONE": 0,
+ "FRAMEBUFFER_COMPLETE": 36053,
+ "FRAMEBUFFER_INCOMPLETE_ATTACHMENT": 36054,
+ "FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT": 36055,
+ "FRAMEBUFFER_INCOMPLETE_DIMENSIONS": 36057,
+ "FRAMEBUFFER_UNSUPPORTED": 36061,
+ "FRAMEBUFFER_BINDING": 36006,
+ "RENDERBUFFER_BINDING": 36007,
+ "MAX_RENDERBUFFER_SIZE": 34024,
+ "INVALID_FRAMEBUFFER_OPERATION": 1286,
+ "UNPACK_FLIP_Y_WEBGL": 37440,
+ "UNPACK_PREMULTIPLY_ALPHA_WEBGL": 37441,
+ "CONTEXT_LOST_WEBGL": 37442,
+ "UNPACK_COLORSPACE_CONVERSION_WEBGL": 37443,
+ "BROWSER_DEFAULT_WEBGL": 37444
+};
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLmethod.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLmethod.js
new file mode 100644
index 0000000..f2659be
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLmethod.js
@@ -0,0 +1,142 @@
+let i = 1;
+
+const GLmethod = {};
+
+GLmethod.activeTexture = i++; //1
+GLmethod.attachShader = i++;
+GLmethod.bindAttribLocation = i++;
+GLmethod.bindBuffer = i++;
+GLmethod.bindFramebuffer = i++;
+GLmethod.bindRenderbuffer = i++;
+GLmethod.bindTexture = i++;
+GLmethod.blendColor = i++;
+GLmethod.blendEquation = i++;
+GLmethod.blendEquationSeparate = i++; //10
+GLmethod.blendFunc = i++;
+GLmethod.blendFuncSeparate = i++;
+GLmethod.bufferData = i++;
+GLmethod.bufferSubData = i++;
+GLmethod.checkFramebufferStatus = i++;
+GLmethod.clear = i++;
+GLmethod.clearColor = i++;
+GLmethod.clearDepth = i++;
+GLmethod.clearStencil = i++;
+GLmethod.colorMask = i++; //20
+GLmethod.compileShader = i++;
+GLmethod.compressedTexImage2D = i++;
+GLmethod.compressedTexSubImage2D = i++;
+GLmethod.copyTexImage2D = i++;
+GLmethod.copyTexSubImage2D = i++;
+GLmethod.createBuffer = i++;
+GLmethod.createFramebuffer = i++;
+GLmethod.createProgram = i++;
+GLmethod.createRenderbuffer = i++;
+GLmethod.createShader = i++; //30
+GLmethod.createTexture = i++;
+GLmethod.cullFace = i++;
+GLmethod.deleteBuffer = i++;
+GLmethod.deleteFramebuffer = i++;
+GLmethod.deleteProgram = i++;
+GLmethod.deleteRenderbuffer = i++;
+GLmethod.deleteShader = i++;
+GLmethod.deleteTexture = i++;
+GLmethod.depthFunc = i++;
+GLmethod.depthMask = i++; //40
+GLmethod.depthRange = i++;
+GLmethod.detachShader = i++;
+GLmethod.disable = i++;
+GLmethod.disableVertexAttribArray = i++;
+GLmethod.drawArrays = i++;
+GLmethod.drawArraysInstancedANGLE = i++;
+GLmethod.drawElements = i++;
+GLmethod.drawElementsInstancedANGLE = i++;
+GLmethod.enable = i++;
+GLmethod.enableVertexAttribArray = i++; //50
+GLmethod.flush = i++;
+GLmethod.framebufferRenderbuffer = i++;
+GLmethod.framebufferTexture2D = i++;
+GLmethod.frontFace = i++;
+GLmethod.generateMipmap = i++;
+GLmethod.getActiveAttrib = i++;
+GLmethod.getActiveUniform = i++;
+GLmethod.getAttachedShaders = i++;
+GLmethod.getAttribLocation = i++;
+GLmethod.getBufferParameter = i++; //60
+GLmethod.getContextAttributes = i++;
+GLmethod.getError = i++;
+GLmethod.getExtension = i++;
+GLmethod.getFramebufferAttachmentParameter = i++;
+GLmethod.getParameter = i++;
+GLmethod.getProgramInfoLog = i++;
+GLmethod.getProgramParameter = i++;
+GLmethod.getRenderbufferParameter = i++;
+GLmethod.getShaderInfoLog = i++;
+GLmethod.getShaderParameter = i++; //70
+GLmethod.getShaderPrecisionFormat = i++;
+GLmethod.getShaderSource = i++;
+GLmethod.getSupportedExtensions = i++;
+GLmethod.getTexParameter = i++;
+GLmethod.getUniform = i++;
+GLmethod.getUniformLocation = i++;
+GLmethod.getVertexAttrib = i++;
+GLmethod.getVertexAttribOffset = i++;
+GLmethod.isBuffer = i++;
+GLmethod.isContextLost = i++; //80
+GLmethod.isEnabled = i++;
+GLmethod.isFramebuffer = i++;
+GLmethod.isProgram = i++;
+GLmethod.isRenderbuffer = i++;
+GLmethod.isShader = i++;
+GLmethod.isTexture = i++;
+GLmethod.lineWidth = i++;
+GLmethod.linkProgram = i++;
+GLmethod.pixelStorei = i++;
+GLmethod.polygonOffset = i++; //90
+GLmethod.readPixels = i++;
+GLmethod.renderbufferStorage = i++;
+GLmethod.sampleCoverage = i++;
+GLmethod.scissor = i++;
+GLmethod.shaderSource = i++;
+GLmethod.stencilFunc = i++;
+GLmethod.stencilFuncSeparate = i++;
+GLmethod.stencilMask = i++;
+GLmethod.stencilMaskSeparate = i++;
+GLmethod.stencilOp = i++; //100
+GLmethod.stencilOpSeparate = i++;
+GLmethod.texImage2D = i++;
+GLmethod.texParameterf = i++;
+GLmethod.texParameteri = i++;
+GLmethod.texSubImage2D = i++;
+GLmethod.uniform1f = i++;
+GLmethod.uniform1fv = i++;
+GLmethod.uniform1i = i++;
+GLmethod.uniform1iv = i++;
+GLmethod.uniform2f = i++; //110
+GLmethod.uniform2fv = i++;
+GLmethod.uniform2i = i++;
+GLmethod.uniform2iv = i++;
+GLmethod.uniform3f = i++;
+GLmethod.uniform3fv = i++;
+GLmethod.uniform3i = i++;
+GLmethod.uniform3iv = i++;
+GLmethod.uniform4f = i++;
+GLmethod.uniform4fv = i++;
+GLmethod.uniform4i = i++; //120
+GLmethod.uniform4iv = i++;
+GLmethod.uniformMatrix2fv = i++;
+GLmethod.uniformMatrix3fv = i++;
+GLmethod.uniformMatrix4fv = i++;
+GLmethod.useProgram = i++;
+GLmethod.validateProgram = i++;
+GLmethod.vertexAttrib1f = i++; //new
+GLmethod.vertexAttrib2f = i++; //new
+GLmethod.vertexAttrib3f = i++; //new
+GLmethod.vertexAttrib4f = i++; //new //130
+GLmethod.vertexAttrib1fv = i++; //new
+GLmethod.vertexAttrib2fv = i++; //new
+GLmethod.vertexAttrib3fv = i++; //new
+GLmethod.vertexAttrib4fv = i++; //new
+GLmethod.vertexAttribPointer = i++;
+GLmethod.viewport = i++;
+
+export default GLmethod;
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLtype.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLtype.js
new file mode 100644
index 0000000..695abcb
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLtype.js
@@ -0,0 +1,23 @@
+const GLtype = {};
+
+[
+ "GLbitfield",
+ "GLboolean",
+ "GLbyte",
+ "GLclampf",
+ "GLenum",
+ "GLfloat",
+ "GLint",
+ "GLintptr",
+ "GLsizei",
+ "GLsizeiptr",
+ "GLshort",
+ "GLubyte",
+ "GLuint",
+ "GLushort"
+].sort().map((typeName, i) => GLtype[typeName] = 1 >> (i + 1));
+
+export default GLtype;
+
+
+
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Program.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Program.js
new file mode 100644
index 0000000..6f5691c
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Program.js
@@ -0,0 +1,21 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLProgram';
+
+function uuid(id) {
+ return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLProgram {
+ className = name;
+
+ constructor(id) {
+ this.id = id;
+ }
+
+ static uuid = uuid;
+
+ uuid() {
+ return uuid(this.id);
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Renderbuffer.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Renderbuffer.js
new file mode 100644
index 0000000..d3182ae
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Renderbuffer.js
@@ -0,0 +1,21 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLRenderBuffer';
+
+function uuid(id) {
+ return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLRenderbuffer {
+ className = name;
+
+ constructor(id) {
+ this.id = id;
+ }
+
+ static uuid = uuid;
+
+ uuid() {
+ return uuid(this.id);
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/RenderingContext.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/RenderingContext.js
new file mode 100644
index 0000000..5f9608f
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/RenderingContext.js
@@ -0,0 +1,1191 @@
+import GLenum from './GLenum';
+import ActiveInfo from './ActiveInfo';
+import Buffer from './Buffer';
+import Framebuffer from './Framebuffer';
+import Renderbuffer from './Renderbuffer';
+import Texture from './Texture';
+import Program from './Program';
+import Shader from './Shader';
+import ShaderPrecisionFormat from './ShaderPrecisionFormat';
+import UniformLocation from './UniformLocation';
+import GLmethod from './GLmethod';
+
+const processArray = (array, checkArrayType = false) => {
+
+ function joinArray(arr, sep) {
+ let res = '';
+ for (let i = 0; i < arr.length; i++) {
+ if (i !== 0) {
+ res += sep;
+ }
+ res += arr[i];
+ }
+ return res;
+ }
+
+ let type = 'Float32Array';
+ if (checkArrayType) {
+ if (array instanceof Uint8Array) {
+ type = 'Uint8Array'
+ } else if (array instanceof Uint16Array) {
+ type = 'Uint16Array';
+ } else if (array instanceof Uint32Array) {
+ type = 'Uint32Array';
+ } else if (array instanceof Float32Array) {
+ type = 'Float32Array';
+ } else {
+ throw new Error('Check array type failed. Array type is ' + typeof array);
+ }
+ }
+
+ const ArrayTypes = {
+ Uint8Array: 1,
+ Uint16Array: 2,
+ Uint32Array: 4,
+ Float32Array: 14
+ };
+ return ArrayTypes[type] + ',' + btoa(joinArray(array, ','))
+}
+
+export default class WebGLRenderingContext {
+
+ // static GBridge = null;
+
+ className = 'WebGLRenderingContext';
+
+ constructor(canvas, type, attrs) {
+ this._canvas = canvas;
+ this._type = type;
+ this._version = 'WebGL 1.0';
+ this._attrs = attrs;
+ this._map = new Map();
+
+ Object.keys(GLenum)
+ .forEach(name => Object.defineProperty(this, name, {
+ value: GLenum[name]
+ }));
+ }
+
+ get canvas() {
+ return this._canvas;
+ }
+
+ activeTexture = function (textureUnit) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.activeTexture + ',' + textureUnit,
+ true
+ );
+ }
+
+ attachShader = function (progarm, shader) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.attachShader + ',' + progarm.id + ',' + shader.id,
+ true
+ );
+ }
+
+ bindAttribLocation = function (program, index, name) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.bindAttribLocation + ',' + program.id + ',' + index + ',' + name,
+ true
+ )
+ }
+
+ bindBuffer = function (target, buffer) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.bindBuffer + ',' + target + ',' + (buffer ? buffer.id : 0),
+ true
+ );
+ }
+
+ bindFramebuffer = function (target, framebuffer) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.bindFramebuffer + ',' + target + ',' + (framebuffer ? framebuffer.id : 0),
+ true
+ )
+ }
+
+ bindRenderbuffer = function (target, renderBuffer) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.bindRenderbuffer + ',' + target + ',' + (renderBuffer ? renderBuffer.id : 0),
+ true
+ )
+ }
+
+ bindTexture = function (target, texture) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.bindTexture + ',' + target + ',' + (texture ? texture.id : 0),
+ true
+ )
+ }
+
+ blendColor = function (r, g, b, a) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.blendColor + ',' + target + ',' + r + ',' + g + ',' + b + ',' + a,
+ true
+ )
+ }
+
+ blendEquation = function (mode) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.blendEquation + ',' + mode,
+ true
+ )
+ }
+
+ blendEquationSeparate = function (modeRGB, modeAlpha) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.blendEquationSeparate + ',' + modeRGB + ',' + modeAlpha,
+ true
+ )
+ }
+
+
+ blendFunc = function (sfactor, dfactor) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.blendFunc + ',' + sfactor + ',' + dfactor,
+ true
+ );
+ }
+
+ blendFuncSeparate = function (srcRGB, dstRGB, srcAlpha, dstAlpha) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.blendFuncSeparate + ',' + srcRGB + ',' + dstRGB + ',' + srcAlpha + ',' + dstAlpha,
+ true
+ );
+ }
+
+ bufferData = function (target, data, usage) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.bufferData + ',' + target + ',' + processArray(data, true) + ',' + usage,
+ true
+ )
+ }
+
+ bufferSubData = function (target, offset, data) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.bufferSubData + ',' + target + ',' + offset + ',' + processArray(data, true),
+ true
+ )
+ }
+
+ checkFramebufferStatus = function (target) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.checkFramebufferStatus + ',' + target
+ );
+ return Number(result);
+ }
+
+ clear = function (mask) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.clear + ',' + mask
+ );
+ this._canvas._needRender = true;
+ }
+
+ clearColor = function (r, g, b, a) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.clearColor + ',' + r + ',' + g + ',' + b,
+ true
+ )
+ }
+
+ clearDepth = function (depth) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.clearDepth + ',' + depth,
+ true
+ )
+ }
+
+ clearStencil = function (s) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.clearStencil + ',' + s
+ );
+ }
+
+ colorMask = function (r, g, b, a) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.colorMask + ',' + r + ',' + g + ',' + b + ',' + a
+ )
+ }
+
+ compileShader = function (shader) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.compileShader + ',' + shader.id,
+ true
+ )
+ }
+
+ compressedTexImage2D = function (target, level, internalformat, width, height, border, pixels) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.compressedTexImage2D + ',' + target + ',' + level + ',' + internalformat + ',' +
+ width + ',' + height + ',' + border + ',' + processArray(pixels),
+ true
+ )
+ }
+
+ compressedTexSubImage2D = function (target, level, xoffset, yoffset, width, height, format, pixels) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.compressedTexSubImage2D + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset + ',' +
+ width + ',' + height + ',' + format + ',' + processArray(pixels),
+ true
+ )
+ }
+
+
+ copyTexImage2D = function (target, level, internalformat, x, y, width, height, border) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.copyTexImage2D + ',' + target + ',' + level + ',' + internalformat + ',' + x + ',' + y + ',' +
+ width + ',' + height + ',' + border,
+ true
+ );
+ }
+
+ copyTexSubImage2D = function (target, level, xoffset, yoffset, x, y, width, height) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.copyTexSubImage2D + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset + ',' + x + ',' + y + ',' +
+ width + ',' + height
+ );
+ }
+
+ createBuffer = function () {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.createBuffer + ''
+ );
+ const buffer = new Buffer(result);
+ this._map.set(buffer.uuid(), buffer);
+ return buffer;
+ }
+
+ createFramebuffer = function () {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.createFramebuffer + ''
+ );
+ const framebuffer = new Framebuffer(result);
+ this._map.set(framebuffer.uuid(), framebuffer);
+ return framebuffer;
+ }
+
+
+ createProgram = function () {
+ const id = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.createProgram + ''
+ );
+ const program = new Program(id);
+ this._map.set(program.uuid(), program);
+ return program;
+ }
+
+ createRenderbuffer = function () {
+ const id = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.createRenderbuffer + ''
+ )
+ const renderBuffer = new Renderbuffer(id);
+ this._map.set(renderBuffer.uuid(), renderBuffer);
+ return renderBuffer;
+ }
+
+ createShader = function (type) {
+ const id = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.createShader + ',' + type
+ )
+ const shader = new Shader(id, type);
+ this._map.set(shader.uuid(), shader);
+ return shader;
+ }
+
+ createTexture = function () {
+ const id = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.createTexture + ''
+ );
+ const texture = new Texture(id);
+ this._map.set(texture.uuid(), texture);
+ return texture;
+ }
+
+ cullFace = function (mode) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.cullFace + ',' + mode,
+ true
+ )
+ }
+
+
+ deleteBuffer = function (buffer) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.deleteBuffer + ',' + buffer.id,
+ true
+ )
+ }
+
+ deleteFramebuffer = function (framebuffer) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.deleteFramebuffer + ',' + framebuffer.id,
+ true
+ )
+ }
+
+ deleteProgram = function (program) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.deleteProgram + ',' + program.id,
+ true
+ )
+ }
+
+ deleteRenderbuffer = function (renderbuffer) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.deleteRenderbuffer + ',' + renderbuffer.id,
+ true
+ )
+ }
+
+ deleteShader = function (shader) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.deleteShader + ',' + shader.id,
+ true
+ )
+ }
+
+ deleteTexture = function (texture) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.deleteTexture + ',' + texture.id,
+ true
+ )
+ }
+
+ depthFunc = function (func) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.depthFunc + ',' + func
+ )
+ }
+
+ depthMask = function (flag) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.depthMask + ',' + Number(flag),
+ true
+ )
+ }
+
+ depthRange = function (zNear, zFar) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.depthRange + ',' + zNear + ',' + zFar,
+ true
+ )
+ }
+
+ detachShader = function (program, shader) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.detachShader + ',' + program.id + ',' + shader.id,
+ true
+ )
+ }
+
+ disable = function (cap) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.disable + ',' + cap,
+ true
+ )
+ }
+
+ disableVertexAttribArray = function (index) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.disableVertexAttribArray + ',' + index,
+ true
+ );
+ }
+
+ drawArrays = function (mode, first, count) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.drawArrays + ',' + mode + ',' + first + ',' + count
+ )
+ this._canvas._needRender = true;
+ }
+
+ drawElements = function (mode, count, type, offset) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.drawElements + ',' + mode + ',' + count + ',' + type + ',' + offset + ';'
+ );
+ this._canvas._needRender = true;
+ }
+
+ enable = function (cap) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.enable + ',' + cap,
+ true
+ );
+ }
+
+ enableVertexAttribArray = function (index) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.enableVertexAttribArray + ',' + index,
+ true
+ )
+ }
+
+
+ flush = function () {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.flush + ''
+ )
+ }
+
+ framebufferRenderbuffer = function (target, attachment, textarget, texture, level) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.framebufferRenderbuffer + ',' + target + ',' + attachment + ',' + textarget + ',' + (texture ? texture.id : 0) + ',' + level,
+ true
+ )
+ }
+
+ framebufferTexture2D = function (target, attachment, textarget, texture, level) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.framebufferTexture2D + ',' + target + ',' + attachment + ',' + textarget + ',' + (texture ? texture.id : 0) + ',' + level,
+ true
+ )
+ }
+
+ frontFace = function (mode) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.frontFace + ',' + mode,
+ true
+ )
+ }
+
+ generateMipmap = function (target) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.generateMipmap + ',' + target,
+ true
+ )
+ }
+
+ getActiveAttrib = function (progarm, index) {
+ const resultString = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getActiveAttrib + ',' + progarm.id + ',' + index
+ )
+ const [type, size, name] = resultString.split(',');
+ return new ActiveInfo({
+ type: Number(type),
+ size: Number(size),
+ name
+ });
+ }
+
+ getActiveUniform = function (progarm, index) {
+ const resultString = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getActiveUniform + ',' + progarm.id + ',' + index
+ );
+ const [type, size, name] = resultString.split(',');
+ return new ActiveInfo({
+ type: Number(type),
+ size: Number(size),
+ name
+ })
+ }
+
+ getAttachedShaders = function (progarm) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getAttachedShaders + ',' + progarm.id
+ );
+ const [type, ...ids] = result;
+ return ids.map(id => this._map.get(Shader.uuid(id)));
+ }
+
+ getAttribLocation = function (progarm, name) {
+ return WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getAttribLocation + ',' + progarm.id + ',' + name
+ )
+ }
+
+ getBufferParameter = function (target, pname) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getBufferParameter + ',' + target + ',' + pname
+ );
+ const [type, res] = getBufferParameter;
+ return res;
+ }
+
+ getError = function () {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getError + ''
+ )
+ return result;
+ }
+
+ getExtension = function (name) {
+ return null;
+ }
+
+ getFramebufferAttachmentParameter = function (target, attachment, pname) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getFramebufferAttachmentParameter + ',' + target + ',' + attachment + ',' + pname
+ )
+ switch (pname) {
+ case GLenum.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
+ return this._map.get(Renderbuffer.uuid(result)) || this._map.get(Texture.uuid(result)) || null;
+ default:
+ return result;
+ }
+ }
+
+ getParameter = function (pname) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getParameter + ',' + pname
+ )
+ switch (pname) {
+ case GLenum.VERSION:
+ return this._version;
+ case GLenum.ARRAY_BUFFER_BINDING: // buffer
+ case GLenum.ELEMENT_ARRAY_BUFFER_BINDING: // buffer
+ return this._map.get(Buffer.uuid(result)) || null;
+ case GLenum.CURRENT_PROGRAM: // program
+ return this._map.get(Program.uuid(result)) || null;
+ case GLenum.FRAMEBUFFER_BINDING: // framebuffer
+ return this._map.get(Framebuffer.uuid(result)) || null;
+ case GLenum.RENDERBUFFER_BINDING: // renderbuffer
+ return this._map.get(Renderbuffer.uuid(result)) || null;
+ case GLenum.TEXTURE_BINDING_2D: // texture
+ case GLenum.TEXTURE_BINDING_CUBE_MAP: // texture
+ return this._map.get(Texture.uuid(result)) || null;
+ case GLenum.ALIASED_LINE_WIDTH_RANGE: // Float32Array
+ case GLenum.ALIASED_POINT_SIZE_RANGE: // Float32Array
+ case GLenum.BLEND_COLOR: // Float32Array
+ case GLenum.COLOR_CLEAR_VALUE: // Float32Array
+ case GLenum.DEPTH_RANGE: // Float32Array
+ case GLenum.MAX_VIEWPORT_DIMS: // Int32Array
+ case GLenum.SCISSOR_BOX: // Int32Array
+ case GLenum.VIEWPORT: // Int32Array
+ case GLenum.COMPRESSED_TEXTURE_FORMATS: // Uint32Array
+ default:
+ const [type, ...res] = result.split(',');
+ if (res.length === 1) {
+ return Number(res[0]);
+ } else {
+ return res.map(Number);
+ }
+ }
+ }
+
+ getProgramInfoLog = function (progarm) {
+ return WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getProgramInfoLog + ',' + progarm.id
+ )
+ }
+
+ getProgramParameter = function (program, pname) {
+ const res = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getProgramParameter + ',' + program.id + ',' + pname
+ );
+
+ const [type, result] = res.split(',').map(i => parseInt(i));
+
+ if (type === 1) {
+ return Boolean(result);
+ } else if (type === 2) {
+ return result;
+ } else {
+ throw new Error('Unrecongized program paramater ' + res + ', type: ' + typeof res);
+ }
+ }
+
+
+ getRenderbufferParameter = function (target, pname) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getRenderbufferParameter + ',' + target + ',' + pname
+ )
+ return result;
+ }
+
+
+ getShaderInfoLog = function (shader) {
+ return WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getShaderInfoLog + ',' + shader.id
+ );
+ }
+
+ getShaderParameter = function (shader, pname) {
+ return WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getShaderParameter + ',' + shader.id + ',' + pname
+ )
+ }
+
+ getShaderPrecisionFormat = function (shaderType, precisionType) {
+ const [rangeMin, rangeMax, precision] = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getShaderPrecisionFormat + ',' + shaderType + ',' + precisionType
+ );
+ const shaderPrecisionFormat = new ShaderPrecisionFormat({
+ rangeMin: Number(rangeMin),
+ rangeMax: Number(rangeMax),
+ precision: Number(precision)
+ });
+ return shaderPrecisionFormat;
+ }
+
+ getShaderSource = function (shader) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getShaderSource + ',' + shader.id
+ );
+ return result;
+ }
+
+ getSupportedExtensions = function () {
+ return Object.keys({});
+ }
+
+ getTexParameter = function (target, pname) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getTexParameter + ',' + target + ',' + pname
+ )
+ return result;
+ }
+
+ getUniformLocation = function (program, name) {
+ const id = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getUniformLocation + ',' + program.id + ',' + name
+ );
+ if (id === -1) {
+ return null;
+ } else {
+ return new UniformLocation(Number(id));
+ }
+ }
+
+ getVertexAttrib = function (index, pname) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getVertexAttrib + ',' + index + ',' + pname
+ );
+ switch (pname) {
+ case GLenum.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
+ return this._map.get(Buffer.uuid(result)) || null;
+ case GLenum.CURRENT_VERTEX_ATTRIB: // Float32Array
+ default:
+ return result;
+ }
+ }
+
+ getVertexAttribOffset = function (index, pname) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.getVertexAttribOffset + ',' + index + ',' + pname
+ )
+ return Number(result);
+ }
+
+ isBuffer = function (buffer) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.isBuffer + ',' + buffer.id
+ )
+ return Boolean(result);
+ }
+
+ isContextLost = function () {
+ return false;
+ }
+
+ isEnabled = function (cap) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.isEnabled + ',' + cap
+ )
+ return Boolean(result);
+ }
+
+ isFramebuffer = function (framebuffer) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.isFramebuffer + ',' + framebuffer.id
+ )
+ return Boolean(result);
+ }
+
+ isProgram = function (program) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.isProgram + ',' + program.id
+ )
+ return Boolean(result);
+ }
+
+ isRenderbuffer = function (renderBuffer) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.isRenderbuffer + ',' + renderbuffer.id
+ )
+ return Boolean(result);
+ }
+
+ isShader = function (shader) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.isShader + ',' + shader.id
+ )
+ return Boolean(result);
+ }
+
+ isTexture = function (texture) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.isTexture + ',' + texture.id
+ );
+ return Boolean(result);
+ }
+
+ lineWidth = function (width) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.lineWidth + ',' + width,
+ true
+ )
+ }
+
+ linkProgram = function (program) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.linkProgram + ',' + program.id,
+ true
+ );
+ }
+
+
+ pixelStorei = function (pname, param) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.pixelStorei + ',' + pname + ',' + Number(param)
+ )
+ }
+
+ polygonOffset = function (factor, units) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.polygonOffset + ',' + factor + ',' + units
+ )
+ }
+
+ readPixels = function (x, y, width, height, format, type, pixels) {
+ const result = WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.readPixels + ',' + x + ',' + y + ',' + width + ',' + height + ',' + format + ',' + type
+ )
+ return result;
+ }
+
+ renderbufferStorage = function (target, internalFormat, width, height) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.renderbufferStorage + ',' + target + ',' + internalFormat + ',' + width + ',' + height,
+ true
+ )
+ }
+
+ sampleCoverage = function (value, invert) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.sampleCoverage + ',' + value + ',' + Number(invert),
+ true
+ )
+ }
+
+ scissor = function (x, y, width, height) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.scissor + ',' + x + ',' + y + ',' + width + ',' + height,
+ true
+ )
+ }
+
+ shaderSource = function (shader, source) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.shaderSource + ',' + shader.id + ',' + source
+ )
+ }
+
+ stencilFunc = function (func, ref, mask) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.stencilFunc + ',' + func + ',' + ref + ',' + mask,
+ true
+ )
+ }
+
+ stencilFuncSeparate = function (face, func, ref, mask) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.stencilFuncSeparate + ',' + face + ',' + func + ',' + ref + ',' + mask,
+ true
+ )
+ }
+
+ stencilMask = function (mask) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.stencilMask + ',' + mask,
+ true
+ )
+ }
+
+ stencilMaskSeparate = function (face, mask) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.stencilMaskSeparate + ',' + face + ',' + mask,
+ true
+ )
+ }
+
+ stencilOp = function (fail, zfail, zpass) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.stencilOp + ',' + fail + ',' + zfail + ',' + zpass
+ )
+ }
+
+ stencilOpSeparate = function (face, fail, zfail, zpass) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.stencilOp + ',' + face + ',' + fail + ',' + zfail + ',' + zpass,
+ true
+ )
+ }
+
+ texImage2D = function (...args) {
+ WebGLRenderingContext.GBridge.texImage2D(this._canvas.id, ...args);
+ }
+
+
+ texParameterf = function (target, pname, param) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.texParameterf + ',' + target + ',' + pname + ',' + param,
+ true
+ )
+ }
+
+ texParameteri = function (target, pname, param) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.texParameteri + ',' + target + ',' + pname + ',' + param
+ )
+ }
+
+ texSubImage2D = function (...args) {
+ WebGLRenderingContext.GBridge.texSubImage2D(this._canvas.id, ...args);
+ }
+
+ uniform1f = function (location, v0) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform1f + ',' + location.id + ',' + v0
+ )
+ }
+
+ uniform1fv = function (location, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform1fv + ',' + location.id + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniform1i = function (location, v0) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform1i + ',' + location.id + ',' + v0,
+ // true
+ )
+ }
+
+ uniform1iv = function (location, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform1iv + ',' + location.id + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniform2f = function (location, v0, v1) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform2f + ',' + location.id + ',' + v0 + ',' + v1,
+ true
+ )
+ }
+
+ uniform2fv = function (location, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform2fv + ',' + location.id + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniform2i = function (location, v0, v1) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform2i + ',' + location.id + ',' + v0 + ',' + v1,
+ true
+ )
+ }
+
+ uniform2iv = function (location, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform2iv + ',' + location.id + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniform3f = function (location, v0, v1, v2) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform3f + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2,
+ true
+ )
+ }
+
+ uniform3fv = function (location, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform3fv + ',' + location.id + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniform3i = function (location, v0, v1, v2) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform3i + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2,
+ true
+ )
+ }
+
+ uniform3iv = function (location, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform3iv + ',' + location.id + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniform4f = function (location, v0, v1, v2, v3) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform4f + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,
+ true
+ )
+ }
+
+ uniform4fv = function (location, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform4fv + ',' + location.id + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniform4i = function (location, v0, v1, v2, v3) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform4i + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,
+ true
+ )
+ }
+
+ uniform4iv = function (location, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniform4iv + ',' + location.id + ',' + processArray(value, true),
+ true
+ )
+ }
+
+ uniformMatrix2fv = function (location, transpose, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniformMatrix2fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniformMatrix3fv = function (location, transpose, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniformMatrix3fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),
+ true
+ )
+ }
+
+ uniformMatrix4fv = function (location, transpose, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.uniformMatrix4fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),
+ true
+ );
+ }
+
+ useProgram = function (progarm) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.useProgram + ',' + progarm.id + '',
+ true
+ )
+ }
+
+
+ validateProgram = function (program) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.validateProgram + ',' + program.id,
+ true
+ )
+ }
+
+ vertexAttrib1f = function (index, v0) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttrib1f + ',' + index + ',' + v0,
+ true
+ )
+ }
+
+ vertexAttrib2f = function (index, v0, v1) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttrib2f + ',' + index + ',' + v0 + ',' + v1,
+ true
+ )
+ }
+
+ vertexAttrib3f = function (index, v0, v1, v2) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttrib3f + ',' + index + ',' + v0 + ',' + v1 + ',' + v2,
+ true
+ )
+ }
+
+ vertexAttrib4f = function (index, v0, v1, v2, v3) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttrib4f + ',' + index + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,
+ true
+ )
+ }
+
+ vertexAttrib1fv = function (index, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttrib1fv + ',' + index + ',' + processArray(value),
+ true
+ )
+ }
+
+ vertexAttrib2fv = function (index, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttrib2fv + ',' + index + ',' + processArray(value),
+ true
+ )
+ }
+
+ vertexAttrib3fv = function (index, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttrib3fv + ',' + index + ',' + processArray(value),
+ true
+ )
+ }
+
+ vertexAttrib4fv = function (index, value) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttrib4fv + ',' + index + ',' + processArray(value),
+ true
+ )
+ }
+
+ vertexAttribPointer = function (index, size, type, normalized, stride, offset) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.vertexAttribPointer + ',' + index + ',' + size + ',' + type + ',' + Number(normalized) + ',' + stride + ',' + offset,
+ true
+ )
+ }
+
+ viewport = function (x, y, width, height) {
+ WebGLRenderingContext.GBridge.callNative(
+ this._canvas.id,
+ GLmethod.viewport + ',' + x + ',' + y + ',' + width + ',' + height,
+ true
+ )
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Shader.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Shader.js
new file mode 100644
index 0000000..a763886
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Shader.js
@@ -0,0 +1,22 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLShader';
+
+function uuid(id) {
+ return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLShader {
+ className = name;
+
+ constructor(id, type) {
+ this.id = id;
+ this.type = type;
+ }
+
+ static uuid = uuid;
+
+ uuid() {
+ return uuid(this.id);
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ShaderPrecisionFormat.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ShaderPrecisionFormat.js
new file mode 100644
index 0000000..208d6c1
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ShaderPrecisionFormat.js
@@ -0,0 +1,11 @@
+export default class WebGLShaderPrecisionFormat {
+ className = 'WebGLShaderPrecisionFormat';
+
+ constructor({
+ rangeMin, rangeMax, precision
+ }) {
+ this.rangeMin = rangeMin;
+ this.rangeMax = rangeMax;
+ this.precision = precision;
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Texture.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Texture.js
new file mode 100644
index 0000000..de4d806
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Texture.js
@@ -0,0 +1,22 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLTexture';
+
+function uuid(id) {
+ return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLTexture {
+ className = name;
+
+ constructor(id, type) {
+ this.id = id;
+ this.type = type;
+ }
+
+ static uuid = uuid;
+
+ uuid() {
+ return uuid(this.id);
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/UniformLocation.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/UniformLocation.js
new file mode 100644
index 0000000..f5e99dc
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/UniformLocation.js
@@ -0,0 +1,22 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLUniformLocation';
+
+function uuid(id) {
+ return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLUniformLocation {
+ className = name;
+
+ constructor(id, type) {
+ this.id = id;
+ this.type = type;
+ }
+
+ static uuid = uuid;
+
+ uuid() {
+ return uuid(this.id);
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/classUtils.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/classUtils.js
new file mode 100644
index 0000000..88716be
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/classUtils.js
@@ -0,0 +1,3 @@
+export function getTransferedObjectUUID(name, id) {
+ return `${name.toLowerCase()}-${id}`;
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/canvas.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/canvas.js
new file mode 100644
index 0000000..a8d9bb9
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/canvas.js
@@ -0,0 +1,74 @@
+import GContext2D from '../context-2d/RenderingContext';
+import GContextWebGL from '../context-webgl/RenderingContext';
+
+export default class GCanvas {
+
+ // static GBridge = null;
+
+ id = null;
+
+ _needRender = true;
+
+ constructor(id, { disableAutoSwap }) {
+ this.id = id;
+
+ this._disableAutoSwap = disableAutoSwap;
+ if (disableAutoSwap) {
+ this._swapBuffers = () => {
+ GCanvas.GBridge.render(this.id);
+ }
+ }
+ }
+
+ getContext(type) {
+
+ let context = null;
+
+ if (type.match(/webgl/i)) {
+ context = new GContextWebGL(this);
+
+ context.componentId = this.id;
+
+ if (!this._disableAutoSwap) {
+ const render = () => {
+ if (this._needRender) {
+ GCanvas.GBridge.render(this.id);
+ this._needRender = false;
+ }
+ }
+ setInterval(render, 16);
+ }
+
+ GCanvas.GBridge.callSetContextType(this.id, 1); // 0 for 2d; 1 for webgl
+ } else if (type.match(/2d/i)) {
+ context = new GContext2D(this);
+
+ context.componentId = this.id;
+
+// const render = ( callback ) => {
+//
+// const commands = context._drawCommands;
+// context._drawCommands = '';
+//
+// GCanvas.GBridge.render2d(this.id, commands, callback);
+// this._needRender = false;
+// }
+// //draw方法触发
+// context._flush = render;
+// //setInterval(render, 16);
+
+ GCanvas.GBridge.callSetContextType(this.id, 0);
+ } else {
+ throw new Error('not supported context ' + type);
+ }
+
+ return context;
+
+ }
+
+ reset() {
+ GCanvas.GBridge.callReset(this.id);
+ }
+
+
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/image.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/image.js
new file mode 100644
index 0000000..9499a51
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/image.js
@@ -0,0 +1,96 @@
+let incId = 1;
+
+const noop = function () { };
+
+class GImage {
+
+ static GBridge = null;
+
+ constructor() {
+ this._id = incId++;
+ this._width = 0;
+ this._height = 0;
+ this._src = undefined;
+ this._onload = noop;
+ this._onerror = noop;
+ this.complete = false;
+ }
+
+ get width() {
+ return this._width;
+ }
+ set width(v) {
+ this._width = v;
+ }
+
+ get height() {
+ return this._height;
+ }
+
+ set height(v) {
+ this._height = v;
+ }
+
+ get src() {
+ return this._src;
+ }
+
+ set src(v) {
+
+ if (v.startsWith('//')) {
+ v = 'http:' + v;
+ }
+
+ this._src = v;
+
+ GImage.GBridge.perloadImage([this._src, this._id], (data) => {
+ if (typeof data === 'string') {
+ data = JSON.parse(data);
+ }
+ if (data.error) {
+ var evt = { type: 'error', target: this };
+ this.onerror(evt);
+ } else {
+ this.complete = true;
+ this.width = typeof data.width === 'number' ? data.width : 0;
+ this.height = typeof data.height === 'number' ? data.height : 0;
+ var evt = { type: 'load', target: this };
+ this.onload(evt);
+ }
+ });
+ }
+
+ addEventListener(name, listener) {
+ if (name === 'load') {
+ this.onload = listener;
+ } else if (name === 'error') {
+ this.onerror = listener;
+ }
+ }
+
+ removeEventListener(name, listener) {
+ if (name === 'load') {
+ this.onload = noop;
+ } else if (name === 'error') {
+ this.onerror = noop;
+ }
+ }
+
+ get onload() {
+ return this._onload;
+ }
+
+ set onload(v) {
+ this._onload = v;
+ }
+
+ get onerror() {
+ return this._onerror;
+ }
+
+ set onerror(v) {
+ this._onerror = v;
+ }
+}
+
+export default GImage;
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/tool.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/tool.js
new file mode 100644
index 0000000..d3fb398
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/tool.js
@@ -0,0 +1,24 @@
+
+export function ArrayBufferToBase64 (buffer) {
+ var binary = '';
+ var bytes = new Uint8ClampedArray(buffer);
+ for (var len = bytes.byteLength, i = 0; i < len; i++) {
+ binary += String.fromCharCode(bytes[i]);
+ }
+ return btoa(binary);
+}
+
+export function Base64ToUint8ClampedArray(base64String) {
+ const padding = '='.repeat((4 - base64String.length % 4) % 4);
+ const base64 = (base64String + padding)
+ .replace(/\-/g, '+')
+ .replace(/_/g, '/');
+
+ const rawData = atob(base64);
+ const outputArray = new Uint8ClampedArray(rawData.length);
+
+ for (let i = 0; i < rawData.length; ++i) {
+ outputArray[i] = rawData.charCodeAt(i);
+ }
+ return outputArray;
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/index.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/index.js
new file mode 100644
index 0000000..a34ad58
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/index.js
@@ -0,0 +1,39 @@
+import GCanvas from './env/canvas';
+import GImage from './env/image';
+
+import GWebGLRenderingContext from './context-webgl/RenderingContext';
+import GContext2D from './context-2d/RenderingContext';
+
+import GBridgeWeex from './bridge/bridge-weex';
+
+export let Image = GImage;
+
+export let WeexBridge = GBridgeWeex;
+
+export function enable(el, { bridge, debug, disableAutoSwap, disableComboCommands } = {}) {
+
+ const GBridge = GImage.GBridge = GCanvas.GBridge = GWebGLRenderingContext.GBridge = GContext2D.GBridge = bridge;
+
+ GBridge.callEnable(el.ref, [
+ 0, // renderMode: 0--RENDERMODE_WHEN_DIRTY, 1--RENDERMODE_CONTINUOUSLY
+ -1, // hybridLayerType: 0--LAYER_TYPE_NONE 1--LAYER_TYPE_SOFTWARE 2--LAYER_TYPE_HARDWARE
+ false, // supportScroll
+ false, // newCanvasMode
+ 1, // compatible
+ 'white',// clearColor
+ false // sameLevel: newCanvasMode = true && true => GCanvasView and Webview is same level
+ ]);
+
+ if (debug === true) {
+ GBridge.callEnableDebug();
+ }
+ if (disableComboCommands) {
+ GBridge.callEnableDisableCombo();
+ }
+
+ var canvas = new GCanvas(el.ref, { disableAutoSwap });
+ canvas.width = el.style.width;
+ canvas.height = el.style.height;
+
+ return canvas;
+};
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/props.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/props.js
new file mode 100644
index 0000000..55df5bb
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/props.js
@@ -0,0 +1,85 @@
+export default {
+ props: {
+ //二维码内容
+ value: {
+ type: [String, Number]
+ },
+ //选项
+ options: {
+ type: Object,
+ default: () => {
+ return {};
+ }
+ },
+ //二维码大小
+ size: {
+ type: [String, Number],
+ default: 200
+ },
+ //导出的文件类型
+ fileType: {
+ type: String,
+ default: 'png'
+ },
+ //是否初始化组件后就开始生成
+ start: {
+ type: Boolean,
+ default: true
+ },
+ //是否数据发生改变自动重绘
+ auto: {
+ type: Boolean,
+ default: true
+ },
+ //隐藏组件
+ hide: {
+ type: Boolean,
+ default: false
+ },
+ /**
+ * canvas 类型,微信小程序默认使用2d,非2d微信官方已放弃维护,问题比较多
+ * 注意:微信小程序type2d手机上正常,PC上微信内打开小程序toDataURL报错,看后期微信官方团队会不会做兼容,不兼容的话只能在自行判断在PC使用非2d,或者直接提示用户请在手机上操作,微信团队的海报中心小程序就是这么做的
+ */
+ type: {
+ type: String,
+ default: () => {
+ // #ifdef MP-WEIXIN
+ return '2d';
+ // #endif
+ // #ifndef MP-WEIXIN
+ return 'normal';
+ // #endif
+ }
+ },
+ //队列绘制,主要针对NVue端
+ queue: {
+ type: Boolean,
+ default: false
+ },
+ //是否队列加载图片,可减少canvas发起的网络资源请求,节省服务器资源
+ isQueueLoadImage: {
+ type: Boolean,
+ default: false
+ },
+ //loading态
+ loading: {
+ type: Boolean,
+ default: undefined
+ },
+ //H5保存即自动下载(在支持的环境下),默认false为仅弹层提示用户需要长按图片保存,不会自动下载
+ h5SaveIsDownload: {
+ type: Boolean,
+ default: false
+ },
+ //H5下载名称
+ h5DownloadName: {
+ type: String,
+ default: 'uvQRCode'
+ },
+ // H5保存二维码时候是否显示提示
+ h5SaveTip: {
+ type: Boolean,
+ default: true
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/qrcode.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/qrcode.js
new file mode 100644
index 0000000..2290ab3
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/qrcode.js
@@ -0,0 +1,34 @@
+//---------------------------------------------------------------------
+// uQRCode二维码生成插件 v4.0.6
+//
+// uQRCode是一款基于Javascript环境开发的二维码生成插件,适用所有Javascript运行环境的前端应用和Node.js。
+//
+// Copyright (c) Sansnn uQRCode All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0.
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// github地址:
+// https://github.com/Sansnn/uQRCode
+//
+// npm地址:
+// https://www.npmjs.com/package/uqrcodejs
+//
+// uni-app插件市场地址:
+// https://ext.dcloud.net.cn/plugin?id=1287
+//
+// 复制使用请保留本段注释,感谢支持开源!
+//
+//---------------------------------------------------------------------
+
+//---------------------------------------------------------------------
+// 当前文件格式为 es,将 bundle 保留为 ES 模块文件,适用于其他打包工具以及支持 <script type=module> 标签的浏览器(别名: esm,module)
+// 如需在其他环境使用,请获取环境对应的格式文件
+// 格式说明:
+// amd - 异步模块定义,适用于 RequireJS 等模块加载器
+// cjs - CommonJS,适用于 Node 环境和其他打包工具(别名:commonjs)
+// es - 将 bundle 保留为 ES 模块文件,适用于其他打包工具以及支持 <script type=module> 标签的浏览器(别名: esm,module)
+// umd - 通用模块定义,生成的包同时支持 amd、cjs 和 iife 三种格式
+//---------------------------------------------------------------------
+
+function o(o){this.mode=r.MODE_8BIT_BYTE,this.data=o;}function e(o,e){this.typeNumber=o,this.errorCorrectLevel=e,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=new Array;}o.prototype={getLength:function(o){return this.data.length},write:function(o){for(var e=0;e<this.data.length;e++)o.put(this.data.charCodeAt(e),8);}},e.prototype={addData:function(e){var r=new o(e);this.dataList.push(r),this.dataCache=null;},isDark:function(o,e){if(o<0||this.moduleCount<=o||e<0||this.moduleCount<=e)throw new Error(o+","+e);return this.modules[o][e]},getModuleCount:function(){return this.moduleCount},make:function(){if(this.typeNumber<1){var o=1;for(o=1;o<40;o++){for(var e=v.getRSBlocks(o,this.errorCorrectLevel),r=new p,t=0,i=0;i<e.length;i++)t+=e[i].dataCount;for(i=0;i<this.dataList.length;i++){var n=this.dataList[i];r.put(n.mode,4),r.put(n.getLength(),h.getLengthInBits(n.mode,o)),n.write(r);}if(r.getLengthInBits()<=8*t)break}this.typeNumber=o;}this.makeImpl(!1,this.getBestMaskPattern());},makeImpl:function(o,r){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var t=0;t<this.moduleCount;t++){this.modules[t]=new Array(this.moduleCount);for(var i=0;i<this.moduleCount;i++)this.modules[t][i]=null;}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(o,r),this.typeNumber>=7&&this.setupTypeNumber(o),null==this.dataCache&&(this.dataCache=e.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,r);},setupPositionProbePattern:function(o,e){for(var r=-1;r<=7;r++)if(!(o+r<=-1||this.moduleCount<=o+r))for(var t=-1;t<=7;t++)e+t<=-1||this.moduleCount<=e+t||(this.modules[o+r][e+t]=0<=r&&r<=6&&(0==t||6==t)||0<=t&&t<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=t&&t<=4);},getBestMaskPattern:function(){for(var o=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var t=h.getLostPoint(this);(0==r||o>t)&&(o=t,e=r);}return e},createMovieClip:function(o,e,r){var t=o.createEmptyMovieClip(e,r);this.make();for(var i=0;i<this.modules.length;i++)for(var n=1*i,a=0;a<this.modules[i].length;a++){var d=1*a;this.modules[i][a]&&(t.beginFill(0,100),t.moveTo(d,n),t.lineTo(d+1,n),t.lineTo(d+1,n+1),t.lineTo(d,n+1),t.endFill());}return t},setupTimingPattern:function(){for(var o=8;o<this.moduleCount-8;o++)null==this.modules[o][6]&&(this.modules[o][6]=o%2==0);for(var e=8;e<this.moduleCount-8;e++)null==this.modules[6][e]&&(this.modules[6][e]=e%2==0);},setupPositionAdjustPattern:function(){for(var o=h.getPatternPosition(this.typeNumber),e=0;e<o.length;e++)for(var r=0;r<o.length;r++){var t=o[e],i=o[r];if(null==this.modules[t][i])for(var n=-2;n<=2;n++)for(var a=-2;a<=2;a++)this.modules[t+n][i+a]=-2==n||2==n||-2==a||2==a||0==n&&0==a;}},setupTypeNumber:function(o){for(var e=h.getBCHTypeNumber(this.typeNumber),r=0;r<18;r++){var t=!o&&1==(e>>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=t;}for(r=0;r<18;r++){t=!o&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=t;}},setupTypeInfo:function(o,e){for(var r=this.errorCorrectLevel<<3|e,t=h.getBCHTypeInfo(r),i=0;i<15;i++){var n=!o&&1==(t>>i&1);i<6?this.modules[i][8]=n:i<8?this.modules[i+1][8]=n:this.modules[this.moduleCount-15+i][8]=n;}for(i=0;i<15;i++){n=!o&&1==(t>>i&1);i<8?this.modules[8][this.moduleCount-i-1]=n:i<9?this.modules[8][15-i-1+1]=n:this.modules[8][15-i-1]=n;}this.modules[this.moduleCount-8][8]=!o;},mapData:function(o,e){for(var r=-1,t=this.moduleCount-1,i=7,n=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var d=0;d<2;d++)if(null==this.modules[t][a-d]){var u=!1;n<o.length&&(u=1==(o[n]>>>i&1)),h.getMask(e,t,a-d)&&(u=!u),this.modules[t][a-d]=u,-1==--i&&(n++,i=7);}if((t+=r)<0||this.moduleCount<=t){t-=r,r=-r;break}}}},e.PAD0=236,e.PAD1=17,e.createData=function(o,r,t){for(var i=v.getRSBlocks(o,r),n=new p,a=0;a<t.length;a++){var d=t[a];n.put(d.mode,4),n.put(d.getLength(),h.getLengthInBits(d.mode,o)),d.write(n);}var u=0;for(a=0;a<i.length;a++)u+=i[a].dataCount;if(n.getLengthInBits()>8*u)throw new Error("code length overflow. ("+n.getLengthInBits()+">"+8*u+")");for(n.getLengthInBits()+4<=8*u&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*u||(n.put(e.PAD0,8),n.getLengthInBits()>=8*u));)n.put(e.PAD1,8);return e.createBytes(n,i)},e.createBytes=function(o,e){for(var r=0,t=0,i=0,n=new Array(e.length),a=new Array(e.length),d=0;d<e.length;d++){var u=e[d].dataCount,s=e[d].totalCount-u;t=Math.max(t,u),i=Math.max(i,s),n[d]=new Array(u);for(var g=0;g<n[d].length;g++)n[d][g]=255&o.buffer[g+r];r+=u;var l=h.getErrorCorrectPolynomial(s),c=new f(n[d],l.getLength()-1).mod(l);a[d]=new Array(l.getLength()-1);for(g=0;g<a[d].length;g++){var m=g+c.getLength()-a[d].length;a[d][g]=m>=0?c.get(m):0;}}var v=0;for(g=0;g<e.length;g++)v+=e[g].totalCount;var p=new Array(v),C=0;for(g=0;g<t;g++)for(d=0;d<e.length;d++)g<n[d].length&&(p[C++]=n[d][g]);for(g=0;g<i;g++)for(d=0;d<e.length;d++)g<a[d].length&&(p[C++]=a[d][g]);return p};for(var r={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},t={L:1,M:0,Q:3,H:2},i=0,n=1,a=2,d=3,u=4,s=5,g=6,l=7,h={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(o){for(var e=o<<10;h.getBCHDigit(e)-h.getBCHDigit(h.G15)>=0;)e^=h.G15<<h.getBCHDigit(e)-h.getBCHDigit(h.G15);return (o<<10|e)^h.G15_MASK},getBCHTypeNumber:function(o){for(var e=o<<12;h.getBCHDigit(e)-h.getBCHDigit(h.G18)>=0;)e^=h.G18<<h.getBCHDigit(e)-h.getBCHDigit(h.G18);return o<<12|e},getBCHDigit:function(o){for(var e=0;0!=o;)e++,o>>>=1;return e},getPatternPosition:function(o){return h.PATTERN_POSITION_TABLE[o-1]},getMask:function(o,e,r){switch(o){case i:return (e+r)%2==0;case n:return e%2==0;case a:return r%3==0;case d:return (e+r)%3==0;case u:return (Math.floor(e/2)+Math.floor(r/3))%2==0;case s:return e*r%2+e*r%3==0;case g:return (e*r%2+e*r%3)%2==0;case l:return (e*r%3+(e+r)%2)%2==0;default:throw new Error("bad maskPattern:"+o)}},getErrorCorrectPolynomial:function(o){for(var e=new f([1],0),r=0;r<o;r++)e=e.multiply(new f([1,c.gexp(r)],0));return e},getLengthInBits:function(o,e){if(1<=e&&e<10)switch(o){case r.MODE_NUMBER:return 10;case r.MODE_ALPHA_NUM:return 9;case r.MODE_8BIT_BYTE:case r.MODE_KANJI:return 8;default:throw new Error("mode:"+o)}else if(e<27)switch(o){case r.MODE_NUMBER:return 12;case r.MODE_ALPHA_NUM:return 11;case r.MODE_8BIT_BYTE:return 16;case r.MODE_KANJI:return 10;default:throw new Error("mode:"+o)}else {if(!(e<41))throw new Error("type:"+e);switch(o){case r.MODE_NUMBER:return 14;case r.MODE_ALPHA_NUM:return 13;case r.MODE_8BIT_BYTE:return 16;case r.MODE_KANJI:return 12;default:throw new Error("mode:"+o)}}},getLostPoint:function(o){for(var e=o.getModuleCount(),r=0,t=0;t<e;t++)for(var i=0;i<e;i++){for(var n=0,a=o.isDark(t,i),d=-1;d<=1;d++)if(!(t+d<0||e<=t+d))for(var u=-1;u<=1;u++)i+u<0||e<=i+u||0==d&&0==u||a==o.isDark(t+d,i+u)&&n++;n>5&&(r+=3+n-5);}for(t=0;t<e-1;t++)for(i=0;i<e-1;i++){var s=0;o.isDark(t,i)&&s++,o.isDark(t+1,i)&&s++,o.isDark(t,i+1)&&s++,o.isDark(t+1,i+1)&&s++,0!=s&&4!=s||(r+=3);}for(t=0;t<e;t++)for(i=0;i<e-6;i++)o.isDark(t,i)&&!o.isDark(t,i+1)&&o.isDark(t,i+2)&&o.isDark(t,i+3)&&o.isDark(t,i+4)&&!o.isDark(t,i+5)&&o.isDark(t,i+6)&&(r+=40);for(i=0;i<e;i++)for(t=0;t<e-6;t++)o.isDark(t,i)&&!o.isDark(t+1,i)&&o.isDark(t+2,i)&&o.isDark(t+3,i)&&o.isDark(t+4,i)&&!o.isDark(t+5,i)&&o.isDark(t+6,i)&&(r+=40);var g=0;for(i=0;i<e;i++)for(t=0;t<e;t++)o.isDark(t,i)&&g++;return r+=10*(Math.abs(100*g/e/e-50)/5)}},c={glog:function(o){if(o<1)throw new Error("glog("+o+")");return c.LOG_TABLE[o]},gexp:function(o){for(;o<0;)o+=255;for(;o>=256;)o-=255;return c.EXP_TABLE[o]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},m=0;m<8;m++)c.EXP_TABLE[m]=1<<m;for(m=8;m<256;m++)c.EXP_TABLE[m]=c.EXP_TABLE[m-4]^c.EXP_TABLE[m-5]^c.EXP_TABLE[m-6]^c.EXP_TABLE[m-8];for(m=0;m<255;m++)c.LOG_TABLE[c.EXP_TABLE[m]]=m;function f(o,e){if(null==o.length)throw new Error(o.length+"/"+e);for(var r=0;r<o.length&&0==o[r];)r++;this.num=new Array(o.length-r+e);for(var t=0;t<o.length-r;t++)this.num[t]=o[t+r];}function v(o,e){this.totalCount=o,this.dataCount=e;}function p(){this.buffer=new Array,this.length=0;}function C(o){return o.setFillStyle=o.setFillStyle||function(e){o.fillStyle=e;},o.setFontSize=o.setFontSize||function(e){o.font=`${e}px`;},o.setTextAlign=o.setTextAlign||function(e){o.textAlign=e;},o.setTextBaseline=o.setTextBaseline||function(e){o.textBaseline=e;},o.setGlobalAlpha=o.setGlobalAlpha||function(e){o.globalAlpha=e;},o.setStrokeStyle=o.setStrokeStyle||function(e){o.strokeStyle=e;},o.setShadow=o.setShadow||function(e,r,t,i){o.shadowOffsetX=e,o.shadowOffsetY=r,o.shadowBlur=t,o.shadowColor=i;},o.draw=o.draw||function(o,e){e&&e();},o.clearRect=o.clearRect||function(e,r,t,i){o.draw(!1);},o}function b(o,e){var r=this.data="",t=this.size=200;this.useDynamicSize=!1,this.dynamicSize=t;var i=this.typeNumber=-1;this.errorCorrectLevel=b.errorCorrectLevel.H;var n=this.margin=0;this.areaColor="#FFFFFF",this.backgroundColor="rgba(255,255,255,0)",this.backgroundImageSrc=void 0;var a=this.backgroundImageWidth=void 0,d=this.backgroundImageHeight=void 0,u=this.backgroundImageX=void 0,s=this.backgroundImageY=void 0;this.backgroundImageAlpha=1,this.backgroundImageBorderRadius=0;var g=this.backgroundPadding=0;this.foregroundColor="#000000",this.foregroundImageSrc=void 0;var l=this.foregroundImageWidth=void 0,h=this.foregroundImageHeight=void 0,c=this.foregroundImageX=void 0,m=this.foregroundImageY=void 0,f=this.foregroundImagePadding=0;this.foregroundImageBackgroundColor="#FFFFFF";var v=this.foregroundImageBorderRadius=0,p=this.foregroundImageShadowOffsetX=0,k=this.foregroundImageShadowOffsetY=0,y=this.foregroundImageShadowBlur=0;this.foregroundImageShadowColor="#808080";var w=this.foregroundPadding=0,I=this.positionProbeBackgroundColor=void 0,B=this.positionProbeForegroundColor=void 0,S=this.separatorColor=void 0,P=this.positionAdjustBackgroundColor=void 0,L=this.positionAdjustForegroundColor=void 0,D=this.timingBackgroundColor=void 0,A=this.timingForegroundColor=void 0,E=this.typeNumberBackgroundColor=void 0,T=this.typeNumberForegroundColor=void 0,N=this.darkBlockColor=void 0;this.base=void 0,this.modules=[],this.moduleCount=0,this.drawModules=[];var M=this.canvasContext=void 0;this.loadImage,this.drawReserve=!1,this.isMaked=!1,Object.defineProperties(this,{data:{get(){if(""===r||void 0===r)throw console.error("[uQRCode]: data must be set!"),new b.Error("data must be set!");return r},set(o){r=String(o);}},size:{get:()=>t,set(o){t=Number(o);}},typeNumber:{get:()=>i,set(o){i=Number(o);}},margin:{get:()=>n,set(o){n=Number(o);}},backgroundImageWidth:{get(){return void 0===a?this.dynamicSize:this.useDynamicSize?this.dynamicSize/this.size*a:a},set(o){a=Number(o);}},backgroundImageHeight:{get(){return void 0===d?this.dynamicSize:this.useDynamicSize?this.dynamicSize/this.size*d:d},set(o){d=Number(o);}},backgroundImageX:{get(){return void 0===u?0:this.useDynamicSize?this.dynamicSize/this.size*u:u},set(o){u=Number(o);}},backgroundImageY:{get(){return void 0===s?0:this.useDynamicSize?this.dynamicSize/this.size*s:s},set(o){s=Number(o);}},backgroundPadding:{get:()=>g,set(o){g=o>1?1:o<0?0:o;}},foregroundImageWidth:{get(){return void 0===l?(this.dynamicSize-2*this.margin)/4:this.useDynamicSize?this.dynamicSize/this.size*l:l},set(o){l=Number(o);}},foregroundImageHeight:{get(){return void 0===h?(this.dynamicSize-2*this.margin)/4:this.useDynamicSize?this.dynamicSize/this.size*h:h},set(o){h=Number(o);}},foregroundImageX:{get(){return void 0===c?this.dynamicSize/2-this.foregroundImageWidth/2:this.useDynamicSize?this.dynamicSize/this.size*c:c},set(o){c=Number(o);}},foregroundImageY:{get(){return void 0===m?this.dynamicSize/2-this.foregroundImageHeight/2:this.useDynamicSize?this.dynamicSize/this.size*m:m},set(o){m=Number(o);}},foregroundImagePadding:{get(){return this.useDynamicSize?this.dynamicSize/this.size*f:f},set(o){f=Number(o);}},foregroundImageBorderRadius:{get(){return this.useDynamicSize?this.dynamicSize/this.size*v:v},set(o){v=Number(o);}},foregroundImageShadowOffsetX:{get(){return this.useDynamicSize?this.dynamicSize/this.size*p:p},set(o){p=Number(o);}},foregroundImageShadowOffsetY:{get(){return this.useDynamicSize?this.dynamicSize/this.size*k:k},set(o){k=Number(o);}},foregroundImageShadowBlur:{get(){return this.useDynamicSize?this.dynamicSize/this.size*y:y},set(o){y=Number(o);}},foregroundPadding:{get:()=>w,set(o){w=o>1?1:o<0?0:o;}},positionProbeBackgroundColor:{get(){return I||this.backgroundColor},set(o){I=o;}},positionProbeForegroundColor:{get(){return B||this.foregroundColor},set(o){B=o;}},separatorColor:{get(){return S||this.backgroundColor},set(o){S=o;}},positionAdjustBackgroundColor:{get(){return P||this.backgroundColor},set(o){P=o;}},positionAdjustForegroundColor:{get(){return L||this.foregroundColor},set(o){L=o;}},timingBackgroundColor:{get(){return D||this.backgroundColor},set(o){D=o;}},timingForegroundColor:{get(){return A||this.foregroundColor},set(o){A=o;}},typeNumberBackgroundColor:{get(){return E||this.backgroundColor},set(o){E=o;}},typeNumberForegroundColor:{get(){return T||this.foregroundColor},set(o){T=o;}},darkBlockColor:{get(){return N||this.foregroundColor},set(o){N=o;}},canvasContext:{get(){if(void 0===M)throw console.error("[uQRCode]: use drawCanvas, you need to set the canvasContext!"),new b.Error("use drawCanvas, you need to set the canvasContext!");return M},set(o){M=C(o);}}}),b.plugins.forEach((o=>o(b,this,!1))),o&&this.setOptions(o),e&&(this.canvasContext=C(e));}f.prototype={get:function(o){return this.num[o]},getLength:function(){return this.num.length},multiply:function(o){for(var e=new Array(this.getLength()+o.getLength()-1),r=0;r<this.getLength();r++)for(var t=0;t<o.getLength();t++)e[r+t]^=c.gexp(c.glog(this.get(r))+c.glog(o.get(t)));return new f(e,0)},mod:function(o){if(this.getLength()-o.getLength()<0)return this;for(var e=c.glog(this.get(0))-c.glog(o.get(0)),r=new Array(this.getLength()),t=0;t<this.getLength();t++)r[t]=this.get(t);for(t=0;t<o.getLength();t++)r[t]^=c.gexp(c.glog(o.get(t))+e);return new f(r,0).mod(o)}},v.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],v.getRSBlocks=function(o,e){var r=v.getRsBlockTable(o,e);if(null==r)throw new Error("bad rs block @ typeNumber:"+o+"/errorCorrectLevel:"+e);for(var t=r.length/3,i=new Array,n=0;n<t;n++)for(var a=r[3*n+0],d=r[3*n+1],u=r[3*n+2],s=0;s<a;s++)i.push(new v(d,u));return i},v.getRsBlockTable=function(o,e){switch(e){case t.L:return v.RS_BLOCK_TABLE[4*(o-1)+0];case t.M:return v.RS_BLOCK_TABLE[4*(o-1)+1];case t.Q:return v.RS_BLOCK_TABLE[4*(o-1)+2];case t.H:return v.RS_BLOCK_TABLE[4*(o-1)+3];default:return}},p.prototype={get:function(o){var e=Math.floor(o/8);return 1==(this.buffer[e]>>>7-o%8&1)},put:function(o,e){for(var r=0;r<e;r++)this.putBit(1==(o>>>e-r-1&1));},getLengthInBits:function(){return this.length},putBit:function(o){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),o&&(this.buffer[e]|=128>>>this.length%8),this.length++;}},e.errorCorrectLevel=t,b.errorCorrectLevel=e.errorCorrectLevel,b.Error=function(o){this.errMsg="[uQRCode]: "+o;},b.plugins=[],b.use=function(o){"function"==typeof o&&b.plugins.push(o);},b.prototype.loadImage=function(o){return Promise.resolve(o)},b.prototype.setOptions=function(o){var e,r,t,i,n,a,d,u,s,g,l,h,c,m,f,v,p,C,b,k,y,w,I,B,S,P,L,D,A,E,T,N,M,z,R,_,O,F,x,H,X,Y,j,W,G,K,Q,U,$,J,q,V,Z,oo,eo,ro;o&&(Object.keys(o).forEach((e=>{this[e]=o[e];})),function(o={},e={},r=!1){let t;t=r?o:{...o};for(let o in e){var i=e[o];null!=i&&(i.constructor==Object?t[o]=this.deepReplace(t[o],i):i.constructor!=String||i?t[o]=i:t[o]=t[o]);}}(this,{data:o.data||o.text,size:o.size,useDynamicSize:o.useDynamicSize,typeNumber:o.typeNumber,errorCorrectLevel:o.errorCorrectLevel,margin:o.margin,areaColor:o.areaColor,backgroundColor:o.backgroundColor||(null===(e=o.background)||void 0===e?void 0:e.color),backgroundImageSrc:o.backgroundImageSrc||(null===(r=o.background)||void 0===r||null===(t=r.image)||void 0===t?void 0:t.src),backgroundImageWidth:o.backgroundImageWidth||(null===(i=o.background)||void 0===i||null===(n=i.image)||void 0===n?void 0:n.width),backgroundImageHeight:o.backgroundImageHeight||(null===(a=o.background)||void 0===a||null===(d=a.image)||void 0===d?void 0:d.height),backgroundImageX:o.backgroundImageX||(null===(u=o.background)||void 0===u||null===(s=u.image)||void 0===s?void 0:s.x),backgroundImageY:o.backgroundImageY||(null===(g=o.background)||void 0===g||null===(l=g.image)||void 0===l?void 0:l.y),backgroundImageAlpha:o.backgroundImageAlpha||(null===(h=o.background)||void 0===h||null===(c=h.image)||void 0===c?void 0:c.alpha),backgroundImageBorderRadius:o.backgroundImageBorderRadius||(null===(m=o.background)||void 0===m||null===(f=m.image)||void 0===f?void 0:f.borderRadius),backgroundPadding:o.backgroundPadding,foregroundColor:o.foregroundColor||(null===(v=o.foreground)||void 0===v?void 0:v.color),foregroundImageSrc:o.foregroundImageSrc||(null===(p=o.foreground)||void 0===p||null===(C=p.image)||void 0===C?void 0:C.src),foregroundImageWidth:o.foregroundImageWidth||(null===(b=o.foreground)||void 0===b||null===(k=b.image)||void 0===k?void 0:k.width),foregroundImageHeight:o.foregroundImageHeight||(null===(y=o.foreground)||void 0===y||null===(w=y.image)||void 0===w?void 0:w.height),foregroundImageX:o.foregroundImageX||(null===(I=o.foreground)||void 0===I||null===(B=I.image)||void 0===B?void 0:B.x),foregroundImageY:o.foregroundImageY||(null===(S=o.foreground)||void 0===S||null===(P=S.image)||void 0===P?void 0:P.y),foregroundImagePadding:o.foregroundImagePadding||(null===(L=o.foreground)||void 0===L||null===(D=L.image)||void 0===D?void 0:D.padding),foregroundImageBackgroundColor:o.foregroundImageBackgroundColor||(null===(A=o.foreground)||void 0===A||null===(E=A.image)||void 0===E?void 0:E.backgroundColor),foregroundImageBorderRadius:o.foregroundImageBorderRadius||(null===(T=o.foreground)||void 0===T||null===(N=T.image)||void 0===N?void 0:N.borderRadius),foregroundImageShadowOffsetX:o.foregroundImageShadowOffsetX||(null===(M=o.foreground)||void 0===M||null===(z=M.image)||void 0===z?void 0:z.shadowOffsetX),foregroundImageShadowOffsetY:o.foregroundImageShadowOffsetY||(null===(R=o.foreground)||void 0===R||null===(_=R.image)||void 0===_?void 0:_.shadowOffsetY),foregroundImageShadowBlur:o.foregroundImageShadowBlur||(null===(O=o.foreground)||void 0===O||null===(F=O.image)||void 0===F?void 0:F.shadowBlur),foregroundImageShadowColor:o.foregroundImageShadowColor||(null===(x=o.foreground)||void 0===x||null===(H=x.image)||void 0===H?void 0:H.shadowColor),foregroundPadding:o.foregroundPadding,positionProbeBackgroundColor:o.positionProbeBackgroundColor||(null===(X=o.positionProbe)||void 0===X?void 0:X.backgroundColor)||(null===(Y=o.positionDetection)||void 0===Y?void 0:Y.backgroundColor),positionProbeForegroundColor:o.positionProbeForegroundColor||(null===(j=o.positionProbe)||void 0===j?void 0:j.foregroundColor)||(null===(W=o.positionDetection)||void 0===W?void 0:W.foregroundColor),separatorColor:o.separatorColor||(null===(G=o.separator)||void 0===G?void 0:G.color),positionAdjustBackgroundColor:o.positionAdjustBackgroundColor||(null===(K=o.positionAdjust)||void 0===K?void 0:K.backgroundColor)||(null===(Q=o.alignment)||void 0===Q?void 0:Q.backgroundColor),positionAdjustForegroundColor:o.positionAdjustForegroundColor||(null===(U=o.positionAdjust)||void 0===U?void 0:U.foregroundColor)||(null===($=o.alignment)||void 0===$?void 0:$.foregroundColor),timingBackgroundColor:o.timingBackgroundColor||(null===(J=o.timing)||void 0===J?void 0:J.backgroundColor),timingForegroundColor:o.timingForegroundColor||(null===(q=o.timing)||void 0===q?void 0:q.foregroundColor),typeNumberBackgroundColor:o.typeNumberBackgroundColor||(null===(V=o.typeNumber)||void 0===V?void 0:V.backgroundColor)||(null===(Z=o.versionInformation)||void 0===Z?void 0:Z.backgroundColor),typeNumberForegroundColor:o.typeNumberForegroundColor||(null===(oo=o.typeNumber)||void 0===oo?void 0:oo.foregroundColor)||(null===(eo=o.versionInformation)||void 0===eo?void 0:eo.foregroundColor),darkBlockColor:o.darkBlockColor||(null===(ro=o.darkBlock)||void 0===ro?void 0:ro.color)},!0));},b.prototype.make=function(){let{foregroundColor:o,backgroundColor:r,typeNumber:t,errorCorrectLevel:i,data:n,size:a,margin:d,useDynamicSize:u}=this;if(o===r)throw console.error("[uQRCode]: foregroundColor and backgroundColor cannot be the same!"),new b.Error("foregroundColor and backgroundColor cannot be the same!");var s=new e(t,i);s.addData(function(o){o=o.toString();for(var e,r="",t=0;t<o.length;t++)(e=o.charCodeAt(t))>=1&&e<=127?r+=o.charAt(t):e>2047?(r+=String.fromCharCode(224|e>>12&15),r+=String.fromCharCode(128|e>>6&63),r+=String.fromCharCode(128|e>>0&63)):(r+=String.fromCharCode(192|e>>6&31),r+=String.fromCharCode(128|e>>0&63));return r}(n)),s.make(),this.base=s,this.typeNumber=s.typeNumber,this.modules=s.modules,this.moduleCount=s.moduleCount,this.dynamicSize=u?Math.ceil((a-2*d)/s.moduleCount)*s.moduleCount+2*d:a,function(o){let{dynamicSize:e,margin:r,backgroundColor:t,backgroundPadding:i,foregroundColor:n,foregroundPadding:a,modules:d,moduleCount:u}=o,s=(e-2*r)/u,g=s,l=0;i>0&&(l=g*i/2,g-=2*l);let h=s,c=0;a>0&&(c=h*a/2,h-=2*c);for(var m=0;m<u;m++)for(var f=0;f<u;f++){var v=f*s+r,p=m*s+r;if(d[m][f]){var C=c,b=v+c,k=p+c,y=h,w=h;d[m][f]={type:["foreground"],color:n,isBlack:!0,isDrawn:!1,destX:v,destY:p,destWidth:s,destHeight:s,x:b,y:k,width:y,height:w,paddingTop:C,paddingRight:C,paddingBottom:C,paddingLeft:C};}else C=l,b=v+l,k=p+l,y=g,w=g,d[m][f]={type:["background"],color:t,isBlack:!1,isDrawn:!1,destX:v,destY:p,destWidth:s,destHeight:s,x:b,y:k,width:y,height:w,paddingTop:C,paddingRight:C,paddingBottom:C,paddingLeft:C};}}(this),function(o){let{modules:e,moduleCount:r,positionProbeBackgroundColor:t,positionProbeForegroundColor:i}=o,n=r-7;[[0,0,1],[1,0,1],[2,0,1],[3,0,1],[4,0,1],[5,0,1],[6,0,1],[0,1,1],[1,1,0],[2,1,0],[3,1,0],[4,1,0],[5,1,0],[6,1,1],[0,2,1],[1,2,0],[2,2,1],[3,2,1],[4,2,1],[5,2,0],[6,2,1],[0,3,1],[1,3,0],[2,3,1],[3,3,1],[4,3,1],[5,3,0],[6,3,1],[0,4,1],[1,4,0],[2,4,1],[3,4,1],[4,4,1],[5,4,0],[6,4,1],[0,5,1],[1,5,0],[2,5,0],[3,5,0],[4,5,0],[5,5,0],[6,5,1],[0,6,1],[1,6,1],[2,6,1],[3,6,1],[4,6,1],[5,6,1],[6,6,1]].forEach((o=>{var r=e[o[0]][o[1]],a=e[o[0]+n][o[1]],d=e[o[0]][o[1]+n];d.type.push("positionProbe"),a.type.push("positionProbe"),r.type.push("positionProbe"),r.color=1==o[2]?i:t,a.color=1==o[2]?i:t,d.color=1==o[2]?i:t;}));}(this),function(o){let{modules:e,moduleCount:r,separatorColor:t}=o;[[7,0],[7,1],[7,2],[7,3],[7,4],[7,5],[7,6],[7,7],[0,7],[1,7],[2,7],[3,7],[4,7],[5,7],[6,7]].forEach((o=>{var i=e[o[0]][o[1]],n=e[r-o[0]-1][o[1]],a=e[o[0]][r-o[1]-1];a.type.push("separator"),n.type.push("separator"),i.type.push("separator"),i.color=t,n.color=t,a.color=t;}));}(this),function(o){let{typeNumber:e,modules:r,moduleCount:t,foregroundColor:i,backgroundColor:n,positionAdjustForegroundColor:a,positionAdjustBackgroundColor:d,timingForegroundColor:u,timingBackgroundColor:s}=o;const g=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]][e-1];if(g){const o=[[-2,-2,1],[-1,-2,1],[0,-2,1],[1,-2,1],[2,-2,1],[-2,-1,1],[-1,-1,0],[0,-1,0],[1,-1,0],[2,-1,1],[-2,0,1],[-1,0,0],[0,0,1],[1,0,0],[2,0,1],[-2,1,1],[-1,1,0],[0,1,0],[1,1,0],[2,1,1],[-2,2,1],[-1,2,1],[0,2,1],[1,2,1],[2,2,1]],e=g.length;for(let l=0;l<e;l++)for(let h=0;h<e;h++){let{x:e,y:c}={x:g[l],y:g[h]};e<9&&c<9||e>t-9-1&&c<9||c>t-9-1&&e<9||o.forEach((o=>{var t=r[e+o[0]][c+o[1]];t.type.push("positionAdjust"),t.type.includes("timing")?1==o[2]?t.color=a==i?u:a:t.color=a==i&&d==n?s:d:t.color=1==o[2]?a:d;}));}}}(this),function(o){let{modules:e,moduleCount:r,timingForegroundColor:t,timingBackgroundColor:i}=o,n=r-16;for(let o=0;o<n;o++){var a=e[6][8+o],d=e[8+o][6];a.type.push("timing"),d.type.push("timing"),a.color=1&o^1?t:i,d.color=1&o^1?t:i;}}(this),function(o){let{modules:e,moduleCount:r,darkBlockColor:t}=o;var i=e[r-7-1][8];i.type.push("darkBlock"),i.color=t;}(this),function(o){let{typeNumber:e,modules:r,moduleCount:t,typeNumberBackgroundColor:i,typeNumberForegroundColor:n}=o;if(e<7)return r;const a=[0,0,0,0,0,0,0,"000111110010010100","001000010110111100","001001101010011001","001010010011010011","001011101111110110","001100011101100010","001101100001000111","001110011000001101","001111100100101000","010000101101111000","010001010001011101","010010101000010111","010011010100110010","010100100110100110","010101011010000011","010110100011001001","010111011111101100","011000111011000100","011001000111100001","011010111110101011","011011000010001110","011100110000011010","011101001100111111","011110110101110101","011111001001010000","100000100111010101","100001011011110000","100010100010111010","100011011110011111","100100101100001011","100101010000101110","100110101001100100","100111010101000001","101000110001101001"];let d=a[e]+a[e],u=[t-11,t-10,t-9];[[5,u[2]],[5,u[1]],[5,u[0]],[4,u[2]],[4,u[1]],[4,u[0]],[3,u[2]],[3,u[1]],[3,u[0]],[2,u[2]],[2,u[1]],[2,u[0]],[1,u[2]],[1,u[1]],[1,u[0]],[0,u[2]],[0,u[1]],[0,u[0]],[u[2],5],[u[1],5],[u[0],5],[u[2],4],[u[1],4],[u[0],4],[u[2],3],[u[1],3],[u[0],3],[u[2],2],[u[1],2],[u[0],2],[u[2],1],[u[1],1],[u[0],1],[u[2],0],[u[1],0],[u[0],0]].forEach(((o,e)=>{var t=r[o[0]][o[1]];t.type.push("typeNumber"),t.color="1"==d[e]?n:i;}));}(this),this.isMaked=!0,this.drawModules=[];},b.prototype.getDrawModules=function(){if(this.drawModules&&this.drawModules.length>0)return this.drawModules;let o=this.drawModules=[],{modules:e,moduleCount:r,dynamicSize:t,areaColor:i,backgroundImageSrc:n,backgroundImageX:a,backgroundImageY:d,backgroundImageWidth:u,backgroundImageHeight:s,backgroundImageAlpha:g,backgroundImageBorderRadius:l,foregroundImageSrc:h,foregroundImageX:c,foregroundImageY:m,foregroundImageWidth:f,foregroundImageHeight:v,foregroundImagePadding:p,foregroundImageBackgroundColor:C,foregroundImageBorderRadius:b,foregroundImageShadowOffsetX:k,foregroundImageShadowOffsetY:y,foregroundImageShadowBlur:w,foregroundImageShadowColor:I}=this;i&&o.push({name:"area",type:"area",color:i,x:0,y:0,width:t,height:t}),n&&o.push({name:"backgroundImage",type:"image",imageSrc:n,mappingName:"backgroundImageSrc",x:a,y:d,width:u,height:s,alpha:g,borderRadius:l});for(var B=0;B<r;B++)for(var S=0;S<r;S++){var P=e[B][S];P.isDrawn||(P.type.includes("foreground")?o.push({name:"foreground",type:"tile",color:P.color,destX:P.destX,destY:P.destY,destWidth:P.destWidth,destHeight:P.destHeight,x:P.x,y:P.y,width:P.width,height:P.height,paddingTop:P.paddingTop,paddingRight:P.paddingRight,paddingBottom:P.paddingBottom,paddingLeft:P.paddingLeft,rowIndex:B,colIndex:S}):o.push({name:"background",type:"tile",color:P.color,destX:P.destX,destY:P.destY,destWidth:P.destWidth,destHeight:P.destHeight,x:P.x,y:P.y,width:P.width,height:P.height,paddingTop:P.paddingTop,paddingRight:P.paddingRight,paddingBottom:P.paddingBottom,paddingLeft:P.paddingLeft,rowIndex:B,colIndex:S}),P.isDrawn=!0);}return h&&o.push({name:"foregroundImage",type:"image",imageSrc:h,mappingName:"foregroundImageSrc",x:c,y:m,width:f,height:v,padding:p,backgroundColor:C,borderRadius:b,shadowOffsetX:k,shadowOffsetY:y,shadowBlur:w,shadowColor:I}),o},b.prototype.isBlack=function(o,e){var r=this.moduleCount;return !(0>o||0>e||o>=r||e>=r)&&this.modules[o][e].isBlack},b.prototype.drawCanvas=function(){let{isMaked:o,canvasContext:e,useDynamicSize:r,dynamicSize:t,foregroundColor:i,foregroundPadding:n,backgroundColor:a,backgroundPadding:d,drawReserve:u,margin:s}=this;if(!o)return console.error("[uQRCode]: please execute the make method first!"),Promise.reject(new b.Error("please execute the make method first!"));let g=this.getDrawModules(),l=async(o,r)=>{try{e.clearRect(0,0,t,t),e.draw(!1);for(var i=0;i<g.length;i++){var n=g[i];switch(e.save(),n.type){case"area":e.setFillStyle(n.color),e.fillRect(n.x,n.y,n.width,n.height);break;case"tile":var a=n.x,d=n.y,s=n.width,l=n.height;e.setFillStyle(n.color),e.fillRect(a,d,s,l);break;case"image":if("backgroundImage"===n.name){a=Math.round(n.x),d=Math.round(n.y),s=Math.round(n.width),l=Math.round(n.height);s<2*(c=Math.round(n.borderRadius))&&(c=s/2),l<2*c&&(c=l/2),e.setGlobalAlpha(n.alpha),c>0&&(e.beginPath(),e.moveTo(a+c,d),e.arcTo(a+s,d,a+s,d+l,c),e.arcTo(a+s,d+l,a,d+l,c),e.arcTo(a,d+l,a,d,c),e.arcTo(a,d,a+s,d,c),e.closePath(),e.setStrokeStyle("rgba(0,0,0,0)"),e.stroke(),e.clip());try{var h=await this.loadImage(n.imageSrc);e.drawImage(h,a,d,s,l);}catch(o){throw console.error(`[uQRCode]: ${n.mappingName} invalid!`),new b.Error(`${n.mappingName} invalid!`)}}else if("foregroundImage"===n.name){a=Math.round(n.x),d=Math.round(n.y),s=Math.round(n.width),l=Math.round(n.height);var c,m=Math.round(n.padding);s<2*(c=Math.round(n.borderRadius))&&(c=s/2),l<2*c&&(c=l/2);var f=a-m,v=d-m,p=s+2*m,C=l+2*m,k=Math.round(p/s*c);p<2*k&&(k=p/2),C<2*k&&(k=C/2),e.save(),e.setShadow(n.shadowOffsetX,n.shadowOffsetY,n.shadowBlur,n.shadowColor),k>0?(e.beginPath(),e.moveTo(f+k,v),e.arcTo(f+p,v,f+p,v+C,k),e.arcTo(f+p,v+C,f,v+C,k),e.arcTo(f,v+C,f,v,k),e.arcTo(f,v,f+p,v,k),e.closePath(),e.setFillStyle(n.backgroundColor),e.fill()):(e.setFillStyle(n.backgroundColor),e.fillRect(f,v,p,C)),e.restore(),e.save(),k>0?(e.beginPath(),e.moveTo(f+k,v),e.arcTo(f+p,v,f+p,v+C,k),e.arcTo(f+p,v+C,f,v+C,k),e.arcTo(f,v+C,f,v,k),e.arcTo(f,v,f+p,v,k),e.closePath(),e.setFillStyle(m>0?n.backgroundColor:"rgba(0,0,0,0)"),e.fill()):(e.setFillStyle(m>0?n.backgroundColor:"rgba(0,0,0,0)"),e.fillRect(f,v,p,C)),e.restore(),c>0&&(e.beginPath(),e.moveTo(a+c,d),e.arcTo(a+s,d,a+s,d+l,c),e.arcTo(a+s,d+l,a,d+l,c),e.arcTo(a,d+l,a,d,c),e.arcTo(a,d,a+s,d,c),e.closePath(),e.setStrokeStyle("rgba(0,0,0,0)"),e.stroke(),e.clip());try{h=await this.loadImage(n.imageSrc);e.drawImage(h,a,d,s,l);}catch(o){throw console.error(`[uQRCode]: ${n.mappingName} invalid!`),new b.Error(`${n.mappingName} invalid!`)}}}u&&e.draw(!0),e.restore();}e.draw(!0),setTimeout(o,150);}catch(o){if(!(o instanceof b.Error))throw o;r(o);}};return new Promise(((o,e)=>{l(o,e);}))},b.prototype.draw=function(){return this.drawCanvas()},b.prototype.register=function(o){o&&o(b,this,!0);};export{b as default};
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/queue.js b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/queue.js
new file mode 100644
index 0000000..be6b1d2
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/queue.js
@@ -0,0 +1,41 @@
+function Queue() {
+ let waitingQueue = this.waitingQueue = [];
+ let isRunning = this.isRunning = false; // 记录是否有未完成的任务
+
+ function execute(task, resolve, reject) {
+ task()
+ .then((data) => {
+ resolve(data);
+ })
+ .catch((e) => {
+ reject(e);
+ })
+ .finally(() => {
+ // 等待任务队列中如果有任务,则触发它;否则设置isRunning = false,表示无任务状态
+ if (waitingQueue.length) {
+ const next = waitingQueue.shift();
+ execute(next.task, next.resolve, next.reject);
+ } else {
+ isRunning = false;
+ }
+ });
+ }
+ this.exec = function(task) {
+ return new Promise((resolve, reject) => {
+ if (isRunning) {
+ waitingQueue.push({
+ task,
+ resolve,
+ reject
+ });
+ } else {
+ isRunning = true;
+ execute(task, resolve, reject);
+ }
+ });
+ }
+}
+
+/* 队列实例,某些平台一起使用多个组件时需要通过队列逐一绘制,否则部分绘制方法异常,nvue端的iOS gcanvas尤其明显,在不通过队列绘制时会出现图片丢失的情况 */
+export const queueDraw = new Queue();
+export const queueLoadImage = new Queue();
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/uv-qrcode.vue b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/uv-qrcode.vue
new file mode 100644
index 0000000..dd8cfc0
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/components/uv-qrcode/uv-qrcode.vue
@@ -0,0 +1,1038 @@
+<template>
+ <view class="uqrcode"
+ :class="{ 'uqrcode-hide': hide }"
+ :style="{ width: `${templateOptions.width}px`, height: `${templateOptions.height}px` }">
+ <view class="uqrcode-canvas-wrapper">
+ <!-- 画布 -->
+ <!-- #ifndef APP-NVUE -->
+ <canvas class="uqrcode-canvas"
+ :id="canvasId"
+ :canvas-id="canvasId"
+ :type="canvasType"
+ :style="{
+ width: `${templateOptions.canvasWidth}px`,
+ height: `${templateOptions.canvasHeight}px`,
+ transform: templateOptions.canvasTransform
+ }"
+ v-if="templateOptions.canvasDisplay"
+ @click="onClick"></canvas>
+ <!-- #endif -->
+
+ <!-- nvue用gcanvas -->
+ <!-- #ifdef APP-NVUE -->
+ <gcanvas class="uqrcode-canvas"
+ ref="gcanvas"
+ :style="{
+ width: `${templateOptions.canvasWidth}px`,
+ height: `${templateOptions.canvasHeight}px`
+ }"
+ v-if="templateOptions.canvasDisplay"
+ @click="onClick"></gcanvas>
+ <!-- #endif -->
+ </view>
+
+ <!-- 加载效果 -->
+ <view class="uqrcode-makeing"
+ v-if="loading === undefined || !loading ? makeing : loading">
+ <slot name="loading">
+ <image class="uqrcode-makeing-image"
+ :style="{ width: `${templateOptions.size / 4}px`, height: `${templateOptions.size / 4}px` }"
+ src="data:image/gif;base64,R0lGODlhAAEAAfIEAOHh4SSsWuDg4N3d3f///wAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDYuMC1jMDAyIDc5LjE2NDQ4OCwgMjAyMC8wNy8xMC0yMjowNjo1MyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIyLjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODhGMzM4RDEwMTExRUM4MDhCRkVBQkE2QUZDQzkwIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjAyODhGMzM5RDEwMTExRUM4MDhCRkVBQkE2QUZDQzkwIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDI4OEYzMzZEMTAxMTFFQzgwOEJGRUFCQTZBRkNDOTAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDI4OEYzMzdEMTAxMTFFQzgwOEJGRUFCQTZBRkNDOTAiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQFFAAEACwAAAAAAAEAAQAD/0i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2Kx2y+16v+CweEwum8/otHrNbrvf8Lh8Tq/b7/i8fs/v+/+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanigCqq6ytrieusbISAbW2t7i5uru8vb66bLLCrLDDw7S/ycrLzLXBxsLF0LHIzdbXzc/Trybb1BHY4eK92t6r0uaq1ePs4+Xp6PDg7fTh7+bx+PP1/Mz33vkA7utH0Ne/bQERDizIMNfBaQkhLmxIMcBDaBExTqzI8P+isYwfN3Ik6PFYt3TnRI7kVzLaSZQA1q0s2HLWS5QyZ/ar+a0ETHUqdbLjyc3nz5xC6RFtBdIkhKQ01/yMeVPeU6g7pR6tqu8q1npLiXEV6PVru7ApjcJEquyEPa1rxyosm83EWzVTm7qk688uNrRA1eIMatDvNcBUBVt9cJdEYzR55Urku8ztX7iDFXdlfLnE4zORNZPlfNiwNcR6bVJua7ou3q2i55I+3brv67ixJ8927bhzmtAkgDv4HIJ4GeEikDMw/oH5GOUgoCtw3oF6GOkesFvfsP0L9g7afY/o7uU7h/ClPYsHDTt4++Hri8c//j55/eXzm+d/fj96/+n/+1UX4HX/ZVcgeRggyIV5G6BHmycMauAgb5xEmMGEtnViIQYYVvbJhhd0yBqEBYJ34ICUgGiBiMmAomIFLP7iYonnnZiehjQ2aOODOE7l449MERbVai1iBuSRO67EVpG3IenkYvDptKSMRj5pZUhENjRlYU1e6aVqu420JTlVfmlmYGFyNCYviJ2ZWZoVrblLm25uFuVMcgJTZp1X5gmWkGzuyeeTfioF6JyCDopkoWcdqmeXilrJ6FCOOpRopD9O6k6luNCJ6V5wUqSpRZd+mqSYnN7iqalFhaplqrasyqpYWXYEqzOlzmpnA0mNKquuiblqa61kQgrsqWreSqqx/8e+eaeSyqIi7bTUVmvttdhmq+223Hbr7bejCCDuuOSWa+656Kar7rrnSjDAu/DGK++89NZr77340vsru/z2224E+QYs8MAEw7uvvwj3627BDDfM8MEJR5zuwg5XbHG9EEusMbkUX+zxxRlvvHHHH5f8cK4ip+wvySa3HHDIKifMsss0Y4xyzDijO3PNPBt8c85Aj7tzzzzDHPS6QxNNs9FHTwyw0lAPwHTT/0IQNdRTU11u0ld/nLXWQj/dddE/g50y12Nb/LXZaKft8Npgt+32ycyafbTccxMMt9Z45y3w3lT37Xe+qEnGruDxzihxalU/ULHiETNuLuI+k7i44f9Ii013j5Fjri7l70Ius+dOW/32hxpLvrXmBYuOsOocs6436pfndrjsA7u+Muk64/437Z3bnrnpDeuuMO+NO/A48KML/7nvLzP/OvKTQ0+49Ls7X7rjp1sevHu1c1889sdr3zvxm1eYOvWro986+fzCHrb7s3vfPPjfK9895/ePMLL1+DKe3c6Hv/fZb4DPM5++4IfA9hWwfvxrIAH9tz/1STCBD8wdAy8oNfYlboMXlF/oQChBEXbwgByMnQLnJcAUmrCFHDTh4FhYNrZ5cIY2q5sLb4hDGuowhjzs4Qd/GMIgCnGERCyhEY8IOAxS8IgVZE8Kk2cfKI4viQ2UIRPAaxi3JQqxiXcDoBXtVbgVOlB/YzTgb9ZnRhWKL40axCIVQ/A/+sExgFwU1wvFeMchrjF8T8xfA/oYxz8Kko5sfCMh71XGDJZPkYvMoSH7V8VDLiCS15Nj9do4P0hiUl6NDCQlGfBJRoLrlKhMpSpXycpWuvKVsIylLGdJy1ra8pa4zKUud8nLXvryl8AMpjCHScxiGvOYyEymMpfJzGY685nQjKY0p0nNalrzmtjMpja3yc1uevOb4AynOMdJhwQAACH5BAUUAAQALDIAMgCcAJwAAAP/KLrcTjDKSWt0OFsIuv9gKI5kaZ6Ztq1s6iorKs90/apsTt1pbP/AIA+mK16Gj41wyWwan8ikpUmtRp/GaMNn7Xq3WJ2Wwf2arWHxmDg9u6np3JpdeduX8da8fO8j83xXSn6EQ4CDa4GFi2CHO3uIjJJkjo+JkZOTlZZjipmFmxNzAp6ffqESo6Wmd6hHl22sjK4ckLGyoLSqmLh9tAS7t72+urZ1QL+LycacNcuEz528M9HErsHHP9WtxbDZNtt24YbTMuNu5zerJulm7S7rJe9e8zjfzt2n+VrxJPVo+wQJo/GvSsFG9wgGFLeQ3EBqDdFFVFcOxUEnE1/0G3GR/0lHOs0UXss10ltIiCX1peRX8cRHIS83iniJLVRNUcgyfonZkp1Oej/tnTT3K87NSkdfgSuaJukhp8ByMsUCNQ/UIFPDVDXKDKe2rFC6IhWrFB/YIlubkq319awak5uuSnWrB+5Yu2VF0pUpBZXctnt7jhqMl63KhMMIU3z4hm9ixY4xMn6sGENkj4IpVyaVuctlzdImn/kMWiDixp1L/z08VPVm0lhTuw59WqLo2YNhz22NO7dsOL9789ANmLfwwlGhBT8Obzke58wtQ499O/qf6bu9WvddHWj37RqxF9cOHrky8ZvTs/wOkH2IwPDjy59Pv779+/jz69/Pv7////8ABijggAQWaOCBCCao4FQDNOjggxBGKOGEFFZooYQrBKDhhhx26OGHIIYo4ogfXmjiiSim6GCGJLbo4oswaqjijDTSyGKMOOYYY4089ljhjToGKWSJPhZpJJBDJimkkUz2iKSSUO7Y5JQqPhnllSRSqeWJVmLpJZFbhjlhl1+WKaOYaEJIpplfpulmg2uyieWbbsYpZ5R0pmnnnUrmieaefA7pp5iABhrkoGEWamiOiG6p6KJSNjrlo5C+KCmVlFba4qWTbqCpl5w2memnIvLIkwVB6mdqUBh6qqOqNZ5aQar5rbpSiqMGAKuNrEaY664zykoBrfjZ6lesruYIbJX/vaqZLI7L4trsg7/WiuytKFZb7LXH8orqq9Z6222wz8YYbbbTrlgujOdymS6c677YronCTkDsfcbaxO2w4G4rrr7/2tsvvvvGVbAE99qXr8EBIzywwgc7srDDyoZLLrbufluxv6EOUFTC9XWsLi0g0ycyvCQ/HPLJH6tsMsu/lDzfyR7H7PLMMKe8McEit7wzxD3b/PPKQesMrcWh+kxqnzm7sjSeTaPyNJQ0Kz31oVGHcnWSVQu9tY5dG/01jmE7PTbYWW9yNtpFm712pDQ3HMHbZEf8lN0E0A03sxjTG6/eIU4sMd6AW4q3VYQXvunhXMkNgeKLOw6I4I9DPiLlGZMnbnngjKsl+ealdq6V5qB7iDnin5f+YQIAIfkEBRQABAAsMgAyAJwAnAAAA/84utxOMMpJa3Q4Wyi6/2AojmRpnpm2rWzqKisqz3T9qmxO3Wls/8AgD6YrXoaPjXDJbBqfyKSlSa1Gn8Zow2fterdYnZbB/ZqtYfGYOD27qencml1525fx1rx87yPzfFdKfoRDgINrgYWLYIc7e4iMkmSOj4mRk5OVlmOKmYWbE3MDnp9+oRKjpaZ3qEeXbayMrhyQsbKgtKqYuH20BLu3vb66tnVAv4vJxpw1y4TPnbwz0cSuwcc/1a3FsNk223bhhtMy427nN6sm6WbtLusl717zON/O3af5WvEk9Wj7BAmj8a9KwUb3CAYUt5DcQGoN0UVUVw7FQScTX/QbcZH/SUc6zRReyzXSW0iIJfWl5FfxxEchLzeKeIktVE1RyDJ+idmSnU56P+2dNPcrzs1KR1+BK5om6SGnwHIyxQI1D9QgU8NUNcoMp7asULoiFasUH9giW5uSrfX1rBqTm65KdasH7li7ZUXSlSkFldy2e3uOGoyXrcqEwwhTfPiGb2LFjjEyfqwYQ2SPgilXJpW5y2XN0iaf+QxaIOLGnUv/PTxU9WbSWFO7Dn1aoujZg2HPbY07t2w4v3vz0A2Yt/DCUaEFPw5vOR7nzC1Dj307+p/pu71a910daPftGrEX1w4euTLxm9Oz/A6QfYjA8OPLn0+/vv37+PPr38+/v////wAGKOCABBZo4IEIJqjgVAE06OCDEEYo4YQUVmihhMQBoOGGHHbo4YcghsjhhSSWaOKJDmYo4oostqghijDGGKOKLtZo44sy5qgjhTTe6OOKOwYpZAA9/mikh0MmKWORRzYJgJJQnsikk0ZGaeWFU1Lp45VcTpilljZ2KeaDX4Lp4pholmkmi2iOqeaaIrYp5ptwgihnl3TWieSdV+ap54h8WunnnzgGCuWghBoaJaJ/KnooeoTW6KiSjOo5aZKV1pnjL5tCp1+nroBaG4ufLkmLqMaJWOqMp5rqXoerwsipq6OuGCuKs7L6Koe3StmqrrWqmh+qmxCbipG9mpirrP+eDktrKMbmVWOyJS6La7P4RXuItsn5SC2J1vq664bfYvkrs+NqWK6F4SqL7X3c5sHtketW2G6179oXbxzzIusssNA+S56N9fJ47rXpAlCwlweLG2yIC7fJU7aXkhnUhxGnebGHGbu5Maz/Vkzkx7yGXPHE8IrcIMr6qjzySgSbfCnL9bn8sl/+UqwyTZHeaDPPPUvqMtBBt/gzyUVvOTTSSYe5NMxNr3k01FGDOTXOVWv6NNZZS721TV3DaXO/YZu5bxpkl63l2WGkrbaTbGPh9ttHxv3E3HT/aLcReOfts8CV9O230AAXC7i0gxOOLiqCJ87m4dtC3q3jThceuOQElP+YAAAh+QQFFAAEACwyADIAnACcAAAD/xi63E4wyklrdDhbOLr/YCiOZGmKWcpsbEuoMHvOdG17sOruVJ7Kt6Aw6NPwjq/iYzNsOkvKJXIXbQCfWGx1NaVuFdesWPgFd13lQHjMpqXP6PK6TSe94ay7pc6HyvEbehV9hCGCgBOHE4WMHYqIEI8RjYySiJYElIWYeJiahJxwnp98oWejpHSmXaipbKtTra5isEiys1p/kIm6g7hjtUe3v03BPMM0uxTFvcpJX3M1zhLM0NORzYtD1xxDxl7We9vc1Vvcz+ZM49flVefIM+ftUe/Z1OvT80r14b5C8t7sQYJ3AiAZgZcQZsLnTF8RfunE/SMXsJ8zgiYMElHYSf9hE403vsWxqG0iu4oRp2EsAdKGyBYrSbSs8TKPR4bKHPqA6E6dyXwoe16LOWKmG46ibv5sGJQeN6IijM6oGUhpkHMdSe6CGgJrUq0Drd7wegppWbDdlpIFl/KiWBtrY5ll9VZaXGFz5aJdqPZu1b1Z25a86petUJV1kxUeKXhr4niLYaaZTFmKP03RjlbePDkzIc8nOIt+3Ae0idGonUrE7HNj6tc6WlMy7Qe2bcvLSNG2c7v3gt1tgKPw7Vv4GOMgiBeX3Qj5B+W9nWOR7gi6bepOsFu/zpyR9u2vsX/srhn8aPE47x00f578Z/eh2bdfPRv+afmi0fed1BQ/VzH/3/lXmX6E0eeSgAPaV0eACP6XBXaRRSjhhBRWaOGFGGao4YYcdujhhyCGKOKIJJZo4okopqjiimQB4OKLMMYo44w01mjjjTMSKMCOPPbo449ABinkkDgWaeSROOpI5JJMNonkk1BGqaSTVFYZ5ZVY3jillVx2meWXSG7p5Zhkgmmmi2KWqeaZbBqZ5ppwtilnjG/GaeecbNZ55554Yqknn4D2eeSfgRYqaI2EGqrooS8muiijkDr6KKSCSjoppXNaeimmeSq46aec2qgpqKH66SmpqJYKwKipqjroqa3yKVWSsP64oaknSVmrj7deOauWu/bYq665QgmhhrgCRexl/1UOayxFy+bGpbNP/ipqsDxSGya0zxropLavFlsttjuC6ya343rbpLlFWosouQKwS6u426rLpLzA0hsus1Tie62+59q7pL/vAtwuvATT6K7CCCPrK7r18vutw9Hm9LDARCacI8T7SmulxjIuvDHGQ4JMJ8cBS7wuxa6GjPK9LLcMo8i2xiwzmi8PbPPNNPO6s8w9C/tzy0FnO7SrRZd7tKpJx7t0qU2bzGjUT4fadKxYn2xw1lwfvHXXYDP8ddhkN5pz2WhfjTbQZ68dttpuM9123De7PDbddZvJatZUk4x3xbsk6/Hfa/atMuGCWww4f4gXPrfYhzferbKTDy554hmBXxz55R0rXvlgnGvO1OJphS665+luTncCADs=">
+ </image>
+ </slot>
+ </view>
+
+ <!-- 错误处理 -->
+ <view class="uqrcode-error"
+ v-if="isError"
+ @click="onClick">
+ <slot name="error"
+ :error="error">
+ <text class="uqrcode-error-message">{{ error.errMsg }}</text>
+ </slot>
+ </view>
+
+ <!-- H5保存提示 -->
+ <!-- #ifdef H5 -->
+ <view class="uqrcode-h5-save"
+ v-if="isH5Save">
+ <slot name="h5save"
+ :tempFilePath="tempFilePath">
+ <image class="uqrcode-h5-save-image"
+ :src="tempFilePath"></image>
+ <text class="uqrcode-h5-save-text">{{ h5SaveIsDownload ? '若保存失败,' : '' }}请长按二维码进行保存</text>
+ </slot>
+ <view class="uqrcode-h5-save-close"
+ @click.stop="isH5Save = false">
+ <view class="uqrcode-h5-save-close-before"></view>
+ <view class="uqrcode-h5-save-close-after"></view>
+ </view>
+ </view>
+ <!-- #endif -->
+ </view>
+</template>
+
+<script>
+ import props from './props.js';
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef VUE3
+ import { toRaw } from 'vue';
+ // #endif
+ /* 引入uvQRCode核心js */
+ import UQRCode from './qrcode';
+ /* 引入nvue所需模块 */
+ // #ifdef APP-NVUE
+ import { enable, WeexBridge } from './gcanvas';
+ const modal = weex.requireModule('modal');
+ // #endif
+ /* 引入队列 */
+ import { queueDraw, queueLoadImage } from './queue';
+ /* 引入缓存图片 */
+ import { cacheImageList } from './cache';
+ let instance = null;
+ /**
+ * qrcode 二维码
+ * @description 二维码生成插件,可扩展性高,它支持自定义渲染二维码,可通过uQRCode API得到二维码绘制关键信息后,使用canvas、svg或js操作dom的方式绘制二维码图案。还可自定义二维码样式,如随机颜色、圆点、方块、块与块之间的间距等。
+ * @tutorial https://www.uvui.cn/components/qrcode.html
+ * @property {String} value 二维码内容 (start为true时必填 )
+ * @property {Object} options 二维码配置选项 (data|size|margin...)
+ * @property {String} fileType 导出的文件类型 (jpg | png)
+ * @property {String} start 是否初始化组件后就开始生成 (默认 true)
+ * @property {String} auto 是否数据发生改变自动重绘 (默认 false)
+ * @property {String} hide 隐藏组件。如果只需要导出二维码作为图片使用,可设置为true,不能在组件或组件父级元素设置v-if="false"、v-show="false"、style="display:none;"等实现隐藏效果,这样会导致导出二维码空白 (默认 false)
+ * @property {String} type canvas组件类型。微信小程序默认2d (默认 undefined)
+ * @property {String} queue 队列绘制 (默认 false)
+ * @property {String} isQueueLoadImage 是否队列加载图片,选择true将通过队列缓存所需要加载的图片。优点是加载重复资源可减少资源请求次数,节省网络资源,缺点是会转化为同步请求,资源不重复且多的情况下,等待时间会更久。总之,请求重复资源较多则选择true,请求不重复资源较多则选择false (默认 false)
+ * @property {String} loading loading态 (默认 false)
+ * @property {String} h5SaveIsDownload H5保存即自动下载(在支持的环境下),默认false为仅弹层提示用户需要长按图片保存,不会自动下载 (默认 false)
+ * @property {String} h5DownloadName H5下载名称
+ * @property {String} h5SaveTip H5保存二维码时候是否显示提示
+ * @example <uv-qrcode ref="uvqrcode" size="400rpx" canvas-id="qrcode" value="https://www.uvui.cn"></uv-qrcode>
+ */
+ export default {
+ name: 'uv-qrcode',
+ mixins: [mpMixin,mixin,props],
+ emits: ['click','change','complete'],
+ data() {
+ return {
+ canvasId: "",
+ canvas: undefined,
+ canvasType: undefined,
+ canvasContext: undefined,
+ makeDelegate: undefined,
+ drawDelegate: undefined,
+ toTempFilePathDelegate: undefined,
+ makeExecuted: false,
+ makeing: false,
+ drawing: false,
+ isError: false,
+ error: undefined,
+ isH5Save: false,
+ tempFilePath: '',
+ templateOptions: {
+ size: 0,
+ width: 0, // 组件宽度
+ height: 0,
+ canvasWidth: 0, // canvas宽度
+ canvasHeight: 0,
+ canvasTransform: '',
+ canvasDisplay: false
+ },
+ uqrcodeOptions: {
+ data: ''
+ },
+ plugins: [],
+ makeingPattern: [
+ [
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true]
+ ],
+ [
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, false, false, false],
+ [true, true, true, true, true, true, false, true, true, true],
+ [true, true, true, true, true, true, false, true, true, true],
+ [true, true, true, true, true, true, false, true, true, true]
+ ],
+ [
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, true, true, true, true, false, false, false],
+ [true, true, true, true, true, true, true, false, false, false],
+ [true, true, true, true, true, true, true, false, false, false],
+ [true, true, true, false, false, false, false, true, true, true],
+ [true, true, true, false, false, false, false, true, true, true]
+ ],
+ [
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, false, false, false, false, false, false, false],
+ [true, true, true, false, false, false, false, false, false, false],
+ [true, true, true, false, false, false, false, false, false, false],
+ [true, true, true, false, false, false, false, false, false, false],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true],
+ [true, true, true, true, true, true, true, true, true, true]
+ ]
+ ]
+ };
+ },
+ watch: {
+ type: {
+ handler(val) {
+ const types = ['2d'];
+ if (types.includes(val)) {
+ this.canvasType = val;
+ } else {
+ this.canvasType = undefined;
+ }
+ },
+ immediate: true
+ },
+ value: {
+ handler() {
+ if (this.auto) {
+ this.remake();
+ }
+ }
+ },
+ size: {
+ handler() {
+ if (this.auto) {
+ this.remake();
+ }
+ }
+ },
+ options: {
+ handler() {
+ if (this.auto) {
+ this.remake();
+ }
+ },
+ deep: true
+ },
+ makeing: {
+ handler(val) {
+ if (!val) {
+ if (typeof this.toTempFilePathDelegate === 'function') {
+ this.toTempFilePathDelegate();
+ }
+ }
+ }
+ }
+ },
+ created() {
+ this.canvasId = this.$uv.guid();
+ },
+ mounted() {
+ this.templateOptions.size = this.$uv.getPx(this.size);
+ this.templateOptions.width = this.templateOptions.size;
+ this.templateOptions.height = this.templateOptions.size;
+ this.templateOptions.canvasWidth = this.templateOptions.size;
+ this.templateOptions.canvasHeight = this.templateOptions.size;
+ if (this.canvasType == '2d') {
+ // #ifndef MP-WEIXIN
+ this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
+ this.templateOptions.canvasHeight})`;
+ // #endif
+ } else {
+ this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
+ this.templateOptions.canvasHeight})`;
+ }
+ if (this.start) {
+ this.$nextTick(()=>{
+ this.make();
+ })
+ }
+ },
+ methods: {
+ /**
+ * 获取模板选项
+ */
+ getTemplateOptions() {
+ var size = this.$uv.getPx(this.size);
+ return deepReplace(this.templateOptions, {
+ size,
+ width: size,
+ height: size
+ });
+ },
+ /**
+ * 获取插件选项
+ */
+ getUqrcodeOptions() {
+ return deepReplace(this.options, {
+ data: String(this.value),
+ size: Number(this.templateOptions.size)
+ });
+ },
+ /**
+ * 重置画布
+ */
+ resetCanvas(callback) {
+ this.templateOptions.canvasDisplay = false;
+ this.$nextTick(() => {
+ this.templateOptions.canvasDisplay = true;
+ this.$nextTick(() => {
+ callback && callback();
+ });
+ });
+ },
+ /**
+ * 绘制二维码
+ */
+ async draw(callback = {}, isDrawDelegate = false) {
+ if (typeof callback.success != 'function') {
+ callback.success = () => {};
+ }
+ if (typeof callback.fail != 'function') {
+ callback.fail = () => {};
+ }
+ if (typeof callback.complete != 'function') {
+ callback.complete = () => {};
+ }
+
+ if (this.drawing) {
+ if (!isDrawDelegate) {
+ this.drawDelegate = () => {
+ this.draw(callback, true);
+ };
+ return;
+ }
+ } else {
+ this.drawing = true;
+ }
+
+ if (!this.canvasId) {
+ console.error('[uQRCode]: canvasId must be set!');
+ this.isError = true;
+ this.drawing = false;
+ callback.fail({
+ errMsg: '[uQRCode]: canvasId must be set!'
+ });
+ callback.complete({
+ errMsg: '[uQRCode]: canvasId must be set!'
+ });
+ return;
+ }
+ if (!this.value) {
+ console.error('[uQRCode]: value must be set!');
+ this.isError = true;
+ this.drawing = false;
+ callback.fail({
+ errMsg: '[uQRCode]: value must be set!'
+ });
+ callback.complete({
+ errMsg: '[uQRCode]: value must be set!'
+ });
+ return;
+ }
+
+ /* 组件数据 */
+ this.templateOptions = this.getTemplateOptions();
+ /* uQRCode选项 */
+ this.uqrcodeOptions = this.getUqrcodeOptions();
+ /* 纠错等级兼容字母写法 */
+ if (typeof this.uqrcodeOptions.errorCorrectLevel === 'string') {
+ this.uqrcodeOptions.errorCorrectLevel = UQRCode.errorCorrectLevel[this.uqrcodeOptions.errorCorrectLevel];
+ }
+ /* nvue不支持动态修改gcanvas尺寸,除nvue外,默认使用useDynamicSize */
+ // #ifndef APP-NVUE
+ if (typeof this.options.useDynamicSize === 'undefined') {
+ this.uqrcodeOptions.useDynamicSize = true;
+ }
+ // #endif
+ // #ifdef APP-NVUE
+ if (typeof this.options.useDynamicSize === 'undefined') {
+ this.uqrcodeOptions.useDynamicSize = false;
+ }
+ // if (typeof this.options.drawReserve === 'undefined') {
+ // this.uqrcodeOptions.drawReserve = true;
+ // }
+ // #endif
+
+ /* 获取uQRCode实例 */
+ const qr = instance = new UQRCode();
+ /* 注册扩展 */
+ this.plugins.forEach(p => qr.register(p.plugin));
+ /* 设置uQRCode选项 */
+ qr.setOptions(this.uqrcodeOptions);
+ /* 调用制作二维码方法 */
+ qr.make();
+
+ /* 获取canvas上下文 */
+ let canvasContext = null;
+ // #ifndef APP-NVUE
+ if (this.canvasType === '2d') {
+ // #ifdef MP-WEIXIN
+ /* 微信小程序获取canvas2d上下文方式 */
+ const canvas = (this.canvas = await new Promise(resolve => {
+ uni
+ .createSelectorQuery()
+ .in(this) // 在组件内使用需要
+ .select(`#${this.canvasId}`)
+ .fields({
+ node: true,
+ size: true
+ })
+ .exec(res => {
+ resolve(res[0].node);
+ });
+ }));
+ canvasContext = this.canvasContext = canvas.getContext('2d');
+ /* 2d的组件设置宽高与实际canvas绘制宽高不是一个,打个比方,组件size=200,canvas.width设置为100,那么绘制出来就是100=200,组件size=400,canvas.width设置为800,绘制大小还是800=400,所以无需理会下方返回的dynamicSize是多少,按dpr重新赋值给canvas即可 */
+ this.templateOptions.canvasWidth = qr.size;
+ this.templateOptions.canvasHeight = qr.size;
+ this.templateOptions.canvasTransform = '';
+ /* 使用dynamicSize+scale,可以解决小块间出现白线问题,dpr可以解决模糊问题 */
+ const dpr = uni.getSystemInfoSync().pixelRatio;
+ canvas.width = qr.dynamicSize * dpr;
+ canvas.height = qr.dynamicSize * dpr;
+ canvasContext.scale(dpr, dpr);
+ /* 微信小程序获取图像方式 */
+ qr.loadImage = this.getLoadImage(function(src) {
+ /* 小程序下获取网络图片信息需先配置download域名白名单才能生效 */
+ return new Promise((resolve, reject) => {
+ const img = canvas.createImage();
+ img.src = src;
+ img.onload = () => {
+ resolve(img);
+ };
+ img.onerror = err => {
+ reject(err);
+ };
+ });
+ });
+ // #endif
+ // #ifndef MP-WEIXIN
+ /* 非微信小程序不支持2d,切换回uniapp获取canvas上下文方式 */
+ canvasContext = this.canvasContext = uni.createCanvasContext(this.canvasId, this);
+ /* 使用dynamicSize,可以解决小块间出现白线问题,再通过scale缩放至size,使其达到所设尺寸 */
+ this.templateOptions.canvasWidth = qr.dynamicSize;
+ this.templateOptions.canvasHeight = qr.dynamicSize;
+ this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
+ this.templateOptions.canvasHeight})`;
+ /* uniapp获取图像方式 */
+ qr.loadImage = this.getLoadImage(function(src) {
+ return new Promise((resolve, reject) => {
+ if (src.startsWith('http')) {
+ uni.getImageInfo({
+ src,
+ success: res => {
+ resolve(res.path);
+ },
+ fail: err => {
+ reject(err);
+ }
+ });
+ } else {
+ if (src.startsWith('.')) {
+ console.error('[uQRCode]: 本地图片路径仅支持绝对路径!');
+ throw new Error('[uQRCode]: local image path only supports absolute path!');
+ } else {
+ resolve(src);
+ }
+ }
+ });
+ });
+ // #endif
+ } else {
+ /* uniapp获取canvas上下文方式 */
+ canvasContext = this.canvasContext = uni.createCanvasContext(this.canvasId, this);
+ /* 使用dynamicSize,可以解决小块间出现白线问题,再通过scale缩放至size,使其达到所设尺寸 */
+ this.templateOptions.canvasWidth = qr.dynamicSize;
+ this.templateOptions.canvasHeight = qr.dynamicSize;
+ this.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /
+ this.templateOptions.canvasHeight})`;
+ /* uniapp获取图像方式 */
+ qr.loadImage = this.getLoadImage(function(src) {
+ return new Promise((resolve, reject) => {
+ /* getImageInfo在微信小程序的bug:本地路径返回路径会把开头的/或../移除,导致路径错误,解决方法:限制只能使用绝对路径 */
+ if (src.startsWith('http')) {
+ uni.getImageInfo({
+ src,
+ success: res => {
+ resolve(res.path);
+ },
+ fail: err => {
+ reject(err);
+ }
+ });
+ } else {
+ if (src.startsWith('.')) {
+ console.error('[uQRCode]: 本地图片路径仅支持绝对路径!');
+ throw new Error('[uQRCode]: local image path only supports absolute path!');
+ } else {
+ resolve(src);
+ }
+ }
+ });
+ });
+ }
+ // #endif
+ // #ifdef APP-NVUE
+ /* NVue获取canvas上下文方式 */
+ const gcanvas = this.$refs['gcanvas'];
+ const canvas = enable(gcanvas, {
+ bridge: WeexBridge
+ });
+ canvasContext = this.canvasContext = canvas.getContext('2d');
+ /* NVue获取图像方式 */
+ qr.loadImage = this.getLoadImage(function(src) {
+ return new Promise((resolve, reject) => {
+ /* getImageInfo在nvue的bug:获取同一个路径的图片信息,同一时间第一次获取成功,后续失败,猜测是写入本地时产生文件写入冲突,所以没有返回,特别是对于网络资源 --- 已实现队列绘制,已解决此问题 */
+ if (src.startsWith('.')) {
+ console.error('[uQRCode]: 本地图片路径仅支持绝对路径!');
+ throw new Error('[uQRCode]: local image path only supports absolute path!');
+ } else {
+ uni.getImageInfo({
+ src,
+ success: res => {
+ resolve(res.path);
+ },
+ fail: err => {
+ reject(err);
+ }
+ });
+ }
+ });
+ });
+ // #endif
+
+ /* 设置uQRCode实例的canvas上下文 */
+ qr.canvasContext = canvasContext;
+ /* 延时等待页面重新绘制完毕 */
+ setTimeout(() => {
+ /* 从插件获取具体要调用哪一个扩展函数 */
+ var plugin = this.plugins.find(p => p.name == qr.style);
+ var drawCanvasName = plugin ? plugin.drawCanvas : 'drawCanvas';
+ /* 虽然qr[drawCanvasName]是直接返回Promise的,但由于js内部this指向问题,故不能直接exec(qr[drawCanvasName])此方式执行,需要改成exec(() => qr[drawCanvasName]())才能正确获取this */
+ var drawCanvas;
+ if (this.queue) {
+ drawCanvas = () => queueDraw.exec(() => qr[drawCanvasName]());
+ // drawCanvas = () => queueDraw.exec(() => new Promise((resolve, reject) => {
+ // setTimeout(() => {
+ // qr[drawCanvasName]().then(resolve).catch(reject);
+ // }, 1000);
+ // }));
+ } else {
+ drawCanvas = () => qr[drawCanvasName]();
+ }
+ /* 调用绘制方法将二维码图案绘制到canvas上 */
+ drawCanvas()
+ .then(() => {
+ if (this.drawDelegate) {
+ /* 高频重绘纠正 */
+ let delegate = this.drawDelegate;
+ this.drawDelegate = undefined;
+ delegate();
+ } else {
+ this.drawing = false;
+ callback.success();
+ }
+ })
+ .catch(err => {
+ console.log(err);
+ if (this.drawDelegate) {
+ /* 高频重绘纠正 */
+ let delegate = this.drawDelegate;
+ this.drawDelegate = undefined;
+ delegate();
+ } else {
+ this.drawing = false;
+ this.isError = true;
+ callback.fail(err);
+ }
+ })
+ .finally(() => {
+ callback.complete();
+ });
+ }, 300);
+ },
+ /**
+ * 生成二维码
+ */
+ make(callback = {}) {
+ this.makeExecuted = true;
+ this.makeing = true;
+ this.isError = false;
+
+ if (typeof callback.success != 'function') {
+ callback.success = () => {};
+ }
+ if (typeof callback.fail != 'function') {
+ callback.fail = () => {};
+ }
+ if (typeof callback.complete != 'function') {
+ callback.complete = () => {};
+ }
+
+ this.resetCanvas(() => {
+ clearTimeout(this.makeDelegate);
+ this.makeDelegate = setTimeout(() => {
+ this.draw({
+ success: () => {
+ setTimeout(() => {
+ callback.success();
+ this.complete(true);
+ }, 300);
+ },
+ fail: err => {
+ callback.fail(err);
+ this.error = err;
+ this.complete(false, err.errMsg);
+ },
+ complete: () => {
+ callback.complete();
+ this.makeing = false;
+ }
+ });
+ }, 300);
+ });
+ },
+ /**
+ * 重新生成
+ */
+ remake(callback) {
+ this.$emit('change');
+ this.make(callback);
+ },
+ /**
+ * 生成完成
+ */
+ complete(success = true, errMsg = '') {
+ if (success) {
+ this.$emit('complete', {
+ success
+ });
+ } else {
+ this.$emit('complete', {
+ success,
+ errMsg
+ });
+ }
+ },
+ /**
+ * 导出临时路径
+ */
+ toTempFilePath(callback = {}) {
+ if (typeof callback.success != 'function') {
+ callback.success = () => {};
+ }
+ if (typeof callback.fail != 'function') {
+ callback.fail = () => {};
+ }
+ if (typeof callback.complete != 'function') {
+ callback.complete = () => {};
+ }
+
+ if (!this.makeExecuted) {
+ console.error('[uQRCode]: make() 方法从未调用!请先成功调用 make() 后再进行操作。');
+ var err = {
+ errMsg: '[uQRCode]: make() method has never been executed! please execute make() successfully before operating.'
+ };
+ callback.fail(err);
+ callback.complete(err);
+ return;
+ }
+
+ if (this.isError) {
+ callback.fail(this.error);
+ callback.complete(this.error);
+ return;
+ }
+
+ if (this.makeing) {
+ /* 如果还在生成状态,那当前操作将托管到委托,监听生成完成后再通过委托复调当前方法 */
+ this.toTempFilePathDelegate = () => {
+ this.toTempFilePath(callback);
+ };
+ return;
+ } else {
+ this.toTempFilePathDelegate = null;
+ }
+
+ // #ifndef APP-NVUE
+ if (this.canvasType === '2d') {
+ // #ifdef MP-WEIXIN
+ try {
+ let dataURL = null;
+ // #ifdef VUE3
+ dataURL = toRaw(this.canvas)
+ .toDataURL();
+ // #endif
+ // #ifndef VUE3
+ dataURL = this.canvas.toDataURL();
+ // #endif
+ callback.success({
+ tempFilePath: dataURL
+ });
+ callback.complete({
+ tempFilePath: dataURL
+ });
+ } catch (e) {
+ callback.fail(e);
+ callback.complete(e);
+ }
+ // #endif
+ } else {
+ uni.canvasToTempFilePath({
+ canvasId: this.canvasId,
+ fileType: this.fileType,
+ width: Number(this.templateOptions.canvasWidth),
+ height: Number(this.templateOptions.canvasHeight),
+ destWidth: Number(this.templateOptions.size),
+ destHeight: Number(this.templateOptions.size),
+ success: res => {
+ callback.success(res);
+ },
+ fail: err => {
+ callback.fail(err);
+ },
+ complete: () => {
+ callback.complete();
+ }
+ },
+ this
+ );
+ }
+ // #endif
+ // #ifdef APP-NVUE
+ const dpr = uni.getSystemInfoSync().pixelRatio;
+ this.canvasContext.toTempFilePath(
+ 0,
+ 0,
+ this.templateOptions.canvasWidth * dpr,
+ this.templateOptions.canvasHeight * dpr,
+ this.templateOptions.size * dpr,
+ this.templateOptions.size * dpr,
+ '',
+ 1,
+ res => {
+ callback.success(res);
+ callback.complete(res);
+ }
+ );
+ // #endif
+ },
+ /**
+ * 保存
+ */
+ save(callback = {}) {
+ if (typeof callback.success != 'function') {
+ callback.success = () => {};
+ }
+ if (typeof callback.fail != 'function') {
+ callback.fail = () => {};
+ }
+ if (typeof callback.complete != 'function') {
+ callback.complete = () => {};
+ }
+
+ this.toTempFilePath({
+ success: res => {
+ // #ifndef H5
+ if (this.canvasType === '2d') {
+ // #ifdef MP-WEIXIN
+ /* 需要将 data:image/png;base64, 这段去除 writeFile 才能正常打开文件,否则是损坏文件,无法打开 */
+ const reg = new RegExp('^data:image/png;base64,', 'g');
+ const dataURL = res.tempFilePath.replace(reg, '');
+ const fs = wx.getFileSystemManager();
+ const tempFilePath = `${wx.env.USER_DATA_PATH}/${new Date().getTime()}${
+ Math.random()
+ .toString()
+ .split('.')[1]
+ }.png`;
+ fs.writeFile({
+ filePath: tempFilePath, // 要写入的文件路径 (本地路径)
+ data: dataURL, // base64图片
+ encoding: 'base64',
+ success: res1 => {
+ uni.saveImageToPhotosAlbum({
+ filePath: tempFilePath,
+ success: res2 => {
+ callback.success(res2);
+ },
+ fail: err2 => {
+ callback.fail(err2);
+ },
+ complete: () => {
+ callback.complete();
+ }
+ });
+ },
+ fail: err => {
+ callback.fail(err);
+ },
+ complete: () => {
+ callback.complete();
+ }
+ });
+ // #endif
+ } else {
+ uni.saveImageToPhotosAlbum({
+ filePath: res.tempFilePath,
+ success: res1 => {
+ callback.success(res1);
+ },
+ fail: err1 => {
+ callback.fail(err1);
+ },
+ complete: () => {
+ callback.complete();
+ }
+ });
+ }
+ // #endif
+
+ // #ifdef H5
+ /* 可以在电脑浏览器下载,移动端iOS不行,安卓微信浏览器不行,安卓外部浏览器可以 */
+ this.isH5Save = this.h5SaveTip;
+ this.tempFilePath = res.tempFilePath;
+ if (this.h5SaveIsDownload) {
+ const aEle = document.createElement('a');
+ aEle.download = this.h5DownloadName; // 设置下载的文件名,默认是'下载'
+ aEle.href = res.tempFilePath;
+ document.body.appendChild(aEle);
+ aEle.click();
+ aEle.remove(); // 下载之后把创建的元素删除
+ }
+ callback.success({
+ errMsg: 'ok'
+ });
+ callback.complete({
+ errMsg: 'ok'
+ });
+ // #endif
+ },
+ fail: err => {
+ callback.fail(err);
+ callback.complete(err);
+ }
+ });
+ },
+ /**
+ * 注册click事件
+ */
+ onClick(e) {
+ this.$emit('click', e);
+ },
+ /**
+ * 获取实例
+ */
+ getInstance() {
+ return instance;
+ },
+ /**
+ * 注册扩展,组件仅支持注册type为style的drawCanvas扩展
+ * @param {Object} plugin
+ */
+ registerStyle(plugin) {
+ if (plugin.Type != 'style') {
+ console.warn('[uQRCode]: registerStyle 仅支持注册 style 类型的扩展!');
+ return {
+ errMsg: 'registerStyle 仅支持注册 style 类型的扩展!'
+ };
+ }
+ if (typeof plugin === 'function') {
+ this.plugins.push({
+ plugin,
+ name: plugin.Name,
+ drawCanvas: plugin.DrawCanvas
+ });
+ }
+ },
+ getLoadImage(loadImage) {
+ var that = this;
+ if (typeof loadImage == 'function') {
+ return function(src) {
+ /* 判断是否是队列加载图片的 */
+ if (that.isQueueLoadImage) {
+ /* 解决iOS APP||NVUE同时绘制多个二维码导致图片丢失需使用队列 */
+ return queueLoadImage.exec(() => {
+ return new Promise((resolve, reject) => {
+ setTimeout(() => {
+ const cache = cacheImageList.find(x => x.src == src);
+ if (cache) {
+ resolve(cache.img);
+ } else {
+ loadImage(src)
+ .then(img => {
+ cacheImageList.push({
+ src,
+ img
+ });
+ resolve(img);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ }
+ }, 10);
+ });
+ });
+ } else {
+ return loadImage(src);
+ }
+ };
+ } else {
+ return function(src) {
+ return Promise.resolve(src);
+ };
+ }
+ }
+ }
+ };
+
+ /**
+ * 对象属性深度替换
+ * @param {Object} o 原始对象/默认对象/被替换的对象
+ * @param {Object} r 从这个对象里取值替换到o对象里
+ * @return {Object} 替换后的新对象
+ */
+ function deepReplace(o = {}, r = {}, c = false) {
+ let obj;
+ if (c) {
+ // 从源替换
+ obj = o;
+ } else {
+ // 不替换源,copy一份备份来替换
+ obj = {
+ ...o
+ };
+ }
+ for (let k in r) {
+ var vr = r[k];
+ if (vr != undefined) {
+ if (vr.constructor == Object) {
+ obj[k] = this.deepReplace(obj[k], vr);
+ } else if (vr.constructor == String && !vr) {
+ obj[k] = obj[k];
+ } else {
+ obj[k] = vr;
+ }
+ }
+ }
+ return obj;
+ }
+</script>
+
+<style scoped>
+ .uqrcode {
+ position: relative;
+ }
+ .uqrcode-hide {
+ position: fixed;
+ left: 7500rpx;
+ }
+ .uqrcode-canvas {
+ transform-origin: top left;
+ }
+ .uqrcode-makeing {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 10;
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ justify-content: center;
+ align-items: center;
+ }
+ .uqrcode-makeing-image {
+ /* #ifndef APP-NVUE */
+ display: block;
+ max-width: 120px;
+ max-height: 120px;
+ /* #endif */
+ }
+ .uqrcode-error {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ justify-content: center;
+ align-items: center;
+ }
+ .uqrcode-error-message {
+ font-size: 12px;
+ color: #939291;
+ }
+ /* #ifdef H5 */
+ .uqrcode-h5-save {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 100;
+ background-color: rgba(0, 0, 0, 0.68);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ }
+ .uqrcode-h5-save-image {
+ width: 512rpx;
+ height: 512rpx;
+ padding: 32rpx;
+ }
+ .uqrcode-h5-save-text {
+ margin-top: 20rpx;
+ font-size: 32rpx;
+ font-weight: 700;
+ color: #ffffff;
+ }
+ .uqrcode-h5-save-close {
+ position: relative;
+ margin-top: 72rpx;
+ width: 60rpx;
+ height: 60rpx;
+ border: 2rpx solid #ffffff;
+ border-radius: 60rpx;
+ padding: 10rpx;
+ }
+ .uqrcode-h5-save-close-before {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) rotate(45deg);
+ width: 40rpx;
+ height: 4rpx;
+ background: #ffffff;
+ }
+ .uqrcode-h5-save-close-after {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) rotate(-45deg);
+ width: 40rpx;
+ height: 4rpx;
+ background: #ffffff;
+ }
+ /* #endif */
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/package.json b/ledApp/uni_modules/uv-qrcode/package.json
new file mode 100644
index 0000000..c85e74f
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-qrcode",
+ "displayName": "uv-qrcode 二维码 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.5",
+ "description": "该组件是超级强大二维码生成功能,可扩展性高。自定义二维码样式,如随机颜色、圆点、方块、块与块之间的间距等等,具体请查看文档。",
+ "keywords": [
+ "uv-qrcode",
+ "qrcode",
+ "uvui",
+ "uv-ui",
+ "二维码"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-qrcode/readme.md b/ledApp/uni_modules/uv-qrcode/readme.md
new file mode 100644
index 0000000..fd4acfe
--- /dev/null
+++ b/ledApp/uni_modules/uv-qrcode/readme.md
@@ -0,0 +1,21 @@
+## QRCode 二维码生成器
+
+> **组件名:uv-qrcode**
+
+超级强大二维码生成组件,可扩展性高。自定义二维码样式,如随机颜色、圆点、方块、块与块之间的间距等等,具体请在下方查看文档。
+
+灵活配置,开箱即用,文档全面,支持自定义二维码风格。
+
+# <a href="https://www.uvui.cn/components/qrcode.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small style="font-size:14px;font-weight:700;">(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-radio/changelog.md b/ledApp/uni_modules/uv-radio/changelog.md
new file mode 100644
index 0000000..e51b181
--- /dev/null
+++ b/ledApp/uni_modules/uv-radio/changelog.md
@@ -0,0 +1,31 @@
+## 1.0.13(2023-12-19)
+1. 修复在group设置labelColor不生效的BUG
+## 1.0.12(2023-11-04)
+1. 修复label文字较多不分行的问题
+## 1.0.11(2023-10-11)
+1. 优化:https://gitee.com/climblee/uv-ui/issues/I872VD
+## 1.0.10(2023-09-01)
+1. 修复点击空隙处无效的问题
+2. label支持插槽下可点击
+## 1.0.9(2023-08-27)
+1. 优化
+## 1.0.8(2023-08-26)
+1. 修复v-model 绑定布尔值控制台报警
+## 1.0.7(2023-08-26)
+1. 修复设置 labelSize 属性设置无效的问题:https://gitee.com/climblee/uv-ui/issues/I7W6UN
+## 1.0.6(2023-08-04)
+1. 修复name为数字0时不能选中的BUG
+## 1.0.5(2023-07-13)
+1. 修复 uv-radio设置value属性不生效的BUG
+## 1.0.4(2023-07-05)
+修复vue3模式下,动态修改v-model绑定的值无效的BUG
+## 1.0.3(2023-06-06)
+1. 修正语法问题
+## 1.0.2(2023-06-06)
+1. uv-radio-group 兼容自定义样式customStyle,方便通过样式调整整体位置等
+2. .uv-radio-group--row增加flex-wrap: wrap;允许换行
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-radio 单选框
diff --git a/ledApp/uni_modules/uv-radio/components/uv-radio-group/props.js b/ledApp/uni_modules/uv-radio/components/uv-radio-group/props.js
new file mode 100644
index 0000000..b0dbd15
--- /dev/null
+++ b/ledApp/uni_modules/uv-radio/components/uv-radio-group/props.js
@@ -0,0 +1,89 @@
+export default {
+ props: {
+ // 绑定的值
+ value: {
+ type: [String, Number, Boolean],
+ default: ''
+ },
+ modelValue: {
+ type: [String, Number, Boolean],
+ default: ''
+ },
+ // 是否禁用全部radio
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 形状,circle-圆形,square-方形
+ shape: {
+ type: String,
+ default: 'circle'
+ },
+ // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+ activeColor: {
+ type: String,
+ default: '#2979ff'
+ },
+ // 未选中的颜色
+ inactiveColor: {
+ type: String,
+ default: '#c8c9cc'
+ },
+ // 标识符
+ name: {
+ type: String,
+ default: ''
+ },
+ // 整个组件的尺寸,默认px
+ size: {
+ type: [String, Number],
+ default: 18
+ },
+ // 布局方式,row-横向,column-纵向
+ placement: {
+ type: String,
+ default: 'row'
+ },
+ // label的文本
+ label: {
+ type: [String],
+ default: ''
+ },
+ // label的颜色 (默认 '#303133' )
+ labelColor: {
+ type: [String],
+ default: '#303133'
+ },
+ // label的字体大小,px单位
+ labelSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 是否禁止点击文本操作checkbox(默认 false )
+ labelDisabled: {
+ type: Boolean,
+ default: false
+ },
+ // 图标颜色
+ iconColor: {
+ type: String,
+ default: '#fff'
+ },
+ // 图标的大小,单位px
+ iconSize: {
+ type: [String, Number],
+ default: 12
+ },
+ // 竖向配列时,是否显示下划线
+ borderBottom: {
+ type: Boolean,
+ default: false
+ },
+ // 图标与文字的对齐方式
+ iconPlacement: {
+ type: String,
+ default: 'left'
+ },
+ ...uni.$uv?.props?.radioGroup
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue b/ledApp/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue
new file mode 100644
index 0000000..6b3069f
--- /dev/null
+++ b/ledApp/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue
@@ -0,0 +1,115 @@
+<template>
+ <view
+ class="uv-radio-group"
+ :class="bemClass"
+ :style="[$uv.addStyle(this.customStyle)]"
+ >
+ <slot></slot>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * radioRroup 单选框父组件
+ * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配uv-radio使用
+ * @tutorial https://www.uvui.cn/components/radio.html
+ * @property {String | Number | Boolean} value 绑定的值
+ * @property {Boolean} disabled 是否禁用所有radio(默认 false )
+ * @property {String} shape 外观形状,shape-方形,circle-圆形(默认 circle )
+ * @property {String} activeColor 选中时的颜色,应用到所有子Radio组件(默认 '#2979ff' )
+ * @property {String} inactiveColor 未选中的颜色 (默认 '#c8c9cc' )
+ * @property {String} name 标识符
+ * @property {String | Number} size 组件整体的大小,单位px(默认 18 )
+ * @property {String} placement 布局方式,row-横向,column-纵向 (默认 'row' )
+ * @property {String} label 文本
+ * @property {String} labelColor label的颜色 (默认 '#303133' )
+ * @property {String | Number} labelSize label的字体大小,px单位 (默认 14 )
+ * @property {Boolean} labelDisabled 是否禁止点击文本操作checkbox(默认 false )
+ * @property {String} iconColor 图标颜色 (默认 '#ffffff' )
+ * @property {String | Number} iconSize 图标的大小,单位px (默认 12 )
+ * @property {Boolean} borderBottom placement为row时,是否显示下边框 (默认 false )
+ * @property {String} iconPlacement 图标与文字的对齐方式 (默认 'left' )
+ * @property {Object} customStyle 组件的样式,对象形式
+ * @event {Function} change 任一个radio状态发生变化时触发
+ * @example <uv-radio-group v-model="value"></uv-radio-group>
+ */
+ export default {
+ name: 'uv-radio-group',
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ // 这里computed的变量,都是子组件uv-radio需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
+ // 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(uv-radio-group)
+ // 拉取父组件新的变化后的参数
+ parentData() {
+ const value = this.value || this.modelValue;
+ return [value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,
+ this.iconSize, this.borderBottom, this.placement]
+ },
+ bemClass() {
+ // this.bem为一个computed变量,在mixin中
+ return this.bem('radio-group', ['placement'])
+ },
+ },
+ watch: {
+ // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+ parentData() {
+ if (this.children.length) {
+ this.children.map(child => {
+ // 判断子组件(uv-radio)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+ typeof(child.init) === 'function' && child.init()
+ })
+ }
+ },
+ },
+ data() {
+ return {
+
+ }
+ },
+ created() {
+ this.children = []
+ },
+ methods: {
+ // 将其他的radio设置为未选中的状态
+ unCheckedOther(childInstance) {
+ this.children.map(child => {
+ // 所有子radio中,被操作组件实例的checked的值无需修改
+ if (childInstance !== child) {
+ child.checked = false
+ }
+ })
+ const {
+ name
+ } = childInstance
+ // 通过emit事件,设置父组件通过v-model双向绑定的值
+ // #ifdef VUE2
+ this.$emit('input', name)
+ // #endif
+ // #ifdef VUE3
+ this.$emit('update:modelValue',name)
+ // #endif
+ // 发出事件
+ this.$emit('change', name)
+ },
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-radio-group {
+ flex: 1;
+
+ &--row {
+ @include flex;
+ flex-wrap: wrap;
+ }
+
+ &--column {
+ @include flex(column);
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-radio/components/uv-radio/props.js b/ledApp/uni_modules/uv-radio/components/uv-radio/props.js
new file mode 100644
index 0000000..1e5e2ac
--- /dev/null
+++ b/ledApp/uni_modules/uv-radio/components/uv-radio/props.js
@@ -0,0 +1,65 @@
+export default {
+ props: {
+ // radio的名称
+ name: {
+ type: [String, Number, Boolean],
+ default: ''
+ },
+ // 形状,square为方形,circle为圆型
+ shape: {
+ type: String,
+ default: ''
+ },
+ // 是否禁用
+ disabled: {
+ type: [String, Boolean],
+ default: ''
+ },
+ // 是否禁止点击提示语选中单选框
+ labelDisabled: {
+ type: [String, Boolean],
+ default: ''
+ },
+ // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+ activeColor: {
+ type: String,
+ default: ''
+ },
+ // 未选中的颜色
+ inactiveColor: {
+ type: String,
+ default: ''
+ },
+ // 图标的大小,单位px
+ iconSize: {
+ type: [String, Number],
+ default: ''
+ },
+ // label的字体大小,px单位
+ labelSize: {
+ type: [String, Number],
+ default: ''
+ },
+ // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+ label: {
+ type: [String, Number, Boolean],
+ default: ''
+ },
+ // 整体的大小
+ size: {
+ type: [String, Number],
+ default: ''
+ },
+ // 图标颜色
+ iconColor: {
+ type: String,
+ default: ''
+ },
+ // label的颜色
+ labelColor: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.radio
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-radio/components/uv-radio/uv-radio.vue b/ledApp/uni_modules/uv-radio/components/uv-radio/uv-radio.vue
new file mode 100644
index 0000000..3c0236c
--- /dev/null
+++ b/ledApp/uni_modules/uv-radio/components/uv-radio/uv-radio.vue
@@ -0,0 +1,348 @@
+<template>
+ <view
+ class="uv-radio"
+ @tap.stop="wrapperClickHandler"
+ :style="[radioStyle]"
+ :class="[`uv-radio-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'uv-border-bottom']"
+ >
+ <view
+ class="uv-radio__icon-wrap"
+ @tap.stop="iconClickHandler"
+ :class="iconClasses"
+ :style="[iconWrapStyle]"
+ >
+ <slot name="icon">
+ <uv-icon
+ class="uv-radio__icon-wrap__icon"
+ name="checkbox-mark"
+ :size="elIconSize"
+ :color="elIconColor"
+ />
+ </slot>
+ </view>
+ <view
+ class="uv-radio__label-wrap"
+ @tap.stop="labelClickHandler">
+ <slot>
+ <text
+ :style="{
+ color: elDisabled ? elInactiveColor : elLabelColor,
+ fontSize: elLabelSize,
+ lineHeight: elLabelSize
+ }"
+ >{{label}}</text>
+ </slot>
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * radio 单选框
+ * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配uv-radio-group使用
+ * @tutorial https://www.uvui.cn/components/radio.html
+ * @property {String | Number} name radio的名称
+ * @property {String} shape 形状,square为方形,circle为圆型
+ * @property {Boolean} disabled 是否禁用
+ * @property {String | Boolean} labelDisabled 是否禁止点击提示语选中单选框
+ * @property {String} activeColor 选中时的颜色,如设置parent的active-color将失效
+ * @property {String} inactiveColor 未选中的颜色
+ * @property {String | Number} iconSize 图标大小,单位px
+ * @property {String | Number} labelSize label字体大小,单位px
+ * @property {String | Number} label label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+ * @property {String | Number} size 整体的大小
+ * @property {String} iconColor 图标颜色
+ * @property {String} labelColor label的颜色
+ * @property {Object} customStyle 组件的样式,对象形式
+ *
+ * @event {Function} change 某个radio状态发生变化时触发(选中状态)
+ * @example <uv-radio :labelDisabled="false">门掩黄昏,无计留春住</uv-radio>
+ */
+ export default {
+ name: "uv-radio",
+
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ checked: false,
+ // 当你看到这段代码的时候,
+ // 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式
+ // 故只能使用如此方法
+ parentData: {
+ iconSize: 12,
+ labelSize: 14,
+ labelColor: '#303133',
+ labelDisabled: null,
+ disabled: null,
+ shape: null,
+ activeColor: null,
+ inactiveColor: null,
+ size: 18,
+ value: null,
+ modelValue: null,
+ iconColor: null,
+ placement: 'row',
+ borderBottom: false,
+ iconPlacement: 'left'
+ }
+ }
+ },
+ computed: {
+ // 是否禁用,如果父组件uv-raios-group禁用的话,将会忽略子组件的配置
+ elDisabled() {
+ return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
+ },
+ // 是否禁用label点击
+ elLabelDisabled() {
+ return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :
+ false;
+ },
+ // 组件尺寸,对应size的值,默认值为21px
+ elSize() {
+ return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);
+ },
+ // 组件的勾选图标的尺寸,默认12px
+ elIconSize() {
+ return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);
+ },
+ // 组件选中激活时的颜色
+ elActiveColor() {
+ return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
+ },
+ // 组件选未中激活时的颜色
+ elInactiveColor() {
+ return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
+ '#c8c9cc');
+ },
+ // label的颜色
+ elLabelColor() {
+ return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
+ },
+ // 组件的形状
+ elShape() {
+ return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
+ },
+ // label大小
+ elLabelSize() {
+ return this.$uv.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :
+ '15'))
+ },
+ elIconColor() {
+ const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
+ '#ffffff');
+ // 图标的颜色
+ if (this.elDisabled) {
+ // disabled状态下,已勾选的radio图标改为elInactiveColor
+ return this.checked ? this.elInactiveColor : 'transparent'
+ } else {
+ return this.checked ? iconColor : 'transparent'
+ }
+ },
+ iconClasses() {
+ let classes = []
+ // 组件的形状
+ classes.push('uv-radio__icon-wrap--' + this.elShape)
+ if (this.elDisabled) {
+ classes.push('uv-radio__icon-wrap--disabled')
+ }
+ if (this.checked && this.elDisabled) {
+ classes.push('uv-radio__icon-wrap--disabled--checked')
+ }
+ // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+ // #ifdef MP-ALIPAY || MP-TOUTIAO
+ classes = classes.join(' ')
+ // #endif
+ return classes
+ },
+ iconWrapStyle() {
+ // radio的整体样式
+ const style = {}
+ style.backgroundColor = this.checked && !this.elDisabled ? this.elActiveColor : '#ffffff'
+ style.borderColor = this.checked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
+ style.width = this.$uv.addUnit(this.elSize)
+ style.height = this.$uv.addUnit(this.elSize)
+ // 如果是图标在右边的话,移除它的右边距
+ if (this.parentData.iconPlacement === 'right') {
+ style.marginRight = 0
+ }
+ return style
+ },
+ radioStyle() {
+ const style = {}
+ if (this.parentData.borderBottom && this.parentData.placement === 'row') {
+ this.$uv.error('检测到您将borderBottom设置为true,需要同时将uv-radio-group的placement设置为column才有效')
+ }
+ // 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
+ if (this.parentData.borderBottom && this.parentData.placement === 'column') {
+ // ios像素密度高,需要多一点的距离
+ style.paddingBottom = this.$uv.os() === 'ios' ? '12px' : '8px'
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ created() {
+ this.init()
+ },
+ methods: {
+ init() {
+ // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+ this.updateParentData()
+ if (!this.parent) {
+ this.$uv.error('uv-radio必须搭配uv-radio-group组件使用')
+ }
+ // 设置初始化时,是否默认选中的状态
+ this.$nextTick(()=>{
+ let parentValue = null;
+ // #ifdef VUE2
+ parentValue = this.parentData.value;
+ // #endif
+ // #ifdef VUE3
+ parentValue = this.parentData.modelValue;
+ // #endif
+ this.checked = this.name === parentValue
+ })
+ },
+ updateParentData() {
+ this.getParentData('uv-radio-group')
+ },
+ // 点击图标
+ iconClickHandler(e) {
+ this.preventEvent(e)
+ // 如果整体被禁用,不允许被点击
+ if (!this.elDisabled) {
+ this.setRadioCheckedStatus()
+ }
+ },
+ // 横向两端排列时,点击组件即可触发选中事件
+ wrapperClickHandler(e) {
+ this.parentData.iconPlacement === 'right' && this.iconClickHandler(e)
+ },
+ // 点击label
+ labelClickHandler(e) {
+ this.preventEvent(e)
+ // 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态
+ if (!this.elLabelDisabled && !this.elDisabled) {
+ this.setRadioCheckedStatus()
+ }
+ },
+ emitEvent() {
+ // uv-radio的checked不为true时(意味着未选中),才发出事件,避免多次点击触发事件
+ if (!this.checked) {
+ this.$emit('change', this.name)
+ // 尝试调用uv-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时
+ this.$nextTick(() => {
+ this.$uv.formValidate(this, 'change')
+ })
+ }
+ },
+ // 改变组件选中状态
+ // 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有uv-radio实例
+ // 将本组件外的其他uv-radio的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态
+ setRadioCheckedStatus() {
+ this.emitEvent()
+ // 将本组件标记为选中状态
+ this.checked = true
+ typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-border: 1;
+ $show-border-bottom: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-radio-label-wrap-padding-right: 6px !default;
+ $uv-radio-wrap-font-size: 20px !default;
+ $uv-radio-wrap-border-width: 1px !default;
+ $uv-radio-wrap-border-color: #c8c9cc !default;
+ $uv-radio-line-height: 0 !default;
+ $uv-radio-circle-border-radius: 100% !default;
+ $uv-radio-square-border-radius: 3px !default;
+ $uv-radio-checked-color: #fff !default;
+ $uv-radio-checked-background-color: red !default;
+ $uv-radio-checked-border-color: #2979ff !default;
+ $uv-radio-disabled-background-color: #ebedf0 !default;
+ $uv-radio-disabled--checked-color: #c8c9cc !default;
+ $uv-radio-label-margin-left: 5px !default;
+ $uv-radio-label-margin-right: 12px !default;
+ $uv-radio-label-color: $uv-content-color !default;
+ $uv-radio-label-font-size: 15px !default;
+ $uv-radio-label-disabled-color: #c8c9cc !default;
+ .uv-radio {
+ /* #ifndef APP-NVUE */
+ @include flex(row);
+ /* #endif */
+ overflow: hidden;
+ flex-direction: row;
+ align-items: center;
+ &-label--left {
+ flex-direction: row
+ }
+ &-label--right {
+ flex-direction: row-reverse;
+ justify-content: space-between
+ }
+ &__icon-wrap {
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ // nvue下,border-color过渡有问题
+ transition-property: border-color, background-color, color;
+ transition-duration: 0.2s;
+ /* #endif */
+ color: $uv-content-color;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ color: transparent;
+ text-align: center;
+ font-size: $uv-radio-wrap-font-size;
+ border-width: $uv-radio-wrap-border-width;
+ border-color: $uv-radio-wrap-border-color;
+ border-style: solid;
+ /* #ifdef MP-TOUTIAO */
+ // 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
+ &__icon {
+ line-height: $uv-radio-line-height;
+ }
+ /* #endif */
+ &--circle {
+ border-radius: $uv-radio-circle-border-radius;
+ }
+ &--square {
+ border-radius: $uv-radio-square-border-radius;
+ }
+ &--checked {
+ color: $uv-radio-checked-color;
+ background-color: $uv-radio-checked-background-color;
+ border-color: $uv-radio-checked-border-color;
+ }
+ &--disabled {
+ background-color: $uv-radio-disabled-background-color !important;
+ }
+ &--disabled--checked {
+ color: $uv-radio-disabled--checked-color !important;
+ }
+ }
+ &__label {
+ /* #ifndef APP-NVUE */
+ word-wrap: break-word;
+ /* #endif */
+ margin-left: $uv-radio-label-margin-left;
+ margin-right: $uv-radio-label-margin-right;
+ color: $uv-radio-label-color;
+ font-size: $uv-radio-label-font-size;
+ &--disabled {
+ color: $uv-radio-label-disabled-color;
+ }
+ }
+ &__label-wrap {
+ flex: 1;
+ padding-left: $uv-radio-label-wrap-padding-right;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-radio/package.json b/ledApp/uni_modules/uv-radio/package.json
new file mode 100644
index 0000000..e789246
--- /dev/null
+++ b/ledApp/uni_modules/uv-radio/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-radio",
+ "displayName": "uv-radio 单选框 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.13",
+ "description": "uv-radio 单选框用于有一个选择,用户只能选择其中一个的场景。",
+ "keywords": [
+ "uv-radio",
+ "uvui",
+ "uv-ui",
+ "radio",
+ "单选框"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-radio/readme.md b/ledApp/uni_modules/uv-radio/readme.md
new file mode 100644
index 0000000..c826898
--- /dev/null
+++ b/ledApp/uni_modules/uv-radio/readme.md
@@ -0,0 +1,19 @@
+## Radio 单选框
+
+> **组件名:uv-radio**
+
+单选框用于有一个选择,用户只能选择其中一个的场景。
+
+# <a href="https://www.uvui.cn/components/radio.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-rate/changelog.md b/ledApp/uni_modules/uv-rate/changelog.md
new file mode 100644
index 0000000..f1cbeea
--- /dev/null
+++ b/ledApp/uni_modules/uv-rate/changelog.md
@@ -0,0 +1,17 @@
+## 1.0.7(2023-08-24)
+1. 修复支付宝不能选半星的BUG
+## 1.0.6(2023-08-23)
+1. 修复支付宝报错的BUG
+## 1.0.5(2023-07-13)
+优化代码
+## 1.0.4(2023-07-13)
+1. 修复value/v-model更改不生效的BUG
+## 1.0.3(2023-06-05)
+1. 修复只读或禁止状态下设置value无效的问题
+## 1.0.2(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.1(2023-05-12)
+1. 修复vue3中双向绑定问题
+## 1.0.0(2023-05-10)
+uv-rate 评分
diff --git a/ledApp/uni_modules/uv-rate/components/uv-rate/props.js b/ledApp/uni_modules/uv-rate/components/uv-rate/props.js
new file mode 100644
index 0000000..6eac114
--- /dev/null
+++ b/ledApp/uni_modules/uv-rate/components/uv-rate/props.js
@@ -0,0 +1,73 @@
+export default {
+ props: {
+ value: {
+ type: [String, Number],
+ default: 0
+ },
+ modelValue: {
+ type: [String, Number],
+ default: 0
+ },
+ // 要显示的星星数量
+ count: {
+ type: [String, Number],
+ default: 5
+ },
+ // 是否不可选中
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否只读
+ readonly: {
+ type: Boolean,
+ default: false
+ },
+ // 星星的大小,单位px
+ size: {
+ type: [String, Number],
+ default: 18
+ },
+ // 未选中时的颜色
+ inactiveColor: {
+ type: String,
+ default: '#b2b2b2'
+ },
+ // 选中的颜色
+ activeColor: {
+ type: String,
+ default: '#FA3534'
+ },
+ // 星星之间的间距,单位px
+ gutter: {
+ type: [String, Number],
+ default: 4
+ },
+ // 最少能选择的星星个数
+ minCount: {
+ type: [String, Number],
+ default: 1
+ },
+ // 是否允许半星
+ allowHalf: {
+ type: Boolean,
+ default: false
+ },
+ // 选中时的图标(星星)
+ activeIcon: {
+ type: String,
+ default: 'star-fill'
+ },
+ // 未选中时的图标(星星)
+ inactiveIcon: {
+ type: String,
+ default: 'star'
+ },
+ // 是否可以通过滑动手势选择评分
+ touchable: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.rate
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-rate/components/uv-rate/uv-rate.vue b/ledApp/uni_modules/uv-rate/components/uv-rate/uv-rate.vue
new file mode 100644
index 0000000..48e7558
--- /dev/null
+++ b/ledApp/uni_modules/uv-rate/components/uv-rate/uv-rate.vue
@@ -0,0 +1,274 @@
+<template>
+ <view
+ class="uv-rate"
+ :id="elId"
+ ref="uv-rate"
+ :style="[$uv.addStyle(customStyle)]"
+ >
+ <view class="uv-rate__content"
+ @touchmove.stop="touchMove"
+ @touchend.stop="touchEnd">
+ <view class="uv-rate__content__item"
+ v-for="(item, index) in Number(count)"
+ :key="index"
+ :class="[elClass]">
+ <view class="uv-rate__content__item__icon-wrap"
+ ref="uv-rate__content__item__icon-wrap"
+ @tap.stop="clickHandler($event, index + 1)">
+ <uv-icon
+ :name="Math.floor(activeIndex) > index? activeIcon : inactiveIcon"
+ :color="disabled ? '#c8c9cc' : Math.floor(activeIndex) > index ? activeColor : inactiveColor"
+ :custom-style="{
+ 'padding-left': $uv.addUnit(gutter / 2),
+ 'padding-right': $uv.addUnit(gutter / 2)
+ }"
+ :size="size"
+ ></uv-icon>
+ </view>
+ <view v-if="allowHalf"
+ @tap.stop="clickHandler($event, index + 1)"
+ class="uv-rate__content__item__icon-wrap uv-rate__content__item__icon-wrap--half"
+ :style="[{ width: $uv.addUnit(rateWidth / 2)}]"
+ ref="uv-rate__content__item__icon-wrap">
+ <uv-icon
+ :name=" Math.ceil(activeIndex) > index ? activeIcon : inactiveIcon "
+ :color=" disabled ? '#c8c9cc' : Math.ceil(activeIndex) > index ? activeColor : inactiveColor "
+ :custom-style="{
+ 'padding-left': $uv.addUnit(gutter / 2),
+ 'padding-right': $uv.addUnit(gutter / 2)
+ }"
+ :size="size">
+ </uv-icon>
+ </view>
+ </view>
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const dom = weex.requireModule("dom");
+ // #endif
+ /**
+ * rate 评分
+ * @description 该组件一般用于满意度调查,星型评分的场景
+ * @tutorial https://www.uvui.cn/components/rate.html
+ * @property {String | Number} value 用于v-model双向绑定选中的星星数量 (默认 1 )
+ * @property {String | Number} count 最多可选的星星数量 (默认 5 )
+ * @property {Boolean} disabled 是否禁止用户操作 (默认 false )
+ * @property {Boolean} readonly 是否只读 (默认 false )
+ * @property {String | Number} size 星星的大小,单位px (默认 18 )
+ * @property {String} inactiveColor 未选中星星的颜色 (默认 '#b2b2b2' )
+ * @property {String} activeColor 选中的星星颜色 (默认 '#FA3534' )
+ * @property {String | Number} gutter 星星之间的距离 (默认 4 )
+ * @property {String | Number} minCount 最少选中星星的个数 (默认 1 )
+ * @property {Boolean} allowHalf 是否允许半星选择 (默认 false )
+ * @property {String} activeIcon 选中时的图标名,只能为uvui的内置图标 (默认 'star-fill' )
+ * @property {String} inactiveIcon 未选中时的图标名,只能为uvui的内置图标 (默认 'star' )
+ * @property {Boolean} touchable 是否可以通过滑动手势选择评分 (默认 'false' )
+ * @property {Object} customStyle 组件的样式,对象形式
+ * @event {Function} change 选中的星星发生变化时触发
+ * @example <uv-rate :count="count" :value="2"></uv-rate>
+ */
+ export default {
+ name: "uv-rate",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 生成一个唯一id,否则一个页面多个评分组件,会造成冲突
+ elId: '',
+ elClass: '',
+ rateBoxLeft: 0, // 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离
+ activeIndex: 0,
+ rateWidth: 0, // 每个星星的宽度
+ // 标识是否正在滑动,由于iOS事件上touch比click先触发,导致快速滑动结束后,接着触发click,导致事件混乱而出错
+ moving: false
+ }
+ },
+ watch: {
+ value(newVal){
+ this.activeIndex = newVal;
+ },
+ modelValue(newVal){
+ this.activeIndex = newVal;
+ }
+ },
+ created() {
+ this.activeIndex = Number(this.value || this.modelValue);
+ this.elId = this.$uv.guid();
+ this.elClass = this.$uv.guid();
+ },
+ mounted() {
+ this.init();
+ },
+ methods: {
+ init() {
+ this.$uv.sleep(200).then(() => {
+ this.getRateItemRect();
+ this.getRateIconWrapRect();
+ })
+ },
+ // 获取评分组件盒子的布局信息
+ async getRateItemRect() {
+ await this.$uv.sleep();
+ // uvui封装的获取节点的方法,详见文档
+ // #ifndef APP-NVUE
+ this.$uvGetRect("#" + this.elId).then((res) => {
+ this.rateBoxLeft = res.left;
+ });
+ // #endif
+ // #ifdef APP-NVUE
+ dom.getComponentRect(this.$refs["uv-rate"], (res) => {
+ this.rateBoxLeft = res.size.left;
+ });
+ // #endif
+ },
+ // 获取单个星星的尺寸
+ getRateIconWrapRect() {
+ // uvui封装的获取节点的方法,详见文档
+ // #ifndef APP-NVUE
+ this.$uvGetRect("." + this.elClass).then((res) => {
+ this.rateWidth = res.width;
+ });
+ // #endif
+ // #ifdef APP-NVUE
+ dom.getComponentRect(this.$refs["uv-rate__content__item__icon-wrap"][0],
+ (res) => {
+ this.rateWidth = res.size.width;
+ });
+ // #endif
+ },
+ // 手指滑动
+ touchMove(e) {
+ // 如果禁止通过手动滑动选择,返回
+ if (!this.touchable) {
+ return;
+ }
+ this.preventEvent(e);
+ const x = e.changedTouches && e.changedTouches[0].pageX || e.detail && e.detail.pageX;
+ this.getActiveIndex(x);
+ },
+ // 停止滑动
+ touchEnd(e) {
+ // 如果禁止通过手动滑动选择,返回
+ if (!this.touchable) {
+ return;
+ }
+ this.preventEvent(e);
+ const x = e.changedTouches && e.changedTouches[0].pageX || e.detail && e.detail.pageX;
+ this.getActiveIndex(x);
+ },
+ // 通过点击,直接选中
+ clickHandler(e, index) {
+ // ios上,moving状态取消事件触发
+ if (this.$uv.os() === "ios" && this.moving) {
+ return;
+ }
+ this.preventEvent(e);
+ let x = 0;
+ // 点击时,在nvue上,无法获得点击的坐标,所以无法实现点击半星选择
+ // #ifndef APP-NVUE
+ x = e.changedTouches && e.changedTouches[0].pageX || e.detail && e.detail.pageX;
+ // #endif
+ // #ifdef APP-NVUE
+ // nvue下,无法通过点击获得坐标信息,这里通过元素的位置尺寸值模拟坐标
+ x = index * this.rateWidth + this.rateBoxLeft;
+ // #endif
+ this.getActiveIndex(x, true);
+ },
+ // 发出事件
+ changeEvent() {
+ this.$emit("change", this.activeIndex);
+ this.$emit("input", this.activeIndex);
+ this.$emit("update:modelValue", this.activeIndex);
+ },
+ // 获取当前激活的评分图标
+ getActiveIndex(x, isClick = false) {
+ if (this.disabled || this.readonly) {
+ return;
+ }
+ // 判断当前操作的点的x坐标值,是否在允许的边界范围内
+ const allRateWidth = this.rateWidth * this.count + this.rateBoxLeft;
+ // 如果小于第一个图标的左边界,设置为最小值,如果大于所有图标的宽度,则设置为最大值
+ x = this.$uv.range(this.rateBoxLeft, allRateWidth, x) - this.rateBoxLeft
+ // 滑动点相对于评分盒子左边的距离
+ const distance = x;
+ // 滑动的距离,相当于多少颗星星
+ let index;
+ // 判断是否允许半星
+ if (this.allowHalf) {
+ index = Math.floor(distance / this.rateWidth);
+ // 取余,判断小数的区间范围
+ const decimal = distance % this.rateWidth;
+ if (decimal <= this.rateWidth / 2 && decimal > 0) {
+ index += 0.5;
+ } else if (decimal > this.rateWidth / 2) {
+ index++;
+ }
+ } else {
+ index = Math.floor(distance / this.rateWidth);
+ // 取余,判断小数的区间范围
+ const decimal = distance % this.rateWidth;
+ // 非半星时,只有超过了图标的一半距离,才认为是选择了这颗星
+ if (isClick) {
+ if (decimal > 0) index++;
+ } else {
+ if (decimal > this.rateWidth / 2) index++;
+ }
+ }
+ this.activeIndex = Math.min(index, this.count);
+ // 对最少颗星星的限制
+ if (this.activeIndex < this.minCount) {
+ this.activeIndex = this.minCount;
+ }
+ this.changeEvent();
+ // 设置延时为了让click事件在touchmove之前触发
+ setTimeout(() => {
+ this.moving = true;
+ }, 10);
+ // 一定时间后,取消标识为移动中状态,是为了让click事件无效
+ setTimeout(() => {
+ this.moving = false;
+ }, 10);
+ }
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-rate-margin: 0 !default;
+ $uv-rate-padding: 0 !default;
+ $uv-rate-item-icon-wrap-half-top: 0 !default;
+ $uv-rate-item-icon-wrap-half-left: 0 !default;
+ .uv-rate {
+ @include flex;
+ align-items: center;
+ margin: $uv-rate-margin;
+ padding: $uv-rate-padding;
+ /* #ifndef APP-NVUE */
+ touch-action: none;
+ /* #endif */
+ &__content {
+ @include flex;
+ &__item {
+ position: relative;
+ &__icon-wrap {
+ &--half {
+ position: absolute;
+ overflow: hidden;
+ top: $uv-rate-item-icon-wrap-half-top;
+ left: $uv-rate-item-icon-wrap-half-left;
+ }
+ }
+ }
+ }
+ }
+ .uv-icon {
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ /* #endif */
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-rate/package.json b/ledApp/uni_modules/uv-rate/package.json
new file mode 100644
index 0000000..ed204d8
--- /dev/null
+++ b/ledApp/uni_modules/uv-rate/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-rate",
+ "displayName": "uv-rate 评分 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.7",
+ "description": "该组件为星型评分,可用于满意度调查等场景...",
+ "keywords": [
+ "uv-rate",
+ "uvui",
+ "uv-ui",
+ "rate",
+ "评分"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-rate/readme.md b/ledApp/uni_modules/uv-rate/readme.md
new file mode 100644
index 0000000..ec6a034
--- /dev/null
+++ b/ledApp/uni_modules/uv-rate/readme.md
@@ -0,0 +1,19 @@
+## Rate 评分
+
+> **组件名:uv-rate**
+
+该组件为星型评分,可用于满意度调查等场景。
+
+# <a href="https://www.uvui.cn/components/rate.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-read-more/changelog.md b/ledApp/uni_modules/uv-read-more/changelog.md
new file mode 100644
index 0000000..890f974
--- /dev/null
+++ b/ledApp/uni_modules/uv-read-more/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-09-13)
+1. 修复全局设置rpx,导致展开高度不对的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-read-more 展开阅读更多
diff --git a/ledApp/uni_modules/uv-read-more/components/uv-read-more/props.js b/ledApp/uni_modules/uv-read-more/components/uv-read-more/props.js
new file mode 100644
index 0000000..c691a01
--- /dev/null
+++ b/ledApp/uni_modules/uv-read-more/components/uv-read-more/props.js
@@ -0,0 +1,62 @@
+export default {
+ props: {
+ // 默认的显示占位高度
+ showHeight: {
+ type: [String, Number],
+ default: 400
+ },
+ // 展开后是否显示"收起"按钮
+ toggle: {
+ type: Boolean,
+ default: false
+ },
+ // 关闭时的提示文字
+ closeText: {
+ type: String,
+ default: '展开阅读全文'
+ },
+ // 展开时的提示文字
+ openText: {
+ type: String,
+ default: '收起'
+ },
+ // 提示的文字颜色
+ color: {
+ type: String,
+ default: '#2979ff'
+ },
+ // 提示文字的大小
+ fontSize: {
+ type: [String, Number],
+ default: 14
+ },
+ // 是否显示阴影
+ // 此参数不能写在props/readMore.js中进行默认配置,因为使用了条件编译,在外部js中
+ // uni无法准确识别当前是否处于nvue还是非nvue下
+ shadowStyle: {
+ type: Object,
+ default: () => ({
+ // #ifndef APP-NVUE
+ backgroundImage: 'linear-gradient(-180deg, rgba(255, 255, 255, 0) 0%, #fff 80%)',
+ // #endif
+ // #ifdef APP-NVUE
+ // nvue上不支持设置复杂的backgroundImage属性
+ backgroundImage: 'linear-gradient(to top, #fff, rgba(255, 255, 255, 0.5))',
+ // #endif
+ paddingTop: '100px',
+ marginTop: '-100px'
+ })
+ },
+ // 段落首行缩进的字符个数
+ textIndent: {
+ type: String,
+ default: '2em'
+ },
+ // open和close事件时,将此参数返回在回调参数中
+ name: {
+ type: [String, Number],
+ default: ''
+ },
+ ...uni.$uv?.props?.readMore
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-read-more/components/uv-read-more/uv-read-more.vue b/ledApp/uni_modules/uv-read-more/components/uv-read-more/uv-read-more.vue
new file mode 100644
index 0000000..9ee47db
--- /dev/null
+++ b/ledApp/uni_modules/uv-read-more/components/uv-read-more/uv-read-more.vue
@@ -0,0 +1,162 @@
+<template>
+ <view class="uv-read-more">
+ <view
+ class="uv-read-more__content"
+ :style="{
+ height: isLongContent && status === 'close' ? $uv.addUnit(showHeight) : $uv.addUnit(contentHeight,'px'),
+ textIndent: textIndent
+ }"
+ >
+ <view
+ class="uv-read-more__content__inner"
+ ref="uv-read-more__content__inner"
+ :class="[elId]"
+ >
+ <slot></slot>
+ </view>
+ </view>
+ <view
+ class="uv-read-more__toggle"
+ :style="[innerShadowStyle]"
+ v-if="isLongContent"
+ >
+ <slot name="toggle">
+ <view
+ class="uv-read-more__toggle__text"
+ @tap="toggleReadMore"
+ >
+ <uv-text
+ :text="status === 'close' ? closeText : openText"
+ :color="color"
+ :size="fontSize"
+ :lineHeight="fontSize"
+ margin="0 5px 0 0"
+ ></uv-text>
+ <view class="uv-read-more__toggle__icon">
+ <uv-icon
+ :color="color"
+ :size="fontSize + 2"
+ :name="status === 'close' ? 'arrow-down' : 'arrow-up'"
+ ></uv-icon>
+ </view>
+ </view>
+ </slot>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ import props from './props.js';
+ /**
+ * readMore 阅读更多
+ * @description 该组件一般用于内容较长,预先收起一部分,点击展开全部内容的场景。
+ * @tutorial https://www.uvui.cn/components/readMore.html
+ * @property {String | Number} showHeight 内容超出此高度才会显示展开全文按钮,单位px(默认 400 )
+ * @property {Boolean} toggle 展开后是否显示收起按钮(默认 false )
+ * @property {String} closeText 关闭时的提示文字(默认 '展开阅读全文' )
+ * @property {String} openText 展开时的提示文字(默认 '收起' )
+ * @property {String} color 提示文字的颜色(默认 '#2979ff' )
+ * @property {String | Number} fontSize 提示文字的大小,单位px (默认 14 )
+ * @property {Object} shadowStyle 显示阴影的样式
+ * @property {String} textIndent 段落首行缩进的字符个数 (默认 '2em' )
+ * @property {String | Number} name 用于在 open 和 close 事件中当作回调参数返回
+ * @event {Function} open 内容被展开时触发
+ * @event {Function} close 内容被收起时触发
+ * @example <uv-read-more><rich-text :nodes="content"></rich-text></uv-read-more>
+ */
+ export default {
+ name: 'uv-read-more',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ isLongContent: false, // 是否需要隐藏一部分内容
+ status: 'close', // 当前隐藏与显示的状态,close-收起状态,open-展开状态
+ elId: '', // 生成唯一class
+ contentHeight: 100, // 内容高度
+ }
+ },
+ computed: {
+ // 展开后无需阴影,收起时才需要阴影样式
+ innerShadowStyle() {
+ if (this.status === 'open') return {}
+ else return this.shadowStyle
+ }
+ },
+ created() {
+ this.elId = this.$uv.guid();
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ async init() {
+ this.getContentHeight().then(height => {
+ this.contentHeight = height
+ // 判断高度,如果真实内容高度大于占位高度,则显示收起与展开的控制按钮
+ if (height > this.$uv.getPx(this.showHeight)) {
+ this.isLongContent = true
+ this.status = 'close'
+ }
+ })
+ },
+ // 获取内容的高度
+ async getContentHeight() {
+ // 延时一定时间再获取节点
+ await this.$uv.sleep(30)
+ return new Promise(resolve => {
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.' + this.elId).then(res => {
+ resolve(res.height)
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ const ref = this.$refs['uv-read-more__content__inner']
+ dom.getComponentRect(ref, (res) => {
+ resolve(res.size.height)
+ })
+ // #endif
+ })
+ },
+ // 展开或者收起
+ toggleReadMore() {
+ this.status = this.status === 'close' ? 'open' : 'close'
+ // 如果toggle为false,隐藏"收起"部分的内容
+ if (this.toggle == false) this.isLongContent = false
+ // 发出打开或者收齐的事件
+ this.$emit(this.status, this.name)
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+.uv-read-more {
+
+ &__content {
+ overflow: hidden;
+ color: $uv-content-color;
+ font-size: 15px;
+ text-align: left;
+ }
+
+ &__toggle {
+ @include flex;
+ justify-content: center;
+
+ &__text {
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ margin-top: 5px;
+ }
+ }
+}
+</style>
diff --git a/ledApp/uni_modules/uv-read-more/package.json b/ledApp/uni_modules/uv-read-more/package.json
new file mode 100644
index 0000000..77df674
--- /dev/null
+++ b/ledApp/uni_modules/uv-read-more/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-read-more",
+ "displayName": "uv-read-more 展开阅读更多 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "uv-read-more 该组件一般用于内容较长,预先收起一部分,点击展开全部内容的场景。",
+ "keywords": [
+ "uv-read-more",
+ "uvui",
+ "uv-ui",
+ "read",
+ "展开阅读更多"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-text"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-read-more/readme.md b/ledApp/uni_modules/uv-read-more/readme.md
new file mode 100644
index 0000000..79e2eae
--- /dev/null
+++ b/ledApp/uni_modules/uv-read-more/readme.md
@@ -0,0 +1,11 @@
+## ReadMore 展开阅读更多
+
+> **组件名:uv-read-more**
+
+该组件一般用于内容较长,预先收起一部分,点击展开全部内容的场景。
+
+### <a href="https://www.uvui.cn/components/readMore.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-row/changelog.md b/ledApp/uni_modules/uv-row/changelog.md
new file mode 100644
index 0000000..3953b4b
--- /dev/null
+++ b/ledApp/uni_modules/uv-row/changelog.md
@@ -0,0 +1,5 @@
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-row layout布局
diff --git a/ledApp/uni_modules/uv-row/components/uv-col/props.js b/ledApp/uni_modules/uv-row/components/uv-col/props.js
new file mode 100644
index 0000000..bd9c7e0
--- /dev/null
+++ b/ledApp/uni_modules/uv-row/components/uv-col/props.js
@@ -0,0 +1,30 @@
+export default {
+ props: {
+ // 占父容器宽度的多少等分,总分为12份
+ span: {
+ type: [String, Number],
+ default: 12
+ },
+ // 指定栅格左侧的间隔数(总12栏)
+ offset: {
+ type: [String, Number],
+ default: 0
+ },
+ // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
+ justify: {
+ type: String,
+ default: 'start'
+ },
+ // 垂直对齐方式,可选值为top、center、bottom、stretch
+ align: {
+ type: String,
+ default: 'stretch'
+ },
+ // 文字对齐方式
+ textAlign: {
+ type: String,
+ default: 'left'
+ },
+ ...uni.$uv?.props?.col
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-row/components/uv-col/uv-col.vue b/ledApp/uni_modules/uv-row/components/uv-col/uv-col.vue
new file mode 100644
index 0000000..9198725
--- /dev/null
+++ b/ledApp/uni_modules/uv-row/components/uv-col/uv-col.vue
@@ -0,0 +1,165 @@
+<template>
+ <view
+ class="uv-col"
+ ref="uv-col"
+ :class="[
+ 'uv-col-' + span
+ ]"
+ :style="[colStyle]"
+ @tap="clickHandler"
+ >
+ <slot></slot>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * CodeInput 栅格系统的列
+ * @description 该组件一般用于Layout 布局 通过基础的 12 分栏,迅速简便地创建布局
+ * @tutorial https://www.uvui.cn/components/Layout.html
+ * @property {String | Number} span 栅格占据的列数,总12等份 (默认 12 )
+ * @property {String | Number} offset 分栏左边偏移,计算方式与span相同 (默认 0 )
+ * @property {String} justify 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) (默认 'start' )
+ * @property {String} align 垂直对齐方式,可选值为top、center、bottom、stretch (默认 'stretch' )
+ * @property {String} textAlign 文字水平对齐方式 (默认 'left' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @event {Function} click col被点击,会阻止事件冒泡到row
+ * @example <uv-col span="3" offset="3" > <view class="demo-layout bg-purple"></view> </uv-col>
+ */
+ export default {
+ name: 'uv-col',
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ width: 0,
+ parentData: {
+ gutter: 0
+ },
+ gridNum: 12
+ }
+ },
+ computed: {
+ uJustify() {
+ if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify
+ else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify
+ else return this.justify
+ },
+ uAlignItem() {
+ if (this.align == 'top') return 'flex-start'
+ if (this.align == 'bottom') return 'flex-end'
+ else return this.align
+ },
+ colStyle() {
+ const style = {
+ // 这里写成"padding: 0 10px"的形式是因为nvue的需要
+ paddingLeft: this.$uv.addUnit(this.$uv.getPx(this.parentData.gutter)/2),
+ paddingRight: this.$uv.addUnit(this.$uv.getPx(this.parentData.gutter)/2),
+ alignItems: this.uAlignItem,
+ justifyContent: this.uJustify,
+ textAlign: this.textAlign,
+ // #ifndef APP-NVUE
+ // 在非nvue上,使用百分比形式
+ flex: `0 0 ${100 / this.gridNum * this.span}%`,
+ marginLeft: 100 / 12 * this.offset + '%',
+ // #endif
+ // #ifdef APP-NVUE
+ // 在nvue上,由于无法使用百分比单位,这里需要获取父组件的宽度,再计算得出该有对应的百分比尺寸
+ width: this.$uv.addUnit(Math.floor(this.width / this.gridNum * Number(this.span))),
+ marginLeft: this.$uv.addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))),
+ // #endif
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ async init() {
+ // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+ this.updateParentData()
+ this.width = await this.parent.getComponentWidth()
+ },
+ updateParentData() {
+ this.getParentData('uv-row')
+ },
+ clickHandler(e) {
+ this.$emit('click');
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-col {
+ padding: 0;
+ /* #ifndef APP-NVUE */
+ box-sizing:border-box;
+ /* #endif */
+ /* #ifdef MP */
+ display: block;
+ /* #endif */
+ }
+
+ // nvue下百分比无效
+ /* #ifndef APP-NVUE */
+ .uv-col-0 {
+ width: 0;
+ }
+
+ .uv-col-1 {
+ width: calc(100%/12);
+ }
+
+ .uv-col-2 {
+ width: calc(100%/12 * 2);
+ }
+
+ .uv-col-3 {
+ width: calc(100%/12 * 3);
+ }
+
+ .uv-col-4 {
+ width: calc(100%/12 * 4);
+ }
+
+ .uv-col-5 {
+ width: calc(100%/12 * 5);
+ }
+
+ .uv-col-6 {
+ width: calc(100%/12 * 6);
+ }
+
+ .uv-col-7 {
+ width: calc(100%/12 * 7);
+ }
+
+ .uv-col-8 {
+ width: calc(100%/12 * 8);
+ }
+
+ .uv-col-9 {
+ width: calc(100%/12 * 9);
+ }
+
+ .uv-col-10 {
+ width: calc(100%/12 * 10);
+ }
+
+ .uv-col-11 {
+ width: calc(100%/12 * 11);
+ }
+
+ .uv-col-12 {
+ width: calc(100%/12 * 12);
+ }
+
+ /* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-row/components/uv-row/props.js b/ledApp/uni_modules/uv-row/components/uv-row/props.js
new file mode 100644
index 0000000..590570d
--- /dev/null
+++ b/ledApp/uni_modules/uv-row/components/uv-row/props.js
@@ -0,0 +1,20 @@
+export default {
+ props: {
+ // 给col添加间距,左右边距各占一半
+ gutter: {
+ type: [String, Number],
+ default: 0
+ },
+ // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
+ justify: {
+ type: String,
+ default: 'start'
+ },
+ // 垂直对齐方式,可选值为top、center、bottom
+ align: {
+ type: String,
+ default: 'center'
+ },
+ ...uni.$uv?.props?.row
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-row/components/uv-row/uv-row.vue b/ledApp/uni_modules/uv-row/components/uv-row/uv-row.vue
new file mode 100644
index 0000000..78ebbe9
--- /dev/null
+++ b/ledApp/uni_modules/uv-row/components/uv-row/uv-row.vue
@@ -0,0 +1,95 @@
+<template>
+ <view
+ class="uv-row"
+ ref="uv-row"
+ :style="[rowStyle]"
+ @tap="clickHandler"
+ >
+ <slot />
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ import props from './props.js';
+ /**
+ * Row 栅格系统中的行
+ * @description 通过基础的 12 分栏,迅速简便地创建布局
+ * @tutorial https://www.uvui.cn/components/layout.html
+ * @property {String | Number} gutter 栅格间隔,左右各为此值的一半,单位px (默认 0 )
+ * @property {String} justify 水平排列方式(微信小程序暂不支持) 可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) (默认 'start' )
+ * @property {String} align 垂直排列方式 (默认 'center' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} click row被点击
+ * @example <uv-row justify="space-between" customStyle="margin-bottom: 10px"></uv-row>
+ */
+ export default {
+ name: "uv-row",
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+
+ }
+ },
+ computed: {
+ uJustify() {
+ if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify
+ else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify
+ else return this.justify
+ },
+ uAlignItem() {
+ if (this.align == 'top') return 'flex-start'
+ if (this.align == 'bottom') return 'flex-end'
+ else return this.align
+ },
+ rowStyle() {
+ const style = {
+ alignItems: this.uAlignItem,
+ justifyContent: this.uJustify
+ }
+ // 通过给uv-row左右两边的负外边距,消除uv-col在有gutter时,第一个和最后一个元素的左内边距和右内边距造成的影响
+ if(this.gutter) {
+ style.marginLeft = this.$uv.addUnit(-Number(this.gutter)/2)
+ style.marginRight = this.$uv.addUnit(-Number(this.gutter)/2)
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ methods: {
+ clickHandler(e) {
+ this.$emit('click')
+ },
+ async getComponentWidth() {
+ // 延时一定时间,以确保节点渲染完成
+ await this.$uv.sleep()
+ return new Promise(resolve => {
+ // uvui封装的获取节点的方法,详见文档
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-row').then(res => {
+ resolve(res.width)
+ })
+ // #endif
+ // #ifdef APP-NVUE
+ // nvue的dom模块用于获取节点
+ dom.getComponentRect(this.$refs['uv-row'], (res) => {
+ resolve(res.size.width)
+ })
+ // #endif
+ })
+ },
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-row {
+ @include flex;
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-row/package.json b/ledApp/uni_modules/uv-row/package.json
new file mode 100644
index 0000000..4bba4de
--- /dev/null
+++ b/ledApp/uni_modules/uv-row/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-row",
+ "displayName": "uv-row layout布局 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.1",
+ "description": "uv-row 通过基础的 12 分栏,迅速简便地创建布局",
+ "keywords": [
+ "uv-row",
+ "uvui",
+ "uv-ui",
+ "layout",
+ "布局"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-row/readme.md b/ledApp/uni_modules/uv-row/readme.md
new file mode 100644
index 0000000..830b7bf
--- /dev/null
+++ b/ledApp/uni_modules/uv-row/readme.md
@@ -0,0 +1,11 @@
+## Layout 布局
+
+> **组件名:uv-row**
+
+通过基础的 12 分栏,迅速简便地创建布局
+
+### <a href="https://www.uvui.cn/components/layout.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-safe-bottom/changelog.md b/ledApp/uni_modules/uv-safe-bottom/changelog.md
new file mode 100644
index 0000000..dbcd565
--- /dev/null
+++ b/ledApp/uni_modules/uv-safe-bottom/changelog.md
@@ -0,0 +1,11 @@
+## 1.0.4(2023-09-14)
+1. 飞书小程序支持
+## 1.0.3(2023-08-14)
+1. 修复百度报错的BUG
+## 1.0.2(2023-07-02)
+uv-safe-bottom 修复,在百度程序,抖音小程序不生效的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-safe-bottom 底部安全区组件
diff --git a/ledApp/uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue b/ledApp/uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue
new file mode 100644
index 0000000..b5439a8
--- /dev/null
+++ b/ledApp/uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue
@@ -0,0 +1,67 @@
+<template>
+ <view
+ class="uv-safe-bottom"
+ :style="[style]"
+ :class="[!isNvue && 'uv-safe-area-inset-bottom']"
+ >
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ /**
+ * SafeBottom 底部安全区
+ * @description 这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
+ * @tutorial https://www.uvui.cn/components/safeAreaInset.html
+ * @property {type} prop_name
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function()}
+ * @example <uv-status-bar></uv-status-bar>
+ */
+ export default {
+ name: "uv-safe-bottom",
+ mixins: [mpMixin, mixin],
+ data() {
+ return {
+ safeAreaBottomHeight: 0,
+ isNvue: false,
+ };
+ },
+ computed: {
+ style() {
+ const style = {};
+ // #ifdef APP-NVUE || MP-TOUTIAO || MP-LARK
+ // nvue下,高度使用js计算填充
+ style.height = this.$uv.addUnit(this.$uv.sys()?.safeAreaInsets?.bottom, 'px');
+ // #endif
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+ },
+ },
+ mounted() {
+ // #ifdef APP-NVUE
+ // 标识为是否nvue
+ this.isNvue = true;
+ // #endif
+ },
+ };
+</script>
+
+<style lang="scss" scoped>
+ .uv-safe-bottom {
+ /* #ifndef APP-NVUE */
+ width: 100%;
+ /* #endif */
+ }
+ /* #ifndef APP-NVUE */
+ // 历遍生成4个方向的底部安全区
+ @each $d in top, right, bottom, left {
+ .uv-safe-area-inset-#{$d} {
+ padding-#{$d}: 0;
+ padding-#{$d}: constant(safe-area-inset-#{$d});
+ padding-#{$d}: env(safe-area-inset-#{$d});
+ }
+ }
+ /* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-safe-bottom/package.json b/ledApp/uni_modules/uv-safe-bottom/package.json
new file mode 100644
index 0000000..5bfaab0
--- /dev/null
+++ b/ledApp/uni_modules/uv-safe-bottom/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-safe-bottom",
+ "displayName": "uv-safe-bottom 底部安全区 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.4",
+ "description": "这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。",
+ "keywords": [
+ "uv-safe-bottom",
+ "uvui",
+ "uv-ui",
+ "bottom",
+ "底部安全区"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-safe-bottom/readme.md b/ledApp/uni_modules/uv-safe-bottom/readme.md
new file mode 100644
index 0000000..8c78882
--- /dev/null
+++ b/ledApp/uni_modules/uv-safe-bottom/readme.md
@@ -0,0 +1,11 @@
+## SafeBottom 底部安全区
+
+> **组件名:uv-safe-bottom**
+
+这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
+
+### <a href="https://www.uvui.cn/guide/safeAreaInset.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-scroll-list/changelog.md b/ledApp/uni_modules/uv-scroll-list/changelog.md
new file mode 100644
index 0000000..2725c3f
--- /dev/null
+++ b/ledApp/uni_modules/uv-scroll-list/changelog.md
@@ -0,0 +1,12 @@
+## 1.0.5(2023-11-15)
+1. 修复微信报错的BUG
+## 1.0.4(2023-11-14)
+1. 兼容全局设置rpx
+## 1.0.3(2023-08-19)
+1. 修复报错导致不能移动指示器的BUG
+## 1.0.2(2023-08-08)
+1. 修复vue2编译报错的BUG
+## 1.0.1(2023-07-21)
+1. 优化
+## 1.0.0(2023-07-21)
+1. 新增uv-scroll-liast 横向滚动表组件
diff --git a/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/nvue.js b/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/nvue.js
new file mode 100644
index 0000000..6bfcfe7
--- /dev/null
+++ b/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/nvue.js
@@ -0,0 +1,29 @@
+// 引入bindingx,此库类似于微信小程序wxs,目的是让js运行在视图层,减少视图层和逻辑层的通信折损
+const BindingX = uni.requireNativePlugin('bindingx')
+export default {
+ methods: {
+ // 此处不写注释,请自行体会
+ nvueScrollHandler(e) {
+ if (this.indicator) {
+ const anchor = this.$refs['uv-scroll-list__scroll-view'].ref
+ const element = this.$refs['uv-scroll-list__indicator__line__bar'].ref
+ const scrollLeft = e.contentOffset.x
+ const contentSize = e.contentSize.width
+ const { scrollWidth } = this
+ const barAllMoveWidth = this.indicatorWidth - this.indicatorBarWidth
+ // 在安卓和iOS上,需要除的倍数不一样,iOS需要除以2
+ const actionNum = this.$uv.os() === 'ios' ? 2 : 1
+ const expression = `(x / ${actionNum}) / ${contentSize - scrollWidth} * ${this.$uv.getPx(this.$uv.addUnit(barAllMoveWidth))}`
+ BindingX.bind({
+ anchor,
+ eventType: 'scroll',
+ props: [{
+ element,
+ property: 'transform.translateX',
+ expression
+ }]
+ })
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/props.js b/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/props.js
new file mode 100644
index 0000000..b38637e
--- /dev/null
+++ b/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/props.js
@@ -0,0 +1,35 @@
+export default {
+ props: {
+ // 指示器的整体宽度
+ indicatorWidth: {
+ type: [String, Number],
+ default: 50
+ },
+ // 滑块的宽度
+ indicatorBarWidth: {
+ type: [String, Number],
+ default: 20
+ },
+ // 是否显示面板指示器
+ indicator: {
+ type: Boolean,
+ default: true
+ },
+ // 指示器非激活颜色
+ indicatorColor: {
+ type: String,
+ default: '#f2f2f2'
+ },
+ // 指示器的激活颜色
+ indicatorActiveColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 指示器样式,可通过bottom,left,right进行定位
+ indicatorStyle: {
+ type: [String, Object],
+ default: ''
+ },
+ ...uni.$uv?.props?.scrollList
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/scrollWxs.wxs b/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/scrollWxs.wxs
new file mode 100644
index 0000000..7aa7f1e
--- /dev/null
+++ b/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/scrollWxs.wxs
@@ -0,0 +1,51 @@
+function scroll(event, ownerInstance) {
+ // detail中含有scroll-view的信息,比如scroll-view的实际宽度,当前时间点scroll-view的移动距离等
+ var detail = event.detail
+ var scrollWidth = detail.scrollWidth
+ var scrollLeft = detail.scrollLeft
+ // 获取当前组件的dataset,说白了就是祸国殃民的腾xun搞出来的垃ji
+ var dataset = event.currentTarget.dataset
+ // 此为scroll-view外部包裹元素的宽度
+ // 某些HX版本(3.1.18),发现view元素中大写的data-scrollWidth,在wxs中,变成了全部小写,所以这里需要特别处理
+ var scrollComponentWidth = dataset.scrollWidth || dataset.scrollwidth || 0
+ // 指示器和滑块的宽度
+ var indicatorWidth = dataset.indicatorWidth || dataset.indicatorwidth || 0
+ var barWidth = dataset.barWidth || dataset.barwidth || 0
+ // 此处的计算理由为:scroll-view的滚动距离与目标滚动距离(scroll-view的实际宽度减去包裹元素的宽度)之比,等于滑块当前移动距离与总需
+ // 滑动距离(指示器的总宽度减去滑块宽度)的比值
+ var x = scrollLeft / (scrollWidth - scrollComponentWidth) * (indicatorWidth - barWidth)
+ setBarStyle(ownerInstance, x, dataset.unit)
+}
+
+// 由于webview的无能,无法保证scroll-view在滑动过程中,一直触发scroll事件,会导致
+// 无法监听到某些滚动值,当在首尾临界值无法监听到时,这是致命的,因为错失这些值会导致滑块无法回到起点和终点
+// 所以这里需要对临界值做监听并处理
+function scrolltolower(event, ownerInstance) {
+ ownerInstance.callMethod('scrollEvent', 'right')
+ // 获取当前组件的dataset
+ var dataset = event.currentTarget.dataset
+ // 指示器和滑块的宽度
+ var indicatorWidth = dataset.indicatorWidth || dataset.indicatorwidth || 0
+ var barWidth = dataset.barWidth || dataset.barwidth || 0
+ // scroll-view滚动到右边终点时,将滑块也设置为到右边的终点,它所需移动的距离为:指示器宽度 - 滑块宽度
+ setBarStyle(ownerInstance, indicatorWidth - barWidth, dataset.unit)
+}
+
+function scrolltoupper(event, ownerInstance) {
+ var dataset = event.currentTarget.dataset
+ ownerInstance.callMethod('scrollEvent', 'left')
+ // 滚动到左边时,将滑块设置为0的偏移距离,回到起点
+ setBarStyle(ownerInstance, 0, dataset.unit)
+}
+
+function setBarStyle(ownerInstance, x, unit) {
+ ownerInstance.selectComponent('.uv-scroll-list__indicator__line__bar') && ownerInstance.selectComponent('.uv-scroll-list__indicator__line__bar').setStyle({
+ transform: unit == 'rpx' ? 'translateX(' + x + 'rpx)' : 'translateX(' + x + 'px)'
+ })
+}
+
+module.exports = {
+ scroll: scroll,
+ scrolltolower: scrolltolower,
+ scrolltoupper: scrolltoupper
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/uv-scroll-list.vue b/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/uv-scroll-list.vue
new file mode 100644
index 0000000..1e051c5
--- /dev/null
+++ b/ledApp/uni_modules/uv-scroll-list/components/uv-scroll-list/uv-scroll-list.vue
@@ -0,0 +1,218 @@
+<template>
+ <view
+ class="uv-scroll-list"
+ ref="uv-scroll-list"
+ >
+ <!-- #ifdef APP-NVUE -->
+ <!-- nvue使用bindingX实现,以得到更好的性能 -->
+ <scroller
+ class="uv-scroll-list__scroll-view"
+ ref="uv-scroll-list__scroll-view"
+ scroll-direction="horizontal"
+ :show-scrollbar="false"
+ :offset-accuracy="1"
+ @scroll="nvueScrollHandler"
+ >
+ <view class="uv-scroll-list__scroll-view__content">
+ <slot />
+ </view>
+ </scroller>
+ <!-- #endif -->
+ <!-- #ifndef APP-NVUE -->
+ <!-- #ifdef MP-WEIXIN || APP-VUE || H5 || MP-QQ -->
+ <!-- 以上平台,支持wxs -->
+ <scroll-view
+ class="uv-scroll-list__scroll-view"
+ scroll-x
+ @scroll="wxs.scroll"
+ @scrolltoupper="wxs.scrolltoupper"
+ @scrolltolower="wxs.scrolltolower"
+ :data-scrollWidth="scrollWidth"
+ :data-barWidth="$uv.getPx(indicatorBarWidth)"
+ :data-indicatorWidth="$uv.getPx(indicatorWidth)"
+ :show-scrollbar="false"
+ :upper-threshold="0"
+ :lower-threshold="0"
+ :data-unit="$uv.unit"
+ >
+ <!-- #endif -->
+ <!-- #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ -->
+ <!-- 非以上平台,只能使用普通js实现 -->
+ <scroll-view
+ class="uv-scroll-list__scroll-view"
+ scroll-x
+ @scroll="scrollHandler"
+ @scrolltoupper="scrolltoupperHandler"
+ @scrolltolower="scrolltolowerHandler"
+ :show-scrollbar="false"
+ :upper-threshold="0"
+ :lower-threshold="0"
+ >
+ <!-- #endif -->
+ <view class="uv-scroll-list__scroll-view__content">
+ <slot />
+ </view>
+ </scroll-view>
+ <!-- #endif -->
+ <view
+ class="uv-scroll-list__indicator"
+ v-if="indicator"
+ :style="[$uv.addStyle(indicatorStyle)]"
+ >
+ <view
+ class="uv-scroll-list__indicator__line"
+ :style="[lineStyle]"
+ >
+ <view
+ class="uv-scroll-list__indicator__line__bar"
+ :style="[barStyle]"
+ ref="uv-scroll-list__indicator__line__bar"
+ ></view>
+ </view>
+ </view>
+ <!-- 避免报错 -->
+ <!-- #ifdef H5 -->
+ <view v-else class="uv-scroll-list__indicator__line__bar"></view>
+ <!-- #endif -->
+ </view>
+</template>
+<script src="./scrollWxs.wxs" module="wxs" lang="wxs"></script>
+<script>
+ /**
+ * scrollList 横向滚动列表
+ * @description 该组件一般用于同时展示多个商品、分类的场景,也可以完成左右滑动的列表。
+ * @tutorial https://www.uviewui.com/components/scrollList.html
+ * @property {String | Number} indicatorWidth 指示器的整体宽度 (默认 50 )
+ * @property {String | Number} indicatorBarWidth 滑块的宽度 (默认 20 )
+ * @property {Boolean} indicator 是否显示面板指示器 (默认 true )
+ * @property {String} indicatorColor 指示器非激活颜色 (默认 '#f2f2f2' )
+ * @property {String} indicatorActiveColor 指示器的激活颜色 (默认 '#3c9cff' )
+ * @property {String | Object} indicatorStyle 指示器样式,可通过bottom,left,right进行定位
+ * @event {Function} left 滑动到左边时触发
+ * @event {Function} right 滑动到右边时触发
+ * @example
+ */
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ import nvueMixin from "./nvue.js"
+ // #endif
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ export default {
+ name: 'uv-scroll-list',
+ // #ifndef APP-NVUE
+ mixins: [mpMixin, mixin, props],
+ // #endif
+ // #ifdef APP-NVUE
+ mixins: [mpMixin, mixin, nvueMixin, props],
+ // #endif
+ data() {
+ return {
+ scrollInfo: {
+ scrollLeft: 0,
+ scrollWidth: 0
+ },
+ scrollWidth: 0
+ }
+ },
+ computed: {
+ // 指示器为线型的样式
+ barStyle() {
+ const style = {}
+ // #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ
+ // 此为普通js方案,只有在非nvue和不支持wxs方案的端才使用、
+ // 此处的计算理由为:scroll-view的滚动距离与目标滚动距离(scroll-view的实际宽度减去包裹元素的宽度)之比,等于滑块当前移动距离与总需
+ // 滑动距离(指示器的总宽度减去滑块宽度)的比值
+ const scrollLeft = this.scrollInfo.scrollLeft,
+ scrollWidth = this.scrollInfo.scrollWidth,
+ barAllMoveWidth = this.indicatorWidth - this.indicatorBarWidth
+ const x = scrollLeft / (scrollWidth - this.scrollWidth) * this.$uv.getPx(this.$uv.addUnit(barAllMoveWidth))
+ style.transform = `translateX(${ x }px)`
+ // #endif
+ // 设置滑块的宽度和背景色,是每个平台都需要的
+ style.width = this.$uv.addUnit(this.indicatorBarWidth)
+ style.backgroundColor = this.indicatorActiveColor
+ return style
+ },
+ lineStyle() {
+ const style = {}
+ // 指示器整体的样式,需要设置其宽度和背景色
+ style.width = this.$uv.addUnit(this.indicatorWidth)
+ style.backgroundColor = this.indicatorColor
+ return style
+ }
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ this.getComponentWidth()
+ },
+ // #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ
+ // scroll-view触发滚动事件
+ scrollHandler(e) {
+ this.scrollInfo = e.detail
+ },
+ scrolltoupperHandler() {
+ this.scrollEvent('left')
+ this.scrollInfo.scrollLeft = 0
+ },
+ scrolltolowerHandler() {
+ this.scrollEvent('right')
+ // 在普通js方案中,滚动到右边时,通过设置this.scrollInfo,模拟出滚动到右边的情况
+ // 因为上方是用过computed计算的,设置后,会自动调整滑块的位置
+ this.scrollInfo.scrollLeft = this.$uv.getPx(this.indicatorWidth) - this.$uv.getPx(this.indicatorBarWidth)
+ },
+ // #endif
+ scrollEvent(status) {
+ this.$emit(status)
+ },
+ // 获取组件的宽度
+ async getComponentWidth() {
+ // 延时一定时间,以获取dom尺寸
+ await this.$uv.sleep(30)
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-scroll-list').then(size => {
+ this.scrollWidth = size.width
+ })
+ // #endif
+ // #ifdef APP-NVUE
+ const ref = this.$refs['uv-scroll-list']
+ ref && dom.getComponentRect(ref, (res) => {
+ this.scrollWidth = res.size.width
+ })
+ // #endif
+ },
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-scroll-list {
+ padding-bottom: 10px;
+ &__scroll-view {
+ @include flex;
+ &__content {
+ @include flex;
+ }
+ }
+ &__indicator {
+ @include flex;
+ justify-content: center;
+ margin-top: 15px;
+ &__line {
+ width: 60px;
+ height: 4px;
+ border-radius: 100px;
+ overflow: hidden;
+ &__bar {
+ width: 20px;
+ height: 4px;
+ border-radius: 100px;
+ }
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-scroll-list/package.json b/ledApp/uni_modules/uv-scroll-list/package.json
new file mode 100644
index 0000000..942a836
--- /dev/null
+++ b/ledApp/uni_modules/uv-scroll-list/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-scroll-list",
+ "displayName": "uv-scroll-list 横向滚动列表 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.5",
+ "description": "该组件一般用于同时展示多个商品、分类的场景,也可以完成左右滑动的列表,往往数量不确定,数量较多支持左右滚动。灵活配置,开箱即用。",
+ "keywords": [
+ "uv-scroll-list",
+ "uvui",
+ "uv-ui",
+ "横向滚动列表",
+ "scroll-view"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-scroll-list/readme.md b/ledApp/uni_modules/uv-scroll-list/readme.md
new file mode 100644
index 0000000..fdf60e4
--- /dev/null
+++ b/ledApp/uni_modules/uv-scroll-list/readme.md
@@ -0,0 +1,19 @@
+## ScrollList 横向滚动列表
+
+> **组件名:uv-scroll-list**
+
+该组件一般用于同时展示多个商品、分类的场景,也可以完成左右滑动的列表,往往数量不确定,数量较多支持左右滚动。灵活配置,开箱即用。
+
+# <a href="https://www.uvui.cn/components/scrollList.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-search/changelog.md b/ledApp/uni_modules/uv-search/changelog.md
new file mode 100644
index 0000000..6519601
--- /dev/null
+++ b/ledApp/uni_modules/uv-search/changelog.md
@@ -0,0 +1,25 @@
+## 1.0.11(2023-11-10)
+1. 调整清除按钮样式的marginLeft,避免微信上多数情况触发不了的BUG
+## 1.0.10(2023-11-04)
+1. 修复设置disabled情况下,@click回调事件触发不了的bug
+## 1.0.9(2023-08-18)
+1. 修复boxStyle失效的BUG
+## 1.0.8(2023-08-07)
+修复值为null或undefined时显示错误的bug
+## 1.0.7(2023-08-05)
+1. 修复在vue2模式下,v-model设置为0时不生效的BUG
+## 1.0.6(2023-07-26)
+1. 增加boxStyle参数,方便控制输入框部分的样式
+## 1.0.5(2023-07-26)
+1. 增加prefix和suffix 前置和后置插槽
+## 1.0.4(2023-07-13)
+1. 修复value值清空不生效的BUG
+## 1.0.3(2023-07-13)
+1. 修复value/v-model更改不生效的BUG
+## 1.0.2(2023-07-03)
+去除无用的插槽
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-search 搜索
diff --git a/ledApp/uni_modules/uv-search/components/uv-search/props.js b/ledApp/uni_modules/uv-search/components/uv-search/props.js
new file mode 100644
index 0000000..338d99c
--- /dev/null
+++ b/ledApp/uni_modules/uv-search/components/uv-search/props.js
@@ -0,0 +1,127 @@
+export default {
+ props: {
+ value: {
+ type: [String, Number],
+ default: ''
+ },
+ modelValue: {
+ type: [String, Number],
+ default: ''
+ },
+ // 搜索框形状,round-圆形,square-方形
+ shape: {
+ type: String,
+ default: 'round'
+ },
+ // 搜索框背景色,默认值#f2f2f2
+ bgColor: {
+ type: String,
+ default: '#f2f2f2'
+ },
+ // 占位提示文字
+ placeholder: {
+ type: String,
+ default: '请输入关键字'
+ },
+ // 是否启用清除控件
+ clearabled: {
+ type: Boolean,
+ default: true
+ },
+ // 是否自动聚焦
+ focus: {
+ type: Boolean,
+ default: false
+ },
+ // 是否在搜索框右侧显示取消按钮
+ showAction: {
+ type: Boolean,
+ default: true
+ },
+ // 右边控件的样式
+ actionStyle: {
+ type: Object,
+ default: () => ({})
+ },
+ // 取消按钮文字
+ actionText: {
+ type: String,
+ default: '搜索'
+ },
+ // 输入框内容对齐方式,可选值为 left|center|right
+ inputAlign: {
+ type: String,
+ default: 'left'
+ },
+ // input输入框的样式,可以定义文字颜色,大小等,对象形式
+ inputStyle: {
+ type: Object,
+ default: () => ({})
+ },
+ // 是否禁用输入框
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 边框颜色
+ borderColor: {
+ type: String,
+ default: 'transparent'
+ },
+ // 搜索图标的颜色,默认同输入框字体颜色
+ searchIconColor: {
+ type: String,
+ default: '#909399'
+ },
+ // 输入框字体颜色
+ color: {
+ type: String,
+ default: '#606266'
+ },
+ // placeholder的颜色
+ placeholderColor: {
+ type: String,
+ default: '#909399'
+ },
+ // 左边输入框的图标,可以为uvui图标名称或图片路径
+ searchIcon: {
+ type: String,
+ default: 'search'
+ },
+ searchIconSize: {
+ type: [Number, String],
+ default: 22
+ },
+ // 组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30px"、"30px 20px"等写法
+ margin: {
+ type: String,
+ default: '0'
+ },
+ // 开启showAction时,是否在input获取焦点时才显示
+ animation: {
+ type: Boolean,
+ default: false
+ },
+ // 输入框最大能输入的长度,-1为不限制长度(来自uniapp文档)
+ maxlength: {
+ type: [String, Number],
+ default: -1
+ },
+ // 搜索框高度,单位px
+ height: {
+ type: [String, Number],
+ default: 32
+ },
+ // 搜索框左侧文本
+ label: {
+ type: [String, Number, null],
+ default: null
+ },
+ // 搜索框扩展样式
+ boxStyle: {
+ type: [String, Object],
+ default: () => ({})
+ },
+ ...uni.$uv?.props?.search
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-search/components/uv-search/uv-search.vue b/ledApp/uni_modules/uv-search/components/uv-search/uv-search.vue
new file mode 100644
index 0000000..216f4e1
--- /dev/null
+++ b/ledApp/uni_modules/uv-search/components/uv-search/uv-search.vue
@@ -0,0 +1,311 @@
+<template>
+ <view
+ class="uv-search"
+ @tap="clickHandler"
+ :style="[{
+ margin: margin,
+ }, $uv.addStyle(customStyle)]"
+ >
+ <view
+ class="uv-search__content"
+ :style="[{
+ backgroundColor: bgColor,
+ borderRadius: shape == 'round' ? '100px' : '4px',
+ borderColor: borderColor,
+ },$uv.addStyle(boxStyle)]"
+ >
+ <view class="uv-search__content__disabled" v-if="disabled"></view>
+ <slot name="prefix">
+ <view class="uv-search__content__icon">
+ <uv-icon
+ @tap="clickIcon"
+ :size="searchIconSize"
+ :name="searchIcon"
+ :color="searchIconColor ? searchIconColor : color"
+ ></uv-icon>
+ </view>
+ </slot>
+ <input
+ confirm-type="search"
+ @blur="blur"
+ :value="keyword"
+ @confirm="search"
+ @input="inputChange"
+ :disabled="disabled"
+ @focus="getFocus"
+ :focus="focus"
+ :maxlength="maxlength"
+ placeholder-class="uv-search__content__input--placeholder"
+ :placeholder="placeholder"
+ :placeholder-style="`color: ${placeholderColor}`"
+ class="uv-search__content__input"
+ type="text"
+ :style="[{
+ textAlign: inputAlign,
+ color: color,
+ backgroundColor: bgColor,
+ height: $uv.addUnit(height)
+ }, inputStyle]"
+ />
+ <view
+ class="uv-search__content__icon uv-search__content__close"
+ v-if="keyword && clearabled && focused"
+ @tap="clear"
+ >
+ <uv-icon
+ name="close"
+ size="11"
+ color="#ffffff"
+ customStyle="line-height: 12px"
+ ></uv-icon>
+ </view>
+ <slot name="suffix"></slot>
+ </view>
+ <text
+ :style="[actionStyle]"
+ class="uv-search__action"
+ :class="[(showActionBtn || show) && 'uv-search__action--active']"
+ @tap.stop.prevent="custom"
+ >{{ actionText }}</text>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * search 搜索框
+ * @description 搜索组件,集成了常见搜索框所需功能,用户可以一键引入,开箱即用。
+ * @tutorial https://www.uvui.cn/components/search.html
+ * @property {String} value/v-model 输入框初始值
+ * @property {String} shape 搜索框形状,round-圆形,square-方形(默认 'round' )
+ * @property {String} bgColor 搜索框背景颜色(默认 '#f2f2f2' )
+ * @property {String} placeholder 占位文字内容(默认 '请输入关键字' )
+ * @property {Boolean} clearabled 是否启用清除控件(默认 true )
+ * @property {Boolean} focus 是否自动获得焦点(默认 false )
+ * @property {Boolean} showAction 是否显示右侧控件(默认 true )
+ * @property {Object} actionStyle 右侧控件的样式,对象形式
+ * @property {String} actionText 右侧控件文字(默认 '搜索' )
+ * @property {String} inputAlign 输入框内容水平对齐方式 (默认 'left' )
+ * @property {Object} inputStyle 自定义输入框样式,对象形式
+ * @property {Boolean} disabled 是否启用输入框(默认 false )
+ * @property {String} borderColor 边框颜色,配置了颜色,才会有边框 (默认 'transparent' )
+ * @property {String} searchIconColor 搜索图标的颜色,默认同输入框字体颜色 (默认 '#909399' )
+ * @property {Number | String} searchIconSize 搜索图标的字体,默认22
+ * @property {String} color 输入框字体颜色(默认 '#606266' )
+ * @property {String} placeholderColor placeholder的颜色(默认 '#909399' )
+ * @property {String} searchIcon 输入框左边的图标,可以为uvui图标名称或图片路径 (默认 'search' )
+ * @property {String} margin 组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30px" (默认 '0' )
+ * @property {Boolean} animation 是否开启动画,见上方说明(默认 false )
+ * @property {String | Number} maxlength 输入框最大能输入的长度,-1为不限制长度 (默认 '-1' )
+ * @property {String | Number} height 输入框高度,单位px(默认 64 )
+ * @property {String | Number} label 搜索框左边显示内容
+ * @property {Object} boxStyle 自定义内容框样式,设置padding
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} change 输入框内容发生变化时触发
+ * @event {Function} search 用户确定搜索时触发,用户按回车键,或者手机键盘右下角的"搜索"键时触发
+ * @event {Function} custom 用户点击右侧控件时触发
+ * @event {Function} clear 用户点击清除按钮时触发
+ * @example <uv-search placeholder="日照香炉生紫烟" v-model="keyword"></uv-search>
+ */
+ export default {
+ name: "uv-search",
+ emits: ['click', 'input', 'change', 'clear', 'search', 'custom', 'focus', 'blur', 'clickIcon', 'update:modelValue'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ keyword: '',
+ showClear: false, // 是否显示右边的清除图标
+ show: false,
+ // 标记input当前状态是否处于聚焦中,如果是,才会显示右侧的清除控件
+ focused: this.focus
+ };
+ },
+ created() {
+ // #ifndef VUE3
+ this.keyword = this.value;
+ // #endif
+ // #ifdef VUE3
+ this.keyword = this.modelValue;
+ // #endif
+ },
+ watch: {
+ value(nVal){
+ this.keyword = nVal;
+ },
+ modelValue(nVal){
+ this.keyword = nVal;
+ }
+ },
+ computed: {
+ showActionBtn() {
+ return !this.animation && this.showAction
+ }
+ },
+ methods: {
+ keywordChange(){
+ this.$emit('input', this.keyword)
+ this.$emit('update:modelValue', this.keyword)
+ this.$emit('change', this.keyword);
+ },
+ // 目前HX2.6.9 v-model双向绑定无效,故监听input事件获取输入框内容的变化
+ inputChange(e) {
+ this.keyword = e.detail.value;
+ this.keywordChange();
+ },
+ // 清空输入
+ // 也可以作为用户通过this.$refs形式调用清空输入框内容
+ clear() {
+ this.keyword = '';
+ this.$nextTick(() => {
+ this.$emit('clear');
+ })
+ this.keywordChange();
+ },
+ // 确定搜索
+ search(e) {
+ this.$emit('search', e.detail.value);
+ try {
+ // 收起键盘
+ uni.hideKeyboard();
+ } catch (e) {}
+ },
+ // 点击右边自定义按钮的事件
+ custom() {
+ this.$emit('custom', this.keyword);
+ try {
+ // 收起键盘
+ uni.hideKeyboard();
+ } catch (e) {}
+ },
+ // 获取焦点
+ getFocus() {
+ this.focused = true;
+ // 开启右侧搜索按钮展开的动画效果
+ if (this.animation && this.showAction) this.show = true;
+ this.$emit('focus', this.keyword);
+ },
+ // 失去焦点
+ blur() {
+ // 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
+ // 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
+ setTimeout(() => {
+ this.focused = false;
+ }, 100)
+ this.show = false;
+ this.$emit('blur', this.keyword);
+ },
+ // 点击搜索框,只有disabled=true时才发出事件,因为禁止了输入,意味着是想跳转真正的搜索页
+ clickHandler() {
+ if (this.disabled) this.$emit('click');
+ },
+ // 点击左边图标
+ clickIcon() {
+ this.$emit('clickIcon');
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-search-content-padding: 0 10px !default;
+ $uv-search-label-color: $uv-main-color !default;
+ $uv-search-label-font-size: 14px !default;
+ $uv-search-label-margin: 0 4px !default;
+ $uv-search-close-size: 20px !default;
+ $uv-search-close-radius: 100px !default;
+ $uv-search-close-bgColor: #C6C7CB !default;
+ $uv-search-close-transform: scale(0.82) !default;
+ $uv-search-input-font-size: 14px !default;
+ $uv-search-input-margin: 0 15px 0 5px !default;
+ $uv-search-input-color: $uv-main-color !default;
+ $uv-search-input-placeholder-color: $uv-tips-color !default;
+ $uv-search-action-font-size: 14px !default;
+ $uv-search-action-color: $uv-main-color !default;
+ $uv-search-action-width: 0 !default;
+ $uv-search-action-active-width: 40px !default;
+ $uv-search-action-margin-left: 5px !default;
+ /* #ifdef H5 */
+ // iOS15在H5下,hx的某些版本,input type=search时,会多了一个搜索图标,进行移除
+ [type="search"]::-webkit-search-decoration {
+ display: none;
+ }
+ /* #endif */
+ .uv-search {
+ @include flex(row);
+ align-items: center;
+ flex: 1;
+ &__content {
+ position: relative;
+ @include flex;
+ align-items: center;
+ padding: $uv-search-content-padding;
+ flex: 1;
+ justify-content: space-between;
+ border-width: 1px;
+ border-color: transparent;
+ border-style: solid;
+ overflow: hidden;
+ &__icon {
+ @include flex;
+ align-items: center;
+ }
+ &__label {
+ color: $uv-search-label-color;
+ font-size: $uv-search-label-font-size;
+ margin: $uv-search-label-margin;
+ }
+ &__close {
+ width: $uv-search-close-size;
+ height: $uv-search-close-size;
+ border-top-left-radius: $uv-search-close-radius;
+ border-top-right-radius: $uv-search-close-radius;
+ border-bottom-left-radius: $uv-search-close-radius;
+ border-bottom-right-radius: $uv-search-close-radius;
+ background-color: $uv-search-close-bgColor;
+ @include flex(row);
+ align-items: center;
+ justify-content: center;
+ transform: $uv-search-close-transform;
+ }
+ &__input {
+ flex: 1;
+ font-size: $uv-search-input-font-size;
+ line-height: 1;
+ margin: $uv-search-input-margin;
+ color: $uv-search-input-color;
+ &--placeholder {
+ color: $uv-search-input-placeholder-color;
+ }
+ }
+ &__disabled {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 2;
+ }
+ }
+ &__action {
+ font-size: $uv-search-action-font-size;
+ color: $uv-search-action-color;
+ width: $uv-search-action-width;
+ overflow: hidden;
+ transition-property: width;
+ transition-duration: 0.3s;
+ /* #ifndef APP-NVUE */
+ white-space: nowrap;
+ /* #endif */
+ text-align: center;
+ &--active {
+ width: $uv-search-action-active-width;
+ margin-left: $uv-search-action-margin-left;
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-search/package.json b/ledApp/uni_modules/uv-search/package.json
new file mode 100644
index 0000000..3709059
--- /dev/null
+++ b/ledApp/uni_modules/uv-search/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-search",
+ "displayName": "uv-search 搜索 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.11",
+ "description": "搜索组件,集成了常见搜索框所需功能,支持前后插槽,用户可以一键引入,开箱即用",
+ "keywords": [
+ "uv-search",
+ "uvui",
+ "uv-ui",
+ "search",
+ "搜索"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-loading-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-search/readme.md b/ledApp/uni_modules/uv-search/readme.md
new file mode 100644
index 0000000..1e85b27
--- /dev/null
+++ b/ledApp/uni_modules/uv-search/readme.md
@@ -0,0 +1,19 @@
+## Search 搜索
+
+> **组件名:uv-search**
+
+搜索组件,集成了常见搜索框所需功能,支持前后插槽,用户可以一键引入,开箱即用。
+
+# <a href="https://www.uvui.cn/components/search.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <span>(请不要 下载插件ZIP)</span>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-skeleton/changelog.md b/ledApp/uni_modules/uv-skeleton/changelog.md
new file mode 100644
index 0000000..c5c0d78
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeleton/changelog.md
@@ -0,0 +1,9 @@
+## 1.0.2(2023-11-02)
+1. **`uv-skeleton`不再维护,强烈推荐使用新版本`uv-skeletons`**
+2. 推荐大家使用新版骨架屏,作者已经运用到大量实战中了,绝对正品:[https://ext.dcloud.net.cn/plugin?name=uv-skeletons](https://ext.dcloud.net.cn/plugin?name=uv-skeletons)
+3. 新版本`uv-skeletons`的优点:灵活度非常高,直接配置数组就可以完成想要的布局,作者已经运用到大量实战中。鼓励老用户迁移新版本,有任何问题可以加Q群549833913交流。
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-skeleton 骨架屏
diff --git a/ledApp/uni_modules/uv-skeleton/components/uv-skeleton/props.js b/ledApp/uni_modules/uv-skeleton/components/uv-skeleton/props.js
new file mode 100644
index 0000000..127e9dd
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeleton/components/uv-skeleton/props.js
@@ -0,0 +1,65 @@
+export default {
+ props: {
+ // 是否展示骨架组件
+ loading: {
+ type: Boolean,
+ default: true
+ },
+ // 是否开启动画效果
+ animate: {
+ type: Boolean,
+ default: true
+ },
+ // 段落占位图行数
+ rows: {
+ type: [String, Number],
+ default: 0
+ },
+ // 段落占位图的宽度
+ rowsWidth: {
+ type: [String, Number, Array],
+ default: '100%'
+ },
+ // 段落占位图的高度
+ rowsHeight: {
+ type: [String, Number, Array],
+ default: 18
+ },
+ // 段落占位图的左边距
+ rowsLeft: {
+ type: [String, Number, Array],
+ default: 0
+ },
+ // 是否展示标题占位图
+ title: {
+ type: Boolean,
+ default: true
+ },
+ // 段落标题的宽度
+ titleWidth: {
+ type: [String, Number],
+ default: '50%'
+ },
+ // 段落标题的高度
+ titleHeight: {
+ type: [String, Number],
+ default: 18
+ },
+ // 是否展示头像占位图
+ avatar: {
+ type: Boolean,
+ default: false
+ },
+ // 头像占位图大小
+ avatarSize: {
+ type: [String, Number],
+ default: 32
+ },
+ // 头像占位图的形状,circle-圆形,square-方形
+ avatarShape: {
+ type: String,
+ default: 'circle'
+ },
+ ...uni.$uv?.props?.skeleton
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-skeleton/components/uv-skeleton/uv-skeleton.vue b/ledApp/uni_modules/uv-skeleton/components/uv-skeleton/uv-skeleton.vue
new file mode 100644
index 0000000..2184b0e
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeleton/components/uv-skeleton/uv-skeleton.vue
@@ -0,0 +1,250 @@
+<template>
+ <view class="uv-skeleton">
+ <view
+ class="uv-skeleton__wrapper"
+ ref="uv-skeleton__wrapper"
+ v-if="loading"
+ style="display: flex; flex-direction: row;"
+ >
+ <view
+ class="uv-skeleton__wrapper__avatar"
+ v-if="avatar"
+ :class="[`uv-skeleton__wrapper__avatar--${avatarShape}`, animate && 'animate']"
+ :style="{
+ height: $uv.addUnit(avatarSize),
+ width: $uv.addUnit(avatarSize)
+ }"
+ ></view>
+ <view
+ class="uv-skeleton__wrapper__content"
+ ref="uv-skeleton__wrapper__content"
+ style="flex: 1;"
+ >
+ <view
+ class="uv-skeleton__wrapper__content__title"
+ v-if="title"
+ :style="{
+ width: uTitleWidth,
+ height: $uv.addUnit(titleHeight),
+ }"
+ :class="[animate && 'animate']"
+ ></view>
+ <view
+ class="uv-skeleton__wrapper__content__rows"
+ :class="[animate && 'animate']"
+ v-for="(item, index) in rowsArray"
+ :key="index"
+ :style="{
+ width: item.width,
+ height: item.height,
+ marginTop: item.marginTop,
+ marginLeft: item.marginLeft
+ }"
+ >
+
+ </view>
+ </view>
+ </view>
+ <slot v-else />
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+ const dom = uni.requireNativePlugin('dom')
+ const animation = uni.requireNativePlugin('animation')
+ // #endif
+ /**
+ * Skeleton 骨架屏
+ * @description 骨架屏一般用于页面在请求远程数据尚未完成时,页面用灰色块预显示本来的页面结构,给用户更好的体验。
+ * @tutorial https://www.uvui.cn/components/skeleton.html
+ * @property {Boolean} loading 是否显示骨架占位图,设置为false将会展示子组件内容 (默认 true )
+ * @property {Boolean} animate 是否开启动画效果 (默认 true )
+ * @property {String | Number} rows 段落占位图行数 (默认 0 )
+ * @property {String | Number | Array} rowsWidth 段落占位图的宽度,可以为百分比,数值,带单位字符串等,可通过数组传入指定每个段落行的宽度 (默认 '100%' )
+ * @property {String | Number | Array} rowsHeight 段落的高度 (默认 18 )
+ * @property {String | Number | Array} rowsLeft 段落的左边距 (默认 0 )
+ * @property {Boolean} title 是否展示标题占位图 (默认 true )
+ * @property {String | Number} titleWidth 标题的宽度 (默认 '50%' )
+ * @property {String | Number} titleHeight 标题的高度 (默认 18 )
+ * @property {Boolean} avatar 是否展示头像占位图 (默认 false )
+ * @property {String | Number} avatarSize 头像占位图大小 (默认 32 )
+ * @property {String} avatarShape 头像占位图的形状,circle-圆形,square-方形 (默认 'circle' )
+ * @example <uv-skeleton :loading="true" :animate="true"></uv-skeleton>
+ */
+ export default {
+ name: 'uv-skeleton',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ width: 0,
+ }
+ },
+ watch: {
+ loading() {
+ this.getComponentWidth()
+ }
+ },
+ computed: {
+ rowsArray() {
+ if (/%$/.test(this.rowsHeight)) {
+ this.$uv.error('rowsHeight参数不支持百分比单位')
+ }
+ const rows = []
+ for (let i = 0; i < this.rows; i++) {
+ let item = {},
+ // 需要预防超出数组边界的情况
+ rowWidth = this.$uv.test.array(this.rowsWidth) ? (this.rowsWidth[i] || (i === this.row - 1 ? '70%' : '100%')) : i ===
+ this.rows - 1 ? '70%' : this.rowsWidth,
+ rowHeight = this.$uv.test.array(this.rowsHeight) ? (this.rowsHeight[i] || '18px') : this.rowsHeight,
+ rowLeft = this.$uv.test.array(this.rowsLeft) ? (this.rowsLeft[i] || 0) : this.rowsLeft;
+ // 如果有title占位图,第一个段落占位图的外边距需要大一些,如果没有title占位图,第一个段落占位图则无需外边距
+ // 之所以需要这么做,是因为weex的无能,以提升性能为借口不支持css的一些伪类
+ item.marginTop = !this.title && i === 0 ? 0 : this.title && i === 0 ? '20px' : '12px'
+ // 如果设置的为百分比的宽度,转换为px值,因为nvue不支持百分比单位
+ if (/%$/.test(rowWidth)) {
+ // 通过parseInt提取出百分比单位中的数值部分,除以100得到百分比的小数值
+ item.width = this.$uv.addUnit(this.width * parseInt(rowWidth) / 100)
+ } else {
+ item.width = this.$uv.addUnit(rowWidth)
+ }
+ item.height = this.$uv.addUnit(rowHeight)
+ item.marginLeft = this.$uv.addUnit(rowLeft)
+ rows.push(item)
+ }
+ // console.log(rows);
+ return rows
+ },
+ uTitleWidth() {
+ let tWidth = 0
+ if (/%$/.test(this.titleWidth)) {
+ // 通过parseInt提取出百分比单位中的数值部分,除以100得到百分比的小数值
+ tWidth = this.$uv.addUnit(this.width * parseInt(this.titleWidth) / 100)
+ } else {
+ tWidth = this.$uv.addUnit(this.titleWidth)
+ }
+ return this.$uv.addUnit(tWidth)
+ },
+
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ this.getComponentWidth()
+ // #ifdef APP-NVUE
+ this.loading && this.animate && this.setNvueAnimation()
+ // #endif
+ },
+ async setNvueAnimation() {
+ // #ifdef APP-NVUE
+ // 为了让opacity:1的状态保持一定时间,这里做一个延时
+ await this.$uv.sleep(500)
+ const skeleton = this.$refs['uv-skeleton__wrapper'];
+ skeleton && this.loading && this.animate && animation.transition(skeleton, {
+ styles: {
+ opacity: 0.5
+ },
+ duration: 600,
+ }, () => {
+ // 这里无需判断是否loading和开启动画状态,因为最终的状态必须达到opacity: 1,否则可能
+ // 会停留在opacity: 0.5的状态中
+ animation.transition(skeleton, {
+ styles: {
+ opacity: 1
+ },
+ duration: 600,
+ }, () => {
+ // 只有在loading中时,才执行动画
+ this.loading && this.animate && this.setNvueAnimation()
+ })
+ })
+ // #endif
+ },
+ // 获取组件的宽度
+ async getComponentWidth() {
+ // 延时一定时间,以获取dom尺寸
+ await this.$uv.sleep(20)
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-skeleton__wrapper__content').then(size => {
+ this.width = size.width
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ const ref = this.$refs['uv-skeleton__wrapper__content']
+ ref && dom.getComponentRect(ref, (res) => {
+ this.width = res.size.width
+ })
+ // #endif
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ @mixin background {
+ /* #ifdef APP-NVUE */
+ background-color: #F1F2F4;
+ /* #endif */
+ /* #ifndef APP-NVUE */
+ background: linear-gradient(90deg, #F1F2F4 25%, #e6e6e6 37%, #F1F2F4 50%);
+ background-size: 400% 100%;
+ /* #endif */
+ }
+
+ .uv-skeleton {
+ flex: 1;
+
+ &__wrapper {
+ @include flex(row);
+
+ &__avatar {
+ @include background;
+ margin-right: 15px;
+
+ &--circle {
+ border-radius: 100px;
+ }
+
+ &--square {
+ border-radius: 4px;
+ }
+ }
+
+ &__content {
+ flex: 1;
+
+ &__rows,
+ &__title {
+ @include background;
+ border-radius: 3px;
+ }
+ }
+ }
+ }
+
+ /* #ifndef APP-NVUE */
+ .animate {
+ animation: skeleton 1.8s ease infinite
+ }
+
+ @keyframes skeleton {
+ 0% {
+ background-position: 100% 50%
+ }
+
+ 100% {
+ background-position: 0 50%
+ }
+ }
+
+ /* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-skeleton/package.json b/ledApp/uni_modules/uv-skeleton/package.json
new file mode 100644
index 0000000..388e096
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeleton/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-skeleton",
+ "displayName": "uv-skeleton 骨架屏 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.2",
+ "description": "骨架屏一般用于页面在请求远程数据尚未完成时,页面用灰色块预显示本来的页面结构,给用户更好的体验。",
+ "keywords": [
+ "uv-skeleton",
+ "uvui",
+ "uv-ui",
+ "skeleton",
+ "骨架屏"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-skeleton/readme.md b/ledApp/uni_modules/uv-skeleton/readme.md
new file mode 100644
index 0000000..02314cd
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeleton/readme.md
@@ -0,0 +1,11 @@
+## Skeleton 骨架屏
+
+> **组件名:uv-skeleton**
+
+骨架屏一般用于页面在请求远程数据尚未完成时,页面用灰色块预显示本来的页面结构,给用户更好的体验。
+
+### <a href="https://www.uvui.cn/components/skeleton.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-skeletons/changelog.md b/ledApp/uni_modules/uv-skeletons/changelog.md
new file mode 100644
index 0000000..c2d58dc
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeletons/changelog.md
@@ -0,0 +1,4 @@
+## 1.0.1(2023-11-15)
+1. 修复支付宝小程序报错的BUG
+## 1.0.0(2023-10-30)
+1. 新版骨架屏上线
diff --git a/ledApp/uni_modules/uv-skeletons/components/uv-skeletons/uv-skeletons.vue b/ledApp/uni_modules/uv-skeletons/components/uv-skeletons/uv-skeletons.vue
new file mode 100644
index 0000000..2b2243b
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeletons/components/uv-skeletons/uv-skeletons.vue
@@ -0,0 +1,247 @@
+<template>
+ <view class="uv-skeleton">
+ <view v-if="loading">
+ <view v-for="(item, index) in elements" :key="index">
+ <!-- 横向并列布局 -->
+ <view class="uv-skeleton__group" :style="[style(item)]" v-if="item.type=='flex'">
+ <view v-for="(item2,index2) in item.children" :class="[item2.clas]" :style="[style(item2)]" :key="index2">
+ <view v-for="(item3,index3) in item2.elements" :key="index3">
+ <!-- 垂直高度站位 -->
+ <view :style="{height: $uv.addUnit(item3.height,'rpx')}" v-if="item3.type=='gap'"></view>
+ <view :class="[item3.clas, roundClass, animateClass]" :style="[style(item3)]" v-else ref="skeleton"></view>
+ </view>
+ </view>
+ </view>
+ <!-- 自定义骨架屏内容 -->
+ <!-- ps 自定义扩展说明: -->
+ <!-- 如果你需要自定义模板,可以参照这个custom的写法,增加一个skeleton配置类型,编写布局和样式就可以了 -->
+ <!-- 注意事项:为了保证骨架效果和动态效果的一致,扩展时,在你希望实际展示在页面中的元素上加上 :class="[style, animateClass]" 和 :style="[style(item)]" 和 ref="skeleton" -->
+ <view :class="[item.clas, animateClass]" :style="[style(item)]" ref="skeleton" v-else-if="item.type == 'custom'"></view>
+ <!-- 垂直高度站位 -->
+ <view :style="{height: $uv.addUnit(item.height,'rpx')}" v-else-if="item.type=='gap'"></view>
+ <!-- 其他基本单位 line avatar 等 -->
+ <view :class="[item.clas, roundClass, animateClass]" :style="[style(item)]" v-else ref="skeleton"></view>
+ </view>
+ </view>
+ <view v-else>
+ <slot />
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef APP-NVUE
+ const animation = weex.requireModule('animation');
+ // #endif
+ export default {
+ name: 'uv-skeletons',
+ mixins: [mpMixin, mixin],
+ props: {
+ // 是否显示骨架
+ loading: {
+ type: Boolean,
+ default: true
+ },
+ // 骨架内容 具体说明参考文档:
+ skeleton: {
+ type: Array,
+ default: () => []
+ },
+ // 是否开启动画效果
+ animate: {
+ type: Boolean,
+ default: true
+ },
+ // 是否圆角骨架风格
+ round: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.skeleton
+ },
+ data() {
+ return {
+ elements: [],
+ opacity: 0.5,
+ duration: 600
+ }
+ },
+ computed: {
+ animateClass() {
+ return this.animate ? 'uv-skeleton--animation' : 'uv-skeleton--static';
+ },
+ roundClass() {
+ return this.round ? 'uv-skeleton--round' : 'uv-skeleton--radius';
+ },
+ style(item) {
+ return item => {
+ const style = {};
+ return this.$uv.deepMerge(style, this.$uv.addStyle(item.style));
+ }
+ }
+ },
+ created() {
+ this.init();
+ // #ifdef APP-NVUE
+ if (this.loading && this.animate) {
+ this.$uv.sleep(50).then(res => {
+ this.createAnimation(this.opacity);
+ })
+ }
+ // #endif
+ },
+ methods: {
+ /**
+ * 初始化数据
+ */
+ init() {
+ let elements = [];
+ if (!this.$uv.test.array(this.skeleton)) return this.$uv.error('skeleton参数必须为数组形式,参考文档示例:');
+ this.skeleton.forEach(el => {
+ const elClass = this.getElCounts(el);
+ elements = elements.concat(elClass);
+ })
+ this.elements = [...elements];
+ },
+ /**
+ * 处理骨架屏参数内容
+ * @param {Object} el 每项数据
+ */
+ getElCounts(el) {
+ let elements = [];
+ let children = [];
+ if (this.$uv.test.number(el)) {
+ elements.push({
+ type: 'gap',
+ height: el
+ });
+ return elements;
+ } else {
+ const type = el.type ? el.type : 'line';
+ const num = el.num ? el.num : 1;
+ const style = el.style ? el.style : {};
+ const styleIsArray = this.$uv.test.array(style);
+ const gap = el.gap ? el.gap : '20rpx';
+ const child = el.children ? el.children : [];
+ for (let i = 0; i < child.length; i++) {
+ children[i] = {
+ clas: child[i].type.indexOf('avatar') > -1 || child[i].type.indexOf('custom') > -1 ? '' : 'uv-skeleton__group__sub',
+ elements: this.getElCounts(child[i])
+ };
+ }
+ for (let i = 0; i < num; i++) {
+ if (gap && i < num && i > 0) {
+ elements.push({
+ type: 'gap',
+ height: gap
+ });
+ }
+ elements.push({
+ clas: `uv-skeleton__${type}`,
+ type,
+ style: styleIsArray ? style[i] : style,
+ gap,
+ children
+ });
+ }
+ return elements;
+ }
+ },
+ /**
+ * 创建动画
+ */
+ createAnimation(opacity = 1) {
+ // loading结束之后或未开启动画不执行
+ if (!this.loading || !this.animate) return;
+ let count = 0;
+ const skeletons = this.$refs.skeleton;
+ skeletons.forEach(item => {
+ animation.transition(item, {
+ styles: {
+ opacity: opacity,
+ },
+ duration: this.duration
+ }, () => {
+ count++;
+ if (count >= skeletons.length) {
+ setTimeout(() => {
+ this.createAnimation(opacity < 1 ? 1 : this.opacity);
+ }, 200)
+ }
+ });
+ })
+ }
+ }
+ }
+</script>
+<style scoped lang="scss">
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @mixin background {
+ /* #ifdef APP-NVUE */
+ background-color: #e6e6e6;
+ /* #endif */
+ /* #ifndef APP-NVUE */
+ background: linear-gradient(90deg, #F1F2F4 25%, #e6e6e6 37%, #F1F2F4 50%);
+ background-size: 400% 100%;
+ /* #endif */
+ }
+ .uv-skeleton__line {
+ height: 32rpx;
+ // margin-bottom: 30rpx;
+ }
+ .uv-skeleton__line--sm {
+ height: 24rpx;
+ margin-bottom: 30rpx;
+ }
+ .uv-skeleton__line--lg {
+ height: 48rpx;
+ margin-bottom: 30rpx;
+ }
+ .uv-skeleton--static {
+ @include background;
+ }
+ .uv-skeleton--radius {
+ border-radius: 8rpx;
+ }
+ .uv-skeleton--round {
+ border-radius: 30rpx;
+ }
+ .uv-skeleton__avatar {
+ width: 100rpx;
+ height: 100rpx;
+ border-radius: 50rpx;
+ }
+ .uv-skeleton__avatar--sm {
+ width: 50rpx;
+ height: 50rpx;
+ border-radius: 25rpx;
+ }
+ .uv-skeleton__avatar--lg {
+ width: 200rpx;
+ height: 200rpx;
+ border-radius: 100rpx;
+ }
+ .uv-skeleton__group {
+ @include flex;
+ &__sub {
+ flex: 1;
+ }
+ }
+ .uv-skeleton--animation {
+ @include background;
+ /* #ifndef APP-NVUE */
+ animation: skeleton 1.8s ease infinite
+ /* #endif */
+ }
+ /* #ifndef APP-NVUE */
+ @keyframes skeleton {
+ 0% {
+ background-position: 100% 50%
+ }
+ 100% {
+ background-position: 0 50%
+ }
+ }
+ /* #endif */
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-skeletons/package.json b/ledApp/uni_modules/uv-skeletons/package.json
new file mode 100644
index 0000000..9a36299
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeletons/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-skeletons",
+ "displayName": "uv-skeletons 骨架屏 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.1",
+ "description": "全新升级骨架屏,更加灵活,体验更加,强烈推荐使用新版骨架屏。一般用于页面在请求远程数据尚未完成时,在内容加载出来前展示与内容布局结构一致的灰白块,提升用户视觉体验。",
+ "keywords": [
+ "uv-skeletons",
+ "uvui",
+ "uv-ui",
+ "skeleton",
+ "骨架屏"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-text"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-skeletons/readme.md b/ledApp/uni_modules/uv-skeletons/readme.md
new file mode 100644
index 0000000..a08cec1
--- /dev/null
+++ b/ledApp/uni_modules/uv-skeletons/readme.md
@@ -0,0 +1,23 @@
+# Skeletons 新版骨架屏
+
+> **组件名:uv-skeletons**
+
+骨架屏一般用于页面在请求远程数据尚未完成时,在内容加载出来前展示与内容布局结构一致的灰白块,提升用户视觉体验。
+
+新版骨架屏与老版本使用参数有很大的区别,所以才有了新版本,无法替换老版本。
+
+新版骨架屏比老版本骨架屏更加灵活,理论上简单的左右上下布局结构均可以轻松实现,样式自由度较高,只要你会写css,推荐使用新版。
+
+# <a href="https://www.uvui.cn/components/skeletons.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-slider/changelog.md b/ledApp/uni_modules/uv-slider/changelog.md
new file mode 100644
index 0000000..d065ed0
--- /dev/null
+++ b/ledApp/uni_modules/uv-slider/changelog.md
@@ -0,0 +1,9 @@
+## 1.0.3(2023-07-25)
+1. 将inactiveColor修正为backgroundColor 参数,避免不生效
+## 1.0.2(2023-07-13)
+1. 修复 uv-slider设置value属性不生效的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-slider 滑动选择器
diff --git a/ledApp/uni_modules/uv-slider/components/uv-slider/props.js b/ledApp/uni_modules/uv-slider/components/uv-slider/props.js
new file mode 100644
index 0000000..1c622de
--- /dev/null
+++ b/ledApp/uni_modules/uv-slider/components/uv-slider/props.js
@@ -0,0 +1,58 @@
+export default {
+ props: {
+ value: {
+ type: [Number, String],
+ default: 0
+ },
+ modelValue: {
+ type: [Number, String],
+ default: 0
+ },
+ // 最小可选值
+ min: {
+ type: [Number, String],
+ default: 0
+ },
+ // 最大可选值
+ max: {
+ type: [Number, String],
+ default: 100
+ },
+ // 步长,取值必须大于 0,并且可被(max - min)整除
+ step: {
+ type: [Number, String],
+ default: 1
+ },
+ // 滑块右侧已选择部分的背景色
+ activeColor: {
+ type: String,
+ default: '#2979ff'
+ },
+ // 滑块左侧未选择部分的背景色
+ backgroundColor: {
+ type: String,
+ default: '#c0c4cc'
+ },
+ // 滑块的大小,取值范围为 12 - 28
+ blockSize: {
+ type: [Number, String],
+ default: 18
+ },
+ // 滑块的颜色
+ blockColor: {
+ type: String,
+ default: '#ffffff'
+ },
+ // 禁用状态
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示当前的选择值
+ showValue: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.slider
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-slider/components/uv-slider/uv-slider.vue b/ledApp/uni_modules/uv-slider/components/uv-slider/uv-slider.vue
new file mode 100644
index 0000000..9c2a86a
--- /dev/null
+++ b/ledApp/uni_modules/uv-slider/components/uv-slider/uv-slider.vue
@@ -0,0 +1,58 @@
+<template>
+ <view
+ class="uv-slider"
+ :style="[$uv.addStyle(customStyle)]"
+ >
+ <slider
+ :min="min"
+ :max="max"
+ :step="step"
+ :value="sliderValue"
+ :activeColor="activeColor"
+ :backgroundColor="backgroundColor"
+ :blockSize="$uv.getPx(blockSize)"
+ :blockColor="blockColor"
+ :showValue="showValue"
+ :disabled="disabled"
+ @changing="changingHandler"
+ @change="changeHandler"
+ ></slider>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js'
+ export default {
+ name: 'uv-slider',
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ sliderValue(){
+ return this.value || this.modelValue;
+ }
+ },
+ methods: {
+ // 拖动过程中触发
+ changingHandler(e) {
+ const { value } = e.detail
+ // 更新v-model的值
+ this.$emit('input', value)
+ this.$emit('update:modelValue',value)
+ // 触发事件
+ this.$emit('changing', value)
+ },
+ // 滑动结束时触发
+ changeHandler(e) {
+ const {
+ value
+ } = e.detail
+ // 更新v-model的值
+ this.$emit('input', value)
+ this.$emit('update:modelValue',value)
+ // 触发事件
+ this.$emit('change', value)
+ }
+ }
+ }
+</script>
+
diff --git a/ledApp/uni_modules/uv-slider/package.json b/ledApp/uni_modules/uv-slider/package.json
new file mode 100644
index 0000000..1393951
--- /dev/null
+++ b/ledApp/uni_modules/uv-slider/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-slider",
+ "displayName": "uv-slider 滑动选择器 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.3",
+ "description": "滑动选择器组件一般用于表单中,手动选择一个区间范围的场景。",
+ "keywords": [
+ "uv-slider",
+ "uvui",
+ "uv-ui",
+ "slider",
+ "滑动选择器"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-slider/readme.md b/ledApp/uni_modules/uv-slider/readme.md
new file mode 100644
index 0000000..c9c00cc
--- /dev/null
+++ b/ledApp/uni_modules/uv-slider/readme.md
@@ -0,0 +1,15 @@
+## Slider 滑动选择器
+
+> **组件名:uv-slider**
+
+该组件一般用于表单中,手动选择一个区间范围的场景。
+
+# <a href="https://www.uvui.cn/components/slider.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-status-bar/changelog.md b/ledApp/uni_modules/uv-status-bar/changelog.md
new file mode 100644
index 0000000..fc04088
--- /dev/null
+++ b/ledApp/uni_modules/uv-status-bar/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-05)
+1. 兼容渐变背景色
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-status-bar组件
diff --git a/ledApp/uni_modules/uv-status-bar/components/uv-status-bar/props.js b/ledApp/uni_modules/uv-status-bar/components/uv-status-bar/props.js
new file mode 100644
index 0000000..f42e525
--- /dev/null
+++ b/ledApp/uni_modules/uv-status-bar/components/uv-status-bar/props.js
@@ -0,0 +1,8 @@
+export default {
+ props: {
+ bgColor: {
+ type: String,
+ default: 'transparent'
+ }
+ }
+}
diff --git a/ledApp/uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue b/ledApp/uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue
new file mode 100644
index 0000000..ee887c7
--- /dev/null
+++ b/ledApp/uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue
@@ -0,0 +1,54 @@
+<template>
+ <view
+ :style="[style]"
+ class="uv-status-bar"
+ >
+ <slot />
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * StatbusBar 状态栏占位
+ * @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
+ * @tutorial https://www.uvui.cn/components/statusBar.html
+ * @property {String} bgColor 背景色 (默认 'transparent' )
+ * @property {String | Object} customStyle 自定义样式
+ * @example <uv-status-bar></uv-status-bar>
+ */
+ export default {
+ name: 'uv-status-bar',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ }
+ },
+ computed: {
+ style() {
+ const style = {}
+ // 状态栏高度,由于某些安卓和微信开发工具无法识别css的顶部状态栏变量,所以使用js获取的方式
+ style.height = this.$uv.addUnit(this.$uv.sys().statusBarHeight, 'px')
+ if(this.bgColor){
+ if (this.bgColor.indexOf("gradient") > -1) {// 渐变色
+ style.backgroundImage = this.bgColor;
+ }else{
+ style.background = this.bgColor;
+ }
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ .uv-status-bar {
+ // nvue会默认100%,如果nvue下,显式写100%的话,会导致宽度不为100%而异常
+ /* #ifndef APP-NVUE */
+ width: 100%;
+ /* #endif */
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-status-bar/package.json b/ledApp/uni_modules/uv-status-bar/package.json
new file mode 100644
index 0000000..dd4311e
--- /dev/null
+++ b/ledApp/uni_modules/uv-status-bar/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-status-bar",
+ "displayName": "uv-status-bar 状态栏占位",
+ "version": "1.0.2",
+ "description": "状态栏占位组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。",
+ "keywords": [
+ "uv-status-bar",
+ "uvui",
+ "uv-ui",
+ "status-bar",
+ "状态栏"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-status-bar/readme.md b/ledApp/uni_modules/uv-status-bar/readme.md
new file mode 100644
index 0000000..fc61e0b
--- /dev/null
+++ b/ledApp/uni_modules/uv-status-bar/readme.md
@@ -0,0 +1,10 @@
+## StatbusBar 状态栏占位
+
+> **组件名:uv-status-bar**
+
+本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
+
diff --git a/ledApp/uni_modules/uv-steps/changelog.md b/ledApp/uni_modules/uv-steps/changelog.md
new file mode 100644
index 0000000..b05c894
--- /dev/null
+++ b/ledApp/uni_modules/uv-steps/changelog.md
@@ -0,0 +1,10 @@
+## 1.0.3(2023-06-29)
+1. 增加customStyle参数,方便设置样式
+## 1.0.2(2023-06-29)
+1. 新增title自定义
+2. 完善文档说明
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-steps 步骤条
diff --git a/ledApp/uni_modules/uv-steps/components/uv-steps-item/props.js b/ledApp/uni_modules/uv-steps/components/uv-steps-item/props.js
new file mode 100644
index 0000000..1af6711
--- /dev/null
+++ b/ledApp/uni_modules/uv-steps/components/uv-steps-item/props.js
@@ -0,0 +1,25 @@
+export default {
+ props: {
+ // 标题
+ title: {
+ type: [String, Number],
+ default: ''
+ },
+ // 描述文本
+ desc: {
+ type: [String, Number],
+ default: ''
+ },
+ // 图标大小
+ iconSize: {
+ type: [String, Number],
+ default: 17
+ },
+ // 当前步骤是否处于失败状态
+ error: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.stepsItem
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-steps/components/uv-steps-item/uv-steps-item.vue b/ledApp/uni_modules/uv-steps/components/uv-steps-item/uv-steps-item.vue
new file mode 100644
index 0000000..cfb6166
--- /dev/null
+++ b/ledApp/uni_modules/uv-steps/components/uv-steps-item/uv-steps-item.vue
@@ -0,0 +1,347 @@
+<template>
+ <view
+ class="uv-steps-item"
+ ref="uv-steps-item"
+ :class="[`uv-steps-item--${parentData.direction}`]"
+ :style="[$uv.addStyle(customStyle)]"
+ >
+ <view
+ class="uv-steps-item__line"
+ v-if="index + 1 < childLength"
+ :class="[`uv-steps-item__line--${parentData.direction}`]"
+ :style="[lineStyle]">
+ </view>
+ <view
+ :class="[
+ 'uv-steps-item__wrapper',
+ `uv-steps-item__wrapper--${parentData.direction}`,
+ parentData.dot && `uv-steps-item__wrapper--${parentData.direction}--dot`
+ ]">
+ <slot name="icon">
+ <view
+ class="uv-steps-item__wrapper__dot"
+ v-if="parentData.dot"
+ :style="{
+ backgroundColor: statusColor
+ }">
+ </view>
+ <view
+ class="uv-steps-item__wrapper__icon"
+ v-else-if="parentData.activeIcon || parentData.inactiveIcon">
+ <uv-icon
+ :name="index <= parentData.current ? parentData.activeIcon : parentData.inactiveIcon"
+ :size="iconSize"
+ :color="index <= parentData.current ? parentData.activeColor : parentData.inactiveColor">
+ </uv-icon>
+ </view>
+ <view
+ v-else
+ :style="{
+ backgroundColor: statusClass === 'process' ? parentData.activeColor : 'transparent',
+ borderColor: statusColor
+ }"
+ class="uv-steps-item__wrapper__circle">
+ <text
+ v-if="statusClass === 'process' || statusClass === 'wait'"
+ class="uv-steps-item__wrapper__circle__text"
+ :style="{
+ color: index == parentData.current ? '#ffffff' : parentData.inactiveColor
+ }">{{ index + 1}}</text>
+ <uv-icon
+ v-else
+ :color="statusClass === 'error' ? 'error' : parentData.activeColor"
+ size="12"
+ :name="statusClass === 'error' ? 'close' : 'checkmark'">
+ </uv-icon>
+ </view>
+ </slot>
+ </view>
+ <view
+ :class="[
+ 'uv-steps-item__content',
+ `uv-steps-item__content--${parentData.direction}`
+ ]"
+ :style="[contentStyle]"
+ >
+ <slot name="title">
+ <uv-text
+ :text="title"
+ :type="parentData.current == index ? 'main' : 'content'"
+ lineHeight="20px"
+ :size="parentData.current == index ? 14 : 13"
+ ></uv-text>
+ </slot>
+ <slot name="desc">
+ <uv-text
+ :text="desc"
+ type="tips"
+ size="12"
+ ></uv-text>
+ </slot>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * StepsItem 步骤条的子组件
+ * @description 本组件需要和uv-steps配合使用
+ * @tutorial https://www.uvui.cn/components/steps.html
+ * @property {String} title 标题文字
+ * @property {String} current 描述文本
+ * @property {String | Number} iconSize 图标大小 (默认 17 )
+ * @property {Boolean} error 当前步骤是否处于失败状态 (默认 false )
+ * @example <uv-steps current="0"><uv-steps-item title="已出库" desc="10:35" ></uv-steps-item></uv-steps>
+ */
+ export default {
+ name: 'uv-steps-item',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ index: 0,
+ childLength: 0,
+ showLine: false,
+ size: {
+ height: 0,
+ width: 0
+ },
+ parentData: {
+ direction: 'row',
+ current: 0,
+ activeColor: '',
+ inactiveColor: '',
+ activeIcon: '',
+ inactiveIcon: '',
+ dot: false
+ }
+ }
+ },
+ watch: {
+ 'parentData'(newValue, oldValue) {}
+ },
+ created() {
+ this.init()
+ },
+ computed: {
+ lineStyle() {
+ const style = {}
+ if (this.parentData.direction === 'row') {
+ style.width = this.size.width + 'px'
+ style.left = this.size.width / 2 + 'px'
+ } else {
+ style.height = this.size.height + 'px'
+ }
+ style.backgroundColor = this.parent.children?.[this.index + 1]?.error ? '#f56c6c' : this.index < this.parentData.current ? this.parentData.activeColor : this.parentData.inactiveColor
+ return style
+ },
+ statusClass() {
+ const {
+ index,
+ error
+ } = this
+ const {
+ current
+ } = this.parentData
+ if (current == index) {
+ return error === true ? 'error' : 'process'
+ } else if (error) {
+ return 'error'
+ } else if (current > index) {
+ return 'finish'
+ } else {
+ return 'wait'
+ }
+ },
+ statusColor() {
+ let color = ''
+ switch (this.statusClass) {
+ case 'finish':
+ color = this.parentData.activeColor
+ break
+ case 'error':
+ color = '#f56c6c'
+ break
+ case 'process':
+ color = this.parentData.dot ? this.parentData.activeColor : 'transparent'
+ break
+ default:
+ color = this.parentData.inactiveColor
+ break
+ }
+ return color
+ },
+ contentStyle() {
+ const style = {}
+ if (this.parentData.direction === 'column') {
+ style.marginLeft = this.parentData.dot ? '2px' : '6px'
+ style.marginTop = this.parentData.dot ? '0px' : '6px'
+ } else {
+ style.marginTop = this.parentData.dot ? '2px' : '6px'
+ style.marginLeft = this.parentData.dot ? '2px' : '6px'
+ }
+ return style
+ }
+ },
+ mounted() {
+ this.parent && this.parent.updateFromChild()
+ this.$uv.sleep().then(() => {
+ this.getStepsItemRect()
+ })
+ },
+ methods: {
+ init() {
+ // 初始化数据
+ this.updateParentData()
+ if (!this.parent) {
+ return this.$uv.error('uv-steps-item必须要搭配uv-steps组件使用')
+ }
+ this.index = this.parent.children.indexOf(this)
+ this.childLength = this.parent.children.length
+ },
+ updateParentData() {
+ // 此方法在mixin中
+ this.getParentData('uv-steps')
+ },
+ // 父组件数据发生变化
+ updateFromParent() {
+ this.init()
+ },
+ // 获取组件的尺寸,用于设置横线的位置
+ getStepsItemRect() {
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-steps-item').then(size => {
+ this.size = size
+ })
+ // #endif
+ // #ifdef APP-NVUE
+ dom.getComponentRect(this.$refs['uv-steps-item'], res => {
+ const {
+ size
+ } = res
+ this.size = size
+ })
+ // #endif
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-steps-item {
+ flex: 1;
+ @include flex;
+
+ &--row {
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ }
+
+ &--column {
+ position: relative;
+ flex-direction: row;
+ justify-content: flex-start;
+ padding-bottom: 5px;
+ }
+
+ &__wrapper {
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ background-color: #fff;
+
+ &--column {
+ width: 20px;
+ height: 32px;
+
+ &--dot {
+ height: 20px;
+ width: 20px;
+ }
+ }
+
+ &--row {
+ width: 32px;
+ height: 20px;
+
+ &--dot {
+ width: 20px;
+ height: 20px;
+ }
+ }
+
+ &__circle {
+ width: 20px;
+ height: 20px;
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ flex-shrink: 0;
+ /* #endif */
+ border-radius: 100px;
+ border-width: 1px;
+ border-color: $uv-tips-color;
+ border-style: solid;
+ @include flex(row);
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.3s;
+
+ &__text {
+ color: $uv-tips-color;
+ font-size: 11px;
+ @include flex(row);
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ line-height: 11px;
+ }
+ }
+
+ &__dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 100px;
+ background-color: $uv-content-color;
+ }
+ }
+
+ &__content {
+ @include flex;
+ flex: 1;
+
+ &--row {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ &--column {
+ flex-direction: column;
+ margin-left: 6px;
+ }
+ }
+
+ &__line {
+ position: absolute;
+ background: $uv-tips-color;
+
+ &--row {
+ top: 10px;
+ height: 1px;
+ }
+
+ &--column {
+ width: 1px;
+ left: 10px;
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-steps/components/uv-steps/props.js b/ledApp/uni_modules/uv-steps/components/uv-steps/props.js
new file mode 100644
index 0000000..61414ed
--- /dev/null
+++ b/ledApp/uni_modules/uv-steps/components/uv-steps/props.js
@@ -0,0 +1,40 @@
+export default {
+ props: {
+ // 排列方向
+ direction: {
+ type: String,
+ default: 'row'
+ },
+ // 设置第几个步骤
+ current: {
+ type: [String, Number],
+ default: 0
+ },
+ // 激活状态颜色
+ activeColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 未激活状态颜色
+ inactiveColor: {
+ type: String,
+ default: '#969799'
+ },
+ // 激活状态的图标
+ activeIcon: {
+ type: String,
+ default: ''
+ },
+ // 未激活状态图标
+ inactiveIcon: {
+ type: String,
+ default: ''
+ },
+ // 是否显示点类型
+ dot: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.steps
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-steps/components/uv-steps/uv-steps.vue b/ledApp/uni_modules/uv-steps/components/uv-steps/uv-steps.vue
new file mode 100644
index 0000000..c448f9e
--- /dev/null
+++ b/ledApp/uni_modules/uv-steps/components/uv-steps/uv-steps.vue
@@ -0,0 +1,83 @@
+<template>
+ <view
+ :class="['uv-steps',`uv-steps--${direction}`]"
+ :style="[$uv.addStyle(customStyle)]"
+ >
+ <slot></slot>
+ </view>
+</template>
+
+<script>
+ import { func } from '@/uni_modules/uv-ui-tools/libs/function/test.js'
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Steps 步骤条
+ * @description 该组件一般用于完成一个任务要分几个步骤,标识目前处于第几步的场景。
+ * @tutorial https://www.uvui.cn/components/steps.html
+ * @property {String} direction row-横向,column-竖向 (默认 'row' )
+ * @property {String | Number} current 设置当前处于第几步 (默认 0 )
+ * @property {String} activeColor 激活状态颜色 (默认 '#3c9cff' )
+ * @property {String} inactiveColor 未激活状态颜色 (默认 '#969799' )
+ * @property {String} activeIcon 激活状态的图标
+ * @property {String} inactiveIcon 未激活状态图标
+ * @property {Boolean} dot 是否显示点类型 (默认 false )
+ * @example <uv-steps current="0"><uv-steps-item title="已出库" desc="10:35" ></uv-steps-item></uv-steps>
+ */
+ export default {
+ name: 'uv-steps',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ }
+ },
+ watch: {
+ children() {
+ this.updateChildData()
+ },
+ parentData() {
+ this.updateChildData()
+ }
+ },
+ computed: {
+ // 监听参数的变化,通过watch中,手动去更新子组件的数据,否则子组件不会自动变化
+ parentData() {
+ return [this.current, this.direction, this.activeColor, this.inactiveColor, this.activeIcon, this.inactiveIcon, this.dot]
+ }
+ },
+ methods: {
+ // 更新子组件的数据
+ updateChildData() {
+ this.children.map(child => {
+ // 先判断子组件是否存在对应的方法
+ func((child || {}).updateFromParent()) && child.updateFromParent()
+ })
+ },
+ // 接受子组件的通知,去修改其他子组件的数据
+ updateFromChild() {
+ this.updateChildData()
+ }
+ },
+ created() {
+ this.children = []
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-steps {
+ @include flex;
+
+ &--column {
+ flex-direction: column
+ }
+
+ &--row {
+ flex-direction: row;
+ flex: 1;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-steps/package.json b/ledApp/uni_modules/uv-steps/package.json
new file mode 100644
index 0000000..a0a938a
--- /dev/null
+++ b/ledApp/uni_modules/uv-steps/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-steps",
+ "displayName": "uv-steps 步骤条 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.3",
+ "description": "步骤条组件一般用于完成一个任务要分几个步骤,标识目前处于第几步的场景。",
+ "keywords": [
+ "uv-steps",
+ "uvui",
+ "uv-ui",
+ "steps",
+ "步骤条"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-text"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "n"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-steps/readme.md b/ledApp/uni_modules/uv-steps/readme.md
new file mode 100644
index 0000000..0f2f574
--- /dev/null
+++ b/ledApp/uni_modules/uv-steps/readme.md
@@ -0,0 +1,11 @@
+## Steps 步骤条
+
+> **组件名:uv-steps**
+
+该组件一般用于完成一个任务要分几个步骤,标识目前处于第几步的场景。
+
+### <a href="https://www.uvui.cn/components/steps.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-sticky/changelog.md b/ledApp/uni_modules/uv-sticky/changelog.md
new file mode 100644
index 0000000..c079cce
--- /dev/null
+++ b/ledApp/uni_modules/uv-sticky/changelog.md
@@ -0,0 +1,11 @@
+## 1.0.4(2023-12-20)
+1. 优化
+## 1.0.3(2023-10-13)
+1. unmounted兼容vue3
+## 1.0.2(2023-08-27)
+1. 优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-sticky 吸顶
diff --git a/ledApp/uni_modules/uv-sticky/components/uv-sticky/props.js b/ledApp/uni_modules/uv-sticky/components/uv-sticky/props.js
new file mode 100644
index 0000000..e7d2d2e
--- /dev/null
+++ b/ledApp/uni_modules/uv-sticky/components/uv-sticky/props.js
@@ -0,0 +1,41 @@
+export default {
+ props: {
+ // 吸顶容器到顶部某个距离的时候,进行吸顶,在H5平台,NavigationBar为44px
+ offsetTop: {
+ type: [String, Number],
+ default: 0
+ },
+ // 自定义导航栏的高度
+ customNavHeight: {
+ type: [String, Number],
+ // #ifdef H5
+ // H5端的导航栏属于“自定义”导航栏的范畴,因为它是非原生的,与普通元素一致
+ default: 44,
+ // #endif
+ // #ifndef H5
+ default: 0
+ // #endif
+ },
+ // 是否禁用吸顶功能
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 吸顶区域的背景颜色
+ bgColor: {
+ type: String,
+ default: 'transparent'
+ },
+ // z-index值
+ zIndex: {
+ type: [String, Number],
+ default: ''
+ },
+ // 列表中的索引值
+ index: {
+ type: [String, Number],
+ default: ''
+ },
+ ...uni.$uv?.props?.sticky
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue b/ledApp/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue
new file mode 100644
index 0000000..4ca7b45
--- /dev/null
+++ b/ledApp/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue
@@ -0,0 +1,223 @@
+<template>
+ <view
+ class="uv-sticky"
+ :id="elId"
+ :style="[style]"
+ >
+ <view
+ :style="[stickyContent]"
+ class="uv-sticky__content"
+ >
+ <slot />
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';;
+ /**
+ * sticky 吸顶
+ * @description 该组件与CSS中position: sticky属性实现的效果一致,当组件达到预设的到顶部距离时, 就会固定在指定位置,组件位置大于预设的顶部距离时,会重新按照正常的布局排列。
+ * @tutorial https://www.uvui.cn/components/sticky.html
+ * @property {String | Number} offsetTop 吸顶时与顶部的距离,单位px(默认 0 )
+ * @property {String | Number} customNavHeight 自定义导航栏的高度 (h5 默认44 其他默认 0 )
+ * @property {Boolean} disabled 是否开启吸顶功能 (默认 false )
+ * @property {String} bgColor 组件背景颜色(默认 '#ffffff' )
+ * @property {String | Number} zIndex 吸顶时的z-index值
+ * @property {String | Number} index 自定义标识,用于区分是哪一个组件
+ * @property {Object} customStyle 组件的样式,对象形式
+ * @example <uv-sticky offsetTop="200"><view>塞下秋来风景异,衡阳雁去无留意</view></uv-sticky>
+ */
+ export default {
+ name: 'uv-sticky',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ cssSticky: false, // 是否使用css的sticky实现
+ stickyTop: 0, // 吸顶的top值,因为可能受自定义导航栏影响,最终的吸顶值非offsetTop值
+ elId: '',
+ left: 0, // js模式时,吸顶的内容因为处于postition: fixed模式,为了和原来保持一致的样式,需要记录并重新设置它的left,height,width属性
+ width: 'auto',
+ height: 'auto',
+ fixed: false, // js模式时,是否处于吸顶模式
+ }
+ },
+ computed: {
+ style() {
+ const style = {}
+ if(!this.disabled) {
+ if (this.cssSticky) {
+ style.position = 'sticky'
+ style.zIndex = this.uZindex
+ style.top = this.$uv.addUnit(this.stickyTop)
+ } else {
+ style.height = this.fixed ? this.height + 'px' : 'auto'
+ }
+ } else {
+ // 无需吸顶时,设置会默认的relative(nvue)和非nvue的static静态模式即可
+ // #ifdef APP-NVUE
+ style.position = 'relative'
+ // #endif
+ // #ifndef APP-NVUE
+ style.position = 'static'
+ // #endif
+ }
+ style.backgroundColor = this.bgColor
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ },
+ // 吸顶内容的样式
+ stickyContent() {
+ const style = {}
+ if (!this.cssSticky) {
+ style.position = this.fixed ? 'fixed' : 'static'
+ style.top = this.stickyTop + 'px'
+ style.left = this.left + 'px'
+ style.width = this.width == 'auto' ? 'auto' : this.width + 'px'
+ style.zIndex = this.uZindex
+ }
+ return style
+ },
+ uZindex() {
+ return this.zIndex ? this.zIndex : 970
+ }
+ },
+ created() {
+ this.elId = this.$uv.guid();
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ this.getStickyTop()
+ // 判断使用的模式
+ this.checkSupportCssSticky()
+ // 如果不支持css sticky,则使用js方案,此方案性能比不上css方案
+ if (!this.cssSticky) {
+ !this.disabled && this.initObserveContent()
+ }
+ },
+ initObserveContent() {
+ // 获取吸顶内容的高度,用于在js吸顶模式时,给父元素一个填充高度,防止"塌陷"
+ this.$uvGetRect('#' + this.elId).then((res) => {
+ this.height = res.height
+ this.left = res.left
+ this.width = res.width
+ this.$nextTick(() => {
+ this.observeContent()
+ })
+ })
+ },
+ observeContent() {
+ // 先断掉之前的观察
+ this.disconnectObserver('contentObserver')
+ const contentObserver = uni.createIntersectionObserver({
+ // 检测的区间范围
+ thresholds: [0.95, 0.98, 1]
+ })
+ // 到屏幕顶部的高度时触发
+ contentObserver.relativeToViewport({
+ top: -this.stickyTop
+ })
+ // 绑定观察的元素
+ contentObserver.observe(`#${this.elId}`, res => {
+ this.setFixed(res.boundingClientRect.top)
+ })
+ this.contentObserver = contentObserver
+ },
+ setFixed(top) {
+ // 判断是否出于吸顶条件范围
+ const fixed = top <= this.stickyTop
+ this.fixed = fixed
+ },
+ disconnectObserver(observerName) {
+ // 断掉观察,释放资源
+ const observer = this[observerName]
+ observer && observer.disconnect()
+ },
+ getStickyTop() {
+ this.stickyTop = this.$uv.getPx(this.offsetTop) + this.$uv.getPx(this.customNavHeight)
+ },
+ async checkSupportCssSticky() {
+ // #ifdef H5
+ // H5,一般都是现代浏览器,是支持css sticky的,这里使用创建元素嗅探的形式判断
+ if (this.checkCssStickyForH5()) {
+ this.cssSticky = true
+ }
+ // #endif
+
+ // 如果安卓版本高于8.0,依然认为是支持css sticky的(因为安卓7在某些机型,可能不支持sticky)
+ if (this.$uv.os() === 'android' && Number(this.$uv.sys().system) > 8) {
+ this.cssSticky = true
+ }
+
+ // APP-Vue和微信平台,通过computedStyle判断是否支持css sticky
+ // #ifdef APP-VUE || MP-WEIXIN
+ this.cssSticky = await this.checkComputedStyle()
+ // #endif
+
+ // ios上,从ios6开始,都是支持css sticky的
+ if (this.$uv.os() === 'ios') {
+ this.cssSticky = true
+ }
+
+ // nvue,是支持css sticky的
+ // #ifdef APP-NVUE
+ this.cssSticky = true
+ // #endif
+ },
+ // 在APP和微信小程序上,通过uni.createSelectorQuery可以判断是否支持css sticky
+ checkComputedStyle() {
+ // 方法内进行判断,避免在其他平台生成无用代码
+ // #ifdef APP-VUE || MP-WEIXIN
+ return new Promise(resolve => {
+ uni.createSelectorQuery().in(this).select('.uv-sticky').fields({
+ computedStyle: ["position"]
+ }).exec(e => {
+ resolve('sticky' === e[0].position)
+ })
+ })
+ // #endif
+ },
+ // H5通过创建元素的形式嗅探是否支持css sticky
+ // 判断浏览器是否支持sticky属性
+ checkCssStickyForH5() {
+ // 方法内进行判断,避免在其他平台生成无用代码
+ // #ifdef H5
+ const vendorList = ['', '-webkit-', '-ms-', '-moz-', '-o-'],
+ vendorListLength = vendorList.length,
+ stickyElement = document.createElement('div')
+ for (let i = 0; i < vendorListLength; i++) {
+ stickyElement.style.position = vendorList[i] + 'sticky'
+ if (stickyElement.style.position !== '') {
+ return true
+ }
+ }
+ return false;
+ // #endif
+ }
+ },
+ // #ifdef VUE2
+ beforeDestroy() {
+ this.disconnectObserver('contentObserver')
+ },
+ // #endif
+ // #ifdef VUE3
+ unmounted() {
+ this.disconnectObserver('contentObserver')
+ }
+ // #endif
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-sticky {
+ /* #ifdef APP-VUE || MP-WEIXIN */
+ // 此处默认写sticky属性,是为了给微信和APP通过uni.createSelectorQuery查询是否支持css sticky使用
+ position: sticky;
+ /* #endif */
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-sticky/package.json b/ledApp/uni_modules/uv-sticky/package.json
new file mode 100644
index 0000000..036f922
--- /dev/null
+++ b/ledApp/uni_modules/uv-sticky/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-sticky",
+ "displayName": "uv-sticky 吸顶 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.4",
+ "description": "吸顶组件与CSS中position: sticky属性实现的效果一致,当组件达到预设的到顶部距离时, 就会固定在指定位置,组件位置大于预设的顶部距离时,会重新按照正常的布局排列。",
+ "keywords": [
+ "uv-sticky",
+ "uvui",
+ "uv-ui",
+ "sticky",
+ "吸顶"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-sticky/readme.md b/ledApp/uni_modules/uv-sticky/readme.md
new file mode 100644
index 0000000..c9319a6
--- /dev/null
+++ b/ledApp/uni_modules/uv-sticky/readme.md
@@ -0,0 +1,11 @@
+## Sticky 吸顶
+
+> **组件名:uv-sticky**
+
+该组件与CSS中position: sticky属性实现的效果一致,当组件达到预设的到顶部距离时, 就会固定在指定位置,组件位置大于预设的顶部距离时,会重新按照正常的布局排列。
+
+### <a href="https://www.uvui.cn/components/sticky.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-subsection/changelog.md b/ledApp/uni_modules/uv-subsection/changelog.md
new file mode 100644
index 0000000..8a0fd65
--- /dev/null
+++ b/ledApp/uni_modules/uv-subsection/changelog.md
@@ -0,0 +1,10 @@
+## 1.0.3(2023-12-20)
+1. 优化
+## 1.0.2(2023-11-27)
+1. 增加customItemStyle属性,方便修改bar样式
+2. 文档后续更新
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-subsection 分段器
diff --git a/ledApp/uni_modules/uv-subsection/components/uv-subsection/props.js b/ledApp/uni_modules/uv-subsection/components/uv-subsection/props.js
new file mode 100644
index 0000000..ef5568e
--- /dev/null
+++ b/ledApp/uni_modules/uv-subsection/components/uv-subsection/props.js
@@ -0,0 +1,54 @@
+export default {
+ props: {
+ // tab的数据
+ list: {
+ type: Array,
+ default: () => []
+ },
+ // 当前活动的tab的index
+ current: {
+ type: [String, Number],
+ default: 0
+ },
+ // 激活的颜色
+ activeColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 未激活的颜色
+ inactiveColor: {
+ type: String,
+ default: '#303133'
+ },
+ // 模式选择,mode=button为按钮形式,mode=subsection时为分段模式
+ mode: {
+ type: String,
+ default: 'button'
+ },
+ // 字体大小
+ fontSize: {
+ type: [String, Number],
+ default: 12
+ },
+ // 激活tab的字体是否加粗
+ bold: {
+ type: Boolean,
+ default: true
+ },
+ // mode = button时,组件背景颜色
+ bgColor: {
+ type: String,
+ default: '#eeeeef'
+ },
+ // 从list元素对象中读取的键名
+ keyName: {
+ type: String,
+ default: 'name'
+ },
+ customItemStyle: {
+ type: [String, Object],
+ default: ''
+ },
+ ...uni.$uv?.props?.subsection
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-subsection/components/uv-subsection/uv-subsection.vue b/ledApp/uni_modules/uv-subsection/components/uv-subsection/uv-subsection.vue
new file mode 100644
index 0000000..64338ae
--- /dev/null
+++ b/ledApp/uni_modules/uv-subsection/components/uv-subsection/uv-subsection.vue
@@ -0,0 +1,269 @@
+<template>
+ <view
+ class="uv-subsection"
+ ref="uv-subsection"
+ :class="[`uv-subsection--${mode}`]"
+ :style="[$uv.addStyle(customStyle), wrapperStyle]">
+ <view
+ class="uv-subsection__bar"
+ ref="uv-subsection__bar"
+ :style="[barStyle]"
+ :class="[
+ mode === 'button' && 'uv-subsection--button__bar',
+ current === 0 && mode === 'subsection' && 'uv-subsection__bar--first',
+ current > 0 && current < list.length - 1 && mode === 'subsection' && 'uv-subsection__bar--center',
+ current === list.length - 1 && mode === 'subsection' && 'uv-subsection__bar--last'
+ ]"
+ ></view>
+ <view class="uv-subsection__item"
+ :class="[
+ `uv-subsection__item--${index}`,
+ index < list.length - 1 && 'uv-subsection__item--no-border-right',
+ index === 0 && 'uv-subsection__item--first',
+ index === list.length - 1 && 'uv-subsection__item--last'
+ ]"
+ :ref="`uv-subsection__item--${index}`"
+ :style="[itemStyle(index)]"
+ @tap="clickHandler(index)"
+ v-for="(item, index) in list"
+ :key="index"
+ >
+ <text
+ class="uv-subsection__item__text"
+ :style="[textStyle(index)]">{{ getText(item) }}
+ </text>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin("dom");
+ const animation = uni.requireNativePlugin("animation");
+ // #endif
+ import props from "./props.js";
+ /**
+ * Subsection 分段器
+ * @description 该分段器一般用于用户从几个选项中选择某一个的场景
+ * @tutorial https://www.uvui.cn/components/subsection.html
+ * @property {Array} list tab的数据
+ * @property {String | Number} current 当前活动的tab的index(默认 0 )
+ * @property {String} activeColor 激活时的颜色(默认 '#3c9cff' )
+ * @property {String} inactiveColor 未激活时的颜色(默认 '#303133' )
+ * @property {String} mode 模式选择,mode=button为按钮形式,mode=subsection时为分段模式(默认 'button' )
+ * @property {String | Number} fontSize 字体大小,单位px(默认 12 )
+ * @property {Boolean} bold 激活选项的字体是否加粗(默认 true )
+ * @property {String} bgColor 组件背景颜色,mode为button时有效(默认 '#eeeeef' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ * @property {String} keyName 从`list`元素对象中读取的键名(默认 'name' )
+ *
+ * @event {Function} change 分段器选项发生改变时触发 回调 index:选项的index索引值,从0开始
+ * @example <uv-subsection :list="list" :current="curNow" @change="sectionChange"></uv-subsection>
+ */
+ export default {
+ name: "uv-subsection",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 组件尺寸
+ itemRect: {
+ width: 0,
+ height: 0,
+ }
+ }
+ },
+ watch: {
+ list: {
+ deep: true,
+ handler(){
+ this.init();
+ }
+ },
+ current: {
+ immediate: true,
+ handler(n) {
+ // #ifdef APP-NVUE
+ // 在安卓nvue上,如果通过translateX进行位移,到最后一个时,会导致右侧无法绘制圆角
+ // 故用animation模块进行位移
+ const ref = this.$refs?.["uv-subsection__bar"]?.ref;
+ // 不存在ref的时候(理解为第一次初始化时,需要渲染dom,进行一定延时再获取ref),这里的100ms是经过测试得出的结果(某些安卓需要延时久一点),勿随意修改
+ this.$uv.sleep(ref ? 0 : 150).then(() => {
+ animation.transition(this.$refs["uv-subsection__bar"].ref, {
+ styles: {
+ transform: `translateX(${ n * this.itemRect.width }px)`,
+ transformOrigin: "center center"
+ },
+ duration: 300,
+ });
+ });
+ // #endif
+ }
+ }
+ },
+ computed: {
+ wrapperStyle() {
+ const style = {};
+ // button模式时,设置背景色
+ if (this.mode === "button") {
+ style.backgroundColor = this.bgColor;
+ }
+ return style;
+ },
+ // 滑块的样式
+ barStyle() {
+ const style = {};
+ style.width = `${this.itemRect.width}px`;
+ style.height = `${this.itemRect.height}px`;
+ // 通过translateX移动滑块,其移动的距离为索引*item的宽度
+ // #ifndef APP-NVUE
+ style.transform = `translateX(${ this.current * this.itemRect.width }px)`;
+ // #endif
+ if (this.mode === "subsection") {
+ // 在subsection模式下,需要动态设置滑块的圆角,因为移动滑块使用的是translateX,无法通过父元素设置overflow: hidden隐藏滑块的直角
+ style.backgroundColor = this.activeColor;
+ }
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customItemStyle));
+ },
+ // 分段器item的样式
+ itemStyle(index) {
+ return (index) => {
+ const style = {};
+ if (this.mode === "subsection") {
+ // 设置border的样式
+ style.borderColor = this.activeColor;
+ style.borderWidth = "1px";
+ style.borderStyle = "solid";
+ }
+ return style;
+ };
+ },
+ // 分段器文字颜色
+ textStyle(index) {
+ return (index) => {
+ const style = {};
+ style.fontWeight = this.bold && this.current === index ? "bold" : "normal";
+ style.fontSize = this.$uv.addUnit(this.fontSize);
+ // subsection模式下,激活时默认为白色的文字
+ if (this.mode === "subsection") {
+ style.color = this.current === index ? "#fff" : this.inactiveColor;
+ } else {
+ // button模式下,激活时文字颜色默认为activeColor
+ style.color = this.current === index ? this.activeColor : this.inactiveColor;
+ }
+ return style;
+ };
+ },
+ },
+ mounted() {
+ this.init();
+ },
+ methods: {
+ init() {
+ this.$uv.sleep().then(() => this.getRect());
+ },
+ // 判断展示文本
+ getText(item) {
+ return typeof item === 'object' ? item[this.keyName] : item
+ },
+ // 获取组件的尺寸
+ getRect() {
+ // #ifndef APP-NVUE
+ this.$uvGetRect(".uv-subsection__item--0").then((size) => {
+ this.itemRect = size;
+ });
+ // #endif
+
+ // #ifdef APP-NVUE
+ const ref = this.$refs["uv-subsection__item--0"][0];
+ ref && dom.getComponentRect(ref, (res) => {
+ this.itemRect = res.size;
+ });
+ // #endif
+ },
+ clickHandler(index) {
+ this.$emit("change", index);
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-subsection {
+ @include flex;
+ position: relative;
+ overflow: hidden;
+ /* #ifndef APP-NVUE */
+ width: 100%;
+ box-sizing: border-box;
+ /* #endif */
+ &--button {
+ height: 32px;
+ background-color: rgb(238, 238, 239);
+ padding: 3px;
+ border-radius: 3px;
+ align-items: stretch;
+ &__bar {
+ background-color: #ffffff;
+ }
+ }
+ &--subsection {
+ height: 30px;
+ }
+ &__bar {
+ position: absolute;
+ /* #ifndef APP-NVUE */
+ transition-property: transform, color;
+ transition-duration: 0.3s;
+ transition-timing-function: ease-in-out;
+ /* #endif */
+ &--first {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ border-top-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+ }
+ &--center {
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+ border-top-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+ }
+ &--last {
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ }
+ }
+ &__item {
+ @include flex;
+ flex: 1;
+ justify-content: center;
+ align-items: center;
+ // vue环境下,需要设置相对定位,因为滑块为绝对定位,item需要在滑块的上面
+ position: relative;
+ &--no-border-right {
+ border-right-width: 0 !important;
+ }
+ &--first {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ }
+ &--last {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ }
+ &__text {
+ font-size: 12px;
+ line-height: 12px;
+ @include flex;
+ align-items: center;
+ transition-property: color;
+ transition-duration: 0.3s;
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-subsection/package.json b/ledApp/uni_modules/uv-subsection/package.json
new file mode 100644
index 0000000..cd433eb
--- /dev/null
+++ b/ledApp/uni_modules/uv-subsection/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-subsection",
+ "displayName": "uv-subsection 分段器 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.3",
+ "description": "该分段器组件一般用于用户从几个选项中选择某一个的场景。",
+ "keywords": [
+ "uv-subsection",
+ "uvui",
+ "uv-ui",
+ "subsection",
+ "分段器"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-subsection/readme.md b/ledApp/uni_modules/uv-subsection/readme.md
new file mode 100644
index 0000000..47de4e6
--- /dev/null
+++ b/ledApp/uni_modules/uv-subsection/readme.md
@@ -0,0 +1,11 @@
+## Subsection 分段器
+
+> **组件名:uv-subsection**
+
+该分段器一般用于用户从几个选项中选择某一个的场景。
+
+### <a href="https://www.uvui.cn/components/subsection.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-swipe-action/changelog.md b/ledApp/uni_modules/uv-swipe-action/changelog.md
new file mode 100644
index 0000000..508543c
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/changelog.md
@@ -0,0 +1,11 @@
+## 1.0.4(2023-10-13)
+1. 优化
+## 1.0.3(2023-10-13)
+1. unmounted兼容vue3
+## 1.0.2(2023-05-27)
+1. 不支持抖音小程序说明
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-swipe-action 滑动单元格
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index - backup.wxs b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index - backup.wxs
new file mode 100644
index 0000000..3d62784
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index - backup.wxs
@@ -0,0 +1,256 @@
+/**
+ * 此为wxs模块,只支持APP-VUE,微信和QQ小程序以及H5平台
+ * wxs内部不支持es6语法,变量只能使用var定义,无法使用解构,箭头函数等特性
+ */
+
+// 开始触摸
+function touchstart(event, ownerInstance) {
+ // 触发事件的组件的ComponentDescriptor实例
+ var instance = event.instance
+ // wxs内的局部变量快照,此快照是属于整个组件的,在touchstart和touchmove事件中都能获取到相同的结果
+ var state = instance.getState()
+ if (state.disable) return
+ var touches = event.touches
+ // 如果进行的是多指触控,不允许进行操作
+ if (touches && touches.length > 1) return
+ // 标识当前为滑动中状态
+ state.moving = true
+ // 记录触摸开始点的坐标值
+ state.startX = touches[0].pageX
+ state.startY = touches[0].pageY
+}
+
+// 触摸滑动
+function touchmove(event, ownerInstance) {
+ // 触发事件的组件的ComponentDescriptor实例
+ var instance = event.instance
+ // wxs内的局部变量快照
+ var state = instance.getState()
+ if (state.disabled || !state.moving) return
+
+ var touches = event.touches
+ var pageX = touches[0].pageX
+ var pageY = touches[0].pageY
+ var moveX = pageX - state.startX
+ var moveY = pageY - state.startY
+ var buttonsWidth = state.buttonsWidth
+
+ // 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
+ if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {
+ event.preventDefault()
+ event.stopPropagation()
+ }
+ // 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
+ if (Math.abs(moveX) < Math.abs(moveY)) return
+
+ // 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
+ // 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
+ // 在超出后,设置为0
+ if (state.status === 'open') {
+ // 在开启状态下,向左滑动,需忽略
+ if (moveX < 0) moveX = 0
+ // 想要收起菜单,最大能移动的距离为按钮的总宽度
+ if (moveX > buttonsWidth) moveX = buttonsWidth
+ // 如果是已经打开了的状态,向左滑动时,移动收起菜单
+ moveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)
+ } else {
+ // 关闭状态下,右滑动需忽略
+ if (moveX > 0) moveX = 0
+ // 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
+ if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
+ // 只要是在滑过程中,就不断移动菜单的内容部分,从而使隐藏的菜单显示出来
+ moveSwipeAction(moveX, instance, ownerInstance)
+ }
+}
+
+// 触摸结束
+function touchend(event, ownerInstance) {
+ // 触发事件的组件的ComponentDescriptor实例
+ var instance = event.instance
+ // wxs内的局部变量快照
+ var state = instance.getState()
+ if (!state.moving || state.disabled) return
+ var touches = event.changedTouches ? event.changedTouches[0] : {}
+ var pageX = touches.pageX
+ var pageY = touches.pageY
+ var moveX = pageX - state.startX
+ if (state.status === 'open') {
+ // 在展开的状态下,继续左滑,无需操作
+ if (moveX < 0) return
+ // 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
+ if (moveX === 0) {
+ return closeSwipeAction(instance, ownerInstance)
+ }
+ // 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
+ if (Math.abs(moveX) < state.threshold) {
+ openSwipeAction(instance, ownerInstance)
+ } else {
+ // 如果滑动距离大于阈值,则执行收起逻辑
+ closeSwipeAction(instance, ownerInstance)
+ }
+ } else {
+ // 在关闭的状态下,右滑,无需操作
+ if (moveX > 0) return
+ // 理由同上
+ if (Math.abs(moveX) < state.threshold) {
+ closeSwipeAction(instance, ownerInstance)
+ } else {
+ openSwipeAction(instance, ownerInstance)
+ }
+ }
+}
+
+// 获取过渡时间
+function getDuration(value) {
+ if (value.toString().indexOf('s') >= 0) return value
+ return value > 30 ? value + 'ms' : value + 's'
+}
+
+// 滑动结束时判断滑动的方向
+function getMoveDirection(instance, ownerInstance) {
+ var state = instance.getState()
+}
+
+// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
+function moveSwipeAction(moveX, instance, ownerInstance) {
+ var state = instance.getState()
+ // 获取所有按钮的实例,需要通过它去设置按钮的位移
+ var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
+ var len = buttons.length
+ var previewButtonsMoveX = 0
+
+ // 设置菜单内容部分的偏移
+ instance.requestAnimationFrame(function() {
+ instance.setStyle({
+ // 设置translateX的值
+ 'transition': 'none',
+ transform: 'translateX(' + moveX + 'px)',
+ '-webkit-transform': 'translateX(' + moveX + 'px)'
+ })
+ // 折叠按钮动画
+ for (var i = len - 1; i >= 0; i--) {
+ // 通过比例,得出元素自身该移动的距离
+ var translateX = state.buttons[i].width / state.buttonsWidth * moveX
+ // 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
+ var realTranslateX = translateX + previewButtonsMoveX
+ buttons[i].setStyle({
+ // 在移动期间,不能使用过渡效果,否则会造成卡顿,本质原因是每次移动一点,就要花一定时间去过渡这个过程
+ 'transition': 'none',
+ 'transform': 'translateX(' + realTranslateX + 'px)',
+ '-webkit-transform': 'translateX(' + realTranslateX + 'px)'
+ })
+ // 记录本按钮之前的所有按钮的移动距离之和
+ previewButtonsMoveX += translateX
+ }
+ })
+}
+
+// 一次性展开滑动菜单
+function openSwipeAction(instance, ownerInstance) {
+ var state = instance.getState()
+ // 获取所有按钮的实例,需要通过它去设置按钮的位移
+ var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
+ var len = buttons.length
+ // 处理duration单位问题
+ const duration = getDuration(state.duration)
+ // 展开过程中,是向左移动,所以X的偏移应该为负值
+ var buttonsWidth = -state.buttonsWidth
+ var previewButtonsMoveX = 0
+ instance.requestAnimationFrame(function() {
+ // 设置菜单主体内容
+ instance.setStyle({
+ 'transition': 'transform ' + duration,
+ 'transform': 'translateX(' + buttonsWidth + 'px)',
+ '-webkit-transform': 'translateX(' + buttonsWidth + 'px)',
+ })
+ // 设置各个隐藏的按钮为展开的状态
+ for (var i = len - 1; i >= 0; i--) {
+ // 通过比例,得出元素自身该移动的距离
+ var translateX = state.buttons[i].width / state.buttonsWidth * buttonsWidth
+ // 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
+ var realTranslateX = translateX + previewButtonsMoveX
+ buttons[i].setStyle({
+ // 在移动期间,需要加上动画效果
+ 'transition': 'transform ' + duration,
+ 'transform': 'translateX(' + realTranslateX + 'px)',
+ '-webkit-transform': 'translateX(' + realTranslateX + 'px)'
+ })
+ // 记录本按钮之前的所有按钮的移动距离之和
+ previewButtonsMoveX += translateX
+ }
+ })
+ setStatus('open', instance)
+}
+
+// 标记菜单的当前状态,open-已经打开,close-已经关闭
+function setStatus(status, instance) {
+ var state = instance.getState()
+ state.status = status
+}
+
+// 一次性收起滑动菜单
+function closeSwipeAction(instance, ownerInstance) {
+ var state = instance.getState()
+ // 获取所有按钮的实例,需要通过它去设置按钮的位移
+ var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
+ var len = buttons.length
+ // 处理duration单位问题
+ const duration = getDuration(state.duration)
+ instance.requestAnimationFrame(function() {
+ // 设置菜单主体内容
+ instance.setStyle({
+ 'transition': 'transform ' + duration,
+ 'transform': 'translateX(0px)',
+ '-webkit-transform': 'translateX(0px)'
+ })
+ // 设置各个隐藏的按钮为收起的状态
+ for (var i = len - 1; i >= 0; i--) {
+ buttons[i].setStyle({
+ 'transition': 'transform ' + duration,
+ 'transform': 'translateX(0px)',
+ '-webkit-transform': 'translateX(0px)'
+ })
+ }
+ })
+ setStatus('close', instance)
+}
+
+// show的状态发生变化
+function showChange(newValue, oldValue, ownerInstance, instance) {
+ var state = instance.getState()
+ if (state.disabled) return
+ // 打开或关闭单元格
+ if (newValue) {
+ openSwipeAction(instance, ownerInstance)
+ } else {
+ closeSwipeAction(instance, ownerInstance)
+ }
+}
+
+// 菜单尺寸发生变化
+function sizeChange(newValue, oldValue, ownerInstance, instance) {
+ // wxs内的局部变量快照
+ var state = instance.getState()
+ state.disabled = newValue.disabled
+ state.duration = newValue.duration
+ state.show = newValue.show
+ state.threshold = newValue.threshold
+ state.buttons = newValue.buttons
+
+ var len = state.buttons.length
+ if (len) {
+ var buttonsWidth = 0
+ var buttons = newValue.buttons
+ for (var i = 0; i < len; i++) {
+ buttonsWidth += buttons[i].width
+ }
+ }
+ state.buttonsWidth = buttonsWidth
+}
+
+module.exports = {
+ touchstart: touchstart,
+ touchmove: touchmove,
+ touchend: touchend,
+ sizeChange: sizeChange
+}
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index.wxs b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index.wxs
new file mode 100644
index 0000000..16add56
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index.wxs
@@ -0,0 +1,225 @@
+/**
+ * 此为wxs模块,只支持APP-VUE,微信和QQ小程序以及H5平台
+ * wxs内部不支持es6语法,变量只能使用var定义,无法使用解构,箭头函数等特性
+ */
+
+// 开始触摸
+function touchstart(event, ownerInstance) {
+ // 触发事件的组件的ComponentDescriptor实例
+ var instance = event.instance
+ // wxs内的局部变量快照,此快照是属于整个组件的,在touchstart和touchmove事件中都能获取到相同的结果
+ var state = instance.getState()
+ if (state.disabled) return
+ var touches = event.touches
+ // 如果进行的是多指触控,不允许进行操作
+ if (touches && touches.length > 1) return
+ // 标识当前为滑动中状态
+ state.moving = true
+ // 记录触摸开始点的坐标值
+ state.startX = touches[0].pageX
+ state.startY = touches[0].pageY
+
+ ownerInstance.callMethod('closeOther')
+}
+
+// 触摸滑动
+function touchmove(event, ownerInstance) {
+ // 触发事件的组件的ComponentDescriptor实例
+ var instance = event.instance
+ // wxs内的局部变量快照
+ var state = instance.getState()
+ if (state.disabled || !state.moving) return
+ var touches = event.touches
+ var pageX = touches[0].pageX
+ var pageY = touches[0].pageY
+ var moveX = pageX - state.startX
+ var moveY = pageY - state.startY
+ var buttonsWidth = state.buttonsWidth
+
+ // 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
+ if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {
+ event.preventDefault && event.preventDefault()
+ event.stopPropagation && event.stopPropagation()
+ }
+ // 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
+ if (Math.abs(moveX) < Math.abs(moveY)) return
+
+ // 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
+ // 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
+ // 在超出后,设置为0
+ if (state.status === 'open') {
+ // 在开启状态下,向左滑动,需忽略
+ if (moveX < 0) moveX = 0
+ // 想要收起菜单,最大能移动的距离为按钮的总宽度
+ if (moveX > buttonsWidth) moveX = buttonsWidth
+ // 如果是已经打开了的状态,向左滑动时,移动收起菜单
+ moveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)
+ } else {
+ // 关闭状态下,右滑动需忽略
+ if (moveX > 0) moveX = 0
+ // 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
+ if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
+ // 只要是在滑过程中,就不断移动单元格内容部分,从而使隐藏的菜单显示出来
+ moveSwipeAction(moveX, instance, ownerInstance)
+ }
+}
+
+// 触摸结束
+function touchend(event, ownerInstance) {
+ // 触发事件的组件的ComponentDescriptor实例
+ var instance = event.instance
+ // wxs内的局部变量快照
+ var state = instance.getState()
+ if (!state.moving || state.disabled) return
+ var touches = event.changedTouches ? event.changedTouches[0] : {}
+ var pageX = touches.pageX
+ var pageY = touches.pageY
+ var moveX = pageX - state.startX
+ if (state.status === 'open') {
+ // 在展开的状态下,继续左滑,无需操作
+ if (moveX < 0) return
+ // 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
+ if (moveX === 0) {
+ return closeSwipeAction(instance, ownerInstance)
+ }
+ // 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
+ if (Math.abs(moveX) < state.threshold) {
+ openSwipeAction(instance, ownerInstance)
+ } else {
+ // 如果滑动距离大于阈值,则执行收起逻辑
+ closeSwipeAction(instance, ownerInstance)
+ }
+ } else {
+ // 在关闭的状态下,右滑,无需操作
+ if (moveX > 0) return
+ // 理由同上
+ if (Math.abs(moveX) < state.threshold) {
+ closeSwipeAction(instance, ownerInstance)
+ } else {
+ openSwipeAction(instance, ownerInstance)
+ }
+ }
+}
+
+// 获取过渡时间
+function getDuration(value) {
+ if (value.toString().indexOf('s') >= 0) return value
+ return value > 30 ? value + 'ms' : value + 's'
+}
+
+// 滑动结束时判断滑动的方向
+function getMoveDirection(instance, ownerInstance) {
+ var state = instance.getState()
+}
+
+// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
+function moveSwipeAction(moveX, instance, ownerInstance) {
+ var state = instance.getState()
+ // 获取所有按钮的实例,需要通过它去设置按钮的位移
+ var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
+
+ // 设置菜单内容部分的偏移
+ instance.requestAnimationFrame(function() {
+ instance.setStyle({
+ // 设置translateX的值
+ 'transition': 'none',
+ transform: 'translateX(' + moveX + 'px)',
+ '-webkit-transform': 'translateX(' + moveX + 'px)'
+ })
+ })
+}
+
+// 一次性展开滑动菜单
+function openSwipeAction(instance, ownerInstance) {
+ var state = instance.getState()
+ // 获取所有按钮的实例,需要通过它去设置按钮的位移
+ var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
+ // 处理duration单位问题
+ var duration = getDuration(state.duration)
+ // 展开过程中,是向左移动,所以X的偏移应该为负值
+ var buttonsWidth = -state.buttonsWidth
+ instance.requestAnimationFrame(function() {
+ // 设置菜单主体内容
+ instance.setStyle({
+ 'transition': 'transform ' + duration,
+ 'transform': 'translateX(' + buttonsWidth + 'px)',
+ '-webkit-transform': 'translateX(' + buttonsWidth + 'px)',
+ })
+ })
+ setStatus('open', instance, ownerInstance)
+}
+
+// 标记菜单的当前状态,open-已经打开,close-已经关闭
+function setStatus(status, instance, ownerInstance) {
+ var state = instance.getState()
+ state.status = status
+ ownerInstance.callMethod('setState', status)
+}
+
+// 一次性收起滑动菜单
+function closeSwipeAction(instance, ownerInstance) {
+ var state = instance.getState()
+ // 获取所有按钮的实例,需要通过它去设置按钮的位移
+ var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
+ var len = buttons.length
+ // 处理duration单位问题
+ var duration = getDuration(state.duration)
+ instance.requestAnimationFrame(function() {
+ // 设置菜单主体内容
+ instance.setStyle({
+ 'transition': 'transform ' + duration,
+ 'transform': 'translateX(0px)',
+ '-webkit-transform': 'translateX(0px)'
+ })
+ // 设置各个隐藏的按钮为收起的状态
+ for (var i = len - 1; i >= 0; i--) {
+ buttons[i].setStyle({
+ 'transition': 'transform ' + duration,
+ 'transform': 'translateX(0px)',
+ '-webkit-transform': 'translateX(0px)'
+ })
+ }
+ })
+ setStatus('close', instance, ownerInstance)
+}
+
+// status的状态发生变化
+function statusChange(newValue, oldValue, ownerInstance, instance) {
+ var state = instance.getState()
+ if (state.disabled) return
+ // 打开或关闭单元格
+ if (newValue === 'close' && state.status === 'open') {
+ closeSwipeAction(instance, ownerInstance)
+ } else if(newValue === 'open' && state.status === 'close') {
+ openSwipeAction(instance, ownerInstance)
+ }
+}
+
+// 菜单尺寸发生变化
+function sizeChange(newValue, oldValue, ownerInstance, instance) {
+ // wxs内的局部变量快照
+ var state = instance.getState()
+ state.disabled = newValue.disabled
+ state.duration = newValue.duration
+ state.show = newValue.show
+ state.threshold = newValue.threshold
+ state.buttons = newValue.buttons
+
+ if (state.buttons) {
+ var len = state.buttons.length
+ var buttonsWidth = 0
+ var buttons = newValue.buttons
+ for (var i = 0; i < len; i++) {
+ buttonsWidth += buttons[i].width
+ }
+ }
+ state.buttonsWidth = buttonsWidth
+}
+
+module.exports = {
+ touchstart: touchstart,
+ touchmove: touchmove,
+ touchend: touchend,
+ sizeChange: sizeChange,
+ statusChange: statusChange
+}
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue - backup.js b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue - backup.js
new file mode 100644
index 0000000..da259e8
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue - backup.js
@@ -0,0 +1,264 @@
+// nvue操作dom的库,用于获取dom的尺寸信息
+const dom = uni.requireNativePlugin('dom')
+// nvue中用于操作元素动画的库,类似于uni.animation,只不过uni.animation不能用于nvue
+const animation = uni.requireNativePlugin('animation')
+import { sleep } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
+export default {
+ data() {
+ return {
+ // 是否滑动中
+ moving: false,
+ // 状态,open-打开状态,close-关闭状态
+ status: 'close',
+ // 开始触摸点的X和Y轴坐标
+ startX: 0,
+ startY: 0,
+ // 所有隐藏按钮的尺寸信息数组
+ buttons: [],
+ // 所有按钮的总宽度
+ buttonsWidth: 0,
+ // 记录上一次移动的位置值
+ moveX: 0,
+ // 记录上一次滑动的位置,用于前后两次做对比,如果移动的距离小于某一阈值,则认为前后之间没有移动,为了解决可能存在的通信阻塞问题
+ lastX: 0
+ }
+ },
+ computed: {
+ // 获取过渡时间
+ getDuratin() {
+ let duration = String(this.duration)
+ // 如果ms为单位,返回ms的数值部分
+ if (duration.indexOf('ms') >= 0) return parseInt(duration)
+ // 如果s为单位,为了得到ms的数值,需要乘以1000
+ if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000
+ // 如果值传了数值,且小于30,认为是s单位
+ duration = Number(duration)
+ return duration < 30 ? duration * 1000 : duration
+ }
+ },
+ watch: {
+ show: {
+ immediate: true,
+ handler(n) {
+
+ }
+ }
+ },
+ mounted() {
+ sleep(20).then(() => {
+ this.queryRect()
+ })
+ },
+ methods: {
+ close() {
+ this.closeSwipeAction()
+ },
+ // 触摸单元格
+ touchstart(event) {
+ if (this.disabled) return
+ this.closeOther()
+ const { touches } = event
+ // 记录触摸开始点的坐标值
+ this.startX = touches[0].pageX
+ this.startY = touches[0].pageY
+ },
+ // // 触摸滑动
+ touchmove(event) {
+ if (this.disabled) return
+ const { touches } = event
+ const { pageX } = touches[0]
+ const { pageY } = touches[0]
+ let moveX = pageX - this.startX
+ const moveY = pageY - this.startY
+ const { buttonsWidth } = this
+ const len = this.buttons.length
+
+ // 判断前后两次的移动距离,如果小于一定值,则不进行移动处理
+ if (Math.abs(pageX - this.lastX) < 0.3) return
+ this.lastX = pageX
+
+ // 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
+ if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > this.threshold) {
+ event.stopPropagation()
+ }
+ // 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
+ if (Math.abs(moveX) < Math.abs(moveY)) return
+
+ // 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
+ // 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
+ // 在超出后,设置为0
+ if (this.status === 'open') {
+ // 在开启状态下,向左滑动,需忽略
+ if (moveX < 0) moveX = 0
+ // 想要收起菜单,最大能移动的距离为按钮的总宽度
+ if (moveX > buttonsWidth) moveX = buttonsWidth
+ // 如果是已经打开了的状态,向左滑动时,移动收起菜单
+ this.moveSwipeAction(-buttonsWidth + moveX)
+ } else {
+ // 关闭状态下,右滑动需忽略
+ if (moveX > 0) moveX = 0
+ // 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
+ if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
+ // 只要是在滑过程中,就不断移动菜单的内容部分,从而使隐藏的菜单显示出来
+ this.moveSwipeAction(moveX)
+ }
+ },
+ // 单元格结束触摸
+ touchend(event) {
+ if (this.disabled) return
+ const touches = event.changedTouches ? event.changedTouches[0] : {}
+ const { pageX } = touches
+ const { pageY } = touches
+ const { buttonsWidth } = this
+ this.moveX = pageX - this.startX
+ if (this.status === 'open') {
+ // 在展开的状态下,继续左滑,无需操作
+ if (this.moveX < 0) this.moveX = 0
+ if (this.moveX > buttonsWidth) this.moveX = buttonsWidth
+ // 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
+ if (this.moveX === 0) {
+ return this.closeSwipeAction()
+ }
+ // 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
+ if (Math.abs(this.moveX) < this.threshold) {
+ this.openSwipeAction()
+ } else {
+ // 如果滑动距离大于阈值,则执行收起逻辑
+ this.closeSwipeAction()
+ }
+ } else {
+ // 在关闭的状态下,右滑,无需操作
+ if (this.moveX >= 0) this.moveX = 0
+ if (this.moveX <= -buttonsWidth) this.moveX = -buttonsWidth
+ // 理由同上
+ if (Math.abs(this.moveX) < this.threshold) {
+ this.closeSwipeAction()
+ } else {
+ this.openSwipeAction()
+ }
+ }
+ },
+ // 移动滑动选择器内容区域,同时显示出其隐藏的菜单
+ moveSwipeAction(moveX) {
+ if (this.moving) return
+ this.moving = true
+
+ let previewButtonsMoveX = 0
+ const len = this.buttons.length
+ animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
+ styles: {
+ transform: `translateX(${moveX}px)`
+ },
+ timingFunction: 'linear'
+ }, () => {
+ this.moving = false
+ })
+ // 按钮的组的长度
+ for (let i = len - 1; i >= 0; i--) {
+ const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
+ // 通过比例,得出元素自身该移动的距离
+ const translateX = this.buttons[i].width / this.buttonsWidth * moveX
+ // 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
+ const realTranslateX = translateX + previewButtonsMoveX
+ animation.transition(buttonRef, {
+ styles: {
+ transform: `translateX(${realTranslateX}px)`
+ },
+ duration: 0,
+ delay: 0,
+ timingFunction: 'linear'
+ }, () => {})
+ // 记录本按钮之前的所有按钮的移动距离之和
+ previewButtonsMoveX += translateX
+ }
+ },
+ // 关闭菜单
+ closeSwipeAction() {
+ if (this.status === 'close') return
+ this.moving = true
+ const { buttonsWidth } = this
+ animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
+ styles: {
+ transform: 'translateX(0px)'
+ },
+ duration: this.getDuratin,
+ timingFunction: 'ease-in-out'
+ }, () => {
+ this.status = 'close'
+ this.moving = false
+ this.closeHandler()
+ })
+ // 按钮的组的长度
+ const len = this.buttons.length
+ for (let i = len - 1; i >= 0; i--) {
+ const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
+ // 如果不满足边界条件,返回
+ if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return
+
+ animation.transition(buttonRef, {
+ styles: {
+ transform: 'translateX(0px)'
+ },
+ duration: this.getDuratin,
+ timingFunction: 'ease-in-out'
+ }, () => {})
+ }
+ },
+ // 打开菜单
+ openSwipeAction() {
+ if (this.status === 'open') return
+ this.moving = true
+ const buttonsWidth = -this.buttonsWidth
+ let previewButtonsMoveX = 0
+ animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
+ styles: {
+ transform: `translateX(${buttonsWidth}px)`
+ },
+ duration: this.getDuratin,
+ timingFunction: 'ease-in-out'
+ }, () => {
+ this.status = 'open'
+ this.moving = false
+ this.openHandler()
+ })
+ // 按钮的组的长度
+ const len = this.buttons.length
+ for (let i = len - 1; i >= 0; i--) {
+ const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
+ // 如果不满足边界条件,返回
+ if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return
+ // 通过比例,得出元素自身该移动的距离
+ const translateX = this.buttons[i].width / this.buttonsWidth * buttonsWidth
+ // 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
+ const realTranslateX = translateX + previewButtonsMoveX
+ animation.transition(buttonRef, {
+ styles: {
+ transform: `translateX(${realTranslateX}px)`
+ },
+ duration: this.getDuratin,
+ timingFunction: 'ease-in-out'
+ }, () => {})
+ previewButtonsMoveX += translateX
+ }
+ },
+ // 查询按钮节点信息
+ queryRect() {
+ // 历遍所有按钮数组,通过getRectByDom返回一个promise
+ const promiseAll = this.rightOptions.map((item, index) => this.getRectByDom(this.$refs[`uv-swipe-action-item__right__button-${index}`][0]))
+ // 通过promise.all方法,让所有按钮的查询结果返回一个数组的形式
+ Promise.all(promiseAll).then((sizes) => {
+ this.buttons = sizes
+ // 计算所有按钮总宽度
+ this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)
+ })
+ },
+ // 通过nvue的dom模块,查询节点信息
+ getRectByDom(ref) {
+ return new Promise((resolve) => {
+ dom.getComponentRect(ref, (res) => {
+ resolve(res.size)
+ })
+ })
+ }
+ }
+}
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue.js b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue.js
new file mode 100644
index 0000000..6eaf491
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue.js
@@ -0,0 +1,182 @@
+// nvue操作dom的库,用于获取dom的尺寸信息
+const dom = uni.requireNativePlugin('dom');
+const bindingX = uni.requireNativePlugin('bindingx');
+const animation = uni.requireNativePlugin('animation');
+import { getDuration, getPx } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
+export default {
+ data() {
+ return {
+ // 所有按钮的总宽度
+ buttonsWidth: 0,
+ // 是否正在移动中
+ moving: false
+ }
+ },
+ computed: {
+ // 获取过渡时间
+ getDuratin() {
+ let duration = String(this.duration)
+ // 如果ms为单位,返回ms的数值部分
+ if (duration.indexOf('ms') >= 0) return parseInt(duration)
+ // 如果s为单位,为了得到ms的数值,需要乘以1000
+ if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000
+ // 如果值传了数值,且小于30,认为是s单位
+ duration = Number(duration)
+ return duration < 30 ? duration * 1000 : duration
+ }
+ },
+ watch: {
+ show(n) {
+ if(n) {
+ this.moveCellByAnimation('open')
+ } else {
+ this.moveCellByAnimation('close')
+ }
+ }
+ },
+ mounted() {
+ setTimeout(()=>{
+ this.initialize()
+ },20)
+ },
+ methods: {
+ initialize() {
+ this.queryRect()
+ },
+ // 关闭单元格,用于打开一个,自动关闭其他单元格的场景
+ closeHandler() {
+ if(this.status === 'open') {
+ // 如果在打开状态下,进行点击的话,直接关闭单元格
+ return this.moveCellByAnimation('close') && this.unbindBindingX()
+ }
+ },
+ // 点击单元格
+ clickHandler() {
+ // 如果在移动中被点击,进行忽略
+ if(this.moving) return
+ // 尝试关闭其他打开的单元格
+ this.parent && this.parent.closeOther(this)
+ if(this.status === 'open') {
+ // 如果在打开状态下,进行点击的话,直接关闭单元格
+ return this.moveCellByAnimation('close') && this.unbindBindingX()
+ }
+ },
+ // 滑动单元格
+ onTouchstart(e) {
+ // 如果当前正在移动中,或者disabled状态,则返回
+ if(this.moving || this.disabled) {
+ return this.unbindBindingX()
+ }
+ if(this.status === 'open') {
+ // 如果在打开状态下,进行点击的话,直接关闭单元格
+ return this.moveCellByAnimation('close') && this.unbindBindingX()
+ }
+ // 特殊情况下,e可能不为一个对象
+ e?.stopPropagation && e.stopPropagation()
+ e?.preventDefault && e.preventDefault()
+ this.moving = true
+ // 获取元素ref
+ const content = this.getContentRef()
+ let expression = `min(max(${-this.buttonsWidth}, x), 0)`
+ // 尝试关闭其他打开的单元格
+ this.parent && this.parent.closeOther(this)
+ // 阿里为了KPI而开源的BindingX
+ this.panEvent = bindingX.bind({
+ anchor: content,
+ eventType: 'pan',
+ props: [{
+ element: content,
+ // 绑定width属性,设置其宽度值
+ property: 'transform.translateX',
+ expression
+ }]
+ }, (res) => {
+ this.moving = false
+ if (res.state === 'end' || res.state === 'exit') {
+ const deltaX = res.deltaX
+ if(deltaX <= -this.buttonsWidth || deltaX >= 0) {
+ // 如果触摸滑动的过程中,大于单元格的总宽度,或者大于0,意味着已经动过滑动达到了打开或者关闭的状态
+ // 这里直接进行状态的标记
+ this.$nextTick(() => {
+ this.status = deltaX <= -this.buttonsWidth ? 'open' : 'close'
+ })
+ } else if(Math.abs(deltaX) > getPx(this.threshold)) {
+ // 在移动大于阈值、并且小于总按钮宽度时,进行自动打开或者关闭
+ // 移动距离大于0时,意味着需要关闭状态
+ if(Math.abs(deltaX) < this.buttonsWidth) {
+ this.moveCellByAnimation(deltaX > 0 ? 'close' : 'open')
+ }
+ } else {
+ // 在小于阈值时,进行关闭操作(如果在打开状态下,将不会执行bindingX)
+ this.moveCellByAnimation('close')
+ }
+ }
+ })
+ },
+ // 释放bindingX
+ unbindBindingX() {
+ // 释放上一次的资源
+ if (this?.panEvent?.token != 0) {
+ bindingX.unbind({
+ token: this.panEvent?.token,
+ // pan为手势事件
+ eventType: 'pan'
+ })
+ }
+ },
+ // 查询按钮节点信息
+ queryRect() {
+ // 历遍所有按钮数组,通过getRectByDom返回一个promise
+ const promiseAll = this.options.map(async(item, index) => {
+ return await this.getRectByDom(this.$refs[`uv-swipe-action-item__right__button-${index}`][0])
+ })
+ // 通过promise.all方法,让所有按钮的查询结果返回一个数组的形式
+ Promise.all(promiseAll).then(sizes => {
+ this.buttons = sizes
+ // 计算所有按钮总宽度
+ this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)
+ })
+ },
+ // 通过nvue的dom模块,查询节点信息
+ getRectByDom(ref) {
+ return new Promise(resolve => {
+ dom.getComponentRect(ref, res => {
+ resolve(res.size)
+ })
+ })
+ },
+ // 移动单元格到左边或者右边尽头
+ moveCellByAnimation(status = 'open') {
+ if(this.moving) return
+ // 标识当前状态
+ this.moveing = true
+ const content = this.getContentRef()
+ const x = status === 'open' ? -this.buttonsWidth : 0
+ animation.transition(content, {
+ styles: {
+ transform: `translateX(${x}px)`,
+ },
+ duration: getDuration(this.duration, false),
+ timingFunction: 'ease-in-out'
+ }, () => {
+ this.moving = false
+ this.status = status
+ this.unbindBindingX()
+ })
+ },
+ // 获取元素ref
+ getContentRef() {
+ return this.$refs['uv-swipe-action-item__content'].ref
+ }
+ },
+ // #ifdef VUE2
+ beforeDestroy() {
+ this.unbindBindingX()
+ },
+ // #endif
+ // #ifdef VUE3
+ unmounted() {
+ this.unbindBindingX()
+ }
+ // #endif
+}
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/props.js b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/props.js
new file mode 100644
index 0000000..5b321e2
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/props.js
@@ -0,0 +1,40 @@
+export default {
+ props: {
+ // 控制打开或者关闭
+ show: {
+ type: Boolean,
+ default: false
+ },
+ // 标识符,如果是v-for,可用index索引值
+ name: {
+ type: [String, Number],
+ default: ''
+ },
+ // 是否禁用
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否自动关闭其他swipe按钮组
+ autoClose: {
+ type: Boolean,
+ default: true
+ },
+ // 滑动距离阈值,只有大于此值,才被认为是要打开菜单
+ threshold: {
+ type: Number,
+ default: 20
+ },
+ // 右侧按钮内容
+ options: {
+ type: Array,
+ default: () => []
+ },
+ // 动画过渡时间,单位ms
+ duration: {
+ type: [String, Number],
+ default: 300
+ },
+ ...uni.$uv?.props?.swipeActionItem
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/uv-swipe-action-item.vue b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/uv-swipe-action-item.vue
new file mode 100644
index 0000000..8fd503a
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/uv-swipe-action-item.vue
@@ -0,0 +1,200 @@
+<template>
+ <view class="uv-swipe-action-item" ref="uv-swipe-action-item">
+ <view class="uv-swipe-action-item__right">
+ <slot name="button">
+ <view v-for="(item,index) in options" :key="index" class="uv-swipe-action-item__right__button"
+ :ref="`uv-swipe-action-item__right__button-${index}`" :style="[{
+ alignItems: item.style && item.style.borderRadius ? 'center' : 'stretch'
+ }]" @tap="buttonClickHandler(item, index)">
+ <view class="uv-swipe-action-item__right__button__wrapper" :style="[{
+ backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
+ borderRadius: item.style && item.style.borderRadius ? item.style.borderRadius : '0',
+ padding: item.style && item.style.borderRadius ? '0' : '0 15px',
+ }, item.style]">
+ <uv-icon v-if="item.icon" :name="item.icon"
+ :color="item.style && item.style.color ? item.style.color : '#ffffff'"
+ :size="item.iconSize ? $uv.addUnit(item.iconSize) : item.style && item.style.fontSize ? $uv.getPx(item.style.fontSize) * 1.2 : 17"
+ :customStyle="{
+ marginRight: item.text ? '2px' : 0
+ }"></uv-icon>
+ <text v-if="item.text" class="uv-swipe-action-item__right__button__wrapper__text uv-line-1"
+ :style="[{
+ color: item.style && item.style.color ? item.style.color : '#ffffff',
+ fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px',
+ lineHeight: item.style && item.style.fontSize ? item.style.fontSize : '16px',
+ }]">{{ item.text }}</text>
+ </view>
+ </view>
+ </slot>
+ </view>
+ <!-- #ifndef APP-NVUE -->
+ <view class="uv-swipe-action-item__content" @touchstart="wxs.touchstart" @touchmove="wxs.touchmove"
+ @touchend="wxs.touchend" :status="status" :change:status="wxs.statusChange" :size="size"
+ :change:size="wxs.sizeChange">
+ <!-- #endif -->
+ <!-- #ifdef APP-NVUE -->
+ <view class="uv-swipe-action-item__content" ref="uv-swipe-action-item__content" @panstart="onTouchstart"
+ @tap="clickHandler">
+ <!-- #endif -->
+ <slot />
+ </view>
+ </view>
+</template>
+<!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->
+<script src="./index.wxs" module="wxs" lang="wxs"></script>
+<!-- #endif -->
+<script>
+ import touch from '@/uni_modules/uv-ui-tools/libs/mixin/touch.js'
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ import nvue from './nvue.js';
+ // #endif
+ // #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ
+ import wxs from './wxs.js';
+ // #endif
+ /**
+ * SwipeActionItem 滑动单元格子组件
+ * @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
+ * @tutorial https://www.uvui.cn/components/swipeAction.html
+ * @property {Boolean} show 控制打开或者关闭(默认 false )
+ * @property {String | Number} index 标识符,如果是v-for,可用index索引
+ * @property {Boolean} disabled 是否禁用(默认 false )
+ * @property {Boolean} autoClose 是否自动关闭其他swipe按钮组(默认 true )
+ * @property {Number} threshold 滑动距离阈值,只有大于此值,才被认为是要打开菜单(默认 30 )
+ * @property {Array} options 右侧按钮内容
+ * @property {String | Number} duration 动画过渡时间,单位ms(默认 350 )
+ * @event {Function(index)} open 组件打开时触发
+ * @event {Function(index)} close 组件关闭时触发
+ * @example <uv-swipe-action><uv-swipe-action-item :options="options1" ></uv-swipe-action-item></uv-swipe-action>
+ */
+ export default {
+ name: 'uv-swipe-action-item',
+ emits: ['click'],
+ // #ifndef APP-NVUE
+ mixins: [mpMixin, mixin, props, touch],
+ // #endif
+ // #ifdef APP-NVUE
+ mixins: [mpMixin, mixin, props, nvue , touch],
+ // #endif
+ // #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ
+ mixins: [mpMixin, mixin, props, touch, wxs],
+ // #endif
+ data() {
+ return {
+ // 按钮的尺寸信息
+ size: {},
+ // 父组件uv-swipe-action的参数
+ parentData: {
+ autoClose: true,
+ },
+ // 当前状态,open-打开,close-关闭
+ status: 'close',
+ }
+ },
+ watch: {
+ // 由于wxs无法直接读取外部的值,需要在外部值变化时,重新执行赋值逻辑
+ wxsInit(newValue, oldValue) {
+ this.queryRect()
+ }
+ },
+ computed: {
+ wxsInit() {
+ return [this.disabled, this.autoClose, this.threshold, this.options, this.duration]
+ }
+ },
+ mounted() {
+ // #ifdef MP-TOUTIAO
+ this.$uv.error('抖音小程序暂不支持wxs,故该组件暂不支持抖音小程序');
+ // #endif
+ this.init()
+ },
+ methods: {
+ init() {
+ // 初始化父组件数据
+ this.updateParentData()
+ // #ifndef APP-NVUE
+ this.$uv.sleep().then(() => {
+ this.queryRect()
+ })
+ // #endif
+ },
+ updateParentData() {
+ // 此方法在mixin中
+ this.getParentData('uv-swipe-action')
+ },
+ // #ifndef APP-NVUE
+ // 查询节点
+ queryRect() {
+ this.$uvGetRect('.uv-swipe-action-item__right__button', true).then(buttons => {
+ this.size = {
+ buttons,
+ show: this.show,
+ disabled: this.disabled,
+ threshold: this.threshold,
+ duration: this.duration
+ }
+ })
+ },
+ // #endif
+ // 按钮被点击
+ buttonClickHandler(item, index) {
+ this.$emit('click', {
+ index,
+ name: this.name
+ })
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-lines: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-swipe-action-item {
+ position: relative;
+ overflow: hidden;
+ /* #ifndef APP-NVUE || MP-WEIXIN */
+ touch-action: pan-y;
+ /* #endif */
+
+ &__content {
+ background-color: #FFFFFF;
+ z-index: 10;
+ }
+
+ &__right {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ @include flex;
+
+ &__button {
+ @include flex;
+ justify-content: center;
+ overflow: hidden;
+ align-items: center;
+
+ &__wrapper {
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 15px;
+
+ &__text {
+ @include flex;
+ align-items: center;
+ color: #FFFFFF;
+ font-size: 15px;
+ text-align: center;
+ justify-content: center;
+ }
+ }
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/wxs.js b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/wxs.js
new file mode 100644
index 0000000..ee49c10
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action-item/wxs.js
@@ -0,0 +1,15 @@
+export default {
+ methods: {
+ // 关闭时执行
+ closeHandler() {
+ this.status = 'close'
+ },
+ setState(status) {
+ this.status = status
+ },
+ closeOther() {
+ // 尝试关闭其他打开的单元格
+ this.parent && this.parent.closeOther(this)
+ }
+ }
+}
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action/props.js b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action/props.js
new file mode 100644
index 0000000..7d06765
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action/props.js
@@ -0,0 +1,10 @@
+export default {
+ props: {
+ // 是否自动关闭其他swipe按钮组
+ autoClose: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.swipeAction
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action/uv-swipe-action.vue b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action/uv-swipe-action.vue
new file mode 100644
index 0000000..ed3e9f9
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/components/uv-swipe-action/uv-swipe-action.vue
@@ -0,0 +1,65 @@
+<template>
+ <view class="uv-swipe-action">
+ <slot></slot>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * SwipeAction 滑动单元格
+ * @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
+ * @tutorial https://www.uvui.cn/components/swipeAction.html
+ * @property {Boolean} autoClose 是否自动关闭其他swipe按钮组
+ * @event {Function(index)} click 点击组件时触发
+ * @example <uv-swipe-action><uv-swipe-action-item :rightOptions="options1" ></uv-swipe-action-item></uv-swipe-action>
+ */
+ export default {
+ name: 'uv-swipe-action',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {}
+ },
+ provide() {
+ return {
+ swipeAction: this
+ }
+ },
+ computed: {
+ // 这里computed的变量,都是子组件uv-swipe-action-item需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
+ // 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(uv-swipe-action-item)
+ // 拉取父组件新的变化后的参数
+ parentData() {
+ return [this.autoClose]
+ }
+ },
+ watch: {
+ // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+ parentData() {
+ if (this.children.length) {
+ this.children.map(child => {
+ // 判断子组件(uv-swipe-action-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+ typeof(child.updateParentData) === 'function' && child.updateParentData()
+ })
+ }
+ },
+ },
+ created() {
+ this.children = []
+ },
+ methods: {
+ closeOther(child) {
+ if (this.autoClose) {
+ // 历遍所有的单元格,找出非当前操作中的单元格,进行关闭
+ this.children.map((item, index) => {
+ if (child !== item) {
+ item.closeHandler()
+ }
+ })
+ }
+ }
+ }
+ }
+</script>
diff --git a/ledApp/uni_modules/uv-swipe-action/package.json b/ledApp/uni_modules/uv-swipe-action/package.json
new file mode 100644
index 0000000..9eef69b
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-swipe-action",
+ "displayName": "uv-swipe-action 滑动单元格 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.4",
+ "description": "滑动单元格组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。",
+ "keywords": [
+ "uv-swipe-action",
+ "uvui",
+ "uv-ui",
+ "swipe-action",
+ "滑动单元格"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-swipe-action/readme.md b/ledApp/uni_modules/uv-swipe-action/readme.md
new file mode 100644
index 0000000..3180696
--- /dev/null
+++ b/ledApp/uni_modules/uv-swipe-action/readme.md
@@ -0,0 +1,11 @@
+## SwipeAction 滑动单元格
+
+> **组件名:uv-swipe-action**
+
+该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。
+
+### <a href="https://www.uvui.cn/components/swipeAction.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-swiper/changelog.md b/ledApp/uni_modules/uv-swiper/changelog.md
new file mode 100644
index 0000000..30f26c6
--- /dev/null
+++ b/ledApp/uni_modules/uv-swiper/changelog.md
@@ -0,0 +1,16 @@
+## 1.0.6(2023-12-20)
+1. 优化
+## 1.0.5(2023-08-24)
+1. 修复标题文字过多未隐藏掉的BUG
+## 1.0.4(2023-07-24)
+1. 增加 滑动方向是否为纵向 属性vertical
+## 1.0.3(2023-06-27)
+1. 增加titleStyle属性,方便修改标题样式
+2. 标题上去掉是否是图片的判断,避免无后缀的图片不显示
+## 1.0.2(2023-06-01)
+1. 修复点击触发两次事件的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-swiper 轮播图,走马灯
diff --git a/ledApp/uni_modules/uv-swiper/components/uv-swiper-indicator/props.js b/ledApp/uni_modules/uv-swiper/components/uv-swiper-indicator/props.js
new file mode 100644
index 0000000..8f305a0
--- /dev/null
+++ b/ledApp/uni_modules/uv-swiper/components/uv-swiper-indicator/props.js
@@ -0,0 +1,30 @@
+export default {
+ props: {
+ // 轮播的长度
+ length: {
+ type: [String, Number],
+ default: 0
+ },
+ // 当前处于活动状态的轮播的索引
+ current: {
+ type: [String, Number],
+ default: 0
+ },
+ // 指示器非激活颜色
+ indicatorActiveColor: {
+ type: String,
+ default: ''
+ },
+ // 指示器的激活颜色
+ indicatorInactiveColor: {
+ type: String,
+ default: ''
+ },
+ // 指示器模式,line-线型,dot-点型
+ indicatorMode: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.swiperIndicator
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-swiper/components/uv-swiper-indicator/uv-swiper-indicator.vue b/ledApp/uni_modules/uv-swiper/components/uv-swiper-indicator/uv-swiper-indicator.vue
new file mode 100644
index 0000000..b326f43
--- /dev/null
+++ b/ledApp/uni_modules/uv-swiper/components/uv-swiper-indicator/uv-swiper-indicator.vue
@@ -0,0 +1,112 @@
+<template>
+ <view class="uv-swiper-indicator">
+ <view
+ class="uv-swiper-indicator__wrapper"
+ v-if="indicatorMode === 'line'"
+ :class="[`uv-swiper-indicator__wrapper--${indicatorMode}`]"
+ :style="{
+ width: $uv.addUnit(lineWidth * length),
+ backgroundColor: indicatorInactiveColor
+ }"
+ >
+ <view
+ class="uv-swiper-indicator__wrapper--line__bar"
+ :style="[lineStyle]"
+ ></view>
+ </view>
+ <view
+ class="uv-swiper-indicator__wrapper"
+ v-if="indicatorMode === 'dot'"
+ >
+ <view
+ class="uv-swiper-indicator__wrapper__dot"
+ v-for="(item, index) in length"
+ :key="index"
+ :class="[index === current && 'uv-swiper-indicator__wrapper__dot--active']"
+ :style="[dotStyle(index)]"
+ >
+
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * SwiperIndicator 轮播图指示器
+ * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用,
+ * @tutorial https://www.uvui.cn/components/swiper.html
+ * @property {String | Number} length 轮播的长度(默认 0 )
+ * @property {String | Number} current 当前处于活动状态的轮播的索引(默认 0 )
+ * @property {String} indicatorActiveColor 指示器非激活颜色
+ * @property {String} indicatorInactiveColor 指示器的激活颜色
+ * @property {String} indicatorMode 指示器模式(默认 'line' )
+ * @example <uv-swiper :list="list4" indicator keyName="url" :autoplay="false"></uv-swiper>
+ */
+ export default {
+ name: 'uv-swiper-indicator',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ lineWidth: 22
+ }
+ },
+ computed: {
+ // 指示器为线型的样式
+ lineStyle() {
+ let style = {}
+ style.width = this.$uv.addUnit(this.lineWidth)
+ style.transform = `translateX(${ this.$uv.addUnit(this.current * this.lineWidth) })`
+ style.backgroundColor = this.indicatorActiveColor
+ return style
+ },
+ // 指示器为点型的样式
+ dotStyle() {
+ return index => {
+ let style = {}
+ style.backgroundColor = index === this.current ? this.indicatorActiveColor : this.indicatorInactiveColor
+ return style
+ }
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-swiper-indicator {
+
+ &__wrapper {
+ @include flex;
+
+ &--line {
+ border-radius: 100px;
+ height: 4px;
+
+ &__bar {
+ width: 22px;
+ height: 4px;
+ border-radius: 100px;
+ background-color: #FFFFFF;
+ transition: transform 0.3s;
+ }
+ }
+
+ &__dot {
+ width: 5px;
+ height: 5px;
+ border-radius: 100px;
+ margin: 0 4px;
+
+ &--active {
+ width: 12px;
+ }
+ }
+
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-swiper/components/uv-swiper/props.js b/ledApp/uni_modules/uv-swiper/components/uv-swiper/props.js
new file mode 100644
index 0000000..66e4787
--- /dev/null
+++ b/ledApp/uni_modules/uv-swiper/components/uv-swiper/props.js
@@ -0,0 +1,136 @@
+export default {
+ props: {
+ // 列表数组,元素可为字符串,如为对象可通过keyName指定目标属性名
+ list: {
+ type: Array,
+ default: () => []
+ },
+ // 是否显示面板指示器
+ indicator: {
+ type: Boolean,
+ default: false
+ },
+ // 指示器非激活颜色
+ indicatorActiveColor: {
+ type: String,
+ default: '#fff'
+ },
+ // 指示器的激活颜色
+ indicatorInactiveColor: {
+ type: String,
+ default: 'rgba(255, 255, 255, 0.35)'
+ },
+ // 指示器样式,可通过bottom,left,right进行定位
+ indicatorStyle: {
+ type: [String, Object],
+ default: ''
+ },
+ // 指示器模式,line-线型,dot-点型
+ indicatorMode: {
+ type: String,
+ default: 'line'
+ },
+ // 是否自动切换
+ autoplay: {
+ type: Boolean,
+ default: true
+ },
+ // 当前所在滑块的 index
+ current: {
+ type: [String, Number],
+ default: 0
+ },
+ // 当前所在滑块的 item-id ,不能与 current 被同时指定
+ currentItemId: {
+ type: String,
+ default: ''
+ },
+ // 滑块自动切换时间间隔
+ interval: {
+ type: [String, Number],
+ default: 3000
+ },
+ // 滑块切换过程所需时间
+ duration: {
+ type: [String, Number],
+ default: 300
+ },
+ // 播放到末尾后是否重新回到开头
+ circular: {
+ type: Boolean,
+ default: false
+ },
+ // 滑动方向是否为纵向
+ vertical: {
+ type: Boolean,
+ default: false
+ },
+ // 前边距,可用于露出前一项的一小部分,nvue和支付宝不支持
+ previousMargin: {
+ type: [String, Number],
+ default: 0
+ },
+ // 后边距,可用于露出后一项的一小部分,nvue和支付宝不支持
+ nextMargin: {
+ type: [String, Number],
+ default: 0
+ },
+ // 当开启时,会根据滑动速度,连续滑动多屏,支付宝不支持
+ acceleration: {
+ type: Boolean,
+ default: false
+ },
+ // 同时显示的滑块数量,nvue、支付宝小程序不支持
+ displayMultipleItems: {
+ type: Number,
+ default: 1
+ },
+ // 指定swiper切换缓动动画类型,有效值:default、linear、easeInCubic、easeOutCubic、easeInOutCubic
+ // 只对微信小程序有效
+ easingFunction: {
+ type: String,
+ default: 'default'
+ },
+ // list数组中指定对象的目标属性名
+ keyName: {
+ type: String,
+ default: 'url'
+ },
+ // 图片的裁剪模式
+ imgMode: {
+ type: String,
+ default: 'aspectFill'
+ },
+ // 组件高度
+ height: {
+ type: [String, Number],
+ default: 130
+ },
+ // 背景颜色
+ bgColor: {
+ type: String,
+ default: '#f3f4f6'
+ },
+ // 组件圆角,数值或带单位的字符串
+ radius: {
+ type: [String, Number],
+ default: 4
+ },
+ // 是否加载中
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示标题,要求数组对象中有title属性
+ showTitle: {
+ type: Boolean,
+ default: false
+ },
+ // 显示的标题样式
+ titleStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ ...uni.$uv?.props?.swiper
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-swiper/components/uv-swiper/uv-swiper.vue b/ledApp/uni_modules/uv-swiper/components/uv-swiper/uv-swiper.vue
new file mode 100644
index 0000000..f897ff9
--- /dev/null
+++ b/ledApp/uni_modules/uv-swiper/components/uv-swiper/uv-swiper.vue
@@ -0,0 +1,263 @@
+<template>
+ <view
+ class="uv-swiper"
+ :style="{
+ backgroundColor: bgColor,
+ height: $uv.addUnit(height),
+ borderRadius: $uv.addUnit(radius)
+ }"
+ >
+ <view
+ class="uv-swiper__loading"
+ v-if="loading"
+ >
+ <uv-loading-icon mode="circle"></uv-loading-icon>
+ </view>
+ <swiper
+ v-else
+ class="uv-swiper__wrapper"
+ :style="{
+ height: $uv.addUnit(height),
+ flex: 1
+ }"
+ @change="change"
+ :circular="circular"
+ :vertical="vertical"
+ :interval="interval"
+ :duration="duration"
+ :autoplay="autoplay"
+ :current="current"
+ :currentItemId="currentItemId"
+ :previousMargin="$uv.addUnit(previousMargin)"
+ :nextMargin="$uv.addUnit(nextMargin)"
+ :acceleration="acceleration"
+ :displayMultipleItems="displayMultipleItems"
+ :easingFunction="easingFunction"
+ >
+ <swiper-item
+ class="uv-swiper__wrapper__item"
+ v-for="(item, index) in list"
+ :key="index"
+ >
+ <view
+ class="uv-swiper__wrapper__item__wrapper"
+ :style="[itemStyle(index)]"
+ >
+ <!-- 在nvue中,image图片的宽度默认为屏幕宽度,需要通过flex:1撑开,另外必须设置高度才能显示图片 -->
+ <image
+ class="uv-swiper__wrapper__item__wrapper__image"
+ v-if="getItemType(item) === 'image'"
+ :src="getSource(item)"
+ :mode="imgMode"
+ @tap="clickHandler(index)"
+ :style="{
+ height: $uv.addUnit(height),
+ borderRadius: $uv.addUnit(radius)
+ }"
+ ></image>
+ <video
+ class="uv-swiper__wrapper__item__wrapper__video"
+ v-if="getItemType(item) === 'video'"
+ :id="`video-${index}`"
+ :enable-progress-gesture="false"
+ :src="getSource(item)"
+ :poster="getPoster(item)"
+ :title="showTitle && $uv.test.object(item) && item.title ? item.title : ''"
+ :style="{
+ height: $uv.addUnit(height)
+ }"
+ controls
+ @tap="clickHandler(index)"
+ ></video>
+ <text
+ v-if="showTitle && $uv.test.object(item) && item.title"
+ class="uv-swiper__wrapper__item__wrapper__title uv-line-1"
+ :style="[$uv.addStyle(titleStyle)]"
+ >{{ item.title }}</text>
+ </view>
+ </swiper-item>
+ </swiper>
+ <view class="uv-swiper__indicator" :style="[$uv.addStyle(indicatorStyle)]">
+ <slot name="indicator">
+ <uv-swiper-indicator
+ v-if="!loading && indicator && !showTitle"
+ :indicatorActiveColor="indicatorActiveColor"
+ :indicatorInactiveColor="indicatorInactiveColor"
+ :length="list.length"
+ :current="currentIndex"
+ :indicatorMode="indicatorMode"
+ ></uv-swiper-indicator>
+ </slot>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Swiper 轮播图
+ * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用,
+ * @tutorial https://www.uvui.cn/components/swiper.html
+ * @property {Array} list 轮播图数据
+ * @property {Boolean} indicator 是否显示面板指示器(默认 false )
+ * @property {String} indicatorActiveColor 指示器非激活颜色(默认 '#FFFFFF' )
+ * @property {String} indicatorInactiveColor 指示器的激活颜色(默认 'rgba(255, 255, 255, 0.35)' )
+ * @property {String | Object} indicatorStyle 指示器样式,可通过bottom,left,right进行定位
+ * @property {String} indicatorMode 指示器模式(默认 'line' )
+ * @property {Boolean} autoplay 是否自动切换(默认 true )
+ * @property {String | Number} current 当前所在滑块的 index(默认 0 )
+ * @property {String} currentItemId 当前所在滑块的 item-id ,不能与 current 被同时指定
+ * @property {String | Number} interval 滑块自动切换时间间隔(ms)(默认 3000 )
+ * @property {String | Number} duration 滑块切换过程所需时间(ms)(默认 300 )
+ * @property {Boolean} circular 播放到末尾后是否重新回到开头(默认 false )
+ * @property {String | Number} previousMargin 前边距,可用于露出前一项的一小部分,nvue和支付宝不支持(默认 0 )
+ * @property {String | Number} nextMargin 后边距,可用于露出后一项的一小部分,nvue和支付宝不支持(默认 0 )
+ * @property {Boolean} acceleration 当开启时,会根据滑动速度,连续滑动多屏,支付宝不支持(默认 false )
+ * @property {Number} displayMultipleItems 同时显示的滑块数量,nvue、支付宝小程序不支持(默认 1 )
+ * @property {String} easingFunction 指定swiper切换缓动动画类型, 只对微信小程序有效(默认 'default' )
+ * @property {String} keyName list数组中指定对象的目标属性名(默认 'url' )
+ * @property {String} imgMode 图片的裁剪模式(默认 'aspectFill' )
+ * @property {String | Number} height 组件高度(默认 130 )
+ * @property {String} bgColor 背景颜色(默认 '#f3f4f6' )
+ * @property {String | Number} radius 组件圆角,数值或带单位的字符串(默认 4 )
+ * @property {Boolean} loading 是否加载中(默认 false )
+ * @property {Boolean} showTitle 是否显示标题,要求数组对象中有title属性(默认 false )
+ * @event {Function(index)} click 点击轮播图时触发 index:点击了第几张图片,从0开始
+ * @event {Function(index)} change 轮播图切换时触发(自动或者手动切换) index:切换到了第几张图片,从0开始
+ * @example <uv-swiper :list="list4" keyName="url" :autoplay="false"></uv-swiper>
+ */
+ export default {
+ name: 'uv-swiper',
+ mixins: [mpMixin, mixin, props],
+ emits: ['click','change'],
+ data() {
+ return {
+ currentIndex: 0
+ }
+ },
+ watch: {
+ current(val, preVal) {
+ if(val === preVal) return;
+ this.currentIndex = val; // 和上游数据关联上
+ }
+ },
+ computed: {
+ itemStyle() {
+ return index => {
+ const style = {}
+ // #ifndef APP-NVUE || MP-TOUTIAO
+ // 左右流出空间的写法不支持nvue和头条
+ // 只有配置了此二值,才加上对应的圆角,以及缩放
+ if (this.nextMargin && this.previousMargin) {
+ style.borderRadius = this.$uv.addUnit(this.radius)
+ if (index !== this.currentIndex) style.transform = 'scale(0.92)'
+ }
+ // #endif
+ return style
+ }
+ }
+ },
+ methods: {
+ getItemType(item) {
+ if (typeof item === 'string') return this.$uv.test.video(this.getSource(item)) ? 'video' : 'image'
+ if (typeof item === 'object' && this.keyName) {
+ if (!item.type) return this.$uv.test.video(this.getSource(item)) ? 'video' : 'image'
+ if (item.type === 'image') return 'image'
+ if (item.type === 'video') return 'video'
+ return 'image'
+ }
+ },
+ // 获取目标路径,可能数组中为字符串,对象的形式,额外可指定对象的目标属性名keyName
+ getSource(item) {
+ if (typeof item === 'string') return item
+ if (typeof item === 'object' && this.keyName) return item[this.keyName]
+ else this.$uv.error('请按格式传递列表参数')
+ return ''
+ },
+ // 轮播切换事件
+ change(e) {
+ // 当前的激活索引
+ const {
+ current
+ } = e.detail
+ this.pauseVideo(this.currentIndex)
+ this.currentIndex = current
+ this.$emit('change', e.detail)
+ },
+ // 切换轮播时,暂停视频播放
+ pauseVideo(index) {
+ const lastItem = this.getSource(this.list[index])
+ if (this.$uv.test.video(lastItem)) {
+ // 当视频隐藏时,暂停播放
+ const video = uni.createVideoContext(`video-${index}`, this)
+ video.pause()
+ }
+ },
+ // 当一个轮播item为视频时,获取它的视频海报
+ getPoster(item) {
+ return typeof item === 'object' && item.poster ? item.poster : ''
+ },
+ // 点击某个item
+ clickHandler(index) {
+ this.$emit('click', index)
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-lines: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-swiper {
+ @include flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+
+ &__wrapper {
+ flex: 1;
+
+ &__item {
+ flex: 1;
+
+ &__wrapper {
+ @include flex;
+ position: relative;
+ overflow: hidden;
+ transition: transform 0.3s;
+ flex: 1;
+
+ &__image {
+ flex: 1;
+ }
+
+ &__video {
+ flex: 1;
+ }
+
+ &__title {
+ position: absolute;
+ background-color: rgba(0, 0, 0, 0.3);
+ bottom: 0;
+ left: 0;
+ right: 0;
+ font-size: 28rpx;
+ height: 60rpx;
+ line-height: 60rpx;
+ color: #FFFFFF;
+ flex: 1;
+ }
+ }
+ }
+ }
+
+ &__indicator {
+ position: absolute;
+ bottom: 10px;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-swiper/package.json b/ledApp/uni_modules/uv-swiper/package.json
new file mode 100644
index 0000000..f561f10
--- /dev/null
+++ b/ledApp/uni_modules/uv-swiper/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-swiper",
+ "displayName": "uv-swiper 轮播图 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.6",
+ "description": "该组件为轮播、跑马灯,支持卡片式。一般用于导航轮播,广告展示等场景,开箱即用",
+ "keywords": [
+ "swiper",
+ "uvui",
+ "uv-ui",
+ "轮播图",
+ "走马灯"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-loading-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-swiper/readme.md b/ledApp/uni_modules/uv-swiper/readme.md
new file mode 100644
index 0000000..cae255f
--- /dev/null
+++ b/ledApp/uni_modules/uv-swiper/readme.md
@@ -0,0 +1,19 @@
+## Swiper 轮播图
+
+> **组件名:uv-swiper**
+
+该组件为轮播、跑马灯,支持卡片式。一般用于导航轮播,广告展示等场景,开箱即用。
+
+# <a href="https://www.uvui.cn/components/swiper.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-switch/changelog.md b/ledApp/uni_modules/uv-switch/changelog.md
new file mode 100644
index 0000000..655c0ae
--- /dev/null
+++ b/ledApp/uni_modules/uv-switch/changelog.md
@@ -0,0 +1,14 @@
+## 1.0.5(2023-09-21)
+1. 优化细节
+## 1.0.4(2023-08-23)
+1. 取消value传值,只能使用v-model传值,避免异步操作不生效的BUG
+## 1.0.3(2023-07-13)
+1. 修复 uv-switch设置value属性不生效的BUG
+## 1.0.2(2023-06-19)
+1. size属性兼容和单位一起使用
+
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-switch 开关选择器
diff --git a/ledApp/uni_modules/uv-switch/components/uv-switch/props.js b/ledApp/uni_modules/uv-switch/components/uv-switch/props.js
new file mode 100644
index 0000000..b3737dd
--- /dev/null
+++ b/ledApp/uni_modules/uv-switch/components/uv-switch/props.js
@@ -0,0 +1,58 @@
+export default {
+ props: {
+ value: {
+ type: [Boolean, String, Number],
+ default: false
+ },
+ modelValue: {
+ type: [Boolean, String, Number],
+ default: false
+ },
+ // 是否为加载中状态
+ loading: {
+ type: Boolean,
+ default: false
+ },
+ // 是否为禁用装填
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 开关尺寸,单位px
+ size: {
+ type: [String, Number],
+ default: 25
+ },
+ // 打开时的背景颜色
+ activeColor: {
+ type: String,
+ default: '#2979ff'
+ },
+ // 关闭时的背景颜色
+ inactiveColor: {
+ type: String,
+ default: '#fff'
+ },
+ // switch打开时的值
+ activeValue: {
+ type: [String, Number, Boolean],
+ default: true
+ },
+ // switch关闭时的值
+ inactiveValue: {
+ type: [String, Number, Boolean],
+ default: false
+ },
+ // 是否开启异步变更,开启后需要手动控制输入值
+ asyncChange: {
+ type: Boolean,
+ default: false
+ },
+ // 圆点与外边框的距离
+ space: {
+ type: [String, Number],
+ default: 0
+ },
+ ...uni.$uv?.props?.switch
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-switch/components/uv-switch/uv-switch.vue b/ledApp/uni_modules/uv-switch/components/uv-switch/uv-switch.vue
new file mode 100644
index 0000000..884963a
--- /dev/null
+++ b/ledApp/uni_modules/uv-switch/components/uv-switch/uv-switch.vue
@@ -0,0 +1,192 @@
+<template>
+ <view
+ class="uv-switch"
+ :class="[disabled && 'uv-switch--disabled']"
+ :style="[switchStyle, $uv.addStyle(customStyle)]"
+ @tap="clickHandler"
+ >
+ <view
+ class="uv-switch__bg"
+ :style="[bgStyle]"
+ >
+ </view>
+ <view
+ class="uv-switch__node"
+ :class="[innerValue && 'uv-switch__node--on']"
+ :style="[nodeStyle]"
+ ref="uv-switch__node"
+ >
+ <uv-loading-icon
+ :show="loading"
+ mode="circle"
+ timingFunction='linear'
+ :color="innerValue ? activeColor : '#AAABAD'"
+ :size="size * 0.6"
+ />
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * switch 开关选择器
+ * @description 选择开关一般用于只有两个选择,且只能选其一的场景。
+ * @tutorial https://www.uvui.cn/components/switch.html
+ * @property {Boolean} loading 是否处于加载中(默认 false )
+ * @property {Boolean} disabled 是否禁用(默认 false )
+ * @property {String | Number} size 开关尺寸,单位px (默认 25 )
+ * @property {String} activeColor 打开时的背景色 (默认 '#2979ff' )
+ * @property {String} inactiveColor 关闭时的背景色 (默认 '#ffffff' )
+ * @property {Boolean | String | Number} value 通过v-model双向绑定的值 (默认 false )
+ * @property {Boolean | String | Number} activeValue 打开选择器时通过change事件发出的值 (默认 true )
+ * @property {Boolean | String | Number} inactiveValue 关闭选择器时通过change事件发出的值 (默认 false )
+ * @property {Boolean} asyncChange 是否开启异步变更,开启后需要手动控制输入值 (默认 false )
+ * @property {String | Number} space 圆点与外边框的距离 (默认 0 )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function} change 在switch打开或关闭时触发
+ * @example <uv-switch v-model="checked" active-color="red" inactive-color="#eee"></uv-switch>
+ */
+ export default {
+ name: "uv-switch",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ bgColor: '#ffffff',
+ innerValue: false
+ }
+ },
+ watch: {
+ // #ifdef VUE2
+ value: {
+ immediate: true,
+ handler(newVal) {
+ if (newVal !== this.inactiveValue && newVal !== this.activeValue) {
+ return this.$uv.error('v-model绑定的值必须为inactiveValue、activeValue二者之一')
+ }
+ this.innerValue = newVal;
+ }
+ },
+ // #endif
+ // #ifndef VUE2
+ modelValue: {
+ immediate: true,
+ handler(newVal) {
+ if (newVal !== this.inactiveValue && newVal !== this.activeValue) {
+ return this.$uv.error('v-model绑定的值必须为inactiveValue、activeValue二者之一')
+ }
+ this.innerValue = newVal;
+ }
+ }
+ // #endif
+ },
+ created() {
+ this.innerValue = this.value || this.modelValue;
+ },
+ computed: {
+ isActive() {
+ return this.innerValue === this.activeValue;
+ },
+ switchStyle() {
+ let style = {}
+ // 这里需要加2,是为了腾出边框的距离,否则圆点node会和外边框紧贴在一起
+ style.width = this.$uv.addUnit(this.$uv.getPx(this.size) * 2 + 2)
+ style.height = this.$uv.addUnit(this.$uv.getPx(this.size) + 2)
+ // 如果自定义了“非激活”演示,name边框颜色设置为透明(跟非激活颜色一致)
+ // 这里不能简单的设置为非激活的颜色,否则打开状态时,会有边框,所以需要透明
+ if (this.customInactiveColor) {
+ style.borderColor = 'rgba(0, 0, 0, 0)'
+ }
+ style.backgroundColor = this.isActive ? this.activeColor : this.inactiveColor
+ return style;
+ },
+ nodeStyle() {
+ let style = {}
+ // 如果自定义非激活颜色,将node圆点的尺寸减少两个像素,让其与外边框距离更大一点
+ style.width = this.$uv.addUnit(this.$uv.getPx(this.size) - this.space)
+ style.height = this.$uv.addUnit(this.$uv.getPx(this.size) - this.space)
+ const translateX = this.isActive ? this.$uv.addUnit(this.space) : this.$uv.addUnit(this.$uv.getPx(this.size));
+ style.transform = `translateX(-${translateX})`
+ return style
+ },
+ bgStyle() {
+ let style = {}
+ // 这里配置一个多余的元素在HTML中,是为了让switch切换时,有更良好的背景色扩充体验(见实际效果)
+ style.width = this.$uv.addUnit(this.$uv.getPx(this.size) * 2 - this.$uv.getPx(this.size) / 2)
+ style.height = this.$uv.addUnit(this.$uv.getPx(this.size))
+ style.backgroundColor = this.inactiveColor
+ // 打开时,让此元素收缩,否则反之
+ style.transform = `scale(${this.isActive ? 0 : 1})`
+ return style
+ },
+ customInactiveColor() {
+ // 之所以需要判断是否自定义了“非激活”颜色,是为了让node圆点离外边框更宽一点的距离
+ return this.inactiveColor !== '#fff' && this.inactiveColor !== '#ffffff'
+ }
+ },
+ methods: {
+ clickHandler() {
+ if (!this.disabled && !this.loading) {
+ const oldValue = this.isActive ? this.inactiveValue : this.activeValue
+ if (!this.asyncChange) {
+ this.$emit('input', oldValue)
+ this.$emit('update:modelValue', oldValue)
+ }
+ // 放到下一个生命周期,因为双向绑定的value修改父组件状态需要时间,且是异步的
+ this.$nextTick(() => {
+ this.$emit('change', oldValue)
+ })
+ }
+ }
+ }
+ };
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-switch {
+ @include flex(row);
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ /* #endif */
+ position: relative;
+ background-color: #fff;
+ border-width: 1px;
+ border-radius: 100px;
+ transition: background-color 0.4s;
+ border-color: rgba(0, 0, 0, 0.12);
+ border-style: solid;
+ justify-content: flex-end;
+ align-items: center;
+ // 由于weex为阿里逗着玩的KPI项目,导致bug奇多,这必须要写这一行,
+ // 否则在iOS上,点击页面任意地方,都会触发switch的点击事件
+ overflow: hidden;
+ &__node {
+ @include flex(row);
+ align-items: center;
+ justify-content: center;
+ border-radius: 100px;
+ background-color: #fff;
+ border-radius: 100px;
+ box-shadow: 1px 1px 1px 0 rgba(0, 0, 0, 0.25);
+ transition-property: transform;
+ transition-duration: 0.4s;
+ transition-timing-function: cubic-bezier(0.3, 1.05, 0.4, 1.05);
+ }
+ &__bg {
+ position: absolute;
+ border-radius: 100px;
+ background-color: #FFFFFF;
+ transition-property: transform;
+ transition-duration: 0.4s;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ transition-timing-function: ease;
+ }
+ &--disabled {
+ opacity: 0.6;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-switch/package.json b/ledApp/uni_modules/uv-switch/package.json
new file mode 100644
index 0000000..2b9c1dd
--- /dev/null
+++ b/ledApp/uni_modules/uv-switch/package.json
@@ -0,0 +1,88 @@
+{
+ "id": "uv-switch",
+ "displayName": "uv-switch 开关选择器 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.5",
+ "description": "选择开关用于在打开和关闭状态之间进行切换,支持异步操作。",
+ "keywords": [
+ "uv-switch",
+ "uvui",
+ "uv-ui",
+ "switch",
+ "开关选择器"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-loading-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-switch/readme.md b/ledApp/uni_modules/uv-switch/readme.md
new file mode 100644
index 0000000..7dbe8ed
--- /dev/null
+++ b/ledApp/uni_modules/uv-switch/readme.md
@@ -0,0 +1,19 @@
+## Switch 开关选择器
+
+> **组件名:uv-switch**
+
+选择开关用于在打开和关闭状态之间进行切换,支持异步操作。
+
+# <a href="https://www.uvui.cn/components/switch.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tabbar/changelog.md b/ledApp/uni_modules/uv-tabbar/changelog.md
new file mode 100644
index 0000000..a3ed25c
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabbar/changelog.md
@@ -0,0 +1,14 @@
+## 1.0.5(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.4(2023-06-13)
+1. 底部安全距离依赖添加
+## 1.0.3(2023-06-09)
+1. 增加iconSize参数
+## 1.0.2(2023-06-01)
+1. 修复点击触发两次事件的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-tabbar 底部导航栏
diff --git a/ledApp/uni_modules/uv-tabbar/components/uv-tabbar-item/props.js b/ledApp/uni_modules/uv-tabbar/components/uv-tabbar-item/props.js
new file mode 100644
index 0000000..e67922a
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabbar/components/uv-tabbar-item/props.js
@@ -0,0 +1,40 @@
+export default {
+ props: {
+ // item标签的名称,作为与uv-tabbar的value参数匹配的标识符
+ name: {
+ type: [String, Number, null],
+ default: null
+ },
+ // uv-ui内置图标或者绝对路径的图片
+ icon: {
+ icon: String,
+ default: ''
+ },
+ // 图标大小,默认uv-tabbar的iconSize=20
+ iconSize: {
+ type: [String, Number],
+ default: ''
+ },
+ // 右上角的角标提示信息
+ badge: {
+ type: [String, Number, null],
+ default: null
+ },
+ // 是否显示圆点,将会覆盖badge参数
+ dot: {
+ type: Boolean,
+ default: false
+ },
+ // 描述文本
+ text: {
+ type: String,
+ default: ''
+ },
+ // 控制徽标的位置,对象或者字符串形式,可以设置top和right属性
+ badgeStyle: {
+ type: [Object, String],
+ default: 'top: 6px;right:2px;'
+ },
+ ...uni.$uv?.props?.tabbarItem
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tabbar/components/uv-tabbar-item/uv-tabbar-item.vue b/ledApp/uni_modules/uv-tabbar/components/uv-tabbar-item/uv-tabbar-item.vue
new file mode 100644
index 0000000..c6fa65e
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabbar/components/uv-tabbar-item/uv-tabbar-item.vue
@@ -0,0 +1,146 @@
+<template>
+ <view
+ class="uv-tabbar-item"
+ :style="[$uv.addStyle(customStyle)]"
+ @tap="clickHandler"
+ >
+ <view class="uv-tabbar-item__icon">
+ <uv-icon
+ v-if="icon"
+ :name="icon"
+ :color="isActive? parentData.activeColor : parentData.inactiveColor"
+ :size="iconSize? iconSize: parentData.iconSize"
+ ></uv-icon>
+ <template v-else>
+ <slot
+ v-if="isActive"
+ name="active-icon"
+ />
+ <slot
+ v-else
+ name="inactive-icon"
+ />
+ </template>
+ <uv-badge
+ absolute
+ :offset="[0, dot ? '34rpx' : badge > 9 ? '14rpx' : '20rpx']"
+ :customStyle="badgeStyle"
+ :isDot="dot"
+ :value="badge || (dot ? 1 : null)"
+ :show="dot || badge > 0"
+ ></uv-badge>
+ </view>
+
+ <slot name="text">
+ <text
+ class="uv-tabbar-item__text"
+ :style="{
+ color: isActive? parentData.activeColor : parentData.inactiveColor
+ }"
+ >{{ text }}</text>
+ </slot>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * TabbarItem 底部导航栏子组件
+ * @description 此组件提供了自定义tabbar的能力。
+ * @tutorial https://www.uvui.cn/components/tabbar.html
+ * @property {String | Number} name item标签的名称,作为与uv-tabbar的value参数匹配的标识符
+ * @property {String} icon uvui内置图标或者绝对路径的图片
+ * @property {String | Number} badge 右上角的角标提示信息
+ * @property {Boolean} dot 是否显示圆点,将会覆盖badge参数(默认 false )
+ * @property {String} text 描述文本
+ * @property {Object | String} badgeStyle 控制徽标的位置,对象或者字符串形式,可以设置top和right属性(默认 'top: 6px;right:2px;' )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @example <uv-tabbar :value="value2" :placeholder="false" @change="name => value2 = name" :fixed="false" :safeAreaInsetBottom="false"><uv-tabbar-item text="首页" icon="home" dot ></uv-tabbar-item></uv-tabbar>
+ */
+ export default {
+ name: 'uv-tabbar-item',
+ mixins: [mpMixin, mixin, props],
+ emits: ['click','change'],
+ data() {
+ return {
+ isActive: false, // 是否处于激活状态
+ parentData: {
+ value: null,
+ activeColor: '',
+ inactiveColor: '',
+ iconSize: 20
+ }
+ }
+ },
+ created() {
+ this.init()
+ },
+ methods: {
+ init() {
+ // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+ this.updateParentData()
+ if (!this.parent) {
+ this.$uv.error('uv-tabbar-item必须搭配uv-tabbar组件使用')
+ }
+ // 本子组件在uv-tabbar的children数组中的索引
+ const index = this.parent.children.indexOf(this)
+ // 判断本组件的name(如果没有定义name,就用index索引)是否等于父组件的value参数
+ this.isActive = (this.name || index) === this.parentData.value
+ },
+ updateParentData() {
+ // 此方法在mixin中
+ this.getParentData('uv-tabbar')
+ },
+ // 此方法将会被父组件uv-tabbar调用
+ updateFromParent() {
+ // 重新初始化
+ this.init()
+ },
+ clickHandler() {
+ this.$nextTick(() => {
+ const index = this.parent.children.indexOf(this)
+ const name = this.name || index
+ // 点击的item为非激活的item才发出change事件
+ if (name !== this.parent.value) {
+ this.parent.$emit('change', name)
+ }
+ this.$emit('click', name)
+ })
+ }
+ },
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-tabbar-item {
+ @include flex(column);
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+
+ &__icon {
+ @include flex;
+ position: relative;
+ width: 150rpx;
+ justify-content: center;
+ }
+
+ &__text {
+ margin-top: 2px;
+ font-size: 12px;
+ color: $uv-content-color;
+ }
+ }
+
+ /* #ifdef MP */
+ // 由于小程序都使用shadow DOM形式实现,需要给影子宿主设置flex: 1才能让其撑开
+ :host {
+ flex: 1
+ }
+ /* #endif */
+</style>
diff --git a/ledApp/uni_modules/uv-tabbar/components/uv-tabbar/props.js b/ledApp/uni_modules/uv-tabbar/components/uv-tabbar/props.js
new file mode 100644
index 0000000..91621aa
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabbar/components/uv-tabbar/props.js
@@ -0,0 +1,50 @@
+export default {
+ props: {
+ // 当前匹配项的name
+ value: {
+ type: [String, Number, null],
+ default: null
+ },
+ // 是否为iPhoneX留出底部安全距离
+ safeAreaInsetBottom: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示上方边框
+ border: {
+ type: Boolean,
+ default: true
+ },
+ // 元素层级z-index
+ zIndex: {
+ type: [String, Number],
+ default: 9
+ },
+ // 选中标签的颜色
+ activeColor: {
+ type: String,
+ default: '#1989fa'
+ },
+ // 未选中标签的颜色
+ inactiveColor: {
+ type: String,
+ default: '#7d7e80'
+ },
+ // 是否固定在底部
+ fixed: {
+ type: Boolean,
+ default: true
+ },
+ // fixed定位固定在底部时,是否生成一个等高元素防止塌陷
+ placeholder: {
+ type: Boolean,
+ default: true
+ },
+ // 图标大小
+ iconSize: {
+ type: [String, Number],
+ default: 20
+ },
+ ...uni.$uv?.props?.tabbar
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue b/ledApp/uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue
new file mode 100644
index 0000000..694ed13
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue
@@ -0,0 +1,146 @@
+<template>
+ <view class="uv-tabbar">
+ <view
+ class="uv-tabbar__content"
+ ref="uv-tabbar__content"
+ @touchmove.stop.prevent="noop"
+ :class="[border && 'uv-border-top', fixed && 'uv-tabbar--fixed']"
+ :style="[tabbarStyle]"
+ >
+ <view class="uv-tabbar__content__item-wrapper">
+ <slot />
+ </view>
+ <uv-safe-bottom v-if="safeAreaInsetBottom"></uv-safe-bottom>
+ </view>
+ <view
+ class="uv-tabbar__placeholder"
+ v-if="placeholder"
+ :style="{
+ height: placeholderHeight + 'px',
+ }"
+ ></view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * Tabbar 底部导航栏
+ * @description 此组件提供了自定义tabbar的能力。
+ * @tutorial https://www.uvui.cn/components/tabbar.html
+ * @property {String | Number} value 当前匹配项的name
+ * @property {Boolean} safeAreaInsetBottom 是否为iPhoneX留出底部安全距离(默认 true )
+ * @property {Boolean} border 是否显示上方边框(默认 true )
+ * @property {String | Number} zIndex 元素层级z-index(默认 1 )
+ * @property {String} activeColor 选中标签的颜色(默认 '#1989fa' )
+ * @property {String} inactiveColor 未选中标签的颜色(默认 '#7d7e80' )
+ * @property {Boolean} fixed 是否固定在底部(默认 true )
+ * @property {Boolean} placeholder fixed定位固定在底部时,是否生成一个等高元素防止塌陷(默认 true )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @example <uv-tabbar :value="value2" :placeholder="false" @change="name => value2 = name" :fixed="false" :safeAreaInsetBottom="false"><uv-tabbar-item text="首页" icon="home" dot ></uv-tabbar-item></uv-tabbar>
+ */
+ export default {
+ name: 'uv-tabbar',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ placeholderHeight: 0
+ }
+ },
+ computed: {
+ tabbarStyle() {
+ const style = {
+ zIndex: this.zIndex
+ }
+ // 合并来自父组件的customStyle样式
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ },
+ // 监听多个参数的变化,通过在computed执行对应的操作
+ updateChild() {
+ return [this.value, this.activeColor, this.inactiveColor]
+ },
+ updatePlaceholder() {
+ return [this.fixed, this.placeholder]
+ }
+ },
+ watch: {
+ updateChild() {
+ // 如果updateChildren中的元素发生了变化,则执行子元素初始化操作
+ this.updateChildren()
+ },
+ updatePlaceholder() {
+ // 如果fixed,placeholder等参数发生变化,重新计算占位元素的高度
+ this.setPlaceholderHeight()
+ }
+ },
+ created() {
+ this.children = []
+ },
+ mounted() {
+ this.setPlaceholderHeight()
+ },
+ methods: {
+ updateChildren() {
+ // 如果存在子元素,则执行子元素的updateFromParent进行更新数据
+ this.children.length && this.children.map(child => child.updateFromParent())
+ },
+ // 设置用于防止塌陷元素的高度
+ async setPlaceholderHeight() {
+ if (!this.fixed || !this.placeholder) return
+ // 延时一定时间
+ await this.$uv.sleep(20)
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-tabbar__content').then(({height = 50}) => {
+ // 修复IOS safearea bottom 未填充高度
+ this.placeholderHeight = height
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ dom.getComponentRect(this.$refs['uv-tabbar__content'], (res) => {
+ const {
+ size
+ } = res
+ this.placeholderHeight = size.height
+ })
+ // #endif
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-border: 1;
+ $show-border-top: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-tabbar {
+ @include flex(column);
+ flex: 1;
+ justify-content: center;
+
+ &__content {
+ @include flex(column);
+ background-color: #fff;
+
+ &__item-wrapper {
+ height: 50px;
+ @include flex(row);
+ }
+ }
+
+ &--fixed {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-tabbar/package.json b/ledApp/uni_modules/uv-tabbar/package.json
new file mode 100644
index 0000000..41c3257
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabbar/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-tabbar",
+ "displayName": "uv-tabbar 底部导航栏 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.5",
+ "description": "底部导航栏组件提供了自定义tabbar的能力,可以满足某些需要鉴权跳转等场景。",
+ "keywords": [
+ "uv-tabbar",
+ "uvui",
+ "uv-ui",
+ "tabbar",
+ "底部导航栏"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-badge",
+ "uv-safe-bottom"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tabbar/readme.md b/ledApp/uni_modules/uv-tabbar/readme.md
new file mode 100644
index 0000000..fece546
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabbar/readme.md
@@ -0,0 +1,21 @@
+## Tabbar 底部导航栏
+
+> **组件名:uv-tabbar**
+
+此组件提供了自定义`tabbar`的能力。
+
+一般固定在底部,可以满足某些需要鉴权跳转等场景。一旦自定义底部导航栏后,为了达到不闪烁的效果,建议在`tabbar`页面进行组件切换。
+
+# <a href="https://www.uvui.cn/components/tabbar.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <span style="font-size:14px;font-weight:700;">(请不要 下载插件ZIP)</span>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tabs/changelog.md b/ledApp/uni_modules/uv-tabs/changelog.md
new file mode 100644
index 0000000..a0924ba
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabs/changelog.md
@@ -0,0 +1,22 @@
+## 1.0.9(2023-12-01)
+1. 修复current为字符串activeStyle不生效的BUG
+2. 优化文档
+## 1.0.8(2023-10-13)
+1. 优化点击一个选项,change事件重复派发的问题
+## 1.0.7(2023-09-14)
+1. 优化首次加载时,处理下划线会有左到右的过渡效果
+## 1.0.6(2023-09-13)
+1. 修复设置lineWidth未带单位产生的误差BUG
+## 1.0.5(2023-06-23)
+添加uv-icon依赖
+## 1.0.4(2023-06-16)
+1. 增加customStyle参数
+## 1.0.3(2023-06-12)
+1. activeStyle设置字体大小,导致下划线位置不对的BUG,增加this.$nextTick机制
+## 1.0.2(2023-05-23)
+1. 修复nvue中不滚动的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-tabs 标签选项卡
diff --git a/ledApp/uni_modules/uv-tabs/components/uv-tabs/props.js b/ledApp/uni_modules/uv-tabs/components/uv-tabs/props.js
new file mode 100644
index 0000000..acab29e
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabs/components/uv-tabs/props.js
@@ -0,0 +1,71 @@
+export default {
+ props: {
+ // 滑块的移动过渡时间,单位ms
+ duration: {
+ type: Number,
+ default: 300
+ },
+ // tabs标签数组
+ list: {
+ type: Array,
+ default: () => []
+ },
+ // 滑块颜色
+ lineColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 菜单选择中时的样式
+ activeStyle: {
+ type: [String, Object],
+ default: () => ({
+ color: '#303133'
+ })
+ },
+ // 菜单非选中时的样式
+ inactiveStyle: {
+ type: [String, Object],
+ default: () => ({
+ color: '#606266'
+ })
+ },
+ // 滑块长度
+ lineWidth: {
+ type: [String, Number],
+ default: 20
+ },
+ // 滑块高度
+ lineHeight: {
+ type: [String, Number],
+ default: 3
+ },
+ // 滑块背景显示大小,当滑块背景设置为图片时使用
+ lineBgSize: {
+ type: String,
+ default: 'cover'
+ },
+ // 菜单item的样式
+ itemStyle: {
+ type: [String, Object],
+ default: () => ({
+ height: '44px'
+ })
+ },
+ // 菜单是否可滚动
+ scrollable: {
+ type: Boolean,
+ default: true
+ },
+ // 当前选中标签的索引
+ current: {
+ type: [Number, String],
+ default: 0
+ },
+ // 默认读取的键名
+ keyName: {
+ type: String,
+ default: 'name'
+ },
+ ...uni.$uv?.props?.tabs
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tabs/components/uv-tabs/uv-tabs.vue b/ledApp/uni_modules/uv-tabs/components/uv-tabs/uv-tabs.vue
new file mode 100644
index 0000000..cfbc3b7
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabs/components/uv-tabs/uv-tabs.vue
@@ -0,0 +1,390 @@
+<template>
+ <view class="uv-tabs" :style="[$uv.addStyle(customStyle)]">
+ <view class="uv-tabs__wrapper">
+ <slot name="left" />
+ <view class="uv-tabs__wrapper__scroll-view-wrapper">
+ <scroll-view
+ :scroll-x="scrollable"
+ :scroll-left="scrollLeft"
+ scroll-with-animation
+ class="uv-tabs__wrapper__scroll-view"
+ :show-scrollbar="false"
+ ref="uv-tabs__wrapper__scroll-view"
+ >
+ <view
+ class="uv-tabs__wrapper__nav"
+ ref="uv-tabs__wrapper__nav"
+ :style="{
+ flex: scrollable ? '' : 1
+ }"
+ >
+ <view
+ class="uv-tabs__wrapper__nav__item"
+ v-for="(item, index) in list"
+ :key="index"
+ @tap="clickHandler(item, index)"
+ :ref="`uv-tabs__wrapper__nav__item-${index}`"
+ :style="[{flex: scrollable ? '' : 1},$uv.addStyle(itemStyle)]"
+ :class="[`uv-tabs__wrapper__nav__item-${index}`, item.disabled && 'uv-tabs__wrapper__nav__item--disabled']"
+ >
+ <text
+ :class="[item.disabled && 'uv-tabs__wrapper__nav__item__text--disabled']"
+ class="uv-tabs__wrapper__nav__item__text"
+ :style="[textStyle(index)]"
+ >{{ item[keyName] }}</text>
+ <uv-badge
+ :show="!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))"
+ :isDot="item.badge && item.badge.isDot || propsBadge.isDot"
+ :value="item.badge && item.badge.value || propsBadge.value"
+ :max="item.badge && item.badge.max || propsBadge.max"
+ :type="item.badge && item.badge.type || propsBadge.type"
+ :showZero="item.badge && item.badge.showZero || propsBadge.showZero"
+ :bgColor="item.badge && item.badge.bgColor || propsBadge.bgColor"
+ :color="item.badge && item.badge.color || propsBadge.color"
+ :shape="item.badge && item.badge.shape || propsBadge.shape"
+ :numberType="item.badge && item.badge.numberType || propsBadge.numberType"
+ :inverted="item.badge && item.badge.inverted || propsBadge.inverted"
+ customStyle="margin-left: 4px;"
+ ></uv-badge>
+ </view>
+ <!-- #ifdef APP-NVUE -->
+ <view
+ class="uv-tabs__wrapper__nav__line"
+ ref="uv-tabs__wrapper__nav__line"
+ :style="[{
+ width: $uv.addUnit(lineWidth),
+ height: firstTime?0:$uv.addUnit(lineHeight),
+ background: lineColor,
+ backgroundSize: lineBgSize
+ }]"
+ >
+ <!-- #endif -->
+ <!-- #ifndef APP-NVUE -->
+ <view
+ class="uv-tabs__wrapper__nav__line"
+ ref="uv-tabs__wrapper__nav__line"
+ :style="[{
+ width: $uv.addUnit(lineWidth),
+ transform: `translate(${lineOffsetLeft}px)`,
+ transitionDuration: `${firstTime ? 0 : duration}ms`,
+ height: firstTime?0:$uv.addUnit(lineHeight),
+ background: lineColor,
+ backgroundSize: lineBgSize,
+ }]"
+ >
+ <!-- #endif -->
+ </view>
+ </view>
+ </scroll-view>
+ </view>
+ <slot name="right" />
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import uvBadgeProps from '@/uni_modules/uv-badge/components/uv-badge/props.js'
+ // #ifdef APP-NVUE
+ const animation = uni.requireNativePlugin('animation')
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ import props from './props.js';
+ /**
+ * Tabs 标签
+ * @description tabs标签组件,在标签多的时候,可以配置为左右滑动,标签少的时候,可以禁止滑动。 该组件的一个特点是配置为滚动模式时,激活的tab会自动移动到组件的中间位置。
+ * @tutorial https://www.uvui.cn/components/tabs.html
+ * @property {Array} list 标签数组,元素为对象,如[{name: '推荐'}]
+ * @property {String | Number} duration 滑块移动一次所需的时间,单位秒(默认 200 )
+ * @property {String | Object} activeStyle 菜单选择中时的样式(默认{ color: '#303133' })
+ * @property {String | Object} inactiveStyle 菜单非选择中时的样式(默认{ color: '#606266' })
+ * @property {String | Number} lineWidth 滑块长度(默认 20)
+ * @property {String | Number} lineHeight 滑块高度(默认 3)
+ * @property {String} lineColor 滑块颜色(默认:'#3c9cff')
+ * @property {String} lineBgSize 滑块背景显示大小,当滑块背景设置为图片时使用(默认 cover)
+ * @property {String | Number} itemStyle 菜单item的样式(默认 { height: '44px' })
+ * @property {String} scrollable 菜单是否可滚动,选项很少的时候设置为false整个tabs自动居中显示(默认:true)
+ * @property {String | Number} current 当前选中标签的索引(默认 0 )
+ * @property {String} keyName 从list元素对象中读取的键名(默认 'name' )
+ * @property {String | Number} swierWidth swiper的宽度(默认 '750rpx' )
+ * @property {String | Object} customStyle 自定义外部样式
+ *
+ * @event {Function(index)} change 标签改变时触发 index: 点击了第几个tab,索引从0开始
+ * @event {Function(index)} click 点击标签时触发 index: 点击了第几个tab,索引从0开始
+ * @example <uv-tabs :list="list" :is-scroll="false" :current="current" @change="change"></uv-tabs>
+ */
+ export default {
+ name: 'uv-tabs',
+ emits: ['click','change'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ firstTime: true,
+ scrollLeft: 0,
+ scrollViewWidth: 0,
+ lineOffsetLeft: 0,
+ tabsRect: {
+ left: 0
+ },
+ innerCurrent: 0,
+ moving: false,
+ }
+ },
+ watch: {
+ current: {
+ immediate: true,
+ handler (newValue, oldValue) {
+ // 内外部值不相等时,才尝试移动滑块
+ if (newValue !== this.innerCurrent) {
+ this.innerCurrent = newValue
+ this.$nextTick(() => {
+ this.resize()
+ })
+ }
+ }
+ },
+ // list变化时,重新渲染list各项信息
+ list() {
+ this.$nextTick(() => {
+ this.resize()
+ })
+ }
+ },
+ computed: {
+ textStyle() {
+ return index => {
+ const style = {}
+ // 取当期是否激活的样式
+ const customeStyle = index == this.innerCurrent ? this.$uv.addStyle(this.activeStyle) : this.$uv
+ .addStyle(
+ this.inactiveStyle)
+ // 如果当前菜单被禁用,则加上对应颜色,需要在此做处理,是因为nvue下,无法在style样式中通过!import覆盖标签的内联样式
+ if (this.list[index].disabled) {
+ style.color = '#c8c9cc'
+ }
+ return this.$uv.deepMerge(customeStyle, style)
+ }
+ },
+ propsBadge() {
+ return uvBadgeProps
+ }
+ },
+ async mounted() {
+ this.init()
+ },
+ methods: {
+ setLineLeft() {
+ const tabItem = this.list[this.innerCurrent];
+ if (!tabItem) {
+ return;
+ }
+ // 获取滑块该移动的位置
+ let lineOffsetLeft = this.list
+ .slice(0, this.innerCurrent)
+ .reduce((total, curr) => total + curr.rect.width, 0);
+ // 获取下划线的数值px表示法
+ let lineWidth = this.$uv.getPx(this.lineWidth);
+ // 如果传的值未带单位+设置了全局单位,则带上单位计算,这样才没有误差
+ if (this.$uv.test.number(this.lineWidth) && this.$uv.unit) {
+ lineWidth = this.$uv.getPx(`${this.lineWidth}${this.$uv.unit}`);
+ }
+ this.lineOffsetLeft = lineOffsetLeft + (tabItem.rect.width - lineWidth) / 2
+ // #ifdef APP-NVUE
+ // 第一次移动滑块,无需过渡时间
+ this.animation(this.lineOffsetLeft, this.firstTime ? 0 : parseInt(this.duration))
+ // #endif
+
+ // 如果是第一次执行此方法,让滑块在初始化时,瞬间滑动到第一个tab item的中间
+ // 这里需要一个定时器,因为在非nvue下,是直接通过style绑定过渡时间,需要等其过渡完成后,再设置为false(非第一次移动滑块)
+ if (this.firstTime) {
+ setTimeout(() => {
+ this.firstTime = false
+ }, 20);
+ }
+ },
+ // nvue下设置滑块的位置
+ animation(x, duration = 0) {
+ // #ifdef APP-NVUE
+ const ref = this.$refs['uv-tabs__wrapper__nav__line']
+ animation.transition(ref, {
+ styles: {
+ transform: `translateX(${x}px)`
+ },
+ duration
+ })
+ // #endif
+ },
+ // 点击某一个标签
+ clickHandler(item, index) {
+ // 因为标签可能为disabled状态,所以click是一定会发出的,但是change事件是需要可用的状态才发出
+ this.$emit('click', {
+ ...item,
+ index
+ })
+ // 如果disabled状态,返回
+ if (item.disabled) return
+ if(this.innerCurrent != index) {
+ this.$emit('change', {
+ ...item,
+ index
+ })
+ }
+ this.innerCurrent = index
+ // #ifndef APP-NVUE
+ this.$nextTick(()=>{
+ this.resize()
+ })
+ // #endif
+ // #ifdef APP-NVUE
+ this.$nextTick(()=>{
+ // nvue模式下再给点延时,确保万无一失
+ this.$uv.sleep(30).then(res=>{
+ this.resize()
+ });
+ })
+ // #endif
+ },
+ init() {
+ this.$uv.sleep().then(() => {
+ this.resize()
+ })
+ },
+ setScrollLeft() {
+ // 当前活动tab的布局信息,有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息
+ const tabRect = this.list[this.innerCurrent]
+ // 累加得到当前item到左边的距离
+ const offsetLeft = this.list
+ .slice(0, this.innerCurrent)
+ .reduce((total, curr) => {
+ return total + curr.rect.width
+ }, 0)
+ // 此处为屏幕宽度
+ const windowWidth = this.$uv.sys().windowWidth
+ // 将活动的tabs-item移动到屏幕正中间,实际上是对scroll-view的移动
+ let scrollLeft = offsetLeft - (this.tabsRect.width - tabRect.rect.width) / 2 - (windowWidth - this.tabsRect
+ .right) / 2 + this.tabsRect.left / 2
+ // 这里做一个限制,限制scrollLeft的最大值为整个scroll-view宽度减去tabs组件的宽度
+ scrollLeft = Math.min(scrollLeft, this.scrollViewWidth - this.tabsRect.width)
+ this.scrollLeft = Math.max(0, scrollLeft)
+ },
+ // 获取所有标签的尺寸
+ resize() {
+ // 如果不存在list,则不处理
+ if(this.list.length === 0) {
+ return
+ }
+ Promise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => {
+ this.tabsRect = tabsRect
+ this.scrollViewWidth = 0
+ itemRect.map((item, index) => {
+ // 计算scroll-view的宽度,这里
+ this.scrollViewWidth += item.width
+ // 另外计算每一个item的中心点X轴坐标
+ this.list[index].rect = item
+ })
+ // 获取了tabs的尺寸之后,设置滑块的位置
+ this.setLineLeft()
+ this.setScrollLeft()
+ })
+ },
+ // 获取导航菜单的尺寸
+ getTabsRect() {
+ return new Promise(resolve => {
+ this.queryRect('uv-tabs__wrapper__scroll-view').then(size => resolve(size))
+ })
+ },
+ // 获取所有标签的尺寸
+ getAllItemRect() {
+ return new Promise(resolve => {
+ const promiseAllArr = this.list.map((item, index) => this.queryRect(
+ `uv-tabs__wrapper__nav__item-${index}`, true))
+ Promise.all(promiseAllArr).then(sizes => resolve(sizes))
+ })
+ },
+ // 获取各个标签的尺寸
+ queryRect(el, item) {
+ // #ifndef APP-NVUE
+ // $uvGetRect为uni-ui自带的节点查询简化方法,详见文档介绍:https://www.uvui.cn/js/getRect.html
+ // 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+ return new Promise(resolve => {
+ this.$uvGetRect(`.${el}`).then(size => {
+ resolve(size)
+ })
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ // nvue下,使用dom模块查询元素高度
+ // 返回一个promise,让调用此方法的主体能使用then回调
+ return new Promise(resolve => {
+ dom.getComponentRect(item ? this.$refs[el][0] : this.$refs[el], res => {
+ resolve(res.size)
+ })
+ })
+ // #endif
+ },
+ },
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-tabs {
+
+ &__wrapper {
+ @include flex;
+ align-items: center;
+
+ &__scroll-view-wrapper {
+ flex: 1;
+ /* #ifndef APP-NVUE */
+ overflow: auto hidden;
+ /* #endif */
+ }
+
+ &__scroll-view {
+ @include flex;
+ flex: 1;
+ }
+
+ &__nav {
+ @include flex;
+ position: relative;
+
+ &__item {
+ padding: 0 11px;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+
+ &--disabled {
+ /* #ifndef APP-NVUE */
+ cursor: not-allowed;
+ /* #endif */
+ }
+
+ &__text {
+ font-size: 15px;
+ color: $uv-content-color;
+
+ &--disabled {
+ color: $uv-disabled-color !important;
+ }
+ }
+ }
+
+ &__line {
+ height: 3px;
+ background: $uv-primary;
+ width: 30px;
+ position: absolute;
+ bottom: 2px;
+ border-radius: 100px;
+ transition-property: transform;
+ transition-duration: 300ms;
+ }
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-tabs/package.json b/ledApp/uni_modules/uv-tabs/package.json
new file mode 100644
index 0000000..b56f108
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabs/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-tabs",
+ "displayName": "uv-tabs 标签选项卡 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.9",
+ "description": "标签选项卡组件,是一个tabs标签组件,在标签多的时候,可以配置为左右滑动,标签少的时候,可以禁止滑动。 该组件的一个特点是配置为滚动模式时,激活的tab会自动移动到组件的中间位置。",
+ "keywords": [
+ "选项卡",
+ "uvui",
+ "uv-ui",
+ "tab",
+ "标签"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-badge",
+ "uv-sticky",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tabs/readme.md b/ledApp/uni_modules/uv-tabs/readme.md
new file mode 100644
index 0000000..88b74aa
--- /dev/null
+++ b/ledApp/uni_modules/uv-tabs/readme.md
@@ -0,0 +1,19 @@
+## Tabs 标签选项卡
+
+> **组件名:uv-tabs**
+
+标签选项卡组件,是一个`tabs`标签组件,在标签多的时候,可以配置为左右滑动,标签少的时候,可以禁止滑动。 该组件的一个特点是配置为滚动模式时,激活的`tab`会自动移动到组件的中间位置。
+
+# <a href="https://www.uvui.cn/components/tabs.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tags/changelog.md b/ledApp/uni_modules/uv-tags/changelog.md
new file mode 100644
index 0000000..96450ba
--- /dev/null
+++ b/ledApp/uni_modules/uv-tags/changelog.md
@@ -0,0 +1,10 @@
+## 1.0.3(2023-10-02)
+1. 兼容customStyle参数
+2. 优化
+## 1.0.2(2023-07-31)
+1. nvue模式下增加cell-child参数,避免在list中使用出现回收后不显示的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-tags 标签
diff --git a/ledApp/uni_modules/uv-tags/components/uv-tags/props.js b/ledApp/uni_modules/uv-tags/components/uv-tags/props.js
new file mode 100644
index 0000000..82b4bf7
--- /dev/null
+++ b/ledApp/uni_modules/uv-tags/components/uv-tags/props.js
@@ -0,0 +1,95 @@
+export default {
+ props: {
+ // 标签类型info、primary、success、warning、error
+ type: {
+ type: String,
+ default: 'primary'
+ },
+ // 不可用
+ disabled: {
+ type: [Boolean, String],
+ default: false
+ },
+ // 标签的大小,large,medium,mini
+ size: {
+ type: String,
+ default: 'medium'
+ },
+ // tag的形状,circle(两边半圆形), square(方形,带圆角)
+ shape: {
+ type: String,
+ default: 'square'
+ },
+ // 标签文字
+ text: {
+ type: [String, Number],
+ default: ''
+ },
+ // 背景颜色,默认为空字符串,即不处理
+ bgColor: {
+ type: String,
+ default: ''
+ },
+ // 标签字体颜色,默认为空字符串,即不处理
+ color: {
+ type: String,
+ default: ''
+ },
+ // 标签的边框颜色
+ borderColor: {
+ type: String,
+ default: ''
+ },
+ // 点击时返回的索引值,用于区分例遍的数组哪个元素被点击了
+ name: {
+ type: [String, Number],
+ default: ''
+ },
+ // 镂空时是否填充背景色
+ plainFill: {
+ type: Boolean,
+ default: false
+ },
+ // 是否镂空
+ plain: {
+ type: Boolean,
+ default: false
+ },
+ // 是否可关闭
+ closable: {
+ type: Boolean,
+ default: false
+ },
+ // 关闭按钮图标的颜色
+ closeColor: {
+ type: String,
+ default: '#C6C7CB'
+ },
+ // 关闭按钮图标的位置 right(右边)right-top(右上) 默认right-top
+ closePlace: {
+ type: String,
+ default: 'right-top'
+ },
+ // 是否显示
+ show: {
+ type: Boolean,
+ default: true
+ },
+ // 内置图标,或绝对路径的图片
+ icon: {
+ type: String,
+ default: ''
+ },
+ // 图标颜色
+ iconColor: {
+ type: String,
+ default: ''
+ },
+ // nvue模式下 是否直接显示,在uv-list等cell下面使用就需要设置
+ cellChild: {
+ type: Boolean,
+ default: false
+ },
+ ...uni.$uv?.props?.tags
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tags/components/uv-tags/uv-tags.vue b/ledApp/uni_modules/uv-tags/components/uv-tags/uv-tags.vue
new file mode 100644
index 0000000..baeb0fd
--- /dev/null
+++ b/ledApp/uni_modules/uv-tags/components/uv-tags/uv-tags.vue
@@ -0,0 +1,379 @@
+<template>
+ <uv-transition
+ mode="fade"
+ :show="show"
+ :cell-child="cellChild"
+ >
+ <view class="uv-tags-wrapper">
+ <view
+ class="uv-tags"
+ :class="[`uv-tags--${shape}`, !plain && `uv-tags--${type}`, plain && `uv-tags--${type}--plain`, `uv-tags--${size}`,`uv-tags--${size}--${closePlace}`, plain && plainFill && `uv-tags--${type}--plain--fill`]"
+ @tap.stop="clickHandler"
+ :style="[{
+ marginRight: closable&& closePlace=='right-top' ? '10px' : 0,
+ marginTop: closable && closePlace=='right-top' ? '10px' : 0,
+ }, style, $uv.addStyle(customStyle)]"
+ >
+ <slot name="icon">
+ <view
+ class="uv-tags__icon"
+ v-if="icon"
+ >
+ <image
+ v-if="$uv.test.image(icon)"
+ :src="icon"
+ :style="[imgStyle]"
+ ></image>
+ <uv-icon
+ v-else
+ :color="elIconColor"
+ :name="icon"
+ :size="iconSize"
+ ></uv-icon>
+ </view>
+ </slot>
+ <text
+ class="uv-tags__text"
+ :style="[textColor]"
+ :class="[`uv-tags__text--${type}`, plain && `uv-tags__text--${type}--plain`, `uv-tags__text--${size}`]"
+ >{{ text }}</text>
+ <view
+ class="uv-tags__close"
+ :class="[`uv-tags__close--${size}`,`uv-tags__close--${closePlace}`]"
+ v-if="closable && closePlace=='right'"
+ @tap.stop="closeHandler"
+ :style="{backgroundColor: closeColor}"
+ >
+ <uv-icon
+ name="close"
+ :size="closeSize"
+ color="#ffffff"
+ ></uv-icon>
+ </view>
+ </view>
+ <view
+ class="uv-tags__close"
+ :class="[`uv-tags__close--${size}`,`uv-tags__close--${closePlace}`]"
+ v-if="closable && closePlace=='right-top'"
+ @tap.stop="closeHandler"
+ :style="{backgroundColor: closeColor}"
+ >
+ <uv-icon
+ name="close"
+ :size="closeSize"
+ color="#ffffff"
+ ></uv-icon>
+ </view>
+ </view>
+ </uv-transition>
+</template>
+
+<script>
+ import { image } from '@/uni_modules/uv-ui-tools/libs/function/test.js'
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Tag 标签
+ * @description tag组件一般用于标记和选择,我们提供了更加丰富的表现形式,能够较全面的涵盖您的使用场景
+ * @tutorial https://www.uvui.cn/components/tag.html
+ * @property {String} type 标签类型info、primary、success、warning、error (默认 'primary' )
+ * @property {Boolean | String} disabled 不可用(默认 false )
+ * @property {String} size 标签的大小,large,medium,mini (默认 'medium' )
+ * @property {String} shape tag的形状,circle(两边半圆形), square(方形,带圆角)(默认 'square' )
+ * @property {String | Number} text 标签的文字内容
+ * @property {String} bgColor 背景颜色,默认为空字符串,即不处理
+ * @property {String} color 标签字体颜色,默认为空字符串,即不处理
+ * @property {String} borderColor 镂空形式标签的边框颜色
+ * @property {String} closeColor 关闭按钮图标的颜色(默认 #C6C7CB)
+ * @property {String | Number} name 点击时返回的索引值,用于区分例遍的数组哪个元素被点击了
+ * @property {Boolean} plainFill 镂空时是否填充背景色(默认 false )
+ * @property {Boolean} plain 是否镂空(默认 false )
+ * @property {Boolean} closable 是否可关闭,设置为true,文字右边会出现一个关闭图标(默认 false )
+ * @property {Boolean} show 标签显示与否(默认 true )
+ * @property {String} icon 内置图标,或绝对路径的图片
+ * @event {Function(index)} click 点击标签时触发 index: 传递的index参数值
+ * @event {Function(index)} close closable为true时,点击标签关闭按钮触发 index: 传递的index参数值
+ * @example <uv-tags text="标签" type="error" plain plainFill></uv-tags>
+ */
+ export default {
+ name: 'uv-tags',
+ emits: ['click','close'],
+ mixins: [mpMixin, mixin, props],
+ computed: {
+ style() {
+ const style = {}
+ if (this.bgColor) {
+ style.backgroundColor = this.bgColor
+ }
+ if (this.color) {
+ style.color = this.color
+ }
+ if(this.borderColor) {
+ style.borderColor = this.borderColor
+ }
+ return style
+ },
+ // nvue下,文本颜色无法继承父元素
+ textColor() {
+ const style = {}
+ if (this.color) {
+ style.color = this.color
+ }
+ return style
+ },
+ imgStyle() {
+ const width = this.size === 'large' ? '17px' : this.size === 'medium' ? '15px' : '13px'
+ return {
+ width,
+ height: width
+ }
+ },
+ // 文本的样式
+ closeSize() {
+ const size = this.size === 'large' ? 15 : this.size === 'medium' ? 13 : 12
+ return size
+ },
+ // 图标大小
+ iconSize() {
+ const size = this.size === 'large' ? 21 : this.size === 'medium' ? 19 : 16
+ return size
+ },
+ // 图标颜色
+ elIconColor() {
+ return this.iconColor ? this.iconColor : this.plain ? this.type : '#ffffff'
+ }
+ },
+ methods: {
+ // 点击关闭按钮
+ closeHandler() {
+ this.$emit('close', this.name)
+ },
+ // 点击标签
+ clickHandler() {
+ this.$emit('click', this.name)
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped >
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-tags-wrapper {
+ position: relative;
+ }
+
+ .uv-tags {
+ @include flex;
+ align-items: center;
+ border-style: solid;
+
+ &--circle {
+ border-radius: 100px;
+ }
+
+ &--square {
+ border-radius: 3px;
+ }
+
+ &__icon {
+ margin-right: 4px;
+ }
+
+ &__text {
+ &--mini {
+ font-size: 12px;
+ line-height: 12px;
+ }
+
+ &--medium {
+ font-size: 13px;
+ line-height: 13px;
+ }
+
+ &--large {
+ font-size: 15px;
+ line-height: 15px;
+ }
+ }
+
+ &--mini {
+ height: 22px;
+ line-height: 22px;
+ padding: 0 5px;
+ &--right {
+ padding-right: 2px;
+ }
+ }
+
+ &--medium {
+ height: 26px;
+ line-height: 22px;
+ padding: 0 10px;
+ &--right {
+ padding: 0 4px 0 8px;
+ }
+ }
+
+ &--large {
+ height: 32px;
+ line-height: 32px;
+ padding: 0 15px;
+ &--right {
+ padding: 0 4px 0 8px;
+ }
+ }
+
+ &--primary {
+ background-color: $uv-primary;
+ border-width: 1px;
+ border-color: $uv-primary;
+ }
+
+ &--primary--plain {
+ border-width: 1px;
+ border-color: $uv-primary;
+ }
+
+ &--primary--plain--fill {
+ background-color: #ecf5ff;
+ }
+
+ &__text--primary {
+ color: #FFFFFF;
+ }
+
+ &__text--primary--plain {
+ color: $uv-primary;
+ }
+
+ &--error {
+ background-color: $uv-error;
+ border-width: 1px;
+ border-color: $uv-error;
+ }
+
+ &--error--plain {
+ border-width: 1px;
+ border-color: $uv-error;
+ }
+
+ &--error--plain--fill {
+ background-color: #fef0f0;
+ }
+
+ &__text--error {
+ color: #FFFFFF;
+ }
+
+ &__text--error--plain {
+ color: $uv-error;
+ }
+
+ &--warning {
+ background-color: $uv-warning;
+ border-width: 1px;
+ border-color: $uv-warning;
+ }
+
+ &--warning--plain {
+ border-width: 1px;
+ border-color: $uv-warning;
+ }
+
+ &--warning--plain--fill {
+ background-color: #fdf6ec;
+ }
+
+ &__text--warning {
+ color: #FFFFFF;
+ }
+
+ &__text--warning--plain {
+ color: $uv-warning;
+ }
+
+ &--success {
+ background-color: $uv-success;
+ border-width: 1px;
+ border-color: $uv-success;
+ }
+
+ &--success--plain {
+ border-width: 1px;
+ border-color: $uv-success;
+ }
+
+ &--success--plain--fill {
+ background-color: #f5fff0;
+ }
+
+ &__text--success {
+ color: #FFFFFF;
+ }
+
+ &__text--success--plain {
+ color: $uv-success;
+ }
+
+ &--info {
+ background-color: $uv-info;
+ border-width: 1px;
+ border-color: $uv-info;
+ }
+
+ &--info--plain {
+ border-width: 1px;
+ border-color: $uv-info;
+ }
+
+ &--info--plain--fill {
+ background-color: #f4f4f5;
+ }
+
+ &__text--info {
+ color: #FFFFFF;
+ }
+
+ &__text--info--plain {
+ color: $uv-info;
+ }
+
+ &__close {
+ border-radius: 100px;
+ background-color: #C6C7CB;
+ @include flex(row);
+ align-items: center;
+ justify-content: center;
+ transform: scale(0.6);
+ &--right-top {
+ position: absolute;
+ z-index: 999;
+ top: 10px;
+ right: 10px;
+ /* #ifndef APP-NVUE */
+ transform: scale(0.6) translate(80%,-80%);
+ /* #endif */
+ /* #ifdef APP-NVUE */
+ transform: scale(0.6) translate(50%, -50%);
+ /* #endif */
+ }
+ &--mini {
+ width: 18px;
+ height: 18px;
+ }
+
+ &--medium {
+ width: 22px;
+ height: 22px;
+ }
+
+ &--large {
+ width: 25px;
+ height: 25px;
+ }
+ }
+
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-tags/package.json b/ledApp/uni_modules/uv-tags/package.json
new file mode 100644
index 0000000..6ad55da
--- /dev/null
+++ b/ledApp/uni_modules/uv-tags/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-tags",
+ "displayName": "uv-tags 标签 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.3",
+ "description": "tag组件一般用于标记和选择,我们提供了更加丰富的表现形式,能够较全面的涵盖您的使用场景。",
+ "keywords": [
+ "uv-tags",
+ "uvui",
+ "uv-ui",
+ "tag",
+ "标签"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-transition",
+ "uv-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tags/readme.md b/ledApp/uni_modules/uv-tags/readme.md
new file mode 100644
index 0000000..6170a2f
--- /dev/null
+++ b/ledApp/uni_modules/uv-tags/readme.md
@@ -0,0 +1,11 @@
+## Tag 标签
+
+> **组件名:uv-tags**
+
+tag组件一般用于标记和选择,我们提供了更加丰富的表现形式,能够较全面的涵盖您的使用场景。
+
+### <a href="https://www.uvui.cn/components/tag.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-text/changelog.md b/ledApp/uni_modules/uv-text/changelog.md
new file mode 100644
index 0000000..a3f748e
--- /dev/null
+++ b/ledApp/uni_modules/uv-text/changelog.md
@@ -0,0 +1,11 @@
+## 1.0.4(2023-08-29)
+1. nvue修复设置align不生效的BUG
+## 1.0.3(2023-08-24)
+1. 修复在nvue不能换行的问题
+## 1.0.2(2023-05-24)
+1. 去掉多余的data-index属性,避免警告
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-text 文本组件
diff --git a/ledApp/uni_modules/uv-text/components/uv-text/props.js b/ledApp/uni_modules/uv-text/components/uv-text/props.js
new file mode 100644
index 0000000..e533c66
--- /dev/null
+++ b/ledApp/uni_modules/uv-text/components/uv-text/props.js
@@ -0,0 +1,113 @@
+export default {
+ props: {
+ // 主题颜色
+ type: {
+ type: String,
+ default: ''
+ },
+ // 是否显示
+ show: {
+ type: Boolean,
+ default: true
+ },
+ // 显示的值
+ text: {
+ type: [String, Number],
+ default: ''
+ },
+ // 前置图标
+ prefixIcon: {
+ type: String,
+ default: ''
+ },
+ // 后置图标
+ suffixIcon: {
+ type: String,
+ default: ''
+ },
+ // 文本处理的匹配模式
+ // text-普通文本,price-价格,phone-手机号,name-姓名,date-日期,link-超链接
+ mode: {
+ type: String,
+ default: ''
+ },
+ // mode=link下,配置的链接
+ href: {
+ type: String,
+ default: ''
+ },
+ // 格式化规则
+ format: {
+ type: [String, Function],
+ default: ''
+ },
+ // mode=phone时,点击文本是否拨打电话
+ call: {
+ type: Boolean,
+ default: true
+ },
+ // 小程序的打开方式
+ openType: {
+ type: String,
+ default: ''
+ },
+ // 是否粗体,默认normal
+ bold: {
+ type: Boolean,
+ default: false
+ },
+ // 是否块状
+ block: {
+ type: Boolean,
+ default: false
+ },
+ // 文本显示的行数,如果设置,超出此行数,将会显示省略号
+ lines: {
+ type: [String, Number],
+ default: ''
+ },
+ // 文本颜色
+ color: {
+ type: String,
+ default: '#303133'
+ },
+ // 字体大小
+ size: {
+ type: [String, Number],
+ default: 15
+ },
+ // 图标的样式
+ iconStyle: {
+ type: [Object, String],
+ default: () => ({
+ fontSize: '15px'
+ })
+ },
+ // 文字装饰,下划线,中划线等,可选值 none|underline|line-through
+ decoration: {
+ type: String,
+ default: 'none'
+ },
+ // 外边距,对象、字符串,数值形式均可
+ margin: {
+ type: [Object, String, Number],
+ default: 0
+ },
+ // 文本行高
+ lineHeight: {
+ type: [String, Number],
+ default: ''
+ },
+ // 文本对齐方式,可选值left|center|right
+ align: {
+ type: String,
+ default: 'left'
+ },
+ // 文字换行,可选值break-word|normal|anywhere
+ wordWrap: {
+ type: String,
+ default: 'normal'
+ },
+ ...uni.$uv?.props?.text
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-text/components/uv-text/uv-text.vue b/ledApp/uni_modules/uv-text/components/uv-text/uv-text.vue
new file mode 100644
index 0000000..973eebe
--- /dev/null
+++ b/ledApp/uni_modules/uv-text/components/uv-text/uv-text.vue
@@ -0,0 +1,218 @@
+<template>
+ <view
+ class="uv-text"
+ :class="[]"
+ v-if="show"
+ :style="{
+ margin: margin,
+ justifyContent: align === 'left' ? 'flex-start' : align === 'center' ? 'center' : 'flex-end'
+ }"
+ @tap="clickHandler"
+ >
+ <text
+ :class="['uv-text__price', type && `uv-text__value--${type}`]"
+ v-if="mode === 'price'"
+ :style="[valueStyle]"
+ >¥</text
+ >
+ <view class="uv-text__prefix-icon" v-if="prefixIcon">
+ <uv-icon
+ :name="prefixIcon"
+ :customStyle="$uv.addStyle(iconStyle)"
+ ></uv-icon>
+ </view>
+ <uv-link
+ v-if="mode === 'link'"
+ :text="value"
+ :href="href"
+ underLine
+ ></uv-link>
+ <template v-else-if="openType && isMp">
+ <button
+ class="uv-reset-button uv-text__value"
+ :style="[valueStyle]"
+ :openType="openType"
+ @getuserinfo="onGetUserInfo"
+ @contact="onContact"
+ @getphonenumber="onGetPhoneNumber"
+ @error="onError"
+ @launchapp="onLaunchApp"
+ @opensetting="onOpenSetting"
+ :lang="lang"
+ :session-from="sessionFrom"
+ :send-message-title="sendMessageTitle"
+ :send-message-path="sendMessagePath"
+ :send-message-img="sendMessageImg"
+ :show-message-card="showMessageCard"
+ :app-parameter="appParameter"
+ >
+ {{ value }}
+ </button>
+ </template>
+ <text
+ v-else
+ class="uv-text__value"
+ :style="[valueStyle]"
+ :class="[
+ type && `uv-text__value--${type}`,
+ lines && `uv-line-${lines}`
+ ]"
+ >{{ value }}</text
+ >
+ <view class="uv-text__suffix-icon" v-if="suffixIcon">
+ <uv-icon
+ :name="suffixIcon"
+ :customStyle="$uv.addStyle(iconStyle)"
+ ></uv-icon>
+ </view>
+ </view>
+</template>
+<script>
+ import value from './value.js'
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import button from '@/uni_modules/uv-ui-tools/libs/mixin/button.js'
+ import openType from '@/uni_modules/uv-ui-tools/libs/mixin/openType.js'
+ import props from './props.js'
+ /**
+ * Text 文本
+ * @description 此组件集成了文本类在项目中的常用功能,包括状态,拨打电话,格式化日期,*替换,超链接...等功能。 您大可不必在使用特殊文本时自己定义,text组件几乎涵盖您能使用的大部分场景。
+ * @tutorial https://www.uvui.cn/components/loading.html
+ * @property {String} type 主题颜色
+ * @property {Boolean} show 是否显示(默认 true )
+ * @property {String | Number} text 显示的值
+ * @property {String} prefixIcon 前置图标
+ * @property {String} suffixIcon 后置图标
+ * @property {String} mode 文本处理的匹配模式 text-普通文本,price-价格,phone-手机号,name-姓名,date-日期,link-超链接
+ * @property {String} href mode=link下,配置的链接
+ * @property {String | Function} format 格式化规则
+ * @property {Boolean} call mode=phone时,点击文本是否拨打电话(默认 false )
+ * @property {String} openType 小程序的打开方式
+ * @property {Boolean} bold 是否粗体,默认normal(默认 false )
+ * @property {Boolean} block 是否块状(默认 false )
+ * @property {String | Number} lines 文本显示的行数,如果设置,超出此行数,将会显示省略号
+ * @property {String} color 文本颜色(默认 '#303133' )
+ * @property {String | Number} size 字体大小(默认 15 )
+ * @property {Object | String} iconStyle 图标的样式 (默认 {fontSize: '15px'} )
+ * @property {String} decoration 文字装饰,下划线,中划线等,可选值 none|underline|line-through(默认 'none' )
+ * @property {Object | String | Number} margin 外边距,对象、字符串,数值形式均可(默认 0 )
+ * @property {String | Number} lineHeight 文本行高
+ * @property {String} align 文本对齐方式,可选值left|center|right(默认 'left' )
+ * @property {String} wordWrap 文字换行,可选值break-word|normal|anywhere(默认 'normal' )
+ * @event {Function} click 点击触发事件
+ * @example <uv-text text="我用十年青春,赴你最后之约"></uv-text>
+ */
+ export default {
+ name: 'uv-text',
+ emits: ['click'],
+ // #ifdef MP
+ mixins: [mpMixin, mixin, value, button, openType, props],
+ // #endif
+ // #ifndef MP
+ mixins: [mpMixin, mixin, value, props],
+ // #endif
+ computed: {
+ valueStyle() {
+ const style = {
+ textDecoration: this.decoration,
+ fontWeight: this.bold ? 'bold' : 'normal',
+ wordWrap: this.wordWrap,
+ fontSize: this.$uv.addUnit(this.size)
+ };
+ !this.type && (style.color = this.color);
+ this.isNvue && this.lines && (style.lines = this.lines);
+ if(this.isNvue && this.mode != 'price' && !this.prefixIcon && !this.suffixIcon) {
+ style.flex = 1;
+ style.textAlign = this.align === 'left' ? 'flex-start' : this.align === 'center' ? 'center' : 'right';
+ }
+ this.lineHeight && (style.lineHeight = this.$uv.addUnit(this.lineHeight));
+ !this.isNvue && this.block && (style.display = 'block');
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+ },
+ isNvue() {
+ let nvue = false
+ // #ifdef APP-NVUE
+ nvue = true
+ // #endif
+ return nvue
+ },
+ isMp() {
+ let mp = false
+ // #ifdef MP
+ mp = true
+ // #endif
+ return mp
+ }
+ },
+ data() {
+ return {}
+ },
+ methods: {
+ clickHandler() {
+ // 如果为手机号模式,拨打电话
+ if (this.call && this.mode === 'phone') {
+ uni.makePhoneCall({
+ phoneNumber: this.text
+ })
+ }
+ this.$emit('click')
+ }
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ $show-lines: 1;
+ $show-reset-button: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-text {
+ @include flex(row);
+ align-items: center;
+ flex-wrap: nowrap;
+ flex: 1;
+ /* #ifndef APP-NVUE */
+ width: 100%;
+ /* #endif */
+ &__price {
+ font-size: 14px;
+ color: $uv-content-color;
+ }
+ &__value {
+ font-size: 14px;
+ @include flex;
+ color: $uv-content-color;
+ flex-wrap: wrap;
+ // flex: 1;
+ text-overflow: ellipsis;
+ align-items: center;
+ &--primary {
+ color: $uv-primary;
+ }
+ &--warning {
+ color: $uv-warning;
+ }
+ &--success {
+ color: $uv-success;
+ }
+ &--info {
+ color: $uv-info;
+ }
+ &--error {
+ color: $uv-error;
+ }
+ &--main {
+ color: $uv-main-color;
+ }
+ &--content {
+ color: $uv-content-color;
+ }
+ &--tips {
+ color: $uv-tips-color;
+ }
+ &--light {
+ color: $uv-light-color;
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-text/components/uv-text/value.js b/ledApp/uni_modules/uv-text/components/uv-text/value.js
new file mode 100644
index 0000000..d0a5f17
--- /dev/null
+++ b/ledApp/uni_modules/uv-text/components/uv-text/value.js
@@ -0,0 +1,87 @@
+import { func, date, url } from '@/uni_modules/uv-ui-tools/libs/function/test.js'
+import { error, timeFormat, priceFormat } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
+export default {
+ computed: {
+ // 经处理后需要显示的值
+ value() {
+ const {
+ text,
+ mode,
+ format,
+ href
+ } = this
+ // 价格类型
+ if (mode === 'price') {
+ // 如果text不为金额进行提示
+ if (!/^\d+(\.\d+)?$/.test(text)) {
+ error('金额模式下,text参数需要为金额格式');
+ }
+ // 进行格式化,判断用户传入的format参数为正则,或者函数,如果没有传入format,则使用默认的金额格式化处理
+ if (func(format)) {
+ // 如果用户传入的是函数,使用函数格式化
+ return format(text)
+ }
+ // 如果format非正则,非函数,则使用默认的金额格式化方法进行操作
+ return priceFormat(text, 2)
+ } if (mode === 'date') {
+ // 判断是否合法的日期或者时间戳
+ !date(text) && error('日期模式下,text参数需要为日期或时间戳格式')
+ // 进行格式化,判断用户传入的format参数为正则,或者函数,如果没有传入format,则使用默认的格式化处理
+ if (func(format)) {
+ // 如果用户传入的是函数,使用函数格式化
+ return format(text)
+ } if (format) {
+ // 如果format非正则,非函数,则使用默认的时间格式化方法进行操作
+ return timeFormat(text, format)
+ }
+ // 如果没有设置format,则设置为默认的时间格式化形式
+ return timeFormat(text, 'yyyy-mm-dd')
+ } if (mode === 'phone') {
+ // 判断是否合法的手机号
+ // !mobile(text) && error('手机号模式下,text参数需要为手机号码格式')
+ if (func(format)) {
+ // 如果用户传入的是函数,使用函数格式化
+ return format(text)
+ } if (format === 'encrypt') {
+ // 如果format为encrypt,则将手机号进行星号加密处理
+ return `${text.substr(0, 3)}****${text.substr(7)}`
+ }
+ return text
+ } if (mode === 'name') {
+ // 判断是否合法的字符粗
+ !(typeof (text) === 'string') && error('姓名模式下,text参数需要为字符串格式')
+ if (func(format)) {
+ // 如果用户传入的是函数,使用函数格式化
+ return format(text)
+ } if (format === 'encrypt') {
+ // 如果format为encrypt,则将姓名进行星号加密处理
+ return this.formatName(text)
+ }
+ return text
+ } if (mode === 'link') {
+ // 判断是否合法的字符粗
+ !url(href) && error('超链接模式下,href参数需要为URL格式')
+ return text
+ }
+ return text
+ }
+ },
+ methods: {
+ // 默认的姓名脱敏规则
+ formatName(name) {
+ let value = ''
+ if (name.length === 2) {
+ value = name.substr(0, 1) + '*'
+ } else if (name.length > 2) {
+ let char = ''
+ for (let i = 0, len = name.length - 2; i < len; i++) {
+ char += '*'
+ }
+ value = name.substr(0, 1) + char + name.substr(-1, 1)
+ } else {
+ value = name
+ }
+ return value
+ }
+ }
+}
diff --git a/ledApp/uni_modules/uv-text/package.json b/ledApp/uni_modules/uv-text/package.json
new file mode 100644
index 0000000..80b8491
--- /dev/null
+++ b/ledApp/uni_modules/uv-text/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-text",
+ "displayName": "uv-text 文本 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.4",
+ "description": "此组件集成了文本类在项目中的常用功能,包括状态,拨打电话,格式化日期,*替换,超链接...等功能。 您大可不必在使用特殊文本时自己定义,text组件涵盖您能使用的大部分场景。",
+ "keywords": [
+ "uv-text",
+ "uvui",
+ "uv-ui",
+ "text",
+ "文本"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-link"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-text/readme.md b/ledApp/uni_modules/uv-text/readme.md
new file mode 100644
index 0000000..9956ffa
--- /dev/null
+++ b/ledApp/uni_modules/uv-text/readme.md
@@ -0,0 +1,17 @@
+## Text 文本
+
+> **组件名:uv-text**
+
+此组件集成了文本类在项目中的常用功能,包括状态,拨打电话,格式化日期,*替换,超链接...等功能。 您大可不必在使用特殊文本时自己定义,text组件涵盖您能使用的大部分场景。
+
+# <a href="https://www.uvui.cn/components/text.html" target="_blank">查看文档</a>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-textarea/changelog.md b/ledApp/uni_modules/uv-textarea/changelog.md
new file mode 100644
index 0000000..63a9094
--- /dev/null
+++ b/ledApp/uni_modules/uv-textarea/changelog.md
@@ -0,0 +1,28 @@
+## 1.0.11(2023-12-26)
+1. 优化confirmType默认值改成return
+## 1.0.10(2023-09-13)
+1. 修复设置autoHeight后出现高度异常的BUG
+## 1.0.9(2023-08-08)
+1. 优化
+## 1.0.8(2023-08-07)
+1. 修复值为null或undefined时显示错误的bug
+## 1.0.7(2023-08-05)
+1. v-model设置为数据时的BUG
+2. 复制过多内容,计数显示错误的BUG
+3. maxlength为-1改成不显示计数
+## 1.0.6(2023-07-30)
+1. 增加confirm-hold参数,参考官方文档
+## 1.0.5(2023-07-25)
+1. 增加textStyle属性,自定义文本样式
+2. 增加countStyle属性,自定义统计数字的样式
+## 1.0.4(2023-07-14)
+1. 修复 设置maxlength为-1时不生效的BUG
+## 1.0.3(2023-07-13)
+1. 修复 uv-textarea设置value属性不生效的BUG
+## 1.0.2(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.1(2023-05-12)
+1. 修复vue3中双向绑定问题
+## 1.0.0(2023-05-10)
+uv-textarea 文本域
diff --git a/ledApp/uni_modules/uv-textarea/components/uv-textarea/props.js b/ledApp/uni_modules/uv-textarea/components/uv-textarea/props.js
new file mode 100644
index 0000000..b0c7889
--- /dev/null
+++ b/ledApp/uni_modules/uv-textarea/components/uv-textarea/props.js
@@ -0,0 +1,138 @@
+export default {
+ props: {
+ value: {
+ type: [String, Number],
+ default: ''
+ },
+ modelValue: {
+ type: [String, Number],
+ default: ''
+ },
+ // 输入框为空时占位符
+ placeholder: {
+ type: [String, Number],
+ default: ''
+ },
+ // 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/
+ placeholderClass: {
+ type: String,
+ default: 'textarea-placeholder'
+ },
+ // 指定placeholder的样式
+ placeholderStyle: {
+ type: [String, Object],
+ default: 'color: #c0c4cc'
+ },
+ // 输入框高度
+ height: {
+ type: [String, Number],
+ default: 70
+ },
+ // 设置键盘右下角按钮的文字,仅微信小程序,App-vue和H5有效
+ confirmType: {
+ type: String,
+ default: 'return'
+ },
+ // 是否禁用
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 是否显示统计字数
+ count: {
+ type: Boolean,
+ default: false
+ },
+ // 是否自动获取焦点,nvue不支持,H5取决于浏览器的实现
+ focus: {
+ type: Boolean,
+ default: false
+ },
+ // 是否自动增加高度
+ autoHeight: {
+ type: Boolean,
+ default: false
+ },
+ // 如果textarea是在一个position:fixed的区域,需要显示指定属性fixed为true
+ fixed: {
+ type: Boolean,
+ default: false
+ },
+ // 指定光标与键盘的距离
+ cursorSpacing: {
+ type: Number,
+ default: 0
+ },
+ // 指定focus时的光标位置
+ cursor: {
+ type: [String, Number],
+ default: ''
+ },
+ // 是否显示键盘上方带有”完成“按钮那一栏,
+ showConfirmBar: {
+ type: Boolean,
+ default: true
+ },
+ // 光标起始位置,自动聚焦时有效,需与selection-end搭配使用
+ selectionStart: {
+ type: Number,
+ default: -1
+ },
+ // 光标结束位置,自动聚焦时有效,需与selection-start搭配使用
+ selectionEnd: {
+ type: Number,
+ default: -1
+ },
+ // 键盘弹起时,是否自动上推页面
+ adjustPosition: {
+ type: Boolean,
+ default: true
+ },
+ // 是否去掉 iOS 下的默认内边距,只微信小程序有效
+ disableDefaultPadding: {
+ type: Boolean,
+ default: false
+ },
+ // focus时,点击页面的时候不收起键盘,只微信小程序有效
+ holdKeyboard: {
+ type: Boolean,
+ default: false
+ },
+ // 最大输入长度,设置为 -1 的时候不限制最大长度
+ maxlength: {
+ type: [String, Number],
+ default: 140
+ },
+ // 边框类型,surround-四周边框,bottom-底部边框
+ border: {
+ type: String,
+ default: 'surround'
+ },
+ // 用于处理或者过滤输入框内容的方法
+ formatter: {
+ type: [Function, null],
+ default: null
+ },
+ // 是否忽略组件内对文本合成系统事件的处理
+ ignoreCompositionEvent: {
+ type: Boolean,
+ default: true
+ },
+ // 是否忽略组件内对文本合成系统事件的处理
+ confirmHold: {
+ type: Boolean,
+ default: false
+ },
+ // 文本样式
+ textStyle: {
+ type: [Object, String],
+ default: () => {}
+ },
+ // 统计数字的样式
+ countStyle: {
+ type: [Object, String],
+ default: () => {}
+ },
+ ...uni.$uv?.props?.textarea
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-textarea/components/uv-textarea/uv-textarea.vue b/ledApp/uni_modules/uv-textarea/components/uv-textarea/uv-textarea.vue
new file mode 100644
index 0000000..cd047ac
--- /dev/null
+++ b/ledApp/uni_modules/uv-textarea/components/uv-textarea/uv-textarea.vue
@@ -0,0 +1,238 @@
+<template>
+ <view class="uv-textarea"
+ :class="textareaClass"
+ :style="[textareaStyle]">
+ <textarea class="uv-textarea__field"
+ :value="innerValue"
+ :style="[
+ {height: autoHeight ? 'auto' :$uv.addUnit(height)},
+ $uv.addStyle(textStyle)
+ ]"
+ :placeholder="placeholder"
+ :placeholder-style="$uv.addStyle(placeholderStyle, 'string')"
+ :placeholder-class="placeholderClass"
+ :disabled="disabled"
+ :focus="focus"
+ :autoHeight="autoHeight"
+ :fixed="fixed"
+ :cursorSpacing="cursorSpacing"
+ :cursor="cursor"
+ :showConfirmBar="showConfirmBar"
+ :selectionStart="selectionStart"
+ :selectionEnd="selectionEnd"
+ :adjustPosition="adjustPosition"
+ :disableDefaultPadding="disableDefaultPadding"
+ :holdKeyboard="holdKeyboard"
+ :maxlength="maxlen"
+ :confirmType="confirmType"
+ :ignoreCompositionEvent="ignoreCompositionEvent"
+ :confirm-hold="confirmHold"
+ @focus="onFocus"
+ @blur="onBlur"
+ @linechange="onLinechange"
+ @input="onInput"
+ @confirm="onConfirm"
+ @keyboardheightchange="onKeyboardheightchange"></textarea>
+ <text class="uv-textarea__count"
+ :style="[{
+ 'background-color': disabled ? 'transparent' : '#fff',
+ },$uv.addStyle(countStyle)]"
+ v-if="count && maxlen!=-1">{{ getCount }}/{{ maxlen }}</text>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from "./props.js";
+ /**
+ * Textarea 文本域
+ * @description 文本域此组件满足了可能出现的表单信息补充,编辑等实际逻辑的功能,内置了字数校验等
+ * @tutorial https://www.uvui.cn/components/textarea.html
+ *
+ * @property {String | Number} value / v-model 输入框的内容
+ * @property {String | Number} placeholder 输入框为空时占位符
+ * @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
+ * @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
+ * @property {String | Number} height 输入框高度(默认 70 )
+ * @property {String} confirmType 设置键盘右下角按钮的文字,仅微信小程序,App-vue和H5有效(默认 'done' )
+ * @property {Boolean} disabled 是否禁用(默认 false )
+ * @property {Boolean} count 是否显示统计字数(默认 false )
+ * @property {Boolean} focus 是否自动获取焦点,nvue不支持,H5取决于浏览器的实现(默认 false )
+ * @property {Boolean | Function} autoHeight 是否自动增加高度(默认 false )
+ * @property {Boolean} fixed 如果textarea是在一个position:fixed的区域,需要显示指定属性fixed为true(默认 false )
+ * @property {Number} cursorSpacing 指定光标与键盘的距离(默认 0 )
+ * @property {String | Number} cursor 指定focus时的光标位置
+ * @property {Function} formatter 内容式化函数
+ * @property {Boolean} showConfirmBar 是否显示键盘上方带有”完成“按钮那一栏,(默认 true )
+ * @property {Number} selectionStart 光标起始位置,自动聚焦时有效,需与selection-end搭配使用,(默认 -1 )
+ * @property {Number | Number} selectionEnd 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认 -1 )
+ * @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面(默认 true )
+ * @property {Boolean | Number} disableDefaultPadding 是否去掉 iOS 下的默认内边距,只微信小程序有效(默认 false )
+ * @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,只微信小程序有效(默认 false )
+ * @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认 140 )
+ * @property {String} border 边框类型,surround-四周边框,none-无边框,bottom-底部边框(默认 'surround' )
+ * @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理
+ * @property {Boolean} confirmHold 点击键盘右下角按钮时是否保持键盘不收起
+ * @property {Object} textStyle 文本样式
+ * @property {Object} countStyle 统计数字的样式
+ *
+ * @event {Function(e)} focus 输入框聚焦时触发,event.detail = { value, height },height 为键盘高度
+ * @event {Function(e)} blur 输入框失去焦点时触发,event.detail = {value, cursor}
+ * @event {Function(e)} linechange 输入框行数变化时调用,event.detail = {height: 0, heightRpx: 0, lineCount: 0}
+ * @event {Function(e)} input 当键盘输入时,触发 input 事件
+ * @event {Function(e)} confirm 点击完成时, 触发 confirm 事件
+ * @event {Function(e)} keyboardheightchange 键盘高度发生变化的时候触发此事件
+ * @example <uv--textarea v-model="value1" placeholder="请输入内容" ></uv--textarea>
+ */
+ export default {
+ name: "uv-textarea",
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 输入框的值
+ innerValue: "",
+ // 是否处于获得焦点状态
+ focused: false,
+ // 过滤处理方法
+ innerFormatter: value => value
+ }
+ },
+ created() {
+ // #ifndef VUE3
+ this.innerValue = this.value;
+ // #endif
+ // #ifdef VUE3
+ this.innerValue = this.modelValue;
+ // #endif
+ },
+ watch: {
+ value(newVal) {
+ this.innerValue = newVal;
+ },
+ modelValue(newVal) {
+ this.innerValue = newVal;
+ }
+ },
+ computed: {
+ // 组件的类名
+ textareaClass() {
+ let classes = [],
+ { border, disabled } = this;
+ border === "surround" && (classes = classes.concat(["uv-border", "uv-textarea--radius"]));
+ border === "bottom" && (classes = classes.concat(["uv-border-bottom", "uv-textarea--no-radius", ]));
+ disabled && classes.push("uv-textarea--disabled");
+ return classes.join(" ");
+ },
+ // 组件的样式
+ textareaStyle() {
+ const style = {};
+ // #ifdef APP-NVUE
+ // 由于textarea在安卓nvue上的差异性,需要额外再调整其内边距
+ if (this.$uv.os() === "android") {
+ style.paddingTop = "6px";
+ style.paddingLeft = "9px";
+ style.paddingBottom = "3px";
+ style.paddingRight = "6px";
+ }
+ // #endif
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+ },
+ maxlen() {
+ return this.maxlength < 0 ? this.maxlength < 0 ? -1 : 140 : this.maxlength;
+ },
+ getCount() {
+ try{
+ return this.innerValue.length > this.maxlen ? this.maxlen: this.innerValue.length;
+ }catch(e){
+ return 0;
+ }
+ }
+ },
+ methods: {
+ // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+ setFormatter(e) {
+ this.innerFormatter = e
+ },
+ onFocus(e) {
+ this.$emit("focus", e);
+ },
+ onBlur(e) {
+ this.$emit("blur", e);
+ // 尝试调用uv-form的验证方法
+ this.$uv.formValidate(this, "blur");
+ },
+ onLinechange(e) {
+ this.$emit("linechange", e);
+ },
+ onInput(e) {
+ let { value = "" } = e.detail || {};
+ // 格式化过滤方法
+ const formatter = this.formatter || this.innerFormatter
+ const formatValue = formatter(value)
+ // 为了避免props的单向数据流特性,需要先将innerValue值设置为当前值,再在$nextTick中重新赋予设置后的值才有效
+ this.innerValue = value
+ this.$nextTick(() => {
+ this.innerValue = formatValue;
+ this.valueChange();
+ })
+ },
+ // 内容发生变化,进行处理
+ valueChange() {
+ const value = this.innerValue;
+ this.$nextTick(() => {
+ this.$emit("input", value);
+ this.$emit("update:modelValue", value);
+ this.$emit("change", value);
+ // 尝试调用uv-form的验证方法
+ this.$uv.formValidate(this, "change");
+ });
+ },
+ onConfirm(e) {
+ this.$emit("confirm", e);
+ },
+ onKeyboardheightchange(e) {
+ this.$emit("keyboardheightchange", e);
+ },
+ },
+ };
+</script>
+<style lang="scss" scoped>
+ $show-border: 1;
+ $show-border-surround: 1;
+ $show-border-bottom: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-textarea {
+ border-radius: 4px;
+ background-color: #fff;
+ position: relative;
+ @include flex;
+ flex: 1;
+ padding: 9px;
+ &--radius {
+ border-radius: 4px;
+ }
+ &--no-radius {
+ border-radius: 0;
+ }
+ &--disabled {
+ background-color: #f5f7fa;
+ }
+ &__field {
+ flex: 1;
+ font-size: 15px;
+ color: $uv-content-color;
+ width: 100%;
+ }
+ &__count {
+ position: absolute;
+ right: 5px;
+ bottom: 2px;
+ font-size: 12px;
+ color: $uv-tips-color;
+ background-color: #ffffff;
+ padding: 1px 4px;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-textarea/package.json b/ledApp/uni_modules/uv-textarea/package.json
new file mode 100644
index 0000000..7df2034
--- /dev/null
+++ b/ledApp/uni_modules/uv-textarea/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-textarea",
+ "displayName": "uv-textarea 文本域 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.11",
+ "description": "文本域此组件满足了可能出现的表单信息补充,编辑等实际逻辑的功能,内置了字数校验等。",
+ "keywords": [
+ "uv-textarea",
+ "uvui",
+ "uv-ui",
+ "textarea",
+ "文本域"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-textarea/readme.md b/ledApp/uni_modules/uv-textarea/readme.md
new file mode 100644
index 0000000..6a64819
--- /dev/null
+++ b/ledApp/uni_modules/uv-textarea/readme.md
@@ -0,0 +1,19 @@
+## Textarea 文本域
+
+> **组件名:uv-textarea**
+
+文本域此组件满足了可能出现的表单信息补充,编辑等实际逻辑的功能,内置了字数校验等。
+
+# <a href="https://www.uvui.cn/components/textarea.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-toast/changelog.md b/ledApp/uni_modules/uv-toast/changelog.md
new file mode 100644
index 0000000..7858c41
--- /dev/null
+++ b/ledApp/uni_modules/uv-toast/changelog.md
@@ -0,0 +1,10 @@
+## 1.0.3(2023-12-06)
+1. 增加z-index参数
+2. 增加overlay参数
+## 1.0.2(2023-10-13)
+1. unmounted兼容vue3
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-toast 消息提示
diff --git a/ledApp/uni_modules/uv-toast/components/uv-toast/uv-toast.vue b/ledApp/uni_modules/uv-toast/components/uv-toast/uv-toast.vue
new file mode 100644
index 0000000..5458a5e
--- /dev/null
+++ b/ledApp/uni_modules/uv-toast/components/uv-toast/uv-toast.vue
@@ -0,0 +1,333 @@
+<template>
+ <view class="uv-toast">
+ <uv-overlay :show="isShow && tmpConfig.overlay" :custom-style="overlayStyle"></uv-overlay>
+ <uv-transition :show="isShow" mode="fade" :custom-style="aniStyle">
+ <view
+ class="uv-toast__content"
+ ref="uvToastContent"
+ :style="[contentStyle]"
+ :class="['uv-type-' + tmpConfig.type, (tmpConfig.type === 'loading' || tmpConfig.loading) ? 'uv-toast__content--loading' : '']"
+ >
+ <uv-loading-icon
+ v-if="tmpConfig.type === 'loading'"
+ mode="circle"
+ color="rgb(255, 255, 255)"
+ inactiveColor="rgb(120, 120, 120)"
+ size="25"
+ ></uv-loading-icon>
+ <uv-icon
+ v-else-if="tmpConfig.type !== 'defalut' && iconName"
+ :name="iconName"
+ size="17"
+ :color="tmpConfig.type"
+ :customStyle="iconStyle"
+ ></uv-icon>
+ <uv-gap
+ v-if="tmpConfig.type === 'loading' || tmpConfig.loading"
+ height="12"
+ bgColor="transparent"
+ ></uv-gap>
+ <text
+ class="uv-toast__content__text"
+ :class="['uv-toast__content__text--' + tmpConfig.type]"
+ style="max-width: 400rpx;"
+ >{{ tmpConfig.message }}</text>
+ </view>
+ </uv-transition>
+ </view>
+</template>
+<script>
+ import { hexToRgb } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js'
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * toast 消息提示
+ * @description 此组件表现形式类似uni的uni.showToastAPI,但也有不同的地方。
+ * @tutorial https://www.uvui.cn/components/toast.html
+ * @property {Boolean} loading 是否加载中 (默认 false )
+ * @property {String | Number} zIndex toast展示时的zIndex值 (默认 10090 )
+ * @property {String | Number} message 显示的文字内容
+ * @property {String} icon 图标,或者绝对路径的图片
+ * @property {String} type 主题类型 (默认 default)
+ * @property {Boolean} overlay 是否显示透明遮罩,防止点击穿透 (默认 false )
+ * @property {String} position 位置 (默认 'center' )
+ * @property {Object} params 跳转的参数
+ * @property {String | Number} duration 展示时间,单位ms (默认 2000 )
+ * @property {Function} complete 执行完后的回调函数
+ *
+ * @event {Function} show 显示toast,如需一进入页面就显示toast,请在onReady生命周期调用
+ * @example <uv-toast ref="uvToastRef" />
+ */
+ export default {
+ name: 'uv-toast',
+ mixins: [mpMixin, mixin],
+ data() {
+ return {
+ isShow: false,
+ timer: null, // 定时器
+ config: {
+ message: '', // 显示文本
+ type: '', // 主题类型,primary,success,error,warning,black
+ duration: 2000, // 显示的时间,毫秒
+ icon: true, // 显示的图标
+ position: 'center', // toast出现的位置
+ complete: null, // 执行完后的回调函数
+ overlay: true, // 是否防止触摸穿透
+ loading: false ,// 是否加载中状态
+ zIndex: 10090 //弹出的层级
+ },
+ tmpConfig: {}, // 将用户配置和内置配置合并后的临时配置变量
+ rect: {},
+ opacity: 0
+ }
+ },
+ computed: {
+ iconName() {
+ // 只有不为none,并且type为error|warning|succes|info时候,才显示图标
+ if (!this.tmpConfig.icon || this.tmpConfig.icon == 'none') {
+ return '';
+ }
+ if (['error', 'warning', 'success', 'primary'].includes(this.tmpConfig.type)) {
+ return this.$uv.type2icon(this.tmpConfig.type)
+ } else {
+ return ''
+ }
+ },
+ overlayStyle() {
+ const style = {
+ justifyContent: 'center',
+ alignItems: 'center',
+ display: 'flex',
+ zIndex: this.tmpConfig.zIndex
+ }
+ // 将遮罩设置为100%透明度,避免出现灰色背景
+ style.backgroundColor = 'rgba(0, 0, 0, 0)'
+ return style
+ },
+ iconStyle() {
+ const style = {}
+ // 图标需要一个右边距,以跟右边的文字有隔开的距离
+ style.marginRight = '4px'
+ // #ifdef APP-NVUE
+ // iOSAPP下,图标有1px的向下偏移,这里进行修正
+ if (this.$uv.os() === 'ios') {
+ style.marginTop = '-1px'
+ }
+ // #endif
+ return style
+ },
+ aniStyle() {
+ const style = {
+ position: 'fixed',
+ zIndex: this.tmpConfig.zIndex
+ }
+ return style
+ },
+ // 内容盒子的样式
+ contentStyle() {
+ const { windowWidth, windowHeight } = this.$uv.sys();
+ const style = {
+ position: 'fixed',
+ top: '50%',
+ left: '50%',
+ // #ifdef APP-NVUE
+ opacity: this.opacity
+ // #endif
+ };
+ let value = 0
+ // #ifndef APP-NVUE
+ // 根据top和bottom,对Y轴进行窗体高度的百分比偏移
+ if (this.tmpConfig.position === 'top') {
+ style.top = '25%'
+ } else if (this.tmpConfig.position === 'bottom') {
+ style.top = '75%'
+ } else {
+ value = '-50%'
+ }
+ style.transform = `translate(-50%,${value})`
+ // #endif
+ // #ifdef APP-NVUE
+ const { width = 0, height = 0 } = this.rect ? this.rect : {};
+ if (width && height) {
+ style.left = `${windowWidth*0.5 - width/2}px`
+ if (this.tmpConfig.position === 'top') {
+ style.top = `${windowHeight*0.5- height/2 - windowHeight*0.25}px`
+ } else if (this.tmpConfig.position === 'bottom') {
+ style.top = `${windowHeight*0.5- height/2 + windowHeight*0.25}px`
+ } else {
+ style.top = `${windowHeight*0.5- height/2}px`
+ }
+ }
+ // #endif
+ return style
+ }
+ },
+ created() {
+ // 通过主题的形式调用toast,批量生成方法函数
+ ['primary', 'success', 'error', 'warning', 'default', 'loading'].map(item => {
+ this[item] = message => this.show({
+ type: item,
+ message
+ })
+ })
+ },
+ methods: {
+ // 显示toast组件,由父组件通过this.$refs.xxx.show(options)形式调用
+ show(options) {
+ // 不将结果合并到this.config变量,避免多次调用uv-toast,前后的配置造成混乱
+ this.tmpConfig = this.$uv.deepMerge(this.config, options)
+ // 清除定时器
+ this.clearTimer()
+ this.isShow = true
+ //#ifdef APP-NVUE
+ this.$nextTick(() => {
+ this.$uv.sleep(100).then(async res => {
+ this.rect = await this.queryRect();
+ this.$nextTick(() => {
+ this.opacity = 1;
+ })
+ })
+ })
+ // #endif
+ this.timer = setTimeout(() => {
+ // 倒计时结束,清除定时器,隐藏toast组件
+ this.clearTimer()
+ // 判断是否存在callback方法,如果存在就执行
+ typeof(this.tmpConfig.complete) === 'function' && this.tmpConfig.complete()
+ }, this.tmpConfig.duration)
+ },
+ // 查询内容高度
+ queryRect() {
+ return new Promise(resolve => {
+ const ref = this.$refs['uvToastContent'];
+ dom.getComponentRect(ref, res => {
+ resolve(res.size)
+ })
+ })
+ },
+ // 隐藏toast组件,由父组件通过this.$refs.xxx.hide()形式调用
+ hide() {
+ this.clearTimer()
+ },
+ clearTimer() {
+ // #ifdef APP-NVUE
+ this.opacity = 0;
+ // #endif
+ this.isShow = false
+ // 清除定时器
+ clearTimeout(this.timer)
+ this.timer = null
+ }
+ },
+ // #ifdef VUE2
+ beforeDestroy() {
+ this.clearTimer()
+ },
+ // #endif
+ // #ifdef VUE3
+ unmounted() {
+ this.clearTimer()
+ }
+ // #endif
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-toast-color: #fff !default;
+ $uv-toast-border-radius: 4px !default;
+ $uv-toast-border-background-color: #585858 !default;
+ $uv-toast-border-font-size: 14px !default;
+ $uv-toast-border-padding: 12px 20px !default;
+ $uv-toast-loading-border-padding: 20px 20px !default;
+ $uv-toast-content-text-color: #fff !default;
+ $uv-toast-content-text-font-size: 15px !default;
+ $uv-toast-uv-icon: 10rpx !default;
+ $uv-toast-uv-type-primary-color: $uv-primary !default;
+ $uv-toast-uv-type-primary-background-color: #ecf5ff !default;
+ $uv-toast-uv-type-primary-border-color: rgb(215, 234, 254) !default;
+ $uv-toast-uv-type-primary-border-width: 1px !default;
+ $uv-toast-uv-type-success-color: $uv-success !default;
+ $uv-toast-uv-type-success-background-color: #dbf1e1 !default;
+ $uv-toast-uv-type-success-border-color: #BEF5C8 !default;
+ $uv-toast-uv-type-success-border-width: 1px !default;
+ $uv-toast-uv-type-error-color: $uv-error !default;
+ $uv-toast-uv-type-error-background-color: #fef0f0 !default;
+ $uv-toast-uv-type-error-border-color: #fde2e2 !default;
+ $uv-toast-uv-type-error-border-width: 1px !default;
+ $uv-toast-uv-type-warning-color: $uv-warning !default;
+ $uv-toast-uv-type-warning-background-color: #fdf6ec !default;
+ $uv-toast-uv-type-warning-border-color: #faecd8 !default;
+ $uv-toast-uv-type-warning-border-width: 1px !default;
+ $uv-toast-uv-type-default-color: #fff !default;
+ $uv-toast-uv-type-default-background-color: #585858 !default;
+ .uv-toast {
+ &__content {
+ @include flex;
+ padding: $uv-toast-border-padding;
+ border-radius: $uv-toast-border-radius;
+ background-color: $uv-toast-border-background-color;
+ color: $uv-toast-color;
+ align-items: center;
+ /* #ifndef APP-NVUE */
+ max-width: 600rpx;
+ /* #endif */
+ position: relative;
+ &--loading {
+ flex-direction: column;
+ padding: $uv-toast-loading-border-padding;
+ }
+ &__text {
+ color: $uv-toast-content-text-color;
+ font-size: $uv-toast-content-text-font-size;
+ line-height: $uv-toast-content-text-font-size;
+ &--default {
+ color: $uv-toast-content-text-color;
+ }
+ &--error {
+ color: $uv-error;
+ }
+ &--primary {
+ color: $uv-primary;
+ }
+ &--success {
+ color: $uv-success;
+ }
+ &--warning {
+ color: $uv-warning;
+ }
+ }
+ }
+ }
+ .uv-type-primary {
+ color: $uv-toast-uv-type-primary-color;
+ background-color: $uv-toast-uv-type-primary-background-color;
+ border-color: $uv-toast-uv-type-primary-border-color;
+ border-width: $uv-toast-uv-type-primary-border-width;
+ }
+ .uv-type-success {
+ color: $uv-toast-uv-type-success-color;
+ background-color: $uv-toast-uv-type-success-background-color;
+ border-color: $uv-toast-uv-type-success-border-color;
+ border-width: 1px;
+ }
+ .uv-type-error {
+ color: $uv-toast-uv-type-error-color;
+ background-color: $uv-toast-uv-type-error-background-color;
+ border-color: $uv-toast-uv-type-error-border-color;
+ border-width: $uv-toast-uv-type-error-border-width;
+ }
+ .uv-type-warning {
+ color: $uv-toast-uv-type-warning-color;
+ background-color: $uv-toast-uv-type-warning-background-color;
+ border-color: $uv-toast-uv-type-warning-border-color;
+ border-width: 1px;
+ }
+ .uv-type-default {
+ color: $uv-toast-uv-type-default-color;
+ background-color: $uv-toast-uv-type-default-background-color;
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-toast/package.json b/ledApp/uni_modules/uv-toast/package.json
new file mode 100644
index 0000000..c77987b
--- /dev/null
+++ b/ledApp/uni_modules/uv-toast/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-toast",
+ "displayName": "uv-toast 消息提示 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.3",
+ "description": "Toast 组件主要用于消息通知、加载提示、操作结果提示等醒目提示效果,我们为其提供了多种丰富的API。",
+ "keywords": [
+ "uv-toast",
+ "uvui",
+ "uv-ui",
+ "toast",
+ "消息提示"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-overlay",
+ "uv-loading-icon",
+ "uv-gap"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-toast/readme.md b/ledApp/uni_modules/uv-toast/readme.md
new file mode 100644
index 0000000..a95a5ae
--- /dev/null
+++ b/ledApp/uni_modules/uv-toast/readme.md
@@ -0,0 +1,11 @@
+## Toast 消息提示
+
+> **组件名:uv-toast**
+
+Toast 组件主要用于消息通知、加载提示、操作结果提示等醒目提示效果,我们为其提供了多种丰富的API。
+
+### <a href="https://www.uvui.cn/components/toast.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-toolbar/changelog.md b/ledApp/uni_modules/uv-toolbar/changelog.md
new file mode 100644
index 0000000..3acfcd6
--- /dev/null
+++ b/ledApp/uni_modules/uv-toolbar/changelog.md
@@ -0,0 +1,2 @@
+## 1.0.0(2023-08-02)
+1. 新增工具条组件
diff --git a/ledApp/uni_modules/uv-toolbar/components/uv-toolbar/props.js b/ledApp/uni_modules/uv-toolbar/components/uv-toolbar/props.js
new file mode 100644
index 0000000..e332412
--- /dev/null
+++ b/ledApp/uni_modules/uv-toolbar/components/uv-toolbar/props.js
@@ -0,0 +1,40 @@
+export default {
+ props: {
+ // 是否展示工具条
+ show: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示下边框
+ showBorder: {
+ type: Boolean,
+ default: false
+ },
+ // 取消按钮的文字
+ cancelText: {
+ type: String,
+ default: '取消'
+ },
+ // 确认按钮的文字
+ confirmText: {
+ type: String,
+ default: '确认'
+ },
+ // 取消按钮的颜色
+ cancelColor: {
+ type: String,
+ default: '#909193'
+ },
+ // 确认按钮的颜色
+ confirmColor: {
+ type: String,
+ default: '#3c9cff'
+ },
+ // 标题文字
+ title: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.toolbar
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-toolbar/components/uv-toolbar/uv-toolbar.vue b/ledApp/uni_modules/uv-toolbar/components/uv-toolbar/uv-toolbar.vue
new file mode 100644
index 0000000..0b3fc9e
--- /dev/null
+++ b/ledApp/uni_modules/uv-toolbar/components/uv-toolbar/uv-toolbar.vue
@@ -0,0 +1,109 @@
+<template>
+ <view
+ :class="['uv-toolbar',{'uv-border-bottom':showBorder}]"
+ @touchmove.stop.prevent="noop"
+ v-if="show"
+ >
+ <view
+ class="uv-toolbar__cancel__wrapper"
+ hover-class="uv-hover-class"
+ >
+ <text
+ class="uv-toolbar__wrapper__cancel"
+ @tap="cancel"
+ :style="{
+ color: cancelColor
+ }"
+ >{{ cancelText }}</text>
+ </view>
+ <text
+ class="uv-toolbar__title uv-line-1"
+ v-if="title"
+ >{{ title }}</text>
+ <view
+ class="uv-toolbar__confirm__wrapper"
+ hover-class="uv-hover-class"
+ >
+ <text
+ class="uv-toolbar__wrapper__confirm"
+ @tap="confirm"
+ :style="{
+ color: confirmColor
+ }"
+ >{{ confirmText }}</text>
+ </view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * Toolbar 工具条
+ * @description
+ * @tutorial https://www.uvui.cn/components/toolbar.html
+ * @property {Boolean} show 是否展示工具条(默认 true )
+ * @property {Boolean} showBorder 是否展示工具条下方边框(默认 false )
+ * @property {String} cancelText 取消按钮的文字(默认 '取消' )
+ * @property {String} confirmText 确认按钮的文字(默认 '确认' )
+ * @property {String} cancelColor 取消按钮的颜色(默认 '#909193' )
+ * @property {String} confirmColor 确认按钮的颜色(默认 '#3c9cff' )
+ * @property {String} title 标题文字
+ * @event {Function}
+ * @example
+ */
+ export default {
+ name: 'uv-toolbar',
+ emits: ['confirm', 'cancel'],
+ mixins: [mpMixin, mixin, props],
+ methods: {
+ // 点击取消按钮
+ cancel() {
+ this.$emit('cancel')
+ },
+ // 点击确定按钮
+ confirm() {
+ this.$emit('confirm')
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ $show-lines: 1;
+ $show-hover: 1;
+ $show-border: 1;
+ $show-border-bottom: 1;
+ @import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ $uv-tips-color: #909193 !default;
+ $uv-main-color: #303133 !default;
+ $uv-primary: #3c9cff !default;
+ .uv-toolbar {
+ height: 42px;
+ @include flex;
+ justify-content: space-between;
+ align-items: center;
+ &__wrapper {
+ &__cancel {
+ color: $uv-tips-color;
+ font-size: 15px;
+ padding: 0 15px;
+ }
+ }
+ &__title {
+ color: $uv-main-color;
+ padding: 0 60rpx;
+ font-size: 16px;
+ flex: 1;
+ text-align: center;
+ }
+ &__wrapper {
+ &__confirm {
+ color: $uv-primary;
+ font-size: 15px;
+ padding: 0 15px;
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-toolbar/package.json b/ledApp/uni_modules/uv-toolbar/package.json
new file mode 100644
index 0000000..d519665
--- /dev/null
+++ b/ledApp/uni_modules/uv-toolbar/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-toolbar",
+ "displayName": "uv-toolbar 工具条",
+ "version": "1.0.0",
+ "description": "该组价是仅用于uv-ui中一个公共小工具,提供一个取消和确定的样式,可以设置标题,主要用于弹窗顶部的选择确定工具条",
+ "keywords": [
+ "uv-toolbar",
+ "uvui",
+ "uv-ui",
+ "工具条",
+ "工具"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-toolbar/readme.md b/ledApp/uni_modules/uv-toolbar/readme.md
new file mode 100644
index 0000000..b33643f
--- /dev/null
+++ b/ledApp/uni_modules/uv-toolbar/readme.md
@@ -0,0 +1,31 @@
+## Toolbar 工具条
+
+> **组件名:uv-toolbar**
+
+该组价是仅用于uv-ui中一个公共小工具,提供一个取消和确定的样式,可以设置标题,主要用于弹窗顶部的选择确定工具条。
+
+### 基本使用
+
+```vue
+<uv-toolbar title="标题文字"></uv-toolbar>
+```
+
+### Toolbar Props
+
+| 属性名 | 类型 | 默认值 | 说明 |
+|:-|:-|:-|:-|
+| show | Boolean | true | 是否展示工具条 |
+| showBorder | Boolean | false | 是否显示下边框 |
+| cancelText | String | '取消' | 取消按钮的文字 |
+| confirmText | String | '确定' | 确定按钮的文字 |
+| cancelColor | String | '#909193' | 取消按钮的颜色 |
+| confirmColor | String | '#3c9cff' | 确认按钮的颜色 |
+| title | String | - | 标题文字 |
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tooltip/changelog.md b/ledApp/uni_modules/uv-tooltip/changelog.md
new file mode 100644
index 0000000..5009f54
--- /dev/null
+++ b/ledApp/uni_modules/uv-tooltip/changelog.md
@@ -0,0 +1,13 @@
+## 1.0.5(2023-07-02)
+修改VUE3模式不显示的BUG
+## 1.0.4(2023-07-02)
+uv-tooltip 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+## 1.0.3(2023-05-17)
+1. 修复报错的BUG
+## 1.0.2(2023-05-17)
+1. vue2模式下报错的BUG修复
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-tooltip 长按提示
diff --git a/ledApp/uni_modules/uv-tooltip/components/uv-tooltip/props.js b/ledApp/uni_modules/uv-tooltip/components/uv-tooltip/props.js
new file mode 100644
index 0000000..4130df5
--- /dev/null
+++ b/ledApp/uni_modules/uv-tooltip/components/uv-tooltip/props.js
@@ -0,0 +1,60 @@
+export default {
+ props: {
+ // 需要显示的提示文字
+ text: {
+ type: [String, Number],
+ default: ''
+ },
+ // 点击复制按钮时,复制的文本,为空则使用text值
+ copyText: {
+ type: [String, Number],
+ default: ''
+ },
+ // 文本大小
+ size: {
+ type: [String, Number],
+ default: 14
+ },
+ // 字体颜色
+ color: {
+ type: String,
+ default: '#606266'
+ },
+ // 弹出提示框时,文本的背景色
+ bgColor: {
+ type: String,
+ default: 'transparent'
+ },
+ // 弹出提示的方向,top-上方,bottom-下方
+ direction: {
+ type: String,
+ default: 'top'
+ },
+ // 弹出提示的z-index,nvue无效
+ zIndex: {
+ type: [String, Number],
+ default: 10071
+ },
+ // 是否显示复制按钮
+ showCopy: {
+ type: Boolean,
+ default: true
+ },
+ // 扩展的按钮组
+ buttons: {
+ type: Array,
+ default: () => []
+ },
+ // 是否显示透明遮罩以防止触摸穿透
+ overlay: {
+ type: Boolean,
+ default: true
+ },
+ // 是否显示复制成功或者失败的toast
+ showToast: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.tooltip
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tooltip/components/uv-tooltip/uv-tooltip.vue b/ledApp/uni_modules/uv-tooltip/components/uv-tooltip/uv-tooltip.vue
new file mode 100644
index 0000000..6e762d7
--- /dev/null
+++ b/ledApp/uni_modules/uv-tooltip/components/uv-tooltip/uv-tooltip.vue
@@ -0,0 +1,372 @@
+<template>
+ <view
+ class="uv-tooltip"
+ :style="[$uv.addStyle(customStyle)]"
+ >
+ <uv-overlay
+ :show="showTooltip && tooltipTop !== -10000 && overlay && timeout>0"
+ customStyle="backgroundColor: rgba(0, 0, 0, 0)"
+ @click="overlayClickHandler"
+ ></uv-overlay>
+ <view class="uv-tooltip__wrapper">
+ <template v-if="isShow">
+ <text
+ class="uv-tooltip__wrapper__text"
+ :id="textId"
+ :ref="textId"
+ :userSelect="false"
+ :selectable="false"
+ @click.stop="longpressHandler"
+ :style="{
+ color: color,
+ backgroundColor: bgColor && showTooltip && tooltipTop !== -10000 ? bgColor : 'transparent'
+ }"
+ >{{ text }}</text>
+ </template>
+ <template v-else>
+ <text
+ class="uv-tooltip__wrapper__text"
+ :id="textId"
+ :ref="textId"
+ :userSelect="false"
+ :selectable="false"
+ @longpress.stop="longpressHandler"
+ :style="{
+ color: color,
+ backgroundColor: bgColor && showTooltip && tooltipTop !== -10000 ? bgColor : 'transparent'
+ }"
+ >{{ text }}</text>
+ </template>
+ <uv-transition
+ mode="fade"
+ :show="showTooltip"
+ :duration="300"
+ :customStyle="{
+ position: 'absolute',
+ top: $uv.addUnit(tooltipTop),
+ zIndex: zIndex,
+ ...tooltipStyle
+ }"
+ >
+ <view
+ class="uv-tooltip__wrapper__popup"
+ :id="tooltipId"
+ :ref="tooltipId"
+ >
+ <view
+ class="uv-tooltip__wrapper__popup__indicator"
+ hover-class="uv-tooltip__wrapper__popup__indicator--hover"
+ v-if="showCopy || buttons.length"
+ :style="[indicatorStyle, {
+ width: $uv.addUnit(indicatorWidth),
+ height: $uv.addUnit(indicatorWidth),
+ }]"
+ >
+ <!-- 由于nvue不支持三角形绘制,这里就做一个四方形,再旋转45deg,得到露出的一个三角 -->
+ </view>
+ <view class="uv-tooltip__wrapper__popup__list">
+ <view
+ v-if="showCopy"
+ class="uv-tooltip__wrapper__popup__list__btn"
+ hover-class="uv-tooltip__wrapper__popup__list__btn--hover"
+ @tap="setClipboardData"
+ >
+ <text
+ class="uv-tooltip__wrapper__popup__list__btn__text"
+ >复制</text>
+ </view>
+ <uv-line
+ direction="column"
+ color="#8d8e90"
+ v-if="showCopy && buttons.length > 0"
+ length="18"
+ ></uv-line>
+ <block v-for="(item , index) in buttons" :key="index">
+ <view
+ class="uv-tooltip__wrapper__popup__list__btn"
+ hover-class="uv-tooltip__wrapper__popup__list__btn--hover"
+ >
+ <text
+ class="uv-tooltip__wrapper__popup__list__btn__text"
+ @tap="btnClickHandler(index)"
+ >{{ item }}</text>
+ </view>
+ <uv-line
+ direction="column"
+ color="#8d8e90"
+ v-if="index < buttons.length - 1"
+ length="18"
+ ></uv-line>
+ </block>
+ </view>
+ </view>
+ </uv-transition>
+ </view>
+ </view>
+</template>
+
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * Tooltip
+ * @description
+ * @tutorial https://www.uvui.cn/components/tooltip.html
+ * @property {String | Number} text 需要显示的提示文字
+ * @property {String | Number} copyText 点击复制按钮时,复制的文本,为空则使用text值
+ * @property {String | Number} size 文本大小(默认 14 )
+ * @property {String} color 字体颜色(默认 '#606266' )
+ * @property {String} bgColor 弹出提示框时,文本的背景色(默认 'transparent' )
+ * @property {String} direction 弹出提示的方向,top-上方,bottom-下方(默认 'top' )
+ * @property {String | Number} zIndex 弹出提示的z-index,nvue无效(默认 10071 )
+ * @property {Boolean} showCopy 是否显示复制按钮(默认 true )
+ * @property {Array} buttons 扩展的按钮组
+ * @property {Boolean} overlay 是否显示透明遮罩以防止触摸穿透(默认 true )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @event {Function}
+ * @example
+ */
+ export default {
+ name: 'uv-tooltip',
+ emits: ['click'],
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ // 是否展示气泡
+ showTooltip: true,
+ // 生成唯一id,防止一个页面多个组件,造成干扰
+ textId: '',
+ tooltipId: '',
+ // 初始时甚至为很大的值,让其移到屏幕外面,为了计算元素的尺寸
+ tooltipTop: -10000,
+ // 气泡的位置信息
+ tooltipInfo: {
+ width: 0,
+ left: 0
+ },
+ // 文本的位置信息
+ textInfo: {
+ width: 0,
+ left: 0
+ },
+ // 三角形指示器的样式
+ indicatorStyle: {},
+ // 气泡在可能超出屏幕边沿范围时,重新定位后,距离屏幕边沿的距离
+ screenGap: 12,
+ // 三角形指示器的宽高,由于对元素进行了角度旋转,精确计算指示器位置时,需要用到其尺寸信息
+ indicatorWidth: 14,
+ timeout: 0
+ }
+ },
+ watch: {
+ propsChange() {
+ this.getElRect()
+ },
+ showTooltip(newVal){
+ if(!newVal) this.timeout = 0;
+ }
+ },
+ computed: {
+ isShow(){
+ const isPC = this.$uv.sys()?.model == 'PC';
+ if(isPC) {
+ return true;
+ }
+ // #ifdef MP-WEIXIN || H5
+ return false;
+ // #endif
+ return true;
+ },
+ // 特别处理H5的复制,因为H5浏览器是自带系统复制功能的,在H5环境
+ // 当一些依赖参数变化时,需要重新计算气泡和指示器的位置信息
+ propsChange() {
+ return [this.text, this.buttons]
+ },
+ // 计算气泡和指示器的位置信息
+ tooltipStyle() {
+ const style = {};
+ if (this.tooltipInfo.width / 2 > this.textInfo.left + this.textInfo.width / 2 - this.screenGap) {
+ this.indicatorStyle = {}
+ style.left = `-${this.$uv.addUnit(this.textInfo.left - this.screenGap)}`
+ this.indicatorStyle.left = this.$uv.addUnit(this.textInfo.width / 2 - this.$uv.getPx(style.left) - this.indicatorWidth /
+ 2)
+ } else if (this.tooltipInfo.width / 2 > this.$uv.sys().windowWidth - this.textInfo.right + this.textInfo.width / 2 -
+ this.screenGap) {
+ this.indicatorStyle = {}
+ style.right = `-${this.$uv.addUnit(this.$uv.sys().windowWidth - this.textInfo.right - this.screenGap)}`
+ this.indicatorStyle.right = this.$uv.addUnit(this.textInfo.width / 2 - this.$uv.getPx(style.right) - this
+ .indicatorWidth / 2)
+ } else {
+ const left = Math.abs(this.textInfo.width / 2 - this.tooltipInfo.width / 2)
+ style.left = this.textInfo.width > this.tooltipInfo.width ? this.$uv.addUnit(left) : -this.$uv.addUnit(left)
+ this.indicatorStyle = {}
+ }
+ if (this.direction === 'top') {
+ style.marginTop = `-${10 + this.tooltipInfo.height}px`
+ this.indicatorStyle.bottom = '-4px'
+ } else {
+ style.marginTop = `${this.tooltipInfo.height-10}px`
+ this.indicatorStyle.top = '-4px'
+ }
+ return style
+ }
+ },
+ created() {
+ this.textId = this.$uv.guid();
+ this.tooltipId = this.$uv.guid();
+ },
+ mounted() {
+ this.init()
+ },
+ methods: {
+ init() {
+ this.getElRect()
+ },
+ // 长按触发事件
+ async longpressHandler(e) {
+ this.tooltipTop = 0
+ this.showTooltip = true
+ setTimeout(()=>{
+ this.timeout = 1;
+ },300)
+ },
+ // 点击透明遮罩
+ overlayClickHandler() {
+ this.showTooltip = false
+ },
+ // 点击弹出按钮
+ btnClickHandler(index) {
+ this.showTooltip = false
+ // 如果需要展示复制按钮,此处index需要加1,因为复制按钮在第一个位置
+ this.$emit('click', this.showCopy ? index + 1 : index)
+ },
+ // 查询内容高度
+ queryRect(ref) {
+ // #ifndef APP-NVUE
+ // 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+ return new Promise(resolve => {
+ this.$uvGetRect(`#${ref}`).then(size => {
+ resolve(size)
+ })
+ })
+ // #endif
+
+ // #ifdef APP-NVUE
+ // nvue下,使用dom模块查询元素高度
+ // 返回一个promise,让调用此方法的主体能使用then回调
+ return new Promise(resolve => {
+ dom.getComponentRect(this.$refs[ref], res => {
+ resolve(res.size)
+ })
+ })
+ // #endif
+ },
+ // 元素尺寸
+ getElRect() {
+ // 调用之前,先将指示器调整到屏幕外,方便获取尺寸
+ this.showTooltip = true
+ this.tooltipTop = -10000
+ this.$uv.sleep(500).then(() => {
+ this.queryRect(this.tooltipId).then(size => {
+ this.tooltipInfo = size
+ // 获取气泡尺寸之后,将其隐藏,为了让下次切换气泡显示与隐藏时,有淡入淡出的效果
+ this.showTooltip = false
+ })
+ this.queryRect(this.textId).then(size => {
+ this.textInfo = size
+ })
+ })
+ },
+ // 复制文本到粘贴板
+ setClipboardData() {
+ // 关闭组件
+ this.showTooltip = false
+ this.$emit('click', 0)
+ uni.setClipboardData({
+ // 优先使用copyText字段,如果没有,则默认使用text字段当做复制的内容
+ data: this.copyText || this.text,
+ success: () => {
+ this.showToast && this.$uv.toast('复制成功')
+ },
+ fail: () => {
+ this.showToast && this.$uv.toast('复制失败')
+ },
+ complete: () => {
+ this.showTooltip = false
+ }
+ })
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+
+ .uv-tooltip {
+ position: relative;
+ @include flex;
+
+ &__wrapper {
+ @include flex;
+ justify-content: center;
+ /* #ifndef APP-NVUE */
+ white-space: nowrap;
+ /* #endif */
+
+ &__text {
+ font-size: 14px;
+ }
+
+ &__popup {
+ @include flex;
+ justify-content: center;
+
+ &__list {
+ background-color: #060607;
+ position: relative;
+ flex: 1;
+ border-radius: 5px;
+ padding: 0px 0;
+ @include flex(row);
+ align-items: center;
+ overflow: hidden;
+
+ &__btn {
+ padding: 11px 13px;
+
+ &--hover {
+ background-color: #58595B;
+ }
+
+ &__text {
+ line-height: 12px;
+ font-size: 13px;
+ color: #FFFFFF;
+ }
+ }
+ }
+
+ &__indicator {
+ position: absolute;
+ background-color: #060607;
+ width: 14px;
+ height: 14px;
+ bottom: -4px;
+ transform: rotate(45deg);
+ border-radius: 2px;
+ z-index: -1;
+
+ &--hover {
+ background-color: #58595B;
+ }
+ }
+ }
+ }
+ }
+</style>
diff --git a/ledApp/uni_modules/uv-tooltip/package.json b/ledApp/uni_modules/uv-tooltip/package.json
new file mode 100644
index 0000000..092dd03
--- /dev/null
+++ b/ledApp/uni_modules/uv-tooltip/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-tooltip",
+ "displayName": "uv-tooltip 长按提示",
+ "version": "1.0.5",
+ "description": "Tooltip组件主要用于长按操作,类似微信的长按气泡。",
+ "keywords": [
+ "uv-tooltip",
+ "uvui",
+ "uv-ui",
+ "tooltip",
+ "长按提示"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-overlay",
+ "uv-transition",
+ "uv-line"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "n"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-tooltip/readme.md b/ledApp/uni_modules/uv-tooltip/readme.md
new file mode 100644
index 0000000..9a97fcb
--- /dev/null
+++ b/ledApp/uni_modules/uv-tooltip/readme.md
@@ -0,0 +1,11 @@
+## Tooltip 长按提示
+
+> **组件名:uv-tooltip**
+
+Tooltip组件主要用于长按操作,类似微信的长按气泡。
+
+### <a href="https://www.uvui.cn/components/tooltip.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-transition/changelog.md b/ledApp/uni_modules/uv-transition/changelog.md
new file mode 100644
index 0000000..59e4968
--- /dev/null
+++ b/ledApp/uni_modules/uv-transition/changelog.md
@@ -0,0 +1,19 @@
+## 1.0.8(2023-10-18)
+1. 修复在APP上不能正常显示的BUG
+## 1.0.7(2023-10-12)
+1. 修复部分情况,修改某属性自动关闭的BUG
+## 1.0.6(2023-07-24)
+1. 优化 nvue模式下增加cellChild参数,是否在list中cell节点下,nvue中cell下建议设置成true
+## 1.0.5(2023-07-02)
+修改VUE3模式下可能存在的BUG
+## 1.0.4(2023-07-02)
+uv-transition 动画组件,代码重构优化,性能更加友好,增加自定义动画功能。详情参考文档:https://www.uvui.cn/components/transition.html
+## 1.0.3(2023-06-12)
+1. 恢复this.$nextTick的使用,经过测试百度等平台无问题
+## 1.0.2(2023-05-23)
+1. 百度小程序等平台不支持this.$nextick,修改成延时
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增动画组件
diff --git a/ledApp/uni_modules/uv-transition/components/uv-transition/createAnimation.js b/ledApp/uni_modules/uv-transition/components/uv-transition/createAnimation.js
new file mode 100644
index 0000000..8f89b18
--- /dev/null
+++ b/ledApp/uni_modules/uv-transition/components/uv-transition/createAnimation.js
@@ -0,0 +1,131 @@
+// const defaultOption = {
+// duration: 300,
+// timingFunction: 'linear',
+// delay: 0,
+// transformOrigin: '50% 50% 0'
+// }
+// #ifdef APP-NVUE
+const nvueAnimation = uni.requireNativePlugin('animation')
+// #endif
+class MPAnimation {
+ constructor(options, _this) {
+ this.options = options
+ // 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
+ this.animation = uni.createAnimation({
+ ...options
+ })
+ this.currentStepAnimates = {}
+ this.next = 0
+ this.$ = _this
+
+ }
+
+ _nvuePushAnimates(type, args) {
+ let aniObj = this.currentStepAnimates[this.next]
+ let styles = {}
+ if (!aniObj) {
+ styles = {
+ styles: {},
+ config: {}
+ }
+ } else {
+ styles = aniObj
+ }
+ if (animateTypes1.includes(type)) {
+ if (!styles.styles.transform) {
+ styles.styles.transform = ''
+ }
+ let unit = ''
+ if(type === 'rotate'){
+ unit = 'deg'
+ }
+ styles.styles.transform += `${type}(${args+unit}) `
+ } else {
+ styles.styles[type] = `${args}`
+ }
+ this.currentStepAnimates[this.next] = styles
+ }
+ _animateRun(styles = {}, config = {}) {
+ let ref = this.$.$refs['ani'].ref
+ if (!ref) return
+ return new Promise((resolve, reject) => {
+ nvueAnimation.transition(ref, {
+ styles,
+ ...config
+ }, res => {
+ resolve()
+ })
+ })
+ }
+
+ _nvueNextAnimate(animates, step = 0, fn) {
+ let obj = animates[step]
+ if (obj) {
+ let {
+ styles,
+ config
+ } = obj
+ this._animateRun(styles, config).then(() => {
+ step += 1
+ this._nvueNextAnimate(animates, step, fn)
+ })
+ } else {
+ this.currentStepAnimates = {}
+ typeof fn === 'function' && fn()
+ this.isEnd = true
+ }
+ }
+
+ step(config = {}) {
+ // #ifndef APP-NVUE
+ this.animation.step(config)
+ // #endif
+ // #ifdef APP-NVUE
+ this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
+ this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
+ this.next++
+ // #endif
+ return this
+ }
+
+ run(fn) {
+ // #ifndef APP-NVUE
+ this.$.animationData = this.animation.export()
+ this.$.timer = setTimeout(() => {
+ typeof fn === 'function' && fn()
+ }, this.$.durationTime)
+ // #endif
+ // #ifdef APP-NVUE
+ this.isEnd = false
+ let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
+ if(!ref) return
+ this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
+ this.next = 0
+ // #endif
+ }
+}
+
+
+const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
+ 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
+ 'translateZ'
+]
+const animateTypes2 = ['opacity', 'backgroundColor']
+const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
+animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
+ MPAnimation.prototype[type] = function(...args) {
+ // #ifndef APP-NVUE
+ this.animation[type](...args)
+ // #endif
+ // #ifdef APP-NVUE
+ this._nvuePushAnimates(type, args)
+ // #endif
+ return this
+ }
+})
+
+export function createAnimation(option, _this) {
+ if(!_this) return
+ clearTimeout(_this.timer)
+ return new MPAnimation(option, _this)
+}
diff --git a/ledApp/uni_modules/uv-transition/components/uv-transition/props.js b/ledApp/uni_modules/uv-transition/components/uv-transition/props.js
new file mode 100644
index 0000000..e8b4e6b
--- /dev/null
+++ b/ledApp/uni_modules/uv-transition/components/uv-transition/props.js
@@ -0,0 +1,31 @@
+export default {
+ props: {
+ // 是否展示组件
+ show: {
+ type: Boolean,
+ default: false
+ },
+ // 使用的动画模式
+ mode: {
+ type: [Array, String, null],
+ default() {
+ return 'fade'
+ }
+ },
+ // 动画的执行时间,单位ms
+ duration: {
+ type: [String, Number],
+ default: 300
+ },
+ // 使用的动画过渡函数
+ timingFunction: {
+ type: String,
+ default: 'ease-out'
+ },
+ customClass: {
+ type: String,
+ default: ''
+ },
+ ...uni.$uv?.props?.transition
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-transition/components/uv-transition/uv-transition.vue b/ledApp/uni_modules/uv-transition/components/uv-transition/uv-transition.vue
new file mode 100644
index 0000000..cf9790d
--- /dev/null
+++ b/ledApp/uni_modules/uv-transition/components/uv-transition/uv-transition.vue
@@ -0,0 +1,320 @@
+<template>
+ <!-- #ifndef APP-NVUE -->
+ <view
+ v-if="isShow"
+ ref="ani"
+ :animation="animationData"
+ :class="customClass"
+ :style="transformStyles"
+ @click="onClick">
+ <slot></slot>
+ </view>
+ <!-- #endif -->
+ <!-- #ifdef APP-NVUE -->
+ <view
+ v-if="isShow"
+ ref="ani"
+ :animation="animationData"
+ :class="customClass"
+ :style="transformStyles"
+ @click="onClick">
+ <slot></slot>
+ </view>
+ <!-- #endif -->
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import { createAnimation } from './createAnimation'
+ /**
+ * transition 动画组件
+ * @description
+ * @tutorial
+ * @property {Boolean} show 控制组件显示或关闭 (默认 false )
+ * @property {Array | String } mode 内置过渡动画类型 (默认 'fade' )
+ * @value fade 渐隐渐出过渡
+ * @value slide-top 由上至下过渡
+ * @value slide-bottom 由下至上过渡
+ * @value slide-left 由左至右过渡
+ * @value slide-right 由右至左过渡
+ * @value zoom-in 由小到大过渡
+ * @value zoom-out 由大到小过渡
+ * @property {String | Number} duration 动画的执行时间,单位ms (默认 300 )
+ * @property {String} timingFunction 使用的动画过渡函数 (默认 'ease-out' )
+ * @property {Object} customStyle 自定义样式
+ * @property {String} customClass 自定义类名
+ * @event {Function} click 点击组件触发
+ * @event {Function} change 过渡动画结束时触发
+ * @example
+ */
+ export default {
+ name: 'uv-transition',
+ mixins: [mpMixin,mixin],
+ emits:['click','change'],
+ props: {
+ // 是否展示组件
+ show: {
+ type: Boolean,
+ default: false
+ },
+ // 使用的动画模式
+ mode: {
+ type: [Array, String, null],
+ default() {
+ return 'fade'
+ }
+ },
+ // 动画的执行时间,单位ms
+ duration: {
+ type: [String, Number],
+ default: 300
+ },
+ // 使用的动画过渡函数
+ timingFunction: {
+ type: String,
+ default: 'ease-out'
+ },
+ customClass: {
+ type: String,
+ default: ''
+ },
+ // nvue模式下 是否直接显示,在uv-list等cell下面使用就需要设置
+ cellChild: {
+ type: Boolean,
+ default: false
+ }
+ },
+ data(){
+ return {
+ isShow: false,
+ transform: '',
+ opacity: 1,
+ animationData: {},
+ durationTime: 300,
+ config: {}
+ }
+ },
+ watch: {
+ show: {
+ handler(newVal) {
+ if (newVal) {
+ this.open();
+ } else {
+ // 避免上来就执行 close,导致动画错乱
+ if (this.isShow) {
+ this.close();
+ }
+ }
+ },
+ immediate: true
+ }
+ },
+ computed: {
+ // 初始化动画条件
+ transformStyles() {
+ const style = {
+ transform: this.transform,
+ opacity: this.opacity,
+ ...this.$uv.addStyle(this.customStyle),
+ 'transition-duration': `${this.duration / 1000}s`
+ };
+ return this.$uv.addStyle(style,'string');
+ }
+ },
+ created() {
+ // 动画默认配置
+ this.config = {
+ duration: this.duration,
+ timingFunction: this.timingFunction,
+ transformOrigin: '50% 50%',
+ delay: 0
+ };
+ this.durationTime = this.duration;
+ },
+ methods: {
+ /**
+ * ref 触发 初始化动画
+ */
+ init(obj = {}) {
+ if (obj.duration) {
+ this.durationTime = obj.duration;
+ }
+ this.animation = createAnimation(Object.assign(this.config, obj),this);
+ },
+ /**
+ * 点击组件触发回调
+ */
+ onClick() {
+ this.$emit('click', {
+ detail: this.isShow
+ })
+ },
+ /**
+ * ref 触发 动画分组
+ * @param {Object} obj
+ */
+ step(obj, config = {}) {
+ if (!this.animation) return;
+ for (let i in obj) {
+ try {
+ if(typeof obj[i] === 'object'){
+ this.animation[i](...obj[i]);
+ }else{
+ this.animation[i](obj[i]);
+ }
+ } catch (e) {
+ console.error(`方法 ${i} 不存在`);
+ }
+ }
+ this.animation.step(config);
+ return this;
+ },
+ /**
+ * ref 触发 执行动画
+ */
+ run(fn) {
+ if (!this.animation) return;
+ this.animation.run(fn);
+ },
+ // 开始过度动画
+ open() {
+ clearTimeout(this.timer);
+ this.transform = '';
+ this.isShow = true;
+ let { opacity, transform } = this.styleInit(false);
+ if (typeof opacity !== 'undefined') {
+ this.opacity = opacity;
+ }
+ this.transform = transform;
+ // 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
+ this.$nextTick(() => {
+ // TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
+ this.timer = setTimeout(() => {
+ this.animation = createAnimation(this.config, this);
+ this.tranfromInit(false).step();
+ // #ifdef APP-NVUE
+ if(this.cellChild) {
+ this.opacity = 1;
+ } else{
+ this.animation.run();
+ }
+ // #endif
+ // #ifndef APP-NVUE
+ this.animation.run();
+ // #endif
+ // #ifdef VUE3
+ // #ifdef H5
+ this.opacity = 1;
+ // #endif
+ // #endif
+ this.$emit('change', {
+ detail: this.isShow
+ })
+ // #ifdef H5
+ // #ifdef VUE3
+ this.transform = '';
+ // #endif
+ // #endif
+ }, 20);
+ })
+ },
+ // 关闭过渡动画
+ close(type) {
+ if (!this.animation) return;
+ this.tranfromInit(true)
+ .step()
+ .run(() => {
+ this.isShow = false;
+ this.animationData = null;
+ this.animation = null;
+ let { opacity, transform } = this.styleInit(false);
+ this.opacity = opacity || 1;
+ this.transform = transform;
+ this.$emit('change', {
+ detail: this.isShow
+ });
+ })
+ },
+ // 处理动画开始前的默认样式
+ styleInit(type) {
+ let styles = {
+ transform: ''
+ };
+ let buildStyle = (type, mode) => {
+ if (mode === 'fade') {
+ styles.opacity = this.animationType(type)[mode];
+ } else {
+ styles.transform += this.animationType(type)[mode] + ' ';
+ }
+ }
+ if (typeof this.mode === 'string') {
+ buildStyle(type, this.mode);
+ } else {
+ this.mode.forEach(mode => {
+ buildStyle(type, mode)
+ })
+ }
+ return styles
+ },
+ // 处理内置组合动画
+ tranfromInit(type) {
+ let buildTranfrom = (type, mode) => {
+ let aniNum = null;
+ if (mode === 'fade') {
+ aniNum = type ? 0 : 1;
+ } else {
+ aniNum = type ? '-100%' : '0';
+ if (mode === 'zoom-in') {
+ aniNum = type ? 0.8 : 1
+ }
+ if (mode === 'zoom-out') {
+ aniNum = type ? 1.2 : 1
+ }
+ if (mode === 'slide-right') {
+ aniNum = type ? '100%' : '0'
+ }
+ if (mode === 'slide-bottom') {
+ aniNum = type ? '100%' : '0'
+ }
+ }
+ this.animation[this.animationMode()[mode]](aniNum)
+ }
+ if (typeof this.mode === 'string') {
+ buildTranfrom(type, this.mode)
+ } else {
+ this.mode.forEach(mode => {
+ buildTranfrom(type, mode)
+ })
+ }
+ return this.animation;
+ },
+ animationType(type) {
+ return {
+ fade: type ? 1 : 0,
+ 'slide-top': `translateY(${type ? '0' : '-100%'})`,
+ 'slide-right': `translateX(${type ? '0' : '100%'})`,
+ 'slide-bottom': `translateY(${type ? '0' : '100%'})`,
+ 'slide-left': `translateX(${type ? '0' : '-100%'})`,
+ 'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
+ 'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
+ }
+ },
+ // 内置动画类型与实际动画对应字典
+ animationMode() {
+ return {
+ fade: 'opacity',
+ 'slide-top': 'translateY',
+ 'slide-right': 'translateX',
+ 'slide-bottom': 'translateY',
+ 'slide-left': 'translateX',
+ 'zoom-in': 'scale',
+ 'zoom-out': 'scale'
+ }
+ },
+ // 驼峰转中横线
+ toLine(name) {
+ return name.replace(/([A-Z])/g, '-$1').toLowerCase()
+ }
+ }
+ }
+</script>
diff --git a/ledApp/uni_modules/uv-transition/package.json b/ledApp/uni_modules/uv-transition/package.json
new file mode 100644
index 0000000..c05eadb
--- /dev/null
+++ b/ledApp/uni_modules/uv-transition/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-transition",
+ "displayName": "uv-transition 动画 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.8",
+ "description": "transition 该组件用于组件的动画过渡效果。",
+ "keywords": [
+ "uv-transition",
+ "uvui",
+ "uv-ui",
+ "transition",
+ "动画"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-transition/readme.md b/ledApp/uni_modules/uv-transition/readme.md
new file mode 100644
index 0000000..fbf8f59
--- /dev/null
+++ b/ledApp/uni_modules/uv-transition/readme.md
@@ -0,0 +1,15 @@
+## Transition 动画
+
+> **组件名:uv-transition**
+
+该组件用于组件的动画过渡效果,支持自定义动画,开箱即用。
+
+# <a href="https://www.uvui.cn/components/transition.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-ui-tools/changelog.md b/ledApp/uni_modules/uv-ui-tools/changelog.md
new file mode 100644
index 0000000..998373e
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/changelog.md
@@ -0,0 +1,76 @@
+## 1.1.25(2024-01-20)
+1.1.20版本更新
+## 1.1.24(2023-12-21)
+1. luch-request更新
+## 1.1.23(2023-12-12)
+1. 1.1.19版本
+## 1.1.22(2023-11-28)
+1. 优化
+## 1.1.21(2023-11-10)
+1. 1.1.17版本
+## 1.1.20(2023-10-30)
+1. 1.1.16版本
+## 1.1.19(2023-10-13)
+1. 兼容vue3
+## 1.1.18(2023-10-12)
+1. 1.1.15版本
+## 1.1.17(2023-09-27)
+1. 1.1.14版本发布
+## 1.1.16(2023-09-15)
+1. 1.1.13版本发布
+## 1.1.15(2023-09-15)
+1. 更新button.js相关按钮支持open-type="agreePrivacyAuthorization"
+## 1.1.14(2023-09-14)
+1. 优化dayjs
+## 1.1.13(2023-09-13)
+1. 优化,$uv中增加unit参数,方便组件中使用
+## 1.1.12(2023-09-10)
+1. 升级版本
+## 1.1.11(2023-09-04)
+1. 1.1.11版本
+## 1.1.10(2023-08-31)
+1. 修复customStyle和customClass存在冲突的问题
+## 1.1.9(2023-08-27)
+1. 版本升级
+2. 优化
+## 1.1.8(2023-08-24)
+1. 版本升级
+## 1.1.7(2023-08-22)
+1. 版本升级
+## 1.1.6(2023-08-18)
+uvui版本:1.1.6
+## 1.0.15(2023-08-14)
+1. 更新uvui版本号
+## 1.0.13(2023-08-06)
+1. 优化
+## 1.0.12(2023-08-06)
+1. 修改版本号
+## 1.0.11(2023-08-06)
+1. 路由增加events参数
+2. 路由拦截修复
+## 1.0.10(2023-08-01)
+1. 优化
+## 1.0.9(2023-06-28)
+优化openType.js
+## 1.0.8(2023-06-15)
+1. 修改支付宝报错的BUG
+## 1.0.7(2023-06-07)
+1. 解决微信小程序使用uvui提示 Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors
+2. 解决上述提示,需要在uni.scss配置$uvui-nvue-style: false; 然后在APP.vue下面引入uvui内置的基础样式:@import '@/uni_modules/uv-ui-tools/index.scss';
+## 1.0.6(2023-06-04)
+1. uv-ui-tools 优化工具组件,兼容更多功能
+2. 小程序分享功能优化等
+## 1.0.5(2023-06-02)
+1. 修改扩展使用mixin中方法的问题
+## 1.0.4(2023-05-23)
+1. 兼容百度小程序修改bem函数
+## 1.0.3(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.2(2023-05-10)
+1. 增加Http请求封装
+2. 优化
+## 1.0.1(2023-05-04)
+1. 修改名称及备注
+## 1.0.0(2023-05-04)
+1. uv-ui工具集首次发布
diff --git a/ledApp/uni_modules/uv-ui-tools/components/uv-ui-tools/uv-ui-tools.vue b/ledApp/uni_modules/uv-ui-tools/components/uv-ui-tools/uv-ui-tools.vue
new file mode 100644
index 0000000..baf45e9
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/components/uv-ui-tools/uv-ui-tools.vue
@@ -0,0 +1,6 @@
+<template>
+</template>
+<script>
+</script>
+<style>
+</style>
diff --git a/ledApp/uni_modules/uv-ui-tools/index.js b/ledApp/uni_modules/uv-ui-tools/index.js
new file mode 100644
index 0000000..71a8b66
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/index.js
@@ -0,0 +1,79 @@
+// 全局挂载引入http相关请求拦截插件
+import Request from './libs/luch-request'
+
+// 引入全局mixin
+import mixin from './libs/mixin/mixin.js'
+// 小程序特有的mixin
+import mpMixin from './libs/mixin/mpMixin.js'
+// #ifdef MP
+import mpShare from './libs/mixin/mpShare.js'
+// #endif
+
+// 路由封装
+import route from './libs/util/route.js'
+// 公共工具函数
+import * as index from './libs/function/index.js'
+// 防抖方法
+import debounce from './libs/function/debounce.js'
+// 节流方法
+import throttle from './libs/function/throttle.js'
+// 规则检验
+import * as test from './libs/function/test.js'
+
+// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制
+import * as colorGradient from './libs/function/colorGradient.js'
+
+// 配置信息
+import config from './libs/config/config.js'
+// 平台
+import platform from './libs/function/platform'
+
+const $uv = {
+ route,
+ config,
+ test,
+ date: index.timeFormat, // 另名date
+ ...index,
+ colorGradient: colorGradient.colorGradient,
+ hexToRgb: colorGradient.hexToRgb,
+ rgbToHex: colorGradient.rgbToHex,
+ colorToRgba: colorGradient.colorToRgba,
+ http: new Request(),
+ debounce,
+ throttle,
+ platform,
+ mixin,
+ mpMixin
+}
+uni.$uv = $uv;
+const install = (Vue,options={}) => {
+ // #ifndef APP-NVUE
+ const cloneMixin = index.deepClone(mixin);
+ delete cloneMixin?.props?.customClass;
+ delete cloneMixin?.props?.customStyle;
+ Vue.mixin(cloneMixin);
+ // #ifdef MP
+ if(options.mpShare){
+ Vue.mixin(mpShare);
+ }
+ // #endif
+ // #endif
+ // #ifdef VUE2
+ // 时间格式化,同时两个名称,date和timeFormat
+ Vue.filter('timeFormat', (timestamp, format) => uni.$uv.timeFormat(timestamp, format));
+ Vue.filter('date', (timestamp, format) => uni.$uv.timeFormat(timestamp, format));
+ // 将多久以前的方法,注入到全局过滤器
+ Vue.filter('timeFrom', (timestamp, format) => uni.$uv.timeFrom(timestamp, format));
+ // 同时挂载到uni和Vue.prototype中
+ // #ifndef APP-NVUE
+ // 只有vue,挂载到Vue.prototype才有意义,因为nvue中全局Vue.prototype和Vue.mixin是无效的
+ Vue.prototype.$uv = $uv;
+ // #endif
+ // #endif
+ // #ifdef VUE3
+ Vue.config.globalProperties.$uv = $uv;
+ // #endif
+}
+export default {
+ install
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/index.scss b/ledApp/uni_modules/uv-ui-tools/index.scss
new file mode 100644
index 0000000..8d05b8d
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/index.scss
@@ -0,0 +1,7 @@
+// 引入公共基础类
+@import "./libs/css/common.scss";
+
+// 非nvue的样式
+/* #ifndef APP-NVUE */
+@import "./libs/css/vue.scss";
+/* #endif */
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/config/config.js b/ledApp/uni_modules/uv-ui-tools/libs/config/config.js
new file mode 100644
index 0000000..f18ae74
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/config/config.js
@@ -0,0 +1,34 @@
+// 此版本发布于2024-01-20
+const version = '1.1.20'
+
+// 开发环境才提示,生产环境不会提示
+if (process.env.NODE_ENV === 'development') {
+ console.log(`\n %c uvui V${version} https://www.uvui.cn/ \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0; border-radius: 5px;');
+}
+
+export default {
+ v: version,
+ version,
+ // 主题名称
+ type: [
+ 'primary',
+ 'success',
+ 'info',
+ 'error',
+ 'warning'
+ ],
+ // 颜色部分,本来可以通过scss的:export导出供js使用,但是奈何nvue不支持
+ color: {
+ 'uv-primary': '#2979ff',
+ 'uv-warning': '#ff9900',
+ 'uv-success': '#19be6b',
+ 'uv-error': '#fa3534',
+ 'uv-info': '#909399',
+ 'uv-main-color': '#303133',
+ 'uv-content-color': '#606266',
+ 'uv-tips-color': '#909399',
+ 'uv-light-color': '#c0c4cc'
+ },
+ // 默认单位,可以通过配置为rpx,那么在用于传入组件大小参数为数值时,就默认为rpx
+ unit: 'px'
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/css/color.scss b/ledApp/uni_modules/uv-ui-tools/libs/css/color.scss
new file mode 100644
index 0000000..ce65743
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/css/color.scss
@@ -0,0 +1,32 @@
+$uv-main-color: #303133 !default;
+$uv-content-color: #606266 !default;
+$uv-tips-color: #909193 !default;
+$uv-light-color: #c0c4cc !default;
+$uv-border-color: #dadbde !default;
+$uv-bg-color: #f3f4f6 !default;
+$uv-disabled-color: #c8c9cc !default;
+
+$uv-primary: #3c9cff !default;
+$uv-primary-dark: #398ade !default;
+$uv-primary-disabled: #9acafc !default;
+$uv-primary-light: #ecf5ff !default;
+
+$uv-warning: #f9ae3d !default;
+$uv-warning-dark: #f1a532 !default;
+$uv-warning-disabled: #f9d39b !default;
+$uv-warning-light: #fdf6ec !default;
+
+$uv-success: #5ac725 !default;
+$uv-success-dark: #53c21d !default;
+$uv-success-disabled: #a9e08f !default;
+$uv-success-light: #f5fff0;
+
+$uv-error: #f56c6c !default;
+$uv-error-dark: #e45656 !default;
+$uv-error-disabled: #f7b2b2 !default;
+$uv-error-light: #fef0f0 !default;
+
+$uv-info: #909399 !default;
+$uv-info-dark: #767a82 !default;
+$uv-info-disabled: #c4c6c9 !default;
+$uv-info-light: #f4f4f5 !default;
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/css/common.scss b/ledApp/uni_modules/uv-ui-tools/libs/css/common.scss
new file mode 100644
index 0000000..7ab99f8
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/css/common.scss
@@ -0,0 +1,100 @@
+// 超出行数,自动显示行尾省略号,最多5行
+// 来自uvui的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】
+@for $i from 1 through 5 {
+ .uv-line-#{$i} {
+ /* #ifdef APP-NVUE */
+ // nvue下,可以直接使用lines属性,这是weex特有样式
+ lines: $i;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ flex: 1;
+ /* #endif */
+
+ /* #ifndef APP-NVUE */
+ // vue下,单行和多行显示省略号需要单独处理
+ @if $i == '1' {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ } @else {
+ display: -webkit-box!important;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ -webkit-line-clamp: $i;
+ -webkit-box-orient: vertical!important;
+ }
+ /* #endif */
+ }
+}
+$uv-bordercolor: #dadbde;
+@if variable-exists(uv-border-color) {
+ $uv-bordercolor: $uv-border-color;
+}
+
+// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时,
+// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效
+// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important
+// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现
+.uv-border {
+ border-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-style: solid;
+}
+
+.uv-border-top {
+ border-top-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-top-style: solid;
+}
+
+.uv-border-left {
+ border-left-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-left-style: solid;
+}
+
+.uv-border-right {
+ border-right-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-right-style: solid;
+}
+
+.uv-border-bottom {
+ border-bottom-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-bottom-style: solid;
+}
+
+.uv-border-top-bottom {
+ border-top-width: 0.5px!important;
+ border-bottom-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-top-style: solid;
+ border-bottom-style: solid;
+}
+
+// 去除button的所有默认样式,让其表现跟普通的view、text元素一样
+.uv-reset-button {
+ padding: 0;
+ background-color: transparent;
+ /* #ifndef APP-PLUS */
+ font-size: inherit;
+ line-height: inherit;
+ color: inherit;
+ /* #endif */
+ /* #ifdef APP-NVUE */
+ border-width: 0;
+ /* #endif */
+}
+
+/* #ifndef APP-NVUE */
+.uv-reset-button::after {
+ border: none;
+}
+/* #endif */
+
+.uv-hover-class {
+ opacity: 0.7;
+}
+
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/css/components.scss b/ledApp/uni_modules/uv-ui-tools/libs/css/components.scss
new file mode 100644
index 0000000..81ce15d
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/css/components.scss
@@ -0,0 +1,23 @@
+@mixin flex($direction: row) {
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ flex-direction: $direction;
+}
+
+/* #ifndef APP-NVUE */
+// 由于uvui是基于nvue环境进行开发的,此环境中普通元素默认为flex-direction: column;
+// 所以在非nvue中,需要对元素进行重置为flex-direction: column; 否则可能会表现异常
+$uvui-nvue-style: true !default;
+@if $uvui-nvue-style == true {
+ view, scroll-view, swiper-item {
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ flex-grow: 0;
+ flex-basis: auto;
+ align-items: stretch;
+ align-content: flex-start;
+ }
+}
+/* #endif */
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/css/variable.scss b/ledApp/uni_modules/uv-ui-tools/libs/css/variable.scss
new file mode 100644
index 0000000..63903c9
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/css/variable.scss
@@ -0,0 +1,111 @@
+// 超出行数,自动显示行尾省略号,最多5行
+// 来自uvui的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】
+@if variable-exists(show-lines) {
+ @for $i from 1 through 5 {
+ .uv-line-#{$i} {
+ /* #ifdef APP-NVUE */
+ // nvue下,可以直接使用lines属性,这是weex特有样式
+ lines: $i;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ flex: 1;
+ /* #endif */
+
+ /* #ifndef APP-NVUE */
+ // vue下,单行和多行显示省略号需要单独处理
+ @if $i == '1' {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ } @else {
+ display: -webkit-box!important;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ -webkit-line-clamp: $i;
+ -webkit-box-orient: vertical!important;
+ }
+ /* #endif */
+ }
+ }
+}
+@if variable-exists(show-border) {
+ $uv-bordercolor: #dadbde;
+ @if variable-exists(uv-border-color) {
+ $uv-bordercolor: $uv-border-color;
+ }
+ // 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时,
+ // App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效
+ // 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important
+ // 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现
+ @if variable-exists(show-border-surround) {
+ .uv-border {
+ border-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-style: solid;
+ }
+ }
+ @if variable-exists(show-border-top) {
+ .uv-border-top {
+ border-top-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-top-style: solid;
+ }
+ }
+ @if variable-exists(show-border-left) {
+ .uv-border-left {
+ border-left-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-left-style: solid;
+ }
+ }
+ @if variable-exists(show-border-right) {
+ .uv-border-right {
+ border-right-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-right-style: solid;
+ }
+ }
+ @if variable-exists(show-border-bottom) {
+ .uv-border-bottom {
+ border-bottom-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-bottom-style: solid;
+ }
+ }
+ @if variable-exists(show-border-top-bottom) {
+ .uv-border-top-bottom {
+ border-top-width: 0.5px!important;
+ border-bottom-width: 0.5px!important;
+ border-color: $uv-bordercolor!important;
+ border-top-style: solid;
+ border-bottom-style: solid;
+ }
+ }
+}
+@if variable-exists(show-reset-button) {
+ // 去除button的所有默认样式,让其表现跟普通的view、text元素一样
+ .uv-reset-button {
+ padding: 0;
+ background-color: transparent;
+ /* #ifndef APP-PLUS */
+ font-size: inherit;
+ line-height: inherit;
+ color: inherit;
+ /* #endif */
+ /* #ifdef APP-NVUE */
+ border-width: 0;
+ /* #endif */
+ }
+
+ /* #ifndef APP-NVUE */
+ .uv-reset-button::after {
+ border: none;
+ }
+ /* #endif */
+}
+@if variable-exists(show-hover) {
+ .uv-hover-class {
+ opacity: 0.7;
+ }
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/css/vue.scss b/ledApp/uni_modules/uv-ui-tools/libs/css/vue.scss
new file mode 100644
index 0000000..bdbefdd
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/css/vue.scss
@@ -0,0 +1,40 @@
+// 历遍生成4个方向的底部安全区
+@each $d in top, right, bottom, left {
+ .uv-safe-area-inset-#{$d} {
+ padding-#{$d}: 0;
+ padding-#{$d}: constant(safe-area-inset-#{$d});
+ padding-#{$d}: env(safe-area-inset-#{$d});
+ }
+}
+
+//提升H5端uni.toast()的层级,避免被uvui的modal等遮盖
+/* #ifdef H5 */
+uni-toast {
+ z-index: 10090;
+}
+uni-toast .uni-toast {
+ z-index: 10090;
+}
+/* #endif */
+
+// 隐藏scroll-view的滚动条
+::-webkit-scrollbar {
+ display: none;
+ width: 0 !important;
+ height: 0 !important;
+ -webkit-appearance: none;
+ background: transparent;
+}
+
+$uvui-nvue-style: true !default;
+@if $uvui-nvue-style == false {
+ view, scroll-view, swiper-item {
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ flex-grow: 0;
+ flex-basis: auto;
+ align-items: stretch;
+ align-content: flex-start;
+ }
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/function/colorGradient.js b/ledApp/uni_modules/uv-ui-tools/libs/function/colorGradient.js
new file mode 100644
index 0000000..55c188f
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/function/colorGradient.js
@@ -0,0 +1,134 @@
+/**
+ * 求两个颜色之间的渐变值
+ * @param {string} startColor 开始的颜色
+ * @param {string} endColor 结束的颜色
+ * @param {number} step 颜色等分的份额
+ * */
+function colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) {
+ const startRGB = hexToRgb(startColor, false) // 转换为rgb数组模式
+ const startR = startRGB[0]
+ const startG = startRGB[1]
+ const startB = startRGB[2]
+
+ const endRGB = hexToRgb(endColor, false)
+ const endR = endRGB[0]
+ const endG = endRGB[1]
+ const endB = endRGB[2]
+
+ const sR = (endR - startR) / step // 总差值
+ const sG = (endG - startG) / step
+ const sB = (endB - startB) / step
+ const colorArr = []
+ for (let i = 0; i < step; i++) {
+ // 计算每一步的hex值
+ let hex = rgbToHex(`rgb(${Math.round((sR * i + startR))},${Math.round((sG * i + startG))},${Math.round((sB
+ * i + startB))})`)
+ // 确保第一个颜色值为startColor的值
+ if (i === 0) hex = rgbToHex(startColor)
+ // 确保最后一个颜色值为endColor的值
+ if (i === step - 1) hex = rgbToHex(endColor)
+ colorArr.push(hex)
+ }
+ return colorArr
+}
+
+// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
+function hexToRgb(sColor, str = true) {
+ const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
+ sColor = String(sColor).toLowerCase()
+ if (sColor && reg.test(sColor)) {
+ if (sColor.length === 4) {
+ let sColorNew = '#'
+ for (let i = 1; i < 4; i += 1) {
+ sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
+ }
+ sColor = sColorNew
+ }
+ // 处理六位的颜色值
+ const sColorChange = []
+ for (let i = 1; i < 7; i += 2) {
+ sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))
+ }
+ if (!str) {
+ return sColorChange
+ }
+ return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})`
+ } if (/^(rgb|RGB)/.test(sColor)) {
+ const arr = sColor.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')
+ return arr.map((val) => Number(val))
+ }
+ return sColor
+}
+
+// 将rgb表示方式转换为hex表示方式
+function rgbToHex(rgb) {
+ const _this = rgb
+ const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
+ if (/^(rgb|RGB)/.test(_this)) {
+ const aColor = _this.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')
+ let strHex = '#'
+ for (let i = 0; i < aColor.length; i++) {
+ let hex = Number(aColor[i]).toString(16)
+ hex = String(hex).length == 1 ? `${0}${hex}` : hex // 保证每个rgb的值为2位
+ if (hex === '0') {
+ hex += hex
+ }
+ strHex += hex
+ }
+ if (strHex.length !== 7) {
+ strHex = _this
+ }
+ return strHex
+ } if (reg.test(_this)) {
+ const aNum = _this.replace(/#/, '').split('')
+ if (aNum.length === 6) {
+ return _this
+ } if (aNum.length === 3) {
+ let numHex = '#'
+ for (let i = 0; i < aNum.length; i += 1) {
+ numHex += (aNum[i] + aNum[i])
+ }
+ return numHex
+ }
+ } else {
+ return _this
+ }
+}
+
+/**
+* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba(255,255,255,0.5)字符串
+* sHex为传入的十六进制的色值
+* alpha为rgba的透明度
+*/
+function colorToRgba(color, alpha) {
+ color = rgbToHex(color)
+ // 十六进制颜色值的正则表达式
+ const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
+ /* 16进制颜色转为RGB格式 */
+ let sColor = String(color).toLowerCase()
+ if (sColor && reg.test(sColor)) {
+ if (sColor.length === 4) {
+ let sColorNew = '#'
+ for (let i = 1; i < 4; i += 1) {
+ sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
+ }
+ sColor = sColorNew
+ }
+ // 处理六位的颜色值
+ const sColorChange = []
+ for (let i = 1; i < 7; i += 2) {
+ sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))
+ }
+ // return sColorChange.join(',')
+ return `rgba(${sColorChange.join(',')},${alpha})`
+ }
+
+ return sColor
+}
+
+export {
+ colorGradient,
+ hexToRgb,
+ rgbToHex,
+ colorToRgba
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/function/debounce.js b/ledApp/uni_modules/uv-ui-tools/libs/function/debounce.js
new file mode 100644
index 0000000..ad3996b
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/function/debounce.js
@@ -0,0 +1,29 @@
+let timeout = null
+
+/**
+ * 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
+ *
+ * @param {Function} func 要执行的回调函数
+ * @param {Number} wait 延时的时间
+ * @param {Boolean} immediate 是否立即执行
+ * @return null
+ */
+function debounce(func, wait = 500, immediate = false) {
+ // 清除定时器
+ if (timeout !== null) clearTimeout(timeout)
+ // 立即执行,此类情况一般用不到
+ if (immediate) {
+ const callNow = !timeout
+ timeout = setTimeout(() => {
+ timeout = null
+ }, wait)
+ if (callNow) typeof func === 'function' && func()
+ } else {
+ // 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
+ timeout = setTimeout(() => {
+ typeof func === 'function' && func()
+ }, wait)
+ }
+}
+
+export default debounce
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/function/digit.js b/ledApp/uni_modules/uv-ui-tools/libs/function/digit.js
new file mode 100644
index 0000000..c8260a0
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/function/digit.js
@@ -0,0 +1,167 @@
+let _boundaryCheckingState = true; // 是否进行越界检查的全局开关
+
+/**
+ * 把错误的数据转正
+ * @private
+ * @example strip(0.09999999999999998)=0.1
+ */
+function strip(num, precision = 15) {
+ return +parseFloat(Number(num).toPrecision(precision));
+}
+
+/**
+ * Return digits length of a number
+ * @private
+ * @param {*number} num Input number
+ */
+function digitLength(num) {
+ // Get digit length of e
+ const eSplit = num.toString().split(/[eE]/);
+ const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);
+ return len > 0 ? len : 0;
+}
+
+/**
+ * 把小数转成整数,如果是小数则放大成整数
+ * @private
+ * @param {*number} num 输入数
+ */
+function float2Fixed(num) {
+ if (num.toString().indexOf('e') === -1) {
+ return Number(num.toString().replace('.', ''));
+ }
+ const dLen = digitLength(num);
+ return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num);
+}
+
+/**
+ * 检测数字是否越界,如果越界给出提示
+ * @private
+ * @param {*number} num 输入数
+ */
+function checkBoundary(num) {
+ if (_boundaryCheckingState) {
+ if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
+ console.warn(`${num} 超出了精度限制,结果可能不正确`);
+ }
+ }
+}
+
+/**
+ * 把递归操作扁平迭代化
+ * @param {number[]} arr 要操作的数字数组
+ * @param {function} operation 迭代操作
+ * @private
+ */
+function iteratorOperation(arr, operation) {
+ const [num1, num2, ...others] = arr;
+ let res = operation(num1, num2);
+
+ others.forEach((num) => {
+ res = operation(res, num);
+ });
+
+ return res;
+}
+
+/**
+ * 高精度乘法
+ * @export
+ */
+export function times(...nums) {
+ if (nums.length > 2) {
+ return iteratorOperation(nums, times);
+ }
+
+ const [num1, num2] = nums;
+ const num1Changed = float2Fixed(num1);
+ const num2Changed = float2Fixed(num2);
+ const baseNum = digitLength(num1) + digitLength(num2);
+ const leftValue = num1Changed * num2Changed;
+
+ checkBoundary(leftValue);
+
+ return leftValue / Math.pow(10, baseNum);
+}
+
+/**
+ * 高精度加法
+ * @export
+ */
+export function plus(...nums) {
+ if (nums.length > 2) {
+ return iteratorOperation(nums, plus);
+ }
+
+ const [num1, num2] = nums;
+ // 取最大的小数位
+ const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
+ // 把小数都转为整数然后再计算
+ return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
+}
+
+/**
+ * 高精度减法
+ * @export
+ */
+export function minus(...nums) {
+ if (nums.length > 2) {
+ return iteratorOperation(nums, minus);
+ }
+
+ const [num1, num2] = nums;
+ const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
+ return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
+}
+
+/**
+ * 高精度除法
+ * @export
+ */
+export function divide(...nums) {
+ if (nums.length > 2) {
+ return iteratorOperation(nums, divide);
+ }
+
+ const [num1, num2] = nums;
+ const num1Changed = float2Fixed(num1);
+ const num2Changed = float2Fixed(num2);
+ checkBoundary(num1Changed);
+ checkBoundary(num2Changed);
+ // 重要,这里必须用strip进行修正
+ return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1))));
+}
+
+/**
+ * 四舍五入
+ * @export
+ */
+export function round(num, ratio) {
+ const base = Math.pow(10, ratio);
+ let result = divide(Math.round(Math.abs(times(num, base))), base);
+ if (num < 0 && result !== 0) {
+ result = times(result, -1);
+ }
+ // 位数不足则补0
+ return result;
+}
+
+/**
+ * 是否进行边界检查,默认开启
+ * @param flag 标记开关,true 为开启,false 为关闭,默认为 true
+ * @export
+ */
+export function enableBoundaryChecking(flag = true) {
+ _boundaryCheckingState = flag;
+}
+
+
+export default {
+ times,
+ plus,
+ minus,
+ divide,
+ round,
+ enableBoundaryChecking,
+};
+
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/function/index.js b/ledApp/uni_modules/uv-ui-tools/libs/function/index.js
new file mode 100644
index 0000000..b35e0ab
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/function/index.js
@@ -0,0 +1,734 @@
+import { number, empty } from './test.js'
+import { round } from './digit.js'
+/**
+ * @description 如果value小于min,取min;如果value大于max,取max
+ * @param {number} min
+ * @param {number} max
+ * @param {number} value
+ */
+function range(min = 0, max = 0, value = 0) {
+ return Math.max(min, Math.min(max, Number(value)))
+}
+
+/**
+ * @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
+ * @param {number|string} value 用户传递值的px值
+ * @param {boolean} unit
+ * @returns {number|string}
+ */
+function getPx(value, unit = false) {
+ if (number(value)) {
+ return unit ? `${value}px` : Number(value)
+ }
+ // 如果带有rpx,先取出其数值部分,再转为px值
+ if (/(rpx|upx)$/.test(value)) {
+ return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
+ }
+ return unit ? `${parseInt(value)}px` : parseInt(value)
+}
+
+/**
+ * @description 进行延时,以达到可以简写代码的目的 比如: await uni.$uv.sleep(20)将会阻塞20ms
+ * @param {number} value 堵塞时间 单位ms 毫秒
+ * @returns {Promise} 返回promise
+ */
+function sleep(value = 30) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve()
+ }, value)
+ })
+}
+/**
+ * @description 运行期判断平台
+ * @returns {string} 返回所在平台(小写)
+ * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
+ */
+function os() {
+ return uni.getSystemInfoSync().platform.toLowerCase()
+}
+/**
+ * @description 获取系统信息同步接口
+ * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
+ */
+function sys() {
+ return uni.getSystemInfoSync()
+}
+
+/**
+ * @description 取一个区间数
+ * @param {Number} min 最小值
+ * @param {Number} max 最大值
+ */
+function random(min, max) {
+ if (min >= 0 && max > 0 && max >= min) {
+ const gab = max - min + 1
+ return Math.floor(Math.random() * gab + min)
+ }
+ return 0
+}
+
+/**
+ * @param {Number} len uuid的长度
+ * @param {Boolean} firstU 将返回的首字母置为"u"
+ * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
+ */
+function guid(len = 32, firstU = true, radix = null) {
+ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
+ const uuid = []
+ radix = radix || chars.length
+
+ if (len) {
+ // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
+ for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
+ } else {
+ let r
+ // rfc4122标准要求返回的uuid中,某些位为固定的字符
+ uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
+ uuid[14] = '4'
+
+ for (let i = 0; i < 36; i++) {
+ if (!uuid[i]) {
+ r = 0 | Math.random() * 16
+ uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
+ }
+ }
+ }
+ // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
+ if (firstU) {
+ uuid.shift()
+ return `u${uuid.join('')}`
+ }
+ return uuid.join('')
+}
+
+/**
+* @description 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
+ this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
+ 这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
+ 值(默认为undefined),就是查找最顶层的$parent
+* @param {string|undefined} name 父组件的参数名
+*/
+function $parent(name = undefined) {
+ let parent = this.$parent
+ // 通过while历遍,这里主要是为了H5需要多层解析的问题
+ while (parent) {
+ // 父组件
+ if (parent.$options && parent.$options.name !== name) {
+ // 如果组件的name不相等,继续上一级寻找
+ parent = parent.$parent
+ } else {
+ return parent
+ }
+ }
+ return false
+}
+
+/**
+ * @description 样式转换
+ * 对象转字符串,或者字符串转对象
+ * @param {object | string} customStyle 需要转换的目标
+ * @param {String} target 转换的目的,object-转为对象,string-转为字符串
+ * @returns {object|string}
+ */
+function addStyle(customStyle, target = 'object') {
+ // 字符串转字符串,对象转对象情形,直接返回
+ if (empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&
+ typeof(customStyle) === 'string') {
+ return customStyle
+ }
+ // 字符串转对象
+ if (target === 'object') {
+ // 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
+ customStyle = trim(customStyle)
+ // 根据";"将字符串转为数组形式
+ const styleArray = customStyle.split(';')
+ const style = {}
+ // 历遍数组,拼接成对象
+ for (let i = 0; i < styleArray.length; i++) {
+ // 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
+ if (styleArray[i]) {
+ const item = styleArray[i].split(':')
+ style[trim(item[0])] = trim(item[1])
+ }
+ }
+ return style
+ }
+ // 这里为对象转字符串形式
+ let string = ''
+ for (const i in customStyle) {
+ // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
+ const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
+ string += `${key}:${customStyle[i]};`
+ }
+ // 去除两端空格
+ return trim(string)
+}
+
+/**
+ * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
+ * @param {string|number} value 需要添加单位的值
+ * @param {string} unit 添加的单位名 比如px
+ */
+function addUnit(value = 'auto', unit = uni?.$uv?.config?.unit ? uni?.$uv?.config?.unit : 'px') {
+ value = String(value)
+ // 用uvui内置验证规则中的number判断是否为数值
+ return number(value) ? `${value}${unit}` : value
+}
+
+/**
+ * @description 深度克隆
+ * @param {object} obj 需要深度克隆的对象
+ * @param cache 缓存
+ * @returns {*} 克隆后的对象或者原值(不是对象)
+ */
+function deepClone(obj, cache = new WeakMap()) {
+ if (obj === null || typeof obj !== 'object') return obj;
+ if (cache.has(obj)) return cache.get(obj);
+ let clone;
+ if (obj instanceof Date) {
+ clone = new Date(obj.getTime());
+ } else if (obj instanceof RegExp) {
+ clone = new RegExp(obj);
+ } else if (obj instanceof Map) {
+ clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));
+ } else if (obj instanceof Set) {
+ clone = new Set(Array.from(obj, value => deepClone(value, cache)));
+ } else if (Array.isArray(obj)) {
+ clone = obj.map(value => deepClone(value, cache));
+ } else if (Object.prototype.toString.call(obj) === '[object Object]') {
+ clone = Object.create(Object.getPrototypeOf(obj));
+ cache.set(obj, clone);
+ for (const [key, value] of Object.entries(obj)) {
+ clone[key] = deepClone(value, cache);
+ }
+ } else {
+ clone = Object.assign({}, obj);
+ }
+ cache.set(obj, clone);
+ return clone;
+}
+
+/**
+ * @description JS对象深度合并
+ * @param {object} target 需要拷贝的对象
+ * @param {object} source 拷贝的来源对象
+ * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
+ */
+function deepMerge(target = {}, source = {}) {
+ target = deepClone(target)
+ if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target;
+ const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target);
+ for (const prop in source) {
+ if (!source.hasOwnProperty(prop)) continue;
+ const sourceValue = source[prop];
+ const targetValue = merged[prop];
+ if (sourceValue instanceof Date) {
+ merged[prop] = new Date(sourceValue);
+ } else if (sourceValue instanceof RegExp) {
+ merged[prop] = new RegExp(sourceValue);
+ } else if (sourceValue instanceof Map) {
+ merged[prop] = new Map(sourceValue);
+ } else if (sourceValue instanceof Set) {
+ merged[prop] = new Set(sourceValue);
+ } else if (typeof sourceValue === 'object' && sourceValue !== null) {
+ merged[prop] = deepMerge(targetValue, sourceValue);
+ } else {
+ merged[prop] = sourceValue;
+ }
+ }
+ return merged;
+}
+
+/**
+ * @description error提示
+ * @param {*} err 错误内容
+ */
+function error(err) {
+ // 开发环境才提示,生产环境不会提示
+ if (process.env.NODE_ENV === 'development') {
+ console.error(`uvui提示:${err}`)
+ }
+}
+
+/**
+ * @description 打乱数组
+ * @param {array} array 需要打乱的数组
+ * @returns {array} 打乱后的数组
+ */
+function randomArray(array = []) {
+ // 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
+ return array.sort(() => Math.random() - 0.5)
+}
+
+// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
+// 所以这里做一个兼容polyfill的兼容处理
+if (!String.prototype.padStart) {
+ // 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
+ String.prototype.padStart = function(maxLength, fillString = ' ') {
+ if (Object.prototype.toString.call(fillString) !== '[object String]') {
+ throw new TypeError(
+ 'fillString must be String'
+ )
+ }
+ const str = this
+ // 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
+ if (str.length >= maxLength) return String(str)
+
+ const fillLength = maxLength - str.length
+ let times = Math.ceil(fillLength / fillString.length)
+ while (times >>= 1) {
+ fillString += fillString
+ if (times === 1) {
+ fillString += fillString
+ }
+ }
+ return fillString.slice(0, fillLength) + str
+ }
+}
+
+/**
+ * @description 格式化时间
+ * @param {String|Number} dateTime 需要格式化的时间戳
+ * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
+ * @returns {string} 返回格式化后的字符串
+ */
+function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
+ let date
+ // 若传入时间为假值,则取当前时间
+ if (!dateTime) {
+ date = new Date()
+ }
+ // 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容)
+ else if (/^\d{10}$/.test(dateTime?.toString().trim())) {
+ date = new Date(dateTime * 1000)
+ }
+ // 若用户传入字符串格式时间戳,new Date无法解析,需做兼容
+ else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
+ date = new Date(Number(dateTime))
+ }
+ // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间
+ // 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03'
+ else if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) {
+ date = new Date(dateTime.replace(/-/g, '/'))
+ }
+ // 其他都认为符合 RFC 2822 规范
+ else {
+ date = new Date(dateTime)
+ }
+
+ const timeSource = {
+ 'y': date.getFullYear().toString(), // 年
+ 'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
+ 'd': date.getDate().toString().padStart(2, '0'), // 日
+ 'h': date.getHours().toString().padStart(2, '0'), // 时
+ 'M': date.getMinutes().toString().padStart(2, '0'), // 分
+ 's': date.getSeconds().toString().padStart(2, '0') // 秒
+ // 有其他格式化字符需求可以继续添加,必须转化成字符串
+ }
+
+ for (const key in timeSource) {
+ const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
+ if (ret) {
+ // 年可能只需展示两位
+ const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
+ formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
+ }
+ }
+
+ return formatStr
+}
+
+/**
+ * @description 时间戳转为多久之前
+ * @param {String|Number} timestamp 时间戳
+ * @param {String|Boolean} format
+ * 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
+ * 如果为布尔值false,无论什么时间,都返回多久以前的格式
+ * @returns {string} 转化后的内容
+ */
+function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
+ if (timestamp == null) timestamp = Number(new Date())
+ timestamp = parseInt(timestamp)
+ // 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
+ if (timestamp.toString().length == 10) timestamp *= 1000
+ let timer = (new Date()).getTime() - timestamp
+ timer = parseInt(timer / 1000)
+ // 如果小于5分钟,则返回"刚刚",其他以此类推
+ let tips = ''
+ switch (true) {
+ case timer < 300:
+ tips = '刚刚'
+ break
+ case timer >= 300 && timer < 3600:
+ tips = `${parseInt(timer / 60)}分钟前`
+ break
+ case timer >= 3600 && timer < 86400:
+ tips = `${parseInt(timer / 3600)}小时前`
+ break
+ case timer >= 86400 && timer < 2592000:
+ tips = `${parseInt(timer / 86400)}天前`
+ break
+ default:
+ // 如果format为false,则无论什么时间戳,都显示xx之前
+ if (format === false) {
+ if (timer >= 2592000 && timer < 365 * 86400) {
+ tips = `${parseInt(timer / (86400 * 30))}个月前`
+ } else {
+ tips = `${parseInt(timer / (86400 * 365))}年前`
+ }
+ } else {
+ tips = timeFormat(timestamp, format)
+ }
+ }
+ return tips
+}
+
+/**
+ * @description 去除空格
+ * @param String str 需要去除空格的字符串
+ * @param String pos both(左右)|left|right|all 默认both
+ */
+function trim(str, pos = 'both') {
+ str = String(str)
+ if (pos == 'both') {
+ return str.replace(/^\s+|\s+$/g, '')
+ }
+ if (pos == 'left') {
+ return str.replace(/^\s*/, '')
+ }
+ if (pos == 'right') {
+ return str.replace(/(\s*$)/g, '')
+ }
+ if (pos == 'all') {
+ return str.replace(/\s+/g, '')
+ }
+ return str
+}
+
+/**
+ * @description 对象转url参数
+ * @param {object} data,对象
+ * @param {Boolean} isPrefix,是否自动加上"?"
+ * @param {string} arrayFormat 规则 indices|brackets|repeat|comma
+ */
+function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
+ const prefix = isPrefix ? '?' : ''
+ const _result = []
+ if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
+ for (const key in data) {
+ const value = data[key]
+ // 去掉为空的参数
+ if (['', undefined, null].indexOf(value) >= 0) {
+ continue
+ }
+ // 如果值为数组,另行处理
+ if (value.constructor === Array) {
+ // e.g. {ids: [1, 2, 3]}
+ switch (arrayFormat) {
+ case 'indices':
+ // 结果: ids[0]=1&ids[1]=2&ids[2]=3
+ for (let i = 0; i < value.length; i++) {
+ _result.push(`${key}[${i}]=${value[i]}`)
+ }
+ break
+ case 'brackets':
+ // 结果: ids[]=1&ids[]=2&ids[]=3
+ value.forEach((_value) => {
+ _result.push(`${key}[]=${_value}`)
+ })
+ break
+ case 'repeat':
+ // 结果: ids=1&ids=2&ids=3
+ value.forEach((_value) => {
+ _result.push(`${key}=${_value}`)
+ })
+ break
+ case 'comma':
+ // 结果: ids=1,2,3
+ let commaStr = ''
+ value.forEach((_value) => {
+ commaStr += (commaStr ? ',' : '') + _value
+ })
+ _result.push(`${key}=${commaStr}`)
+ break
+ default:
+ value.forEach((_value) => {
+ _result.push(`${key}[]=${_value}`)
+ })
+ }
+ } else {
+ _result.push(`${key}=${value}`)
+ }
+ }
+ return _result.length ? prefix + _result.join('&') : ''
+}
+
+/**
+ * 显示消息提示框
+ * @param {String} title 提示的内容,长度与 icon 取值有关。
+ * @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000
+ */
+function toast(title, duration = 2000) {
+ uni.showToast({
+ title: String(title),
+ icon: 'none',
+ duration
+ })
+}
+
+/**
+ * @description 根据主题type值,获取对应的图标
+ * @param {String} type 主题名称,primary|info|error|warning|success
+ * @param {boolean} fill 是否使用fill填充实体的图标
+ */
+function type2icon(type = 'success', fill = false) {
+ // 如果非预置值,默认为success
+ if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'
+ let iconName = ''
+ // 目前(2019-12-12),info和primary使用同一个图标
+ switch (type) {
+ case 'primary':
+ iconName = 'info-circle'
+ break
+ case 'info':
+ iconName = 'info-circle'
+ break
+ case 'error':
+ iconName = 'close-circle'
+ break
+ case 'warning':
+ iconName = 'error-circle'
+ break
+ case 'success':
+ iconName = 'checkmark-circle'
+ break
+ default:
+ iconName = 'checkmark-circle'
+ }
+ // 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
+ if (fill) iconName += '-fill'
+ return iconName
+}
+
+/**
+ * @description 数字格式化
+ * @param {number|string} number 要格式化的数字
+ * @param {number} decimals 保留几位小数
+ * @param {string} decimalPoint 小数点符号
+ * @param {string} thousandsSeparator 千分位符号
+ * @returns {string} 格式化后的数字
+ */
+function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
+ number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
+ const n = !isFinite(+number) ? 0 : +number
+ const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
+ const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
+ const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
+ let s = ''
+
+ s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
+ const re = /(-?\d+)(\d{3})/
+ while (re.test(s[0])) {
+ s[0] = s[0].replace(re, `$1${sep}$2`)
+ }
+
+ if ((s[1] || '').length < prec) {
+ s[1] = s[1] || ''
+ s[1] += new Array(prec - s[1].length + 1).join('0')
+ }
+ return s.join(dec)
+}
+
+/**
+ * @description 获取duration值
+ * 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位
+ * 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画
+ * @param {String|number} value 比如: "1s"|"100ms"|1|100
+ * @param {boolean} unit 提示: 如果是false 默认返回number
+ * @return {string|number}
+ */
+function getDuration(value, unit = true) {
+ const valueNum = parseInt(value)
+ if (unit) {
+ if (/s$/.test(value)) return value
+ return value > 30 ? `${value}ms` : `${value}s`
+ }
+ if (/ms$/.test(value)) return valueNum
+ if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000
+ return valueNum
+}
+
+/**
+ * @description 日期的月或日补零操作
+ * @param {String} value 需要补零的值
+ */
+function padZero(value) {
+ return `00${value}`.slice(-2)
+}
+
+/**
+ * @description 在uv-form的子组件内容发生变化,或者失去焦点时,尝试通知uv-form执行校验方法
+ * @param {*} instance
+ * @param {*} event
+ */
+function formValidate(instance, event) {
+ const formItem = $parent.call(instance, 'uv-form-item')
+ const form = $parent.call(instance, 'uv-form')
+ // 如果发生变化的input或者textarea等,其父组件中有uv-form-item或者uv-form等,就执行form的validate方法
+ // 同时将form-item的pros传递给form,让其进行精确对象验证
+ if (formItem && form) {
+ form.validateField(formItem.prop, () => {}, event)
+ }
+}
+
+/**
+ * @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
+ * @param {object} obj 对象
+ * @param {string} key 需要获取的属性字段
+ * @returns {*}
+ */
+function getProperty(obj, key) {
+ if (!obj) {
+ return
+ }
+ if (typeof key !== 'string' || key === '') {
+ return ''
+ }
+ if (key.indexOf('.') !== -1) {
+ const keys = key.split('.')
+ let firstObj = obj[keys[0]] || {}
+
+ for (let i = 1; i < keys.length; i++) {
+ if (firstObj) {
+ firstObj = firstObj[keys[i]]
+ }
+ }
+ return firstObj
+ }
+ return obj[key]
+}
+
+/**
+ * @description 设置对象的属性值,如果'a.b.c'的形式进行设置
+ * @param {object} obj 对象
+ * @param {string} key 需要设置的属性
+ * @param {string} value 设置的值
+ */
+function setProperty(obj, key, value) {
+ if (!obj) {
+ return
+ }
+ // 递归赋值
+ const inFn = function(_obj, keys, v) {
+ // 最后一个属性key
+ if (keys.length === 1) {
+ _obj[keys[0]] = v
+ return
+ }
+ // 0~length-1个key
+ while (keys.length > 1) {
+ const k = keys[0]
+ if (!_obj[k] || (typeof _obj[k] !== 'object')) {
+ _obj[k] = {}
+ }
+ const key = keys.shift()
+ // 自调用判断是否存在属性,不存在则自动创建对象
+ inFn(_obj[k], keys, v)
+ }
+ }
+
+ if (typeof key !== 'string' || key === '') {
+
+ } else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
+ const keys = key.split('.')
+ inFn(obj, keys, value)
+ } else {
+ obj[key] = value
+ }
+}
+
+/**
+ * @description 获取当前页面路径
+ */
+function page() {
+ const pages = getCurrentPages();
+ const route = pages[pages.length - 1]?.route;
+ // 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组
+ return `/${route ? route : ''}`
+}
+
+/**
+ * @description 获取当前路由栈实例数组
+ */
+function pages() {
+ const pages = getCurrentPages()
+ return pages
+}
+
+/**
+ * 获取页面历史栈指定层实例
+ * @param back {number} [0] - 0或者负数,表示获取历史栈的哪一层,0表示获取当前页面实例,-1 表示获取上一个页面实例。默认0。
+ */
+function getHistoryPage(back = 0) {
+ const pages = getCurrentPages()
+ const len = pages.length
+ return pages[len - 1 + back]
+}
+
+
+
+/**
+ * @description 修改uvui内置属性值
+ * @param {object} props 修改内置props属性
+ * @param {object} config 修改内置config属性
+ * @param {object} color 修改内置color属性
+ * @param {object} zIndex 修改内置zIndex属性
+ */
+function setConfig({
+ props = {},
+ config = {},
+ color = {},
+ zIndex = {}
+}) {
+ const {
+ deepMerge,
+ } = uni.$uv
+ uni.$uv.config = deepMerge(uni.$uv.config, config)
+ uni.$uv.props = deepMerge(uni.$uv.props, props)
+ uni.$uv.color = deepMerge(uni.$uv.color, color)
+ uni.$uv.zIndex = deepMerge(uni.$uv.zIndex, zIndex)
+}
+
+export {
+ range,
+ getPx,
+ sleep,
+ os,
+ sys,
+ random,
+ guid,
+ $parent,
+ addStyle,
+ addUnit,
+ deepClone,
+ deepMerge,
+ error,
+ randomArray,
+ timeFormat,
+ timeFrom,
+ trim,
+ queryParams,
+ toast,
+ type2icon,
+ priceFormat,
+ getDuration,
+ padZero,
+ formValidate,
+ getProperty,
+ setProperty,
+ page,
+ pages,
+ getHistoryPage,
+ setConfig
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/function/platform.js b/ledApp/uni_modules/uv-ui-tools/libs/function/platform.js
new file mode 100644
index 0000000..d6b926e
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/function/platform.js
@@ -0,0 +1,75 @@
+/**
+ * 注意:
+ * 此部分内容,在vue-cli模式下,需要在vue.config.js加入如下内容才有效:
+ * module.exports = {
+ * transpileDependencies: ['uview-v2']
+ * }
+ */
+
+let platform = 'none'
+
+// #ifdef VUE3
+platform = 'vue3'
+// #endif
+
+// #ifdef VUE2
+platform = 'vue2'
+// #endif
+
+// #ifdef APP-PLUS
+platform = 'plus'
+// #endif
+
+// #ifdef APP-NVUE
+platform = 'nvue'
+// #endif
+
+// #ifdef H5
+platform = 'h5'
+// #endif
+
+// #ifdef MP-WEIXIN
+platform = 'weixin'
+// #endif
+
+// #ifdef MP-ALIPAY
+platform = 'alipay'
+// #endif
+
+// #ifdef MP-BAIDU
+platform = 'baidu'
+// #endif
+
+// #ifdef MP-TOUTIAO
+platform = 'toutiao'
+// #endif
+
+// #ifdef MP-QQ
+platform = 'qq'
+// #endif
+
+// #ifdef MP-KUAISHOU
+platform = 'kuaishou'
+// #endif
+
+// #ifdef MP-360
+platform = '360'
+// #endif
+
+// #ifdef MP
+platform = 'mp'
+// #endif
+
+// #ifdef QUICKAPP-WEBVIEW
+platform = 'quickapp-webview'
+// #endif
+
+// #ifdef QUICKAPP-WEBVIEW-HUAWEI
+platform = 'quickapp-webview-huawei'
+// #endif
+
+// #ifdef QUICKAPP-WEBVIEW-UNION
+platform = 'quckapp-webview-union'
+// #endif
+
+export default platform
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/function/test.js b/ledApp/uni_modules/uv-ui-tools/libs/function/test.js
new file mode 100644
index 0000000..7c8b747
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/function/test.js
@@ -0,0 +1,287 @@
+/**
+ * 验证电子邮箱格式
+ */
+function email(value) {
+ return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
+}
+
+/**
+ * 验证手机格式
+ */
+function mobile(value) {
+ return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
+}
+
+/**
+ * 验证URL格式
+ */
+function url(value) {
+ return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/
+ .test(value)
+}
+
+/**
+ * 验证日期格式
+ */
+function date(value) {
+ if (!value) return false
+ // 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳
+ if (number(value)) value = +value
+ return !/Invalid|NaN/.test(new Date(value).toString())
+}
+
+/**
+ * 验证ISO类型的日期格式
+ */
+function dateISO(value) {
+ return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
+}
+
+/**
+ * 验证十进制数字
+ */
+function number(value) {
+ return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
+}
+
+/**
+ * 验证字符串
+ */
+function string(value) {
+ return typeof value === 'string'
+}
+
+/**
+ * 验证整数
+ */
+function digits(value) {
+ return /^\d+$/.test(value)
+}
+
+/**
+ * 验证身份证号码
+ */
+function idCard(value) {
+ return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
+ value
+ )
+}
+
+/**
+ * 是否车牌号
+ */
+function carNo(value) {
+ // 新能源车牌
+ const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
+ // 旧车牌
+ const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
+ if (value.length === 7) {
+ return creg.test(value)
+ } if (value.length === 8) {
+ return xreg.test(value)
+ }
+ return false
+}
+
+/**
+ * 金额,只允许2位小数
+ */
+function amount(value) {
+ // 金额,只允许保留两位小数
+ return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
+}
+
+/**
+ * 中文
+ */
+function chinese(value) {
+ const reg = /^[\u4e00-\u9fa5]+$/gi
+ return reg.test(value)
+}
+
+/**
+ * 只能输入字母
+ */
+function letter(value) {
+ return /^[a-zA-Z]*$/.test(value)
+}
+
+/**
+ * 只能是字母或者数字
+ */
+function enOrNum(value) {
+ // 英文或者数字
+ const reg = /^[0-9a-zA-Z]*$/g
+ return reg.test(value)
+}
+
+/**
+ * 验证是否包含某个值
+ */
+function contains(value, param) {
+ return value.indexOf(param) >= 0
+}
+
+/**
+ * 验证一个值范围[min, max]
+ */
+function range(value, param) {
+ return value >= param[0] && value <= param[1]
+}
+
+/**
+ * 验证一个长度范围[min, max]
+ */
+function rangeLength(value, param) {
+ return value.length >= param[0] && value.length <= param[1]
+}
+
+/**
+ * 是否固定电话
+ */
+function landline(value) {
+ const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
+ return reg.test(value)
+}
+
+/**
+ * 判断是否为空
+ */
+function empty(value) {
+ switch (typeof value) {
+ case 'undefined':
+ return true
+ case 'string':
+ if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
+ break
+ case 'boolean':
+ if (!value) return true
+ break
+ case 'number':
+ if (value === 0 || isNaN(value)) return true
+ break
+ case 'object':
+ if (value === null || value.length === 0) return true
+ for (const i in value) {
+ return false
+ }
+ return true
+ }
+ return false
+}
+
+/**
+ * 是否json字符串
+ */
+function jsonString(value) {
+ if (typeof value === 'string') {
+ try {
+ const obj = JSON.parse(value)
+ if (typeof obj === 'object' && obj) {
+ return true
+ }
+ return false
+ } catch (e) {
+ return false
+ }
+ }
+ return false
+}
+
+/**
+ * 是否数组
+ */
+function array(value) {
+ if (typeof Array.isArray === 'function') {
+ return Array.isArray(value)
+ }
+ return Object.prototype.toString.call(value) === '[object Array]'
+}
+
+/**
+ * 是否对象
+ */
+function object(value) {
+ return Object.prototype.toString.call(value) === '[object Object]'
+}
+
+/**
+ * 是否短信验证码
+ */
+function code(value, len = 6) {
+ return new RegExp(`^\\d{${len}}$`).test(value)
+}
+
+/**
+ * 是否函数方法
+ * @param {Object} value
+ */
+function func(value) {
+ return typeof value === 'function'
+}
+
+/**
+ * 是否promise对象
+ * @param {Object} value
+ */
+function promise(value) {
+ return object(value) && func(value.then) && func(value.catch)
+}
+
+/** 是否图片格式
+ * @param {Object} value
+ */
+function image(value) {
+ const newValue = value.split('?')[0]
+ const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
+ return IMAGE_REGEXP.test(newValue)
+}
+
+/**
+ * 是否视频格式
+ * @param {Object} value
+ */
+function video(value) {
+ const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
+ return VIDEO_REGEXP.test(value)
+}
+
+/**
+ * 是否为正则对象
+ * @param {Object}
+ * @return {Boolean}
+ */
+function regExp(o) {
+ return o && Object.prototype.toString.call(o) === '[object RegExp]'
+}
+
+export {
+ email,
+ mobile,
+ url,
+ date,
+ dateISO,
+ number,
+ digits,
+ idCard,
+ carNo,
+ amount,
+ chinese,
+ letter,
+ enOrNum,
+ contains,
+ range,
+ rangeLength,
+ empty,
+ jsonString,
+ landline,
+ object,
+ array,
+ code,
+ func,
+ promise,
+ video,
+ image,
+ regExp,
+ string
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/function/throttle.js b/ledApp/uni_modules/uv-ui-tools/libs/function/throttle.js
new file mode 100644
index 0000000..2f33611
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/function/throttle.js
@@ -0,0 +1,30 @@
+let timer; let
+ flag
+/**
+ * 节流原理:在一定时间内,只能触发一次
+ *
+ * @param {Function} func 要执行的回调函数
+ * @param {Number} wait 延时的时间
+ * @param {Boolean} immediate 是否立即执行
+ * @return null
+ */
+function throttle(func, wait = 500, immediate = true) {
+ if (immediate) {
+ if (!flag) {
+ flag = true
+ // 如果是立即执行,则在wait毫秒内开始时执行
+ typeof func === 'function' && func()
+ timer = setTimeout(() => {
+ flag = false
+ }, wait)
+ }
+ } else if (!flag) {
+ flag = true
+ // 如果是非立即执行,则在wait毫秒内的结束处执行
+ timer = setTimeout(() => {
+ flag = false
+ typeof func === 'function' && func()
+ }, wait)
+ }
+}
+export default throttle
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/adapters/index.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/adapters/index.js
new file mode 100644
index 0000000..31a5cfc
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/adapters/index.js
@@ -0,0 +1,132 @@
+import buildURL from '../helpers/buildURL'
+import buildFullPath from '../core/buildFullPath'
+import settle from '../core/settle'
+import {isUndefined} from "../utils"
+
+/**
+ * 返回可选值存在的配置
+ * @param {Array} keys - 可选值数组
+ * @param {Object} config2 - 配置
+ * @return {{}} - 存在的配置项
+ */
+const mergeKeys = (keys, config2) => {
+ let config = {}
+ keys.forEach(prop => {
+ if (!isUndefined(config2[prop])) {
+ config[prop] = config2[prop]
+ }
+ })
+ return config
+}
+export default (config) => {
+ return new Promise((resolve, reject) => {
+ let fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params, config.paramsSerializer)
+ const _config = {
+ url: fullPath,
+ header: config.header,
+ complete: (response) => {
+ config.fullPath = fullPath
+ response.config = config
+ response.rawData = response.data
+ try {
+ let jsonParseHandle = false
+ const forcedJSONParsingType = typeof config.forcedJSONParsing
+ if (forcedJSONParsingType === 'boolean') {
+ jsonParseHandle = config.forcedJSONParsing
+ } else if (forcedJSONParsingType === 'object') {
+ const includesMethod = config.forcedJSONParsing.include || []
+ jsonParseHandle = includesMethod.includes(config.method)
+ }
+
+ // 对可能字符串不是json 的情况容错
+ if (jsonParseHandle && typeof response.data === 'string') {
+ response.data = JSON.parse(response.data)
+ }
+ // eslint-disable-next-line no-empty
+ } catch (e) {
+ }
+ settle(resolve, reject, response)
+ }
+ }
+ let requestTask
+ if (config.method === 'UPLOAD') {
+ delete _config.header['content-type']
+ delete _config.header['Content-Type']
+ let otherConfig = {
+ // #ifdef MP-ALIPAY
+ fileType: config.fileType,
+ // #endif
+ filePath: config.filePath,
+ name: config.name
+ }
+ const optionalKeys = [
+ // #ifdef APP-PLUS || H5
+ 'files',
+ // #endif
+ // #ifdef H5
+ 'file',
+ // #endif
+ // #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
+ 'timeout',
+ // #endif
+ 'formData'
+ ]
+ requestTask = uni.uploadFile({..._config, ...otherConfig, ...mergeKeys(optionalKeys, config)})
+ } else if (config.method === 'DOWNLOAD') {
+ const optionalKeys = [
+ // #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
+ 'timeout',
+ // #endif
+ // #ifdef MP
+ 'filePath',
+ // #endif
+ ]
+ requestTask = uni.downloadFile({..._config, ...mergeKeys(optionalKeys, config)})
+ } else {
+ const optionalKeys = [
+ 'data',
+ 'method',
+ // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+ 'timeout',
+ // #endif
+ 'dataType',
+ // #ifndef MP-ALIPAY
+ 'responseType',
+ // #endif
+ // #ifdef APP-PLUS
+ 'sslVerify',
+ // #endif
+ // #ifdef H5
+ 'withCredentials',
+ // #endif
+ // #ifdef APP-PLUS
+ 'firstIpv4',
+ // #endif
+ // #ifdef MP-WEIXIN
+ 'enableHttp2',
+ 'enableQuic',
+ // #endif
+ // #ifdef MP-TOUTIAO || MP-WEIXIN
+ 'enableCache',
+ // #endif
+ // #ifdef MP-WEIXIN
+ 'enableHttpDNS',
+ 'httpDNSServiceId',
+ 'enableChunked',
+ 'forceCellularNetwork',
+ // #endif
+ // #ifdef MP-ALIPAY
+ 'enableCookie',
+ // #endif
+ // #ifdef MP-BAIDU
+ 'cloudCache',
+ 'defer'
+ // #endif
+ ]
+ requestTask = uni.request({..._config, ...mergeKeys(optionalKeys, config)})
+ }
+ if (config.getTask) {
+ config.getTask(requestTask, config)
+ }
+ })
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/InterceptorManager.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/InterceptorManager.js
new file mode 100644
index 0000000..3ea0d5e
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/InterceptorManager.js
@@ -0,0 +1,51 @@
+'use strict'
+
+
+function InterceptorManager() {
+ this.handlers = []
+}
+
+/**
+ * Add a new interceptor to the stack
+ *
+ * @param {Function} fulfilled The function to handle `then` for a `Promise`
+ * @param {Function} rejected The function to handle `reject` for a `Promise`
+ *
+ * @return {Number} An ID used to remove interceptor later
+ */
+InterceptorManager.prototype.use = function use(fulfilled, rejected) {
+ this.handlers.push({
+ fulfilled: fulfilled,
+ rejected: rejected
+ })
+ return this.handlers.length - 1
+}
+
+/**
+ * Remove an interceptor from the stack
+ *
+ * @param {Number} id The ID that was returned by `use`
+ */
+InterceptorManager.prototype.eject = function eject(id) {
+ if (this.handlers[id]) {
+ this.handlers[id] = null
+ }
+}
+
+/**
+ * Iterate over all the registered interceptors
+ *
+ * This method is particularly useful for skipping over any
+ * interceptors that may have become `null` calling `eject`.
+ *
+ * @param {Function} fn The function to call for each interceptor
+ */
+InterceptorManager.prototype.forEach = function forEach(fn) {
+ this.handlers.forEach(h => {
+ if (h !== null) {
+ fn(h)
+ }
+ })
+}
+
+export default InterceptorManager
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/Request.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/Request.js
new file mode 100644
index 0000000..96c89a8
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/Request.js
@@ -0,0 +1,201 @@
+/**
+ * @Class Request
+ * @description luch-request http请求插件
+ * @Author lu-ch
+ * @Email webwork.s@qq.com
+ * 文档: https://www.quanzhan.co/luch-request/
+ * github: https://github.com/lei-mu/luch-request
+ * DCloud: http://ext.dcloud.net.cn/plugin?id=392
+ */
+
+
+import dispatchRequest from './dispatchRequest'
+import InterceptorManager from './InterceptorManager'
+import mergeConfig from './mergeConfig'
+import defaults from './defaults'
+import { isPlainObject } from '../utils'
+import clone from '../utils/clone'
+
+export default class Request {
+ /**
+ * @param {Object} arg - 全局配置
+ * @param {String} arg.baseURL - 全局根路径
+ * @param {Object} arg.header - 全局header
+ * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
+ * @param {String} arg.dataType = [json] - 全局默认的dataType
+ * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持
+ * @param {Object} arg.custom - 全局默认的自定义参数
+ * @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序
+ * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+)
+ * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+)
+ * @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+)
+ * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300
+ */
+ constructor(arg = {}) {
+ if (!isPlainObject(arg)) {
+ arg = {}
+ console.warn('设置全局参数必须接收一个Object')
+ }
+ this.config = clone({...defaults, ...arg})
+ this.interceptors = {
+ request: new InterceptorManager(),
+ response: new InterceptorManager()
+ }
+ }
+
+ /**
+ * @Function
+ * @param {Request~setConfigCallback} f - 设置全局默认配置
+ */
+ setConfig(f) {
+ this.config = f(this.config)
+ }
+
+ middleware(config) {
+ config = mergeConfig(this.config, config)
+ let chain = [dispatchRequest, undefined]
+ let promise = Promise.resolve(config)
+
+ this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
+ chain.unshift(interceptor.fulfilled, interceptor.rejected)
+ })
+
+ this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
+ chain.push(interceptor.fulfilled, interceptor.rejected)
+ })
+
+ while (chain.length) {
+ promise = promise.then(chain.shift(), chain.shift())
+ }
+
+ return promise
+ }
+
+ /**
+ * @Function
+ * @param {Object} config - 请求配置项
+ * @prop {String} options.url - 请求路径
+ * @prop {Object} options.data - 请求参数
+ * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
+ * @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse
+ * @prop {Object} [options.header = config.header] - 请求header
+ * @prop {Object} [options.method = config.method] - 请求方法
+ * @returns {Promise<unknown>}
+ */
+ request(config = {}) {
+ return this.middleware(config)
+ }
+
+ get(url, options = {}) {
+ return this.middleware({
+ url,
+ method: 'GET',
+ ...options
+ })
+ }
+
+ post(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'POST',
+ ...options
+ })
+ }
+
+ // #ifndef MP-ALIPAY || MP-KUAISHOU || MP-JD
+ put(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'PUT',
+ ...options
+ })
+ }
+
+ // #endif
+
+ // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+ delete(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'DELETE',
+ ...options
+ })
+ }
+
+ // #endif
+
+ // #ifdef H5 || MP-WEIXIN
+ connect(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'CONNECT',
+ ...options
+ })
+ }
+
+ // #endif
+
+ // #ifdef H5 || MP-WEIXIN || MP-BAIDU
+ head(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'HEAD',
+ ...options
+ })
+ }
+
+ // #endif
+
+ // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+ options(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'OPTIONS',
+ ...options
+ })
+ }
+
+ // #endif
+
+ // #ifdef H5 || MP-WEIXIN
+ trace(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'TRACE',
+ ...options
+ })
+ }
+
+ // #endif
+
+ upload(url, config = {}) {
+ config.url = url
+ config.method = 'UPLOAD'
+ return this.middleware(config)
+ }
+
+ download(url, config = {}) {
+ config.url = url
+ config.method = 'DOWNLOAD'
+ return this.middleware(config)
+ }
+
+ get version () {
+ return '3.1.0'
+ }
+}
+
+
+/**
+ * setConfig回调
+ * @return {Object} - 返回操作后的config
+ * @callback Request~setConfigCallback
+ * @param {Object} config - 全局默认config
+ */
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/buildFullPath.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/buildFullPath.js
new file mode 100644
index 0000000..f2852f4
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/buildFullPath.js
@@ -0,0 +1,20 @@
+'use strict'
+
+import isAbsoluteURL from '../helpers/isAbsoluteURL'
+import combineURLs from '../helpers/combineURLs'
+
+/**
+ * Creates a new URL by combining the baseURL with the requestedURL,
+ * only when the requestedURL is not already an absolute URL.
+ * If the requestURL is absolute, this function returns the requestedURL untouched.
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} requestedURL Absolute or relative URL to combine
+ * @returns {string} The combined full path
+ */
+export default function buildFullPath(baseURL, requestedURL) {
+ if (baseURL && !isAbsoluteURL(requestedURL)) {
+ return combineURLs(baseURL, requestedURL)
+ }
+ return requestedURL
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/defaults.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/defaults.js
new file mode 100644
index 0000000..db74609
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/defaults.js
@@ -0,0 +1,33 @@
+/**
+ * 默认的全局配置
+ */
+
+
+export default {
+ baseURL: '',
+ header: {},
+ method: 'GET',
+ dataType: 'json',
+ paramsSerializer: null,
+ // #ifndef MP-ALIPAY
+ responseType: 'text',
+ // #endif
+ custom: {},
+ // #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
+ timeout: 60000,
+ // #endif
+ // #ifdef APP-PLUS
+ sslVerify: true,
+ // #endif
+ // #ifdef H5
+ withCredentials: false,
+ // #endif
+ // #ifdef APP-PLUS
+ firstIpv4: false,
+ // #endif
+ validateStatus: function validateStatus(status) {
+ return status >= 200 && status < 300
+ },
+ // 是否尝试将响应数据json化
+ forcedJSONParsing: true
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/dispatchRequest.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/dispatchRequest.js
new file mode 100644
index 0000000..c5f2c85
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/dispatchRequest.js
@@ -0,0 +1,6 @@
+import adapter from '../adapters/index'
+
+
+export default (config) => {
+ return adapter(config)
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/mergeConfig.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/mergeConfig.js
new file mode 100644
index 0000000..99c8ecd
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/mergeConfig.js
@@ -0,0 +1,126 @@
+import {deepMerge, isUndefined} from '../utils'
+
+/**
+ * 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局
+ * @param {Array} keys - 配置项
+ * @param {Object} globalsConfig - 当前的全局配置
+ * @param {Object} config2 - 局部配置
+ * @return {{}}
+ */
+const mergeKeys = (keys, globalsConfig, config2) => {
+ let config = {}
+ keys.forEach(prop => {
+ if (!isUndefined(config2[prop])) {
+ config[prop] = config2[prop]
+ } else if (!isUndefined(globalsConfig[prop])) {
+ config[prop] = globalsConfig[prop]
+ }
+ })
+ return config
+}
+/**
+ *
+ * @param globalsConfig - 当前实例的全局配置
+ * @param config2 - 当前的局部配置
+ * @return - 合并后的配置
+ */
+export default (globalsConfig, config2 = {}) => {
+ const method = config2.method || globalsConfig.method || 'GET'
+ let config = {
+ baseURL: config2.baseURL || globalsConfig.baseURL || '',
+ method: method,
+ url: config2.url || '',
+ params: config2.params || {},
+ custom: {...(globalsConfig.custom || {}), ...(config2.custom || {})},
+ header: deepMerge(globalsConfig.header || {}, config2.header || {})
+ }
+ const defaultToConfig2Keys = ['getTask', 'validateStatus', 'paramsSerializer', 'forcedJSONParsing']
+ config = {...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2)}
+
+ // eslint-disable-next-line no-empty
+ if (method === 'DOWNLOAD') {
+ const downloadKeys = [
+ // #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
+ 'timeout',
+ // #endif
+ // #ifdef MP
+ 'filePath',
+ // #endif
+ ]
+ config = {...config, ...mergeKeys(downloadKeys, globalsConfig, config2)}
+ } else if (method === 'UPLOAD') {
+ delete config.header['content-type']
+ delete config.header['Content-Type']
+ const uploadKeys = [
+ // #ifdef APP-PLUS || H5
+ 'files',
+ // #endif
+ // #ifdef MP-ALIPAY
+ 'fileType',
+ // #endif
+ // #ifdef H5
+ 'file',
+ // #endif
+ 'filePath',
+ 'name',
+ // #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
+ 'timeout',
+ // #endif
+ 'formData',
+ ]
+ uploadKeys.forEach(prop => {
+ if (!isUndefined(config2[prop])) {
+ config[prop] = config2[prop]
+ }
+ })
+ // #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
+ if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) {
+ config['timeout'] = globalsConfig['timeout']
+ }
+ // #endif
+ } else {
+ const defaultsKeys = [
+ 'data',
+ // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+ 'timeout',
+ // #endif
+ 'dataType',
+ // #ifndef MP-ALIPAY
+ 'responseType',
+ // #endif
+ // #ifdef APP-PLUS
+ 'sslVerify',
+ // #endif
+ // #ifdef H5
+ 'withCredentials',
+ // #endif
+ // #ifdef APP-PLUS
+ 'firstIpv4',
+ // #endif
+ // #ifdef MP-WEIXIN
+ 'enableHttp2',
+ 'enableQuic',
+ // #endif
+ // #ifdef MP-TOUTIAO || MP-WEIXIN
+ 'enableCache',
+ // #endif
+ // #ifdef MP-WEIXIN
+ 'enableHttpDNS',
+ 'httpDNSServiceId',
+ 'enableChunked',
+ 'forceCellularNetwork',
+ // #endif
+ // #ifdef MP-ALIPAY
+ 'enableCookie',
+ // #endif
+ // #ifdef MP-BAIDU
+ 'cloudCache',
+ 'defer'
+ // #endif
+
+ ]
+ config = {...config, ...mergeKeys(defaultsKeys, globalsConfig, config2)}
+ }
+
+ return config
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/settle.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/settle.js
new file mode 100644
index 0000000..b2f1659
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/core/settle.js
@@ -0,0 +1,16 @@
+/**
+ * Resolve or reject a Promise based on response status.
+ *
+ * @param {Function} resolve A function that resolves the promise.
+ * @param {Function} reject A function that rejects the promise.
+ * @param {object} response The response.
+ */
+export default function settle(resolve, reject, response) {
+ const validateStatus = response.config.validateStatus
+ const status = response.statusCode
+ if (status && (!validateStatus || validateStatus(status))) {
+ resolve(response)
+ } else {
+ reject(response)
+ }
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/buildURL.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/buildURL.js
new file mode 100644
index 0000000..e90b908
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/buildURL.js
@@ -0,0 +1,64 @@
+'use strict'
+
+import * as utils from './../utils'
+
+function encode(val) {
+ return encodeURIComponent(val).replace(/%40/gi, '@').replace(/%3A/gi, ':').replace(/%24/g, '$').replace(/%2C/gi, ',').replace(/%20/g, '+').replace(/%5B/gi, '[').replace(/%5D/gi, ']')
+}
+
+/**
+ * Build a URL by appending params to the end
+ *
+ * @param {string} url The base of the url (e.g., http://www.google.com)
+ * @param {object} [params] The params to be appended
+ * @returns {string} The formatted url
+ */
+export default function buildURL(url, params, paramsSerializer) {
+ /*eslint no-param-reassign:0*/
+ if (!params) {
+ return url
+ }
+
+ var serializedParams
+ if (paramsSerializer) {
+ serializedParams = paramsSerializer(params)
+ } else if (utils.isURLSearchParams(params)) {
+ serializedParams = params.toString()
+ } else {
+ var parts = []
+
+ utils.forEach(params, function serialize(val, key) {
+ if (val === null || typeof val === 'undefined') {
+ return
+ }
+
+ if (utils.isArray(val)) {
+ key = key + '[]'
+ } else {
+ val = [val]
+ }
+
+ utils.forEach(val, function parseValue(v) {
+ if (utils.isDate(v)) {
+ v = v.toISOString()
+ } else if (utils.isObject(v)) {
+ v = JSON.stringify(v)
+ }
+ parts.push(encode(key) + '=' + encode(v))
+ })
+ })
+
+ serializedParams = parts.join('&')
+ }
+
+ if (serializedParams) {
+ var hashmarkIndex = url.indexOf('#')
+ if (hashmarkIndex !== -1) {
+ url = url.slice(0, hashmarkIndex)
+ }
+
+ url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
+ }
+
+ return url
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/combineURLs.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/combineURLs.js
new file mode 100644
index 0000000..7b9d1ef
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/combineURLs.js
@@ -0,0 +1,14 @@
+'use strict'
+
+/**
+ * Creates a new URL by combining the specified URLs
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} relativeURL The relative URL
+ * @returns {string} The combined URL
+ */
+export default function combineURLs(baseURL, relativeURL) {
+ return relativeURL
+ ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
+ : baseURL
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/isAbsoluteURL.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/isAbsoluteURL.js
new file mode 100644
index 0000000..2a82517
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/helpers/isAbsoluteURL.js
@@ -0,0 +1,14 @@
+'use strict'
+
+/**
+ * Determines whether the specified URL is absolute
+ *
+ * @param {string} url The URL to test
+ * @returns {boolean} True if the specified URL is absolute, otherwise false
+ */
+export default function isAbsoluteURL(url) {
+ // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
+ // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
+ // by any combination of letters, digits, plus, period, or hyphen.
+ return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/index.d.ts b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/index.d.ts
new file mode 100644
index 0000000..62d3fb9
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/index.d.ts
@@ -0,0 +1,197 @@
+export type HttpTask = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask;
+
+export type HttpRequestTask = UniApp.RequestTask;
+
+export type HttpUploadTask = UniApp.UploadTask;
+
+export type HttpDownloadTask = UniApp.DownloadTask;
+
+export type HttpMethod =
+ "GET"
+ | "POST"
+ | "PUT"
+ | "DELETE"
+ | "CONNECT"
+ | "HEAD"
+ | "OPTIONS"
+ | "TRACE"
+ | "UPLOAD"
+ | "DOWNLOAD";
+
+export type HttpRequestHeader = Record<string, string>;
+
+export type HttpParams = Record<string, any>;
+
+export type HttpData = Record<string, any>;
+
+export type HttpResponseType = 'arraybuffer' | 'text';
+
+export type HttpCustom = Record<string, any>;
+
+export type HttpFileType = 'image' | 'video' | 'audio';
+
+export type HttpFormData = Record<string, any>;
+
+export type HttpResponseHeader = Record<string, string> & {
+ "set-cookie"?: string[]
+};
+
+export interface HttpRequestConfig<T = HttpTask> {
+ /** @desc 请求服务器接口地址 */
+ url?: string;
+ /** @desc 请求方式,默认为 GET */
+ method?: HttpMethod;
+ /** @desc 请求基地址 */
+ baseURL?: string;
+ /** @desc 请求头信息,不能设置 Referer,App、H5 端会自动带上 cookie,且 H5 端不可手动修改 */
+ header?: HttpRequestHeader;
+ /** @desc 请求查询参数,自动拼接为查询字符串 */
+ params?: HttpParams;
+ /** @desc 请求体参数 */
+ data?: HttpData;
+ /** @desc 超时时间,单位 ms,默认为 60000,仅 H5 (HBuilderX 2.9.9+)、APP (HBuilderX 2.9.9+)、微信小程序 (2.10.0)、支付宝小程序支持 */
+ timeout?: number;
+ /** @desc 跨域请求时是否携带凭证 (cookies),默认为 false,仅 H5 (HBuilderX 2.6.15+) 支持 */
+ withCredentials?: boolean;
+ /** @desc 设置响应的数据类型,支付宝小程序不支持 */
+ responseType?: HttpResponseType;
+ /** @desc 全局自定义验证器 */
+ validateStatus?: ((statusCode: number) => boolean) | null;
+
+
+ /** params 参数自定义处理 */
+ paramsSerializer?: (params: AnyObject) => string | void;
+
+ /** @desc 默认为 json,如果设为 json,会尝试对返回的数据做一次 JSON.parse */
+ dataType?: string;
+ /** @desc DNS 解析时是否优先使用 ipv4,默认为 false,仅 App-Android (HBuilderX 2.8.0+) 支持 */
+ firstIpv4?: boolean;
+ /** @desc 是否验证 SSL 证书,默认为 true,仅 App-Android (HBuilderX 2.3.3+) 支持 */
+ sslVerify?: boolean;
+
+ /** @desc 开启 http2;微信小程序 */
+ enableHttp2?: boolean;
+
+ /** @desc 开启 quic;微信小程序 */
+ enableQuic?: boolean;
+ /** @desc 开启 cache;微信小程序、字节跳动小程序 2.31.0+ */
+ enableCache?: boolean;
+ /** @desc 开启 httpDNS;微信小程序 */
+ enableHttpDNS?: boolean;
+ /** @desc httpDNS 服务商;微信小程序 */
+ httpDNSServiceId?: string;
+ /** @desc 开启 transfer-encoding chunked;微信小程序 */
+ enableChunked?: boolean;
+ /** @desc wifi下使用移动网络发送请求;微信小程序 */
+ forceCellularNetwork?: boolean;
+ /** @desc 开启后可在headers中编辑cookie;支付宝小程序 10.2.33+ */
+ enableCookie?: boolean;
+ /** @desc 是否开启云加速;百度小程序 3.310.11+ */
+ cloudCache?: boolean | object;
+ /** @desc 控制当前请求是否延时至首屏内容渲染后发送;百度小程序 3.310.11+ */
+ defer?: boolean;
+
+ /** @desc 自定义参数 */
+ custom?: HttpCustom;
+
+ /** @desc 返回当前请求的 task 和 options,不要在这里修改 options */
+ getTask?: (task: T, options: HttpRequestConfig<T>) => void;
+
+ /** @desc 需要上传的文件列表,使用 files 时,filePath 和 name 不生效,仅支持 App、H5 (2.6.15+) */
+ files?: { name?: string; file?: File; uri: string; }[];
+ /** @desc 文件类型,仅支付宝小程序支持且为必填项 */
+ fileType?: HttpFileType;
+ /** @desc 要上传的文件对象,仅 H5 (2.6.15+) 支持 */
+ file?: File;
+ /** @desc 要上传文件资源的路径,使用 files 时,filePath 和 name 不生效 */
+ filePath?: string;
+ /** @desc 文件对应的 key,开发者在服务器端通过这个 key 可以获取到文件二进制内容,使用 files 时,filePath 和 name 不生效 */
+ name?: string;
+ /** @desc 请求中其他额外的 form data */
+ formData?: HttpFormData;
+}
+
+export interface HttpResponse<T = any, D = HttpTask> {
+ data: T;
+ statusCode: number;
+ header: HttpResponseHeader;
+ config: HttpRequestConfig<D>;
+ cookies: string[];
+ errMsg: string;
+ rawData: any;
+}
+
+export interface HttpUploadResponse<T = any, D = HttpTask> {
+ data: T;
+ statusCode: number;
+ config: HttpRequestConfig<D>;
+ errMsg: string;
+ rawData: any;
+}
+
+export interface HttpDownloadResponse extends HttpResponse {
+ tempFilePath: string;
+ apFilePath?: string;
+ filePath?: string;
+ fileContent?: string;
+}
+
+export interface HttpError<T = any, D = HttpTask> {
+ data?: T;
+ statusCode?: number;
+ header?: HttpResponseHeader;
+ config: HttpRequestConfig<D>;
+ cookies?: string[];
+ errMsg: string;
+}
+
+export interface HttpPromise<T = any> extends Promise<HttpResponse<T>> {
+}
+
+export interface HttpInterceptorManager<V, E = V> {
+ use(onFulfilled?: (value: V) => V | Promise<V>, onRejected?: (error: E) => T | Promise<E>): void;
+
+ eject(id: number): void;
+}
+
+export abstract class HttpRequestAbstract {
+ constructor(config?: HttpRequestConfig);
+
+ interceptors: {
+ request: HttpInterceptorManager<HttpRequestConfig>;
+ response: HttpInterceptorManager<HttpResponse, HttpError>;
+ }
+
+ request<T = any, R = HttpResponse<T>, D = HttpRequestTask>(config: HttpRequestConfig<D>): Promise<R>;
+
+ get<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, config?: HttpRequestConfig<D>): Promise<R>;
+
+ delete<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>;
+
+ head<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>;
+
+ options<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>;
+
+ post<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>;
+
+ put<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>;
+
+ config: HttpRequestConfig;
+
+ setConfig<D = HttpTask>(onSend: (config: HttpRequestConfig<D>) => HttpRequestConfig<D>): void;
+
+ connect<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>;
+
+ trace<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>;
+
+ upload<T = any, R = HttpUploadResponse<T>, D = HttpUploadTask>(url: string, config?: HttpRequestConfig<D>): Promise<R>;
+
+ download<T = any, R = HttpDownloadResponse<T>, D = HttpDownloadTask>(url: string, config?: HttpRequestConfig<D>): Promise<R>;
+
+ middleware<T = any, R = HttpResponse<T>, D = HttpTask>(config: HttpRequestConfig<D>): Promise<R>;
+}
+
+declare class HttpRequest extends HttpRequestAbstract {
+}
+
+export default HttpRequest;
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/index.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/index.js
new file mode 100644
index 0000000..d8fe348
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/index.js
@@ -0,0 +1,2 @@
+import Request from './core/Request'
+export default Request
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/utils.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/utils.js
new file mode 100644
index 0000000..0b5bf21
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/utils.js
@@ -0,0 +1,135 @@
+'use strict'
+
+// utils is a library of generic helper functions non-specific to axios
+
+var toString = Object.prototype.toString
+
+/**
+ * Determine if a value is an Array
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Array, otherwise false
+ */
+export function isArray (val) {
+ return toString.call(val) === '[object Array]'
+}
+
+
+/**
+ * Determine if a value is an Object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Object, otherwise false
+ */
+export function isObject (val) {
+ return val !== null && typeof val === 'object'
+}
+
+/**
+ * Determine if a value is a Date
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a Date, otherwise false
+ */
+export function isDate (val) {
+ return toString.call(val) === '[object Date]'
+}
+
+/**
+ * Determine if a value is a URLSearchParams object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a URLSearchParams object, otherwise false
+ */
+export function isURLSearchParams (val) {
+ return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams
+}
+
+
+/**
+ * Iterate over an Array or an Object invoking a function for each item.
+ *
+ * If `obj` is an Array callback will be called passing
+ * the value, index, and complete array for each item.
+ *
+ * If 'obj' is an Object callback will be called passing
+ * the value, key, and complete object for each property.
+ *
+ * @param {Object|Array} obj The object to iterate
+ * @param {Function} fn The callback to invoke for each item
+ */
+export function forEach (obj, fn) {
+ // Don't bother if no value provided
+ if (obj === null || typeof obj === 'undefined') {
+ return
+ }
+
+ // Force an array if not already something iterable
+ if (typeof obj !== 'object') {
+ /*eslint no-param-reassign:0*/
+ obj = [obj]
+ }
+
+ if (isArray(obj)) {
+ // Iterate over array values
+ for (var i = 0, l = obj.length; i < l; i++) {
+ fn.call(null, obj[i], i, obj)
+ }
+ } else {
+ // Iterate over object keys
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ fn.call(null, obj[key], key, obj)
+ }
+ }
+ }
+}
+
+/**
+ * 是否为boolean 值
+ * @param val
+ * @returns {boolean}
+ */
+export function isBoolean(val) {
+ return typeof val === 'boolean'
+}
+
+/**
+ * 是否为真正的对象{} new Object
+ * @param {any} obj - 检测的对象
+ * @returns {boolean}
+ */
+export function isPlainObject(obj) {
+ return Object.prototype.toString.call(obj) === '[object Object]'
+}
+
+
+
+/**
+ * Function equal to merge with the difference being that no reference
+ * to original objects is kept.
+ *
+ * @see merge
+ * @param {Object} obj1 Object to merge
+ * @returns {Object} Result of all merge properties
+ */
+export function deepMerge(/* obj1, obj2, obj3, ... */) {
+ let result = {}
+ function assignValue(val, key) {
+ if (typeof result[key] === 'object' && typeof val === 'object') {
+ result[key] = deepMerge(result[key], val)
+ } else if (typeof val === 'object') {
+ result[key] = deepMerge({}, val)
+ } else {
+ result[key] = val
+ }
+ }
+ for (let i = 0, l = arguments.length; i < l; i++) {
+ forEach(arguments[i], assignValue)
+ }
+ return result
+}
+
+export function isUndefined (val) {
+ return typeof val === 'undefined'
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js
new file mode 100644
index 0000000..2fee704
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js
@@ -0,0 +1,264 @@
+/* eslint-disable */
+var clone = (function() {
+ 'use strict';
+
+ function _instanceof(obj, type) {
+ return type != null && obj instanceof type;
+ }
+
+ var nativeMap;
+ try {
+ nativeMap = Map;
+ } catch(_) {
+ // maybe a reference error because no `Map`. Give it a dummy value that no
+ // value will ever be an instanceof.
+ nativeMap = function() {};
+ }
+
+ var nativeSet;
+ try {
+ nativeSet = Set;
+ } catch(_) {
+ nativeSet = function() {};
+ }
+
+ var nativePromise;
+ try {
+ nativePromise = Promise;
+ } catch(_) {
+ nativePromise = function() {};
+ }
+
+ /**
+ * Clones (copies) an Object using deep copying.
+ *
+ * This function supports circular references by default, but if you are certain
+ * there are no circular references in your object, you can save some CPU time
+ * by calling clone(obj, false).
+ *
+ * Caution: if `circular` is false and `parent` contains circular references,
+ * your program may enter an infinite loop and crash.
+ *
+ * @param `parent` - the object to be cloned
+ * @param `circular` - set to true if the object to be cloned may contain
+ * circular references. (optional - true by default)
+ * @param `depth` - set to a number if the object is only to be cloned to
+ * a particular depth. (optional - defaults to Infinity)
+ * @param `prototype` - sets the prototype to be used when cloning an object.
+ * (optional - defaults to parent prototype).
+ * @param `includeNonEnumerable` - set to true if the non-enumerable properties
+ * should be cloned as well. Non-enumerable properties on the prototype
+ * chain will be ignored. (optional - false by default)
+ */
+ function clone(parent, circular, depth, prototype, includeNonEnumerable) {
+ if (typeof circular === 'object') {
+ depth = circular.depth;
+ prototype = circular.prototype;
+ includeNonEnumerable = circular.includeNonEnumerable;
+ circular = circular.circular;
+ }
+ // maintain two arrays for circular references, where corresponding parents
+ // and children have the same index
+ var allParents = [];
+ var allChildren = [];
+
+ var useBuffer = typeof Buffer != 'undefined';
+
+ if (typeof circular == 'undefined')
+ circular = true;
+
+ if (typeof depth == 'undefined')
+ depth = Infinity;
+
+ // recurse this function so we don't reset allParents and allChildren
+ function _clone(parent, depth) {
+ // cloning null always returns null
+ if (parent === null)
+ return null;
+
+ if (depth === 0)
+ return parent;
+
+ var child;
+ var proto;
+ if (typeof parent != 'object') {
+ return parent;
+ }
+
+ if (_instanceof(parent, nativeMap)) {
+ child = new nativeMap();
+ } else if (_instanceof(parent, nativeSet)) {
+ child = new nativeSet();
+ } else if (_instanceof(parent, nativePromise)) {
+ child = new nativePromise(function (resolve, reject) {
+ parent.then(function(value) {
+ resolve(_clone(value, depth - 1));
+ }, function(err) {
+ reject(_clone(err, depth - 1));
+ });
+ });
+ } else if (clone.__isArray(parent)) {
+ child = [];
+ } else if (clone.__isRegExp(parent)) {
+ child = new RegExp(parent.source, __getRegExpFlags(parent));
+ if (parent.lastIndex) child.lastIndex = parent.lastIndex;
+ } else if (clone.__isDate(parent)) {
+ child = new Date(parent.getTime());
+ } else if (useBuffer && Buffer.isBuffer(parent)) {
+ if (Buffer.from) {
+ // Node.js >= 5.10.0
+ child = Buffer.from(parent);
+ } else {
+ // Older Node.js versions
+ child = new Buffer(parent.length);
+ parent.copy(child);
+ }
+ return child;
+ } else if (_instanceof(parent, Error)) {
+ child = Object.create(parent);
+ } else {
+ if (typeof prototype == 'undefined') {
+ proto = Object.getPrototypeOf(parent);
+ child = Object.create(proto);
+ }
+ else {
+ child = Object.create(prototype);
+ proto = prototype;
+ }
+ }
+
+ if (circular) {
+ var index = allParents.indexOf(parent);
+
+ if (index != -1) {
+ return allChildren[index];
+ }
+ allParents.push(parent);
+ allChildren.push(child);
+ }
+
+ if (_instanceof(parent, nativeMap)) {
+ parent.forEach(function(value, key) {
+ var keyChild = _clone(key, depth - 1);
+ var valueChild = _clone(value, depth - 1);
+ child.set(keyChild, valueChild);
+ });
+ }
+ if (_instanceof(parent, nativeSet)) {
+ parent.forEach(function(value) {
+ var entryChild = _clone(value, depth - 1);
+ child.add(entryChild);
+ });
+ }
+
+ for (var i in parent) {
+ var attrs = Object.getOwnPropertyDescriptor(parent, i);
+ if (attrs) {
+ child[i] = _clone(parent[i], depth - 1);
+ }
+
+ try {
+ var objProperty = Object.getOwnPropertyDescriptor(parent, i);
+ if (objProperty.set === 'undefined') {
+ // no setter defined. Skip cloning this property
+ continue;
+ }
+ child[i] = _clone(parent[i], depth - 1);
+ } catch(e){
+ if (e instanceof TypeError) {
+ // when in strict mode, TypeError will be thrown if child[i] property only has a getter
+ // we can't do anything about this, other than inform the user that this property cannot be set.
+ continue
+ } else if (e instanceof ReferenceError) {
+ //this may happen in non strict mode
+ continue
+ }
+ }
+
+ }
+
+ if (Object.getOwnPropertySymbols) {
+ var symbols = Object.getOwnPropertySymbols(parent);
+ for (var i = 0; i < symbols.length; i++) {
+ // Don't need to worry about cloning a symbol because it is a primitive,
+ // like a number or string.
+ var symbol = symbols[i];
+ var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
+ if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
+ continue;
+ }
+ child[symbol] = _clone(parent[symbol], depth - 1);
+ Object.defineProperty(child, symbol, descriptor);
+ }
+ }
+
+ if (includeNonEnumerable) {
+ var allPropertyNames = Object.getOwnPropertyNames(parent);
+ for (var i = 0; i < allPropertyNames.length; i++) {
+ var propertyName = allPropertyNames[i];
+ var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
+ if (descriptor && descriptor.enumerable) {
+ continue;
+ }
+ child[propertyName] = _clone(parent[propertyName], depth - 1);
+ Object.defineProperty(child, propertyName, descriptor);
+ }
+ }
+
+ return child;
+ }
+
+ return _clone(parent, depth);
+ }
+
+ /**
+ * Simple flat clone using prototype, accepts only objects, usefull for property
+ * override on FLAT configuration object (no nested props).
+ *
+ * USE WITH CAUTION! This may not behave as you wish if you do not know how this
+ * works.
+ */
+ clone.clonePrototype = function clonePrototype(parent) {
+ if (parent === null)
+ return null;
+
+ var c = function () {};
+ c.prototype = parent;
+ return new c();
+ };
+
+// private utility functions
+
+ function __objToStr(o) {
+ return Object.prototype.toString.call(o);
+ }
+ clone.__objToStr = __objToStr;
+
+ function __isDate(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object Date]';
+ }
+ clone.__isDate = __isDate;
+
+ function __isArray(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object Array]';
+ }
+ clone.__isArray = __isArray;
+
+ function __isRegExp(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
+ }
+ clone.__isRegExp = __isRegExp;
+
+ function __getRegExpFlags(re) {
+ var flags = '';
+ if (re.global) flags += 'g';
+ if (re.ignoreCase) flags += 'i';
+ if (re.multiline) flags += 'm';
+ return flags;
+ }
+ clone.__getRegExpFlags = __getRegExpFlags;
+
+ return clone;
+})();
+
+export default clone
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/mixin/button.js b/ledApp/uni_modules/uv-ui-tools/libs/mixin/button.js
new file mode 100644
index 0000000..0c019c2
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/mixin/button.js
@@ -0,0 +1,13 @@
+export default {
+ props: {
+ lang: String,
+ sessionFrom: String,
+ sendMessageTitle: String,
+ sendMessagePath: String,
+ sendMessageImg: String,
+ showMessageCard: Boolean,
+ appParameter: String,
+ formType: String,
+ openType: String
+ }
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/mixin/mixin.js b/ledApp/uni_modules/uv-ui-tools/libs/mixin/mixin.js
new file mode 100644
index 0000000..0dd3b03
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/mixin/mixin.js
@@ -0,0 +1,172 @@
+import * as index from '../function/index.js';
+import * as test from '../function/test.js';
+import route from '../util/route.js';
+import debounce from '../function/debounce.js';
+import throttle from '../function/throttle.js';
+export default {
+ // 定义每个组件都可能需要用到的外部样式以及类名
+ props: {
+ // 每个组件都有的父组件传递的样式,可以为字符串或者对象形式
+ customStyle: {
+ type: [Object, String],
+ default: () => ({})
+ },
+ customClass: {
+ type: String,
+ default: ''
+ },
+ // 跳转的页面路径
+ url: {
+ type: String,
+ default: ''
+ },
+ // 页面跳转的类型
+ linkType: {
+ type: String,
+ default: 'navigateTo'
+ }
+ },
+ data() {
+ return {}
+ },
+ onLoad() {
+ // getRect挂载到$uv上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出
+ this.$uv.getRect = this.$uvGetRect
+ },
+ created() {
+ // 组件当中,只有created声明周期,为了能在组件使用,故也在created中将方法挂载到$uv
+ this.$uv.getRect = this.$uvGetRect
+ },
+ computed: {
+ $uv() {
+ return {
+ ...index,
+ test,
+ route,
+ debounce,
+ throttle,
+ unit: uni?.$uv?.config?.unit
+ }
+ },
+ /**
+ * 生成bem规则类名
+ * 由于微信小程序,H5,nvue之间绑定class的差异,无法通过:class="[bem()]"的形式进行同用
+ * 故采用如下折中做法,最后返回的是数组(一般平台)或字符串(支付宝和字节跳动平台),类似['a', 'b', 'c']或'a b c'的形式
+ * @param {String} name 组件名称
+ * @param {Array} fixed 一直会存在的类名
+ * @param {Array} change 会根据变量值为true或者false而出现或者隐藏的类名
+ * @returns {Array|string}
+ */
+ bem() {
+ return function(name, fixed, change) {
+ // 类名前缀
+ const prefix = `uv-${name}--`
+ const classes = {}
+ if (fixed) {
+ fixed.map((item) => {
+ // 这里的类名,会一直存在
+ classes[prefix + this[item]] = true
+ })
+ }
+ if (change) {
+ change.map((item) => {
+ // 这里的类名,会根据this[item]的值为true或者false,而进行添加或者移除某一个类
+ this[item] ? (classes[prefix + item] = this[item]) : (delete classes[prefix + item])
+ })
+ }
+ return Object.keys(classes)
+ // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+ // #ifdef MP-ALIPAY || MP-TOUTIAO || MP-LARK || MP-BAIDU
+ .join(' ')
+ // #endif
+ }
+ }
+ },
+ methods: {
+ // 跳转某一个页面
+ openPage(urlKey = 'url') {
+ const url = this[urlKey]
+ if (url) {
+ // 执行类似uni.navigateTo的方法
+ uni[this.linkType]({
+ url
+ })
+ }
+ },
+ // 查询节点信息
+ // 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21)
+ // 解决办法为在组件根部再套一个没有任何作用的view元素
+ $uvGetRect(selector, all) {
+ return new Promise((resolve) => {
+ uni.createSelectorQuery()
+ .in(this)[all ? 'selectAll' : 'select'](selector)
+ .boundingClientRect((rect) => {
+ if (all && Array.isArray(rect) && rect.length) {
+ resolve(rect)
+ }
+ if (!all && rect) {
+ resolve(rect)
+ }
+ })
+ .exec()
+ })
+ },
+ getParentData(parentName = '') {
+ // 避免在created中去定义parent变量
+ if (!this.parent) this.parent = {}
+ // 这里的本质原理是,通过获取父组件实例(也即类似uv-radio的父组件uv-radio-group的this)
+ // 将父组件this中对应的参数,赋值给本组件(uv-radio的this)的parentData对象中对应的属性
+ // 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化
+ // 此处并不会自动更新子组件的数据,而是依赖父组件uv-radio-group去监听data的变化,手动调用更新子组件的方法去重新获取
+ this.parent = this.$uv.$parent.call(this, parentName)
+ if (this.parent.children) {
+ // 如果父组件的children不存在本组件的实例,才将本实例添加到父组件的children中
+ this.parent.children.indexOf(this) === -1 && this.parent.children.push(this)
+ }
+ if (this.parent && this.parentData) {
+ // 历遍parentData中的属性,将parent中的同名属性赋值给parentData
+ Object.keys(this.parentData).map((key) => {
+ this.parentData[key] = this.parent[key]
+ })
+ }
+ },
+ // 阻止事件冒泡
+ preventEvent(e) {
+ e && typeof(e.stopPropagation) === 'function' && e.stopPropagation()
+ },
+ // 空操作
+ noop(e) {
+ this.preventEvent(e)
+ }
+ },
+ onReachBottom() {
+ uni.$emit('uvOnReachBottom')
+ },
+ beforeDestroy() {
+ // 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
+ // 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
+ if (this.parent && test.array(this.parent.children)) {
+ // 组件销毁时,移除父组件中的children数组中对应的实例
+ const childrenList = this.parent.children
+ childrenList.map((child, index) => {
+ // 如果相等,则移除
+ if (child === this) {
+ childrenList.splice(index, 1)
+ }
+ })
+ }
+ },
+ // 兼容vue3
+ unmounted() {
+ if (this.parent && test.array(this.parent.children)) {
+ // 组件销毁时,移除父组件中的children数组中对应的实例
+ const childrenList = this.parent.children
+ childrenList.map((child, index) => {
+ // 如果相等,则移除
+ if (child === this) {
+ childrenList.splice(index, 1)
+ }
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js b/ledApp/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js
new file mode 100644
index 0000000..90b6903
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js
@@ -0,0 +1,8 @@
+export default {
+ // #ifdef MP-WEIXIN
+ // 将自定义节点设置成虚拟的(去掉自定义组件包裹层),更加接近Vue组件的表现,能更好的使用flex属性
+ options: {
+ virtualHost: true
+ }
+ // #endif
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/mixin/mpShare.js b/ledApp/uni_modules/uv-ui-tools/libs/mixin/mpShare.js
new file mode 100644
index 0000000..c9695a0
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/mixin/mpShare.js
@@ -0,0 +1,13 @@
+export default {
+ onLoad() {
+ // 设置默认的转发参数
+ uni.$uv.mpShare = {
+ title: '', // 默认为小程序名称
+ path: '', // 默认为当前页面路径
+ imageUrl: '' // 默认为当前页面的截图
+ }
+ },
+ onShareAppMessage() {
+ return uni.$uv.mpShare
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/mixin/openType.js b/ledApp/uni_modules/uv-ui-tools/libs/mixin/openType.js
new file mode 100644
index 0000000..1b94b7e
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/mixin/openType.js
@@ -0,0 +1,47 @@
+export default {
+ props: {
+ openType: String
+ },
+ emits: ['getphonenumber','getuserinfo','error','opensetting','launchapp','contact','chooseavatar','addgroupapp','chooseaddress','subscribe','login','im'],
+ methods: {
+ onGetPhoneNumber(event) {
+ this.$emit('getphonenumber', event.detail)
+ },
+ onGetUserInfo(event) {
+ this.$emit('getuserinfo', event.detail)
+ },
+ onError(event) {
+ this.$emit('error', event.detail)
+ },
+ onOpenSetting(event) {
+ this.$emit('opensetting', event.detail)
+ },
+ onLaunchApp(event) {
+ this.$emit('launchapp', event.detail)
+ },
+ onContact(event) {
+ this.$emit('contact', event.detail)
+ },
+ onChooseavatar(event) {
+ this.$emit('chooseavatar', event.detail)
+ },
+ onAgreeprivacyauthorization(event) {
+ this.$emit('agreeprivacyauthorization', event.detail)
+ },
+ onAddgroupapp(event) {
+ this.$emit('addgroupapp', event.detail)
+ },
+ onChooseaddress(event) {
+ this.$emit('chooseaddress', event.detail)
+ },
+ onSubscribe(event) {
+ this.$emit('subscribe', event.detail)
+ },
+ onLogin(event) {
+ this.$emit('login', event.detail)
+ },
+ onIm(event) {
+ this.$emit('im', event.detail)
+ }
+ }
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/mixin/touch.js b/ledApp/uni_modules/uv-ui-tools/libs/mixin/touch.js
new file mode 100644
index 0000000..0ecbd88
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/mixin/touch.js
@@ -0,0 +1,59 @@
+const MIN_DISTANCE = 10
+
+function getDirection(x, y) {
+ if (x > y && x > MIN_DISTANCE) {
+ return 'horizontal'
+ }
+ if (y > x && y > MIN_DISTANCE) {
+ return 'vertical'
+ }
+ return ''
+}
+
+export default {
+ methods: {
+ getTouchPoint(e) {
+ if (!e) {
+ return {
+ x: 0,
+ y: 0
+ }
+ } if (e.touches && e.touches[0]) {
+ return {
+ x: e.touches[0].pageX,
+ y: e.touches[0].pageY
+ }
+ } if (e.changedTouches && e.changedTouches[0]) {
+ return {
+ x: e.changedTouches[0].pageX,
+ y: e.changedTouches[0].pageY
+ }
+ }
+ return {
+ x: e.clientX || 0,
+ y: e.clientY || 0
+ }
+ },
+ resetTouchStatus() {
+ this.direction = ''
+ this.deltaX = 0
+ this.deltaY = 0
+ this.offsetX = 0
+ this.offsetY = 0
+ },
+ touchStart(event) {
+ this.resetTouchStatus()
+ const touch = this.getTouchPoint(event)
+ this.startX = touch.x
+ this.startY = touch.y
+ },
+ touchMove(event) {
+ const touch = this.getTouchPoint(event)
+ this.deltaX = touch.x - this.startX
+ this.deltaY = touch.y - this.startY
+ this.offsetX = Math.abs(this.deltaX)
+ this.offsetY = Math.abs(this.deltaY)
+ this.direction = this.direction || getDirection(this.offsetX, this.offsetY)
+ }
+ }
+}
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/util/dayjs.js b/ledApp/uni_modules/uv-ui-tools/libs/util/dayjs.js
new file mode 100644
index 0000000..c84ab68
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/util/dayjs.js
@@ -0,0 +1,216 @@
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __commonJS = (cb, mod) => function __require() {
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
+};
+
+var require_dayjs_min = __commonJS({
+ "uvuidayjs"(exports, module) {
+ !function(t, e) {
+ "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e();
+ }(exports, function() {
+ "use strict";
+ var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", f = "month", h = "quarter", c = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) {
+ var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100;
+ return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]";
+ } }, m = function(t2, e2, n2) {
+ var r2 = String(t2);
+ return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2;
+ }, v = { s: m, z: function(t2) {
+ var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60;
+ return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0");
+ }, m: function t2(e2, n2) {
+ if (e2.date() < n2.date())
+ return -t2(n2, e2);
+ var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, f), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), f);
+ return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0);
+ }, a: function(t2) {
+ return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2);
+ }, p: function(t2) {
+ return { M: f, y: c, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: h }[t2] || String(t2 || "").toLowerCase().replace(/s$/, "");
+ }, u: function(t2) {
+ return void 0 === t2;
+ } }, g = "en", D = {};
+ D[g] = M;
+ var p = function(t2) {
+ return t2 instanceof _;
+ }, S = function t2(e2, n2, r2) {
+ var i2;
+ if (!e2)
+ return g;
+ if ("string" == typeof e2) {
+ var s2 = e2.toLowerCase();
+ D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2);
+ var u2 = e2.split("-");
+ if (!i2 && u2.length > 1)
+ return t2(u2[0]);
+ } else {
+ var a2 = e2.name;
+ D[a2] = e2, i2 = a2;
+ }
+ return !r2 && i2 && (g = i2), i2 || !r2 && g;
+ }, w = function(t2, e2) {
+ if (p(t2))
+ return t2.clone();
+ var n2 = "object" == typeof e2 ? e2 : {};
+ return n2.date = t2, n2.args = arguments, new _(n2);
+ }, O = v;
+ O.l = S, O.i = p, O.w = function(t2, e2) {
+ return w(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset });
+ };
+ var _ = function() {
+ function M2(t2) {
+ this.$L = S(t2.locale, null, true), this.parse(t2);
+ }
+ var m2 = M2.prototype;
+ return m2.parse = function(t2) {
+ this.$d = function(t3) {
+ var e2 = t3.date, n2 = t3.utc;
+ if (null === e2)
+ return new Date(NaN);
+ if (O.u(e2))
+ return new Date();
+ if (e2 instanceof Date)
+ return new Date(e2);
+ if ("string" == typeof e2 && !/Z$/i.test(e2)) {
+ var r2 = e2.match($);
+ if (r2) {
+ var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3);
+ return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2);
+ }
+ }
+ return new Date(e2);
+ }(t2), this.$x = t2.x || {}, this.init();
+ }, m2.init = function() {
+ var t2 = this.$d;
+ this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds();
+ }, m2.$utils = function() {
+ return O;
+ }, m2.isValid = function() {
+ return !(this.$d.toString() === l);
+ }, m2.isSame = function(t2, e2) {
+ var n2 = w(t2);
+ return this.startOf(e2) <= n2 && n2 <= this.endOf(e2);
+ }, m2.isAfter = function(t2, e2) {
+ return w(t2) < this.startOf(e2);
+ }, m2.isBefore = function(t2, e2) {
+ return this.endOf(e2) < w(t2);
+ }, m2.$g = function(t2, e2, n2) {
+ return O.u(t2) ? this[e2] : this.set(n2, t2);
+ }, m2.unix = function() {
+ return Math.floor(this.valueOf() / 1e3);
+ }, m2.valueOf = function() {
+ return this.$d.getTime();
+ }, m2.startOf = function(t2, e2) {
+ var n2 = this, r2 = !!O.u(e2) || e2, h2 = O.p(t2), l2 = function(t3, e3) {
+ var i2 = O.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2);
+ return r2 ? i2 : i2.endOf(a);
+ }, $2 = function(t3, e3) {
+ return O.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2);
+ }, y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : "");
+ switch (h2) {
+ case c:
+ return r2 ? l2(1, 0) : l2(31, 11);
+ case f:
+ return r2 ? l2(1, M3) : l2(0, M3 + 1);
+ case o:
+ var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2;
+ return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3);
+ case a:
+ case d:
+ return $2(v2 + "Hours", 0);
+ case u:
+ return $2(v2 + "Minutes", 1);
+ case s:
+ return $2(v2 + "Seconds", 2);
+ case i:
+ return $2(v2 + "Milliseconds", 3);
+ default:
+ return this.clone();
+ }
+ }, m2.endOf = function(t2) {
+ return this.startOf(t2, false);
+ }, m2.$set = function(t2, e2) {
+ var n2, o2 = O.p(t2), h2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = h2 + "Date", n2[d] = h2 + "Date", n2[f] = h2 + "Month", n2[c] = h2 + "FullYear", n2[u] = h2 + "Hours", n2[s] = h2 + "Minutes", n2[i] = h2 + "Seconds", n2[r] = h2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2;
+ if (o2 === f || o2 === c) {
+ var y2 = this.clone().set(d, 1);
+ y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d;
+ } else
+ l2 && this.$d[l2]($2);
+ return this.init(), this;
+ }, m2.set = function(t2, e2) {
+ return this.clone().$set(t2, e2);
+ }, m2.get = function(t2) {
+ return this[O.p(t2)]();
+ }, m2.add = function(r2, h2) {
+ var d2, l2 = this;
+ r2 = Number(r2);
+ var $2 = O.p(h2), y2 = function(t2) {
+ var e2 = w(l2);
+ return O.w(e2.date(e2.date() + Math.round(t2 * r2)), l2);
+ };
+ if ($2 === f)
+ return this.set(f, this.$M + r2);
+ if ($2 === c)
+ return this.set(c, this.$y + r2);
+ if ($2 === a)
+ return y2(1);
+ if ($2 === o)
+ return y2(7);
+ var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3;
+ return O.w(m3, this);
+ }, m2.subtract = function(t2, e2) {
+ return this.add(-1 * t2, e2);
+ }, m2.format = function(t2) {
+ var e2 = this, n2 = this.$locale();
+ if (!this.isValid())
+ return n2.invalidDate || l;
+ var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = O.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, f2 = n2.months, h2 = function(t3, n3, i3, s3) {
+ return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3);
+ }, c2 = function(t3) {
+ return O.s(s2 % 12 || 12, t3, "0");
+ }, d2 = n2.meridiem || function(t3, e3, n3) {
+ var r3 = t3 < 12 ? "AM" : "PM";
+ return n3 ? r3.toLowerCase() : r3;
+ }, $2 = { YY: String(this.$y).slice(-2), YYYY: this.$y, M: a2 + 1, MM: O.s(a2 + 1, 2, "0"), MMM: h2(n2.monthsShort, a2, f2, 3), MMMM: h2(f2, a2), D: this.$D, DD: O.s(this.$D, 2, "0"), d: String(this.$W), dd: h2(n2.weekdaysMin, this.$W, o2, 2), ddd: h2(n2.weekdaysShort, this.$W, o2, 3), dddd: o2[this.$W], H: String(s2), HH: O.s(s2, 2, "0"), h: c2(1), hh: c2(2), a: d2(s2, u2, true), A: d2(s2, u2, false), m: String(u2), mm: O.s(u2, 2, "0"), s: String(this.$s), ss: O.s(this.$s, 2, "0"), SSS: O.s(this.$ms, 3, "0"), Z: i2 };
+ return r2.replace(y, function(t3, e3) {
+ return e3 || $2[t3] || i2.replace(":", "");
+ });
+ }, m2.utcOffset = function() {
+ return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
+ }, m2.diff = function(r2, d2, l2) {
+ var $2, y2 = O.p(d2), M3 = w(r2), m3 = (M3.utcOffset() - this.utcOffset()) * e, v2 = this - M3, g2 = O.m(this, M3);
+ return g2 = ($2 = {}, $2[c] = g2 / 12, $2[f] = g2, $2[h] = g2 / 3, $2[o] = (v2 - m3) / 6048e5, $2[a] = (v2 - m3) / 864e5, $2[u] = v2 / n, $2[s] = v2 / e, $2[i] = v2 / t, $2)[y2] || v2, l2 ? g2 : O.a(g2);
+ }, m2.daysInMonth = function() {
+ return this.endOf(f).$D;
+ }, m2.$locale = function() {
+ return D[this.$L];
+ }, m2.locale = function(t2, e2) {
+ if (!t2)
+ return this.$L;
+ var n2 = this.clone(), r2 = S(t2, e2, true);
+ return r2 && (n2.$L = r2), n2;
+ }, m2.clone = function() {
+ return O.w(this.$d, this);
+ }, m2.toDate = function() {
+ return new Date(this.valueOf());
+ }, m2.toJSON = function() {
+ return this.isValid() ? this.toISOString() : null;
+ }, m2.toISOString = function() {
+ return this.$d.toISOString();
+ }, m2.toString = function() {
+ return this.$d.toUTCString();
+ }, M2;
+ }(), T = _.prototype;
+ return w.prototype = T, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", f], ["$y", c], ["$D", d]].forEach(function(t2) {
+ T[t2[1]] = function(e2) {
+ return this.$g(e2, t2[0], t2[1]);
+ };
+ }), w.extend = function(t2, e2) {
+ return t2.$i || (t2(e2, _, w), t2.$i = true), w;
+ }, w.locale = S, w.isDayjs = p, w.unix = function(t2) {
+ return w(1e3 * t2);
+ }, w.en = D[g], w.Ls = D, w.p = {}, w;
+ });
+ }
+});
+export default require_dayjs_min();
diff --git a/ledApp/uni_modules/uv-ui-tools/libs/util/route.js b/ledApp/uni_modules/uv-ui-tools/libs/util/route.js
new file mode 100644
index 0000000..80c0afd
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/libs/util/route.js
@@ -0,0 +1,126 @@
+/**
+ * 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷
+ * 并且带有路由拦截功能
+ */
+import { queryParams, deepMerge, page } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
+class Router {
+ constructor() {
+ // 原始属性定义
+ this.config = {
+ type: 'navigateTo',
+ url: '',
+ delta: 1, // navigateBack页面后退时,回退的层数
+ params: {}, // 传递的参数
+ animationType: 'pop-in', // 窗口动画,只在APP有效
+ animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
+ intercept: false ,// 是否需要拦截
+ events: {} // 页面间通信接口,用于监听被打开页面发送到当前页面的数据。hbuilderx 2.8.9+ 开始支持。
+ }
+ // 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文
+ // 这里在构造函数中进行this绑定
+ this.route = this.route.bind(this)
+ }
+
+ // 判断url前面是否有"/",如果没有则加上,否则无法跳转
+ addRootPath(url) {
+ return url[0] === '/' ? url : `/${url}`
+ }
+
+ // 整合路由参数
+ mixinParam(url, params) {
+ url = url && this.addRootPath(url)
+
+ // 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
+ // 如果有url中有get参数,转换后无需带上"?"
+ let query = ''
+ if (/.*\/.*\?.*=.*/.test(url)) {
+ // object对象转为get类型的参数
+ query = queryParams(params, false)
+ // 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
+ return url += `&${query}`
+ }
+ // 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号
+ query = queryParams(params)
+ return url += query
+ }
+
+ // 对外的方法名称
+ async route(options = {}, params = {}) {
+ // 合并用户的配置和内部的默认配置
+ let mergeConfig = {}
+
+ if (typeof options === 'string') {
+ // 如果options为字符串,则为route(url, params)的形式
+ mergeConfig.url = this.mixinParam(options, params)
+ mergeConfig.type = 'navigateTo'
+ } else {
+ mergeConfig = deepMerge(this.config, options)
+ // 否则正常使用mergeConfig中的url和params进行拼接
+ mergeConfig.url = this.mixinParam(options.url, options.params)
+ }
+ // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题
+ if (mergeConfig.url === page()) return
+
+ if (params.intercept) {
+ mergeConfig.intercept = params.intercept
+ }
+ // params参数也带给拦截器
+ mergeConfig.params = params
+ // 合并内外部参数
+ mergeConfig = deepMerge(this.config, mergeConfig)
+ // 判断用户是否定义了拦截器
+ if (typeof mergeConfig.intercept === 'function') {
+ // 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
+ const isNext = await new Promise((resolve, reject) => {
+ mergeConfig.intercept(mergeConfig, resolve)
+ })
+ // 如果isNext为true,则执行路由跳转
+ isNext && this.openPage(mergeConfig)
+ } else {
+ this.openPage(mergeConfig)
+ }
+ }
+
+ // 执行路由跳转
+ openPage(config) {
+ // 解构参数
+ const {
+ url,
+ type,
+ delta,
+ animationType,
+ animationDuration,
+ events
+ } = config
+ if (config.type == 'navigateTo' || config.type == 'to') {
+ uni.navigateTo({
+ url,
+ animationType,
+ animationDuration,
+ events
+ })
+ }
+ if (config.type == 'redirectTo' || config.type == 'redirect') {
+ uni.redirectTo({
+ url
+ })
+ }
+ if (config.type == 'switchTab' || config.type == 'tab') {
+ uni.switchTab({
+ url
+ })
+ }
+ if (config.type == 'reLaunch' || config.type == 'launch') {
+ uni.reLaunch({
+ url
+ })
+ }
+ if (config.type == 'navigateBack' || config.type == 'back') {
+ uni.navigateBack({
+ delta
+ })
+ }
+ }
+}
+
+export default (new Router()).route
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/package.json b/ledApp/uni_modules/uv-ui-tools/package.json
new file mode 100644
index 0000000..2d940f6
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/package.json
@@ -0,0 +1,81 @@
+{
+ "id": "uv-ui-tools",
+ "displayName": "uv-ui-tools 工具集 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.1.25",
+ "description": "uv-ui-tools,集成工具库,强大的Http请求封装,清晰的文档说明,开箱即用。方便使用,可以全局使用",
+ "keywords": [
+ "uv-ui-tools,uv-ui组件库,工具集,uvui,uView2.x"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "y",
+ "快手": "y",
+ "飞书": "y",
+ "京东": "y"
+ },
+ "快应用": {
+ "华为": "y",
+ "联盟": "y"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/readme.md b/ledApp/uni_modules/uv-ui-tools/readme.md
new file mode 100644
index 0000000..79a7df5
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/readme.md
@@ -0,0 +1,23 @@
+## uv-ui-tools 工具集
+
+> **组件名:uv-ui-tools**
+
+uv-ui工具集成,包括网络Http请求、便捷工具、节流防抖、对象操作、时间格式化、路由跳转、全局唯一标识符、规则校验等等。
+
+该组件推荐配合[uv-ui组件库](https://www.uvui.cn/components/intro.html)使用,单独下载也可以在自己项目中使用,需要做相应的配置,可查看文档。强烈推荐使用[uv-ui组件库](https://www.uvui.cn/components/intro.html),导入组件都会自动导入`uv-ui-tools`。需要在自己的项目中使用请参考[扩展配置](https://www.uvui.cn/components/setting.html)。
+
+uv-ui破釜沉舟之兼容vue3+2、app、h5、多端小程序的uni-app生态框架,大部分组件基于uView2.x,在经过改进后全面支持vue3,部分组件做了进一步的优化,修复大量BUG,支持单独导入,方便开发者选择导入需要的组件。开箱即用,灵活配置。
+
+# <a href="https://www.uvui.cn/js/intro.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui-tools/theme.scss b/ledApp/uni_modules/uv-ui-tools/theme.scss
new file mode 100644
index 0000000..cfaae92
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui-tools/theme.scss
@@ -0,0 +1,43 @@
+// 此文件为uvUI的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于
+// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
+// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
+
+$uv-main-color: #303133;
+$uv-content-color: #606266;
+$uv-tips-color: #909193;
+$uv-light-color: #c0c4cc;
+$uv-border-color: #dadbde;
+$uv-bg-color: #f3f4f6;
+$uv-disabled-color: #c8c9cc;
+
+$uv-primary: #3c9cff;
+$uv-primary-dark: #398ade;
+$uv-primary-disabled: #9acafc;
+$uv-primary-light: #ecf5ff;
+
+$uv-warning: #f9ae3d;
+$uv-warning-dark: #f1a532;
+$uv-warning-disabled: #f9d39b;
+$uv-warning-light: #fdf6ec;
+
+$uv-success: #5ac725;
+$uv-success-dark: #53c21d;
+$uv-success-disabled: #a9e08f;
+$uv-success-light: #f5fff0;
+
+$uv-error: #f56c6c;
+$uv-error-dark: #e45656;
+$uv-error-disabled: #f7b2b2;
+$uv-error-light: #fef0f0;
+
+$uv-info: #909399;
+$uv-info-dark: #767a82;
+$uv-info-disabled: #c4c6c9;
+$uv-info-light: #f4f4f5;
+
+@mixin flex($direction: row) {
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ flex-direction: $direction;
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui/changelog.md b/ledApp/uni_modules/uv-ui/changelog.md
new file mode 100644
index 0000000..fce7860
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui/changelog.md
@@ -0,0 +1,353 @@
+## 1.1.20(2024-01-20)
+1. 优化 uv-loading-page重构,避免初始加载的时候先显示页面的问题,小程序平台由于性能问题,可能还是会短暂出现,请注意优化处理
+2. 优化 uv-textarea confirmType默认值改成return,支持换行
+3. 优化 uv-ui-tools luch-request更新
+4. 修复 uv-drop-down parentData不变的BUG
+5. 修复 uv-picker 上个版本引出的BUG
+6. 修复 uv-image 设置show-menu-by-longpress不生效的BUG
+7. 修复 uv-upload 动态设置deletable为false不生效的BUG
+## 1.1.19(2023-12-12)
+1. 优化 uv-toast部分重构,支持取消遮罩不影响点击其他元素 a. 增加z-index参数;b. 增加overlay参数
+2. 优化 关于组件如何阻止事件冒泡,增加相关说明:[组件怎么阻止事件冒泡](https://www.uvui.cn/components/feature.html#组件怎么阻止事件冒泡)
+3. 优化 持续优化文档
+4. 修复 uv-tabs current为字符串activeStyle不生效的BUG
+5. 修复 uv-vtabs 在change回调中设置current,快速点击菜单,导致死循环的BUG
+6. 修复 uv-cell right-icon插槽编译到APP端不显示的BUG,问题来源:[https://gitee.com/climblee/uv-ui/issues/I8LXZI](https://gitee.com/climblee/uv-ui/issues/I8LXZI)
+## 1.1.18(2023-11-28)
+0. 优化 uv-datetime-picker 增加round属性,设置弹窗圆角
+1. 优化 uv-subsection 增加customItemStyle属性,方便修改bar样式,比如设置圆角
+2. 修复 uv-picker issues反馈的问题uv-picker在组合式API的自定义组件中,columns动态赋值无法显示选项:[https://gitee.com/climblee/uv-ui/issues/I8H0GQ](https://gitee.com/climblee/uv-ui/issues/I8H0GQ)
+3. 修复 uv-popup issues问题:[https://gitee.com/climblee/uv-ui/issues/I8HDLO](https://gitee.com/climblee/uv-ui/issues/I8HDLO)
+4. 修复 uv-skeletons 支付宝小程序报错的BUG
+5. 修复 uv-image webp之前未使用的BUG & 微信报错的BUG
+6. 修复 uv-modal 上版本引出的确认和取消按钮均不显示,还有高度的BUG
+## 1.1.17(2023-11-10)
+0. 交流反馈 欢迎加入uv-ui官方群1交流反馈: 549833913(1000+)
+1. 交流反馈 欢迎加入uv-ui官方群2交流反馈: 206060892
+2. 新增 新增其他小组件下载入口:其他小组件
+3. 优化 uv-calendars 增加readonly属性,是否为只读状态,只读状态下禁止选择日期
+4. 修复 uv-search 禁用时,不能触发click的BUG
+5. 修复 uv-checkbox and uv-radio label文字过长,不换行的相关说明及处理
+6. 修复 uv-input and uv-search 点击clear按钮在微信小程序不好触发的BUG
+7. 修复 uv-list 修复设置ellipsis不生效的BUG
+8. 修复 uv-index-list 修复sticky属性不生效的BUG
+## 1.1.16(2023-10-30)
+1. 交流反馈 欢迎加入uv-ui官方群1交流反馈: 549833913(940+/1000)
+2. 交流反馈 欢迎加入uv-ui官方群2交流反馈: 206060892
+3. 新增 uv-skeletons 骨架屏,全新升级骨架屏,更加灵活,体验更加,强烈推荐使用新版骨架屏。一般用于页面在请求远程数据尚未完成时,在内容加载出来前展示与内容布局结构一致的灰白块,提升用户视觉体验。
+4. 优化 uv-button 增加后置插槽suffix,方便在按钮文字后面增加图标等
+5. 优化 uv-popup vue模式内容有背景色,设置圆角被遮挡的情况
+6. 优化 uv-tabs 点击一个选项,change事件重复派发的问题
+7. 优化 持续优化文档及其他
+8. 修复 uv-album 设置singleSize、multipleSize、space等值带单位,存在不显示的BUG
+9. 修复 uv-picker 省市级示例设置defaultValue,再次选择第二列错乱的BUG
+10. 修复 uv-icon imgMode默认值改成aspectFit,否则会导致支付宝平台name设置为本地图片显示不全的BUG
+11. 修复 uv-transition 在APP-IOS上不能正常显示的BUG
+12. 修复 uv-toast、uv-swipe-action、uv-sticky、uv-notify、uv-notice-bar、uv-grid、uv-count-down、uv-code等组件,unmounted兼容vue3
+## 1.1.15(2023-10-12)
+1. 优化 uv-keyboard a. 增加disKeys参数,mode = "car"下,被禁用的键,如:['I','O']; b. 增加customabc参数,mode = "car"下,是否启用自定义中英文切换内容模式,为了兼容支付宝等小程序不兼容嵌套插槽,导致同时显示自定义内容和原始内容; c. 增加ref方法changeCarMode,mode = "car"下, 调用此方法可以切换中英文模式; d. 增加@changeCarInputMode,mode = "car"下,调用此方法可以进行切换中英文; e. 增加插槽abc,mode = "car"下,自定义中英文切换内容
+2. 优化 uv-checkbox uv-radio 优化:https://gitee.com/climblee/uv-ui/issues/I872VD
+3. 优化 uv-picker 将immediate-change默认值改为true,该值在于change回调的及时性,微信小程序生效
+4. 优化 uv-tags 兼容customStyle参数等优化
+5. 修复 uv-transition 部分情况,修改某属性自动关闭的BUG
+6. 修复 uv-calendars 懒加载报错:https://gitee.com/climblee/uv-ui/issues/I869JS
+7. 修复 uv-datetime-picker 设置minDate出现选择错乱的BUG
+8. 修复 uv-input 搜狗输入法下存在不可清空的情况
+9. 修复 uv-calendars selected没有设置了info或者info设置为空字符串后,文本则无法恢复BUG
+## 1.1.14(2023-09-27)
+1. 优化 uv-list-item 可使用customStyle变量进行样式控制
+2. 优化 uv-cell 增加cellStyle参数,方便自定义单元格的样式
+3. 优化 uv-switch 优化细节
+4. 优化 不断优化[文档](https://www.uvui.cn/)
+5. 修复 uv-button 通过customStyle修改按钮宽度,组件中最外层节点不改变的问题
+6. 修复 uv-calendars a. 修复range模式下,selected设置了info后选中后,导致文本不恢复的问题;b. 修复multiple模式下,selected自定义信息的颜色没变,依然是白色
+7. 修复 uv-checkbox uv-checkbox-group之change回调中v-model值不更新的BUG
+## 1.1.13(2023-09-15)
+1. 优化 uv-button a. 增加参数iconSize,用于控制图标的大小;b. 增加open-type="agreePrivacyAuthorization"类型,用户同意隐私协议事件回调
+2. 优化 uv-picker 三级联动的案例:[https://www.uvui.cn/components/picker.html#省市区三级联动](https://www.uvui.cn/components/picker.html#省市区三级联动)
+3. 修复 uv-read-more 全局设置rpx时,导致展开高度不对的BUG
+4. 修复 uv-tabs a. 设置lineWidth未带单位产生的误差BUG;b. 首次加载时,处理下划线会有左到右的过渡效果
+5. 修复 uv-textaera 设置autoHeight后出现高度异常的BUG
+6. 修复 uv-input H5等情况设置禁用或可读情况下,点击事件无效的问题,nvue需要特殊处理
+7. 修复 uv-calendars a. 在vue2+小程序渲染时闪烁的问题;b. 增加allowSameDay参数,是否允许日期范围的起止时间为同一天,mode=range时有效
+8. 修复 uv-safe-bottom 兼容飞书小程序
+9. 修复 uv-album 添加依赖,避免导入运行有误
+10. 修复 uv-ui-tools 优化组件用到的相关
+## 1.1.12(2023-09-10)
+1. 修复 uv-popup a. h5初始zIndex错误的问题;b. 修复全局设置prop无效的问题
+2. 修复 uv-button 修复多个按钮由view包裹,显示在一行宽度不正常的BUG
+3. 修复 uv-modal a. 修复两个按钮之间竖线不显示的问题;b. uv-ui项目自定义按钮示例修复
+4. 修复 uv-calendars 修复国际化失效的BUG
+5. 修复 uv-keyboard 修复键盘change回调事件产生冲突的BUG
+## 1.1.11(2023-09-02)
+1. 优化 uv-calendars a. 去除range参数,由mode="range"替换;b. 新增mode参数,不传 / multiple / range,分别为单日期, 多个日期,选择日期范围;c. 与uv-calendar选择日期的功能保持一致
+2. 优化 uv-modal a. 增加align参数,设置文本对齐方式;b. 增加textStyle参数,扩展文本样式
+3. 优化 uv-datetime-picker a. 增加mode="year"模式,方便只选择年;b. 增加clearDate参数,是否清除上次选择
+4. 修复 uv-ui-tools 设置customstyle同名计算属性报错:The computed property "customStyle" is already defined as a prop
+5. 修复 uv-image a. 设置widthFix时出现显示不全的BUG;b. 修复抖音等平台在width和height属性改变时出现不显示的BUG
+6. 修复 uv-checkbox 点击空隙处或label插槽内容不会选中的问题
+7. 修复 uv-radio 点击空隙处或label插槽内容不会选中的问题
+8. 修复 uv-calendars 在pages.json中设置easycom会报错的BUG
+9. 修复 uv-index-list 设置customNavHeight导致定位不准确的BUG
+## 1.1.10(2023-08-30)
+1. 交流反馈 欢迎加入uv-ui官方群1交流反馈: 549833913
+2. 交流反馈 欢迎加入uv-ui官方群2交流反馈: 206060892
+3. 优化 uv-calendars 1. 去除range参数,由mode="range"替换;2. 新增mode参数,不传 / multiple / range,分别为单日期, 多个日期,选择日期范围;3. 与uv-calendar选择日期的功能保持一致
+4. 新增 uv-album 新增相册组件及相关文档
+5. 优化 其他优化
+6. 修复 uv-text app-nvue设置align不生效的BUG
+7. 修复 uv-drop-down 自定义内容,点击自定义内容时会自动关闭弹窗的问题
+8. 修复 uv-image 异步修改宽高不生效的问题,问题来源:https://gitee.com/climblee/uv-ui/issues/I7WUQ3
+9. 修复 uv-calendars 通过setConfig修改属性不生效的问题,出自评论区:https://ext.dcloud.net.cn/plugin?id=12287
+10. 修复 uv-list 设置边框不生效的BUG
+## 1.1.9(2023-08-27)
+1. 优化 uv-calendars 1. 去除range参数,由mode="range"替换;2. 新增mode参数,不传 / multiple / range,分别为单日期, 多个日期,选择日期范围;3. 与uv-calendar选择日期的功能保持一致
+2. 优化 uv-picker 增加round属性,设置圆角
+3. 修复 uv-calendars 点击返回今天按钮时,monthSwitch方法回调参数返回月份不是当天对应月份
+4. 修复 uv-radio 1. 设置 labelSize 属性设置无效的问题:https://gitee.com/climblee/uv-ui/issues/I7W6UN;2. v-model 绑定布尔值控制台报警:https://gitee.com/climblee/uv-ui/issues/I7W714
+5. 修复 uv-checkbox 1. 设置 label 属性为布尔值不生效的BUG
+## 1.1.8(2023-08-24)
+1. 优化 uv-popup 弹出不丝滑优化思路:https://www.uvui.cn/components/popup.html#yh
+2. 修复 uv-switch 取消value传值,只能使用v-model传值,避免异步操作不生效的BUG
+3. 修复 uv-index-list ios端滚动过程中+快速点击右侧导航页面出现空白的BUG
+4. 修复 uv-rate 1. 支付宝报错的BUG; 2. 不能选半星的BUG
+5. 修复 uv-model 异步loading时,确认回调还会一直触发的BUG
+6. 修复 uv-swiper 标题文字过多未隐藏掉的BUG
+7. 修复 uv-text app-nvue编译不能自动换行的BUG
+## 1.1.7(2023-08-22)
+1. 优化 uv-drop-down a. 增加@change回调,返回弹窗关闭状态; b. 增加init方法,方便位置改变进行调整
+2. 优化 部分文档优化
+3. 修复 uv-input a. app-nvue-ios端不能输入的BUG;b. 键盘高度等值不返回BUG
+4. 修复 uv-scroll-list 报错导致不能移动指示器的BUG
+5. 修复 uv-search 边距值在上次更新中误改导致不对的BUG
+6. 修复 uv-image 设置width和height为100%不生效的BUG
+## 1.1.6(2023-08-18)
+1. 优化 优化文档
+2. 修复 uv-list 使用列表右侧显示 switch,switchChange回调中返回数据为undefined的BUG
+3. 修复 uv-checkbox 数据多不换行的BUG
+4. 修复 uv-upload 1. 图片预览位置错误的BUG;2. 视频预览不生效的BUG;3. 改变上传视频宽高不生效的BUG
+5. 修复 uv-navbar 在部分ios高版本机型,返回按钮不好操作的问题
+6. 修复 uv-waterfall 只有一条数据的时候,切换的时候数据会左右显示错误的BUG
+## 1.1.5(2023-08-14)
+1. 优化 uv-pick-color 删除scrollTop参数,内部修改后就不需要了
+2. 优化 uv-loading-icon 增加textStyle参数,可自定义文本样式,比如给上边距
+3. 修复 uv-safe-bottom 百度小程序报错的BUG
+4. 修复 uv-form 设置labelWidth属性时,节点渲染有闪动的BUG
+5. 修复 uv-grid 设置col属性时,节点渲染有闪动的BUG
+6. 修复 uv-parse 阻止a标签跳转文档说明
+## 1.1.4(2023-08-13)
+1. 优化 nvue自定义图标 [详细文档-nvue中自定义图标库](https://www.uvui.cn/guide/customIcon.html#nvue%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9B%BE%E6%A0%87%E5%BA%93)
+2. 优化 uv.$uv.http 在APP.vue页面使用报错的BUG: [Api集中管理](https://www.uvui.cn/js/http.html#_3-api%E9%9B%86%E4%B8%AD%E7%AE%A1%E7%90%86)
+3. 修复 uv-navbar app-nvue运行ios存在背景图片错乱的问题
+4. 修复 uv-list app-nvue运行ios存在,分包页面不滚动
+5. 修复 uv-textarea 值为null或undefined时显示错误的bug
+6. 修复 uv-search 值为null或undefined时显示错误的bug
+7. 修复 uv-scroll-list vue2编译报错的BUG
+8. 修复 uv-calendars 选择月份弹窗层级的问题
+9. 修复 uv-form 动画在vue3 setup语法糖中错乱,以及表单其他相关问题解决: [Issues](https://gitee.com/my_dear_li_pan/uv-ui/issues/I7SNTT)
+10. 修复 uv-picker-color 滚动页面无法点击的BUG:增加scrollTop参数,设置滚动条的位置。不设置如果页面出现滚动就需要传该值,会出现颜色面板无法进行选颜色的情况。
+11. 交流反馈 欢迎加入uv-ui官方群1交流反馈: [549833913](https://www.uvui.cn/components/addQQGroup.html)
+12. 交流反馈 欢迎加入uv-ui官方群2交流反馈: [206060892](https://www.uvui.cn/components/addQQGroup.html)
+## 1.1.3(2023-08-06)
+1. 优化 uv-calendars 1. 增加startText参数; 2. 增加endText参数; 3. 增加selected中的参数; 4. 优化日历范围选择
+2. 优化 uv-empty icon属性支持base64图片
+3. 优化 uv-navbar 增加背景图片的裁剪模式参数imgMode
+4. 优化 uv-picker-color 颜色值不对的BUG
+5. 优化 [API文档优化](https://www.uvui.cn/components/changelog.html)
+6. 优化 常见问题增加:[怎么隐藏uv-tabs等组件的滚动条](https://www.uvui.cn/components/problem.html#%E4%B9%9D%E3%80%81%E6%80%8E%E4%B9%88%E9%9A%90%E8%97%8Fuv-tabs%E7%AD%89%E7%BB%84%E4%BB%B6%E7%9A%84%E6%BB%9A%E5%8A%A8%E6%9D%A1)
+7. 修复 uv-radio name为数字0时不能选中的BUG
+8. 修复 uv-textarea 1. v-model设置为数据时的BUG;2. 复制过多内容,计数显示错误的BUG;3. maxlength为-1改成不显示计数
+9. 修复 uv-code-input 在vue2模式下,v-model设置为0时不生效的BUG
+10. 修复 uv-input 在vue2模式下,v-model设置为0时不生效的BUG
+11. 修复 uv-search 在vue2模式下,v-model设置为0时不生效的BUG
+12. 修复 uv-ui-tools 1. 路由拦截修复;2. 增加events参数
+## 1.1.2(2023-08-03)
+1. 新增 uv-calendars 新版日历发布
+2. 新增 uv-toolbar 组件独立发布,老用户更新uv-picker,需要手动删除uv-picker目录下的uv-toolbar目录,否则会有冲突提示
+3. 优化 uv-tags 增加cellChild参数
+4. 优化 uv-navbar 兼容背景图片
+5. 优化 uv-notice-bar 竖向滚动时候增加change回调
+## 1.1.1(2023-07-30)
+1. 新增 uv-drop-down 下拉筛选组件,兼容app-nvue及多端
+2. 优化 uv-textarea 增加confirm-hold参数,方便设置进行换行处理
+3. 优化 其他关于文档的优化等
+## 1.1.0(2023-07-26)
+1. 重构 uv-list 全面重构,提高性能,放弃使用scroll-view,具体文档参考:uv-list列表
+2. 优化 uv-search 1. 增加prefix和suffix 前置和后置插槽;2. 增加boxStyle参数,方便控制输入框部分的样式
+3. 优化 文档优化:获取节点布局信息,文档新增nvue获取方式的说明
+## 1.0.22(2023-07-26)
+1. 优化 uv-textarea 组件 增加textStyle和countStyle属性,方便控制文本样式
+2. 优化 uv-swiper 增加竖向播放属性:vertical
+3. 优化 uv-icon 支持base64图片格式
+4. 优化 uv-transition 和 uv-image 增加参数cellChild属性,避免nvue中出现回收后不显示的BUG
+5. 优化 uv-button 增加customTextStyle属性,方便自定义按钮文字样式
+6. 优化 优化部分文档说明
+7. 修复 uv-slider 修改背景颜色属性为backgroundColor,避免设置不生效
+8. 修复 uv-index-list 1. 修复全局设置成rpx存在的高度BUG;2. 修复其他BUG
+## 1.0.21(2023-07-22)
+1. 新增 uv-scroll-list 横向滚动列表组件
+2. 优化 增加测试占位图,方便开发者使用线上图片进行测试:[https://www.uvui.cn/components/testPic.html](https://www.uvui.cn/components/testPic.html)
+3. 优化 uv-calendar 组件文档示例等优化,增加setFormatter说明
+4. 优化 uv-notice-bar 优化文档,说明不显示左边图标的使用方法
+5. 修复 uv-input 在微信小程序端清除内容存在不能清除的BUG
+6. 修复 uv-button 1. 解决微信小程序动态设置hover-class点击态不消失的BUG; 2. 文档优化
+7. 修复 uv-waterfall 在tab切换等场景快速切换时,会出现报错的BUG
+8. 优化 优化其他
+## 1.0.20(2023-07-18)
+1. 修复 uv-textarea 设置-1不生效
+2. 修复 uv-icon 恢复uv-empty相关的图标
+3. 修复 uv-empty 恢复设置mode属性的内置图标
+4. 优化 [优化文档](https://www.uvui.cn)
+## 1.0.19(2023-07-14)
+1. 优化 uv-waterfall 当changeList未处理数据时,正确返回对应列的数据,避免误导
+2. 修复 uv-rate VUE3模式下设置value属性不生效的BUG
+3. 修复 uv-input VUE3模式下设置value属性不生效的BUG
+4. 修复 uv-search VUE3模式下设置value属性不生效的BUG
+5. 修复 uv-code-input VUE3模式下设置value属性不生效的BUG
+6. 修复 uv-number-box VUE3模式下设置value属性不生效的BUG
+7. 修复 uv-radio VUE3模式下设置value属性不生效的BUG
+8. 修复 uv-checkbox VUE3模式下设置value属性不生效的BUG
+9. 修复 uv-textarea VUE3模式下设置value属性不生效的BUG
+10. 修复 uv-switch VUE3模式下设置value属性不生效的BUG
+11. 修复 uv-slider VUE3模式下设置value属性不生效的BUG
+12. 修复 uv-datetime-picker VUE3模式下设置value属性不生效的BUG
+13. 修复 uv-icon 部分图标错误的BUG
+## 1.0.18(2023-07-06)
+1. 优化 uv-icon 1. 更新图标,删除一些不常用的图标;2. 删除base64,修改成ttf文件引入读取图标。uv-icon 图标
+2. 优化 uv-icon nvue自定义图标用法,文档说明:[点击跳转](https://www.uvui.cn/guide/customIcon.html)
+3. 优化 uv-upload 文档示例代码,增加fileList参数说明:[点击跳转](https://www.uvui.cn/components/upload.html#filelist-options)
+4. 修复 uv-checkbox vue3模式下,动态修改v-model绑定的值无效的BUG
+5. 修复 uv-radio vue3模式下,动态修改v-model绑定的值无效的BUG
+6. 修复 uv-datetime-picker vue3模式下,动态修改v-model绑定的值无效的BUG
+## 1.0.17(2023-07-04)
+1. 优化 uv-icon 修复,NVUE平台主题颜色在APP不生效的BUG
+2. 优化 uv-notice-bar 优化,增加disableScroll属性
+3. 优化 uv-input uv-back-top uv-cell uv-form uv-search uv-modal uv-navbar uv-index-list uv-empty uv-upload 去除插槽判断,避免某些平台不显示的BUG
+4. 优化 uv-form 优化文档
+5. 优化 优化其他相关文档
+## 1.0.16(2023-07-03)
+1. 优化 uv-transition 动画组件,代码重构优化,性能更加友好,增加自定义动画功能。详情[参考文档](https://www.uvui.cn/components/transition.html)
+2. 优化 uv-popup 弹出层,代码重构优化,性能翻倍,小程序体验性能更加,避免卡顿。打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/popup.html)
+3. 优化 uv-calendar 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/actionSheet.html)
+4. 优化 uv-action-sheet 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/calendar.html)
+5. 优化 uv-datetime-picker 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/datetimePicker.html)
+6. 优化 uv-form 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+7. 优化 uv-keyboard 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/keyboard.html)
+8. 优化 uv-modal 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/modal.html)
+9. 优化 uv-notify 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/notify.html)
+10. 优化 uv-overlay 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+11. 优化 uv-pick-color 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/pickColor.html)
+12. 优化 uv-picker 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/picker.html)
+13. 优化 uv-tooltip 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+14. 优化 uv-loading-page 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+15. 优化 相关文档的优化更改。
+16. 修复 uv-safe-bottom 修复,在百度程序,抖音小程序不生效的BUG
+## 1.0.15(2023-06-29)
+1. 欢迎加QQ群交流:[549833913](https://www.uvui.cn/components/addQQGroup.html)
+2. 优化 uv-swiper 优化:1. 增加titleStyle属性,方便修改标题样式;2. 标题上去掉是否是图片的判断,避免无后缀的图片不显示
+3. 优化 uv-steps 优化:1. 增加插槽title; 3. 文档关于插槽相关的参数说明完善;增加customStyle属性
+4. 优化 uv-checkbox 优化:增加label文字插槽,与radio保持一致,优化文档相关说明
+5. 优化 uv-modal 优化:增加closeLoading方法,方便异步加载手动取消加载状态,更新文档
+6. 优化 uv-image 增加文档说明:uv-list、 uv-waterfall等组件在 Android平台使用了list封装,所以在该组件中仍然不能使用uv-image等组件
+7. 优化 优化更多文档
+8. 修复 uv-vtabs 修复非联动情况下,内容过多的情况,滚动一段距离,再切换未滚动到顶部的BUG
+9. 修复 uv-image 修复:duration属性不生效的BUG
+10. 修复 uv-code-input 修复:使用:disabledKeyboard="true"属性,事件全部失效的BUG
+11. 修复 uv-button 修复:设置open-type="chooseAvatar"等值不生效的BUG
+## 1.0.14(2023-06-25)
+1. 欢迎加QQ群交流:[549833913](https://www.uvui.cn/components/addQQGroup.html)
+2. 优化 uv-count-down 增加外部样式customStyle参数
+3. 优化 文档的全面优化
+4. 修复 uv-count-to 1. 修复继续滚动的函数 2. 修改文档错误 4. 适配px和rpx的单位 4. 适配customStyle参数
+5. 修复 uv-load-more 修复customStyle参数设置背景等不生效的BUG
+6. 修复 uv-code-input 优化下边框
+7. 修复 uv-tabs 添加uv-icon依赖
+8. 修复 uv-grid 优化修改
+9. 修复 uv-cell 优化修改
+## 1.0.13(2023-06-20)
+1. 优化 uv-calendar formatter格式化中增加topInfo参数
+2. 优化 uv-tabs 增加customStyle参数
+3. 优化 文档优化,便于开发者直接开干
+4. 优化 uv-switch 优化size属性,适配单位传递
+5. 修复 uv-ui-tools、uv-form、uv-picker 修复vue3编译支付宝异常
+6. 修复 uv-ui-tools、uv-form、uv-picker 修复vue3编译支付宝异常
+7. 修复 uv-parse 修复在nvue不显示的BUG
+8. 修复 uv-form 修复某些条件下报错的BUG
+## 1.0.12(2023-06-14)
+1. 优化部分组件,优化文档部分细节
+2. uv-popup、uv-modal 修复遮罩层zIndex问题
+3. uv-form 在vue3的setup语法中ref使用uvForm会导致报错
+4. uv-tabs activeStyle设置字体大小,可能会导致下划线位置不对BUG
+5. uv-pick-color 百度小程序点击报错
+6. uv-transition 恢复this.$nextTick
+7. uv-picker 抖音小程序选择的时候报错,导致不能关闭的BUG
+8. uv-checkbox 多余的属性labelDisabled,导致APP中报错提示
+9. uv-tabbar 底部安全距离组件无效的BUG
+10. uv-vtabs 头部存在的时候,联动不准确的BUG
+## 1.0.11(2023-06-12)
+1. uv-radio-group、uv-checkbox-group 兼容自定义样式customStyle,方便通过样式调整整体位置等,数据较多时允许换行
+2. uv-ui-tools 优化内置样式等,解决微信小程序使用uvui提示 Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors,[详情](https://www.uvui.cn/components/problem.html)
+3. uv-datetime-picker 取消defaultIndex参数,目前传该值也没实际意义
+4. uv-tabbar 增加iconSize参数
+5. uv-calendar 增加change回调
+6. uv-calendar 修复BUG
+7. uv-rate 修复只读或禁止状态下设置value无效的问题
+8. uv-popup 修复zIndex问题
+9. uv-modal 修复zIndex问题
+10. 文档-扩展配置更新:[扩展配置](https://www.uvui.cn/components/setting.html)
+11. 文档-优化更新:[uv-ui文档](https://www.uvui.cn/components/changelog.html)
+12. 文档-新增常见问题:[常见问题](https://www.uvui.cn/components/problem.html)
+13. 优化其他
+## 1.0.10(2023-06-05)
+1. uv-navbar 渐变背景色兼容
+2. uv-calendar 日历选择BUG修复
+## 1.0.9(2023-06-05)
+1. 新增uv-vtabs垂直选项卡组件,主要用于分类展示,分类切换功能,支持联动和不联动两种模式
+2. uv-qrcode,uv-datetime-picker,uv-subsection等文档说明优化,避免开发困难;优化API相关说明
+3. uv-notice-bar 1. 修复在触发error函数报错的BUG;2. 修复在text值为undefined的时候,解决报错BUG
+4. uv-button 等组件修复触发两次事件的BUG
+5. uv-datetime-picker 1. 修复重置值存在不更新的BUG;2. 优化文档,增加filter使用方法说明
+6. uv-badge 修复type等属性为null或undefined的时候不显示徽标的BUG
+7. uv-ui-tools 优化工具组件,兼容更多功能,小程序分享功能优化等
+...
+## 1.0.8(2023-05-27)
+1. uv-waterfall修复在百度小程序中可能存在的BUG;去掉原有的slot方式
+2. uv-image修复可能报错的问题
+3. uv-pick-color 在文档预览模式中无法点击的问题
+4. uv-index-list 修复select事件不触发的问题
+5. 优化其他组件及示例项目等
+## 1.0.7(2023-05-25)
+1. uv-icon 将线上ttf字体包替换成base64,避免加载时或者网络差时候显示白色方块
+2. uv-text 去掉多余的data-index属性,避免警告
+3. uv-upload 在fileList的watch中增加deep属性
+4. uv-pick-color 去掉template中存在的this.导致头条小程序编译警告
+5. uv-image 去掉template中存在的this.导致头条小程序编译警告
+## 1.0.6(2023-05-23)
+1. 新增uv-pick-color颜色选择器组件
+2. uv-toolbar组件增加showBorder属性,是否显示下边框
+3. uv-transition组件在百度小程序等平台不支持this.$nextick导致下面的逻辑不执行,使用延时替换方案
+4. uv-ui-tools组件中bem()函数兼容百度/头条小程序等
+5. uv-waterfall组件修复在百度/头条小程序显示异常等BUG,增加changeList回调函数处理数据,同步更新示例等
+6. uv-image组件修复在百度/头条小程序等开启observeLazyLoad后显示异常BUG
+7. uv-tabs组件修复上次更新导致的在nvue中不滚动的BUG
+8. uv-qrcode组件修复在部分平台不显示加载的BUG
+9. 修复其他已知问题等
+## 1.0.5(2023-05-17)
+1. 新增uv-qrcode二维码组件
+2. 修复uv-tooltip在vue2模式下的BUG
+3. 优化部分问题
+## 1.0.4(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.3(2023-05-12)
+1. 修复uv-input在vue3模式下双向绑定问题
+2. 修复uv-textarea在vue3模式下双向绑定问题
+3. 修复uv-rate在vue3模式下双向绑定问题
+## 1.0.2(2023-05-11)
+1. 更新文档
+2. 增加插件下载入口
+## 1.0.1(2023-05-10)
+1. 所有组件依赖
+2. 上传示例项目
+## 1.0.0(2023-05-10)
+1. uv-ui
diff --git a/ledApp/uni_modules/uv-ui/components/uv-ui/uv-ui.vue b/ledApp/uni_modules/uv-ui/components/uv-ui/uv-ui.vue
new file mode 100644
index 0000000..e023095
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui/components/uv-ui/uv-ui.vue
@@ -0,0 +1,7 @@
+<template>
+ <view>占位组件,请勿使用;如需下载示例项目,请使用【下载插件并导入HBuilderX】或【使用 HBuilderX 导入示例项目】或【下载示例项目ZIP】</view>
+</template>
+<script>
+</script>
+<style>
+</style>
diff --git a/ledApp/uni_modules/uv-ui/package.json b/ledApp/uni_modules/uv-ui/package.json
new file mode 100644
index 0000000..5e919a2
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui/package.json
@@ -0,0 +1,162 @@
+{
+ "id": "uv-ui",
+ "displayName": "uv-ui 破釜沉舟之兼容vue3+2、app、h5、小程序等多端,灵活导入,利剑出击",
+ "version": "1.1.20",
+ "description": "uv-ui 是基于uni-app、部分组件基于uView2.x、全端兼容、支持独立导入、内容丰富的UI框架。破釜沉舟之兼容vue3+2、app、h5、小程序等多端,利剑出击,开箱即用。",
+ "keywords": [
+ "uv-ui",
+ "uvui",
+ "UI组件库",
+ "ui框架",
+ "ui库"
+ ],
+ "repository": "https://github.com/climblee/uv-ui",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": "https://www.npmjs.com/package/@climblee/uv-ui"
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-skeletons",
+ "uv-album",
+ "uv-drop-down",
+ "uv-calendars",
+ "uv-scroll-list",
+ "uv-vtabs",
+ "uv-pick-color",
+ "uv-qrcode",
+ "uv-ui-tools",
+ "uv-action-sheet",
+ "uv-alert",
+ "uv-avatar",
+ "uv-back-top",
+ "uv-badge",
+ "uv-button",
+ "uv-calendar",
+ "uv-cell",
+ "uv-checkbox",
+ "uv-code",
+ "uv-code-input",
+ "uv-collapse",
+ "uv-count-down",
+ "uv-count-to",
+ "uv-datetime-picker",
+ "uv-divider",
+ "uv-empty",
+ "uv-form",
+ "uv-gap",
+ "uv-grid",
+ "uv-icon",
+ "uv-image",
+ "uv-index-list",
+ "uv-input",
+ "uv-keyboard",
+ "uv-line",
+ "uv-line-progress",
+ "uv-link",
+ "uv-list",
+ "uv-loading-icon",
+ "uv-loading-page",
+ "uv-load-more",
+ "uv-modal",
+ "uv-navbar",
+ "uv-no-network",
+ "uv-notice-bar",
+ "uv-notify",
+ "uv-number-box",
+ "uv-overlay",
+ "uv-parse",
+ "uv-picker",
+ "uv-popup",
+ "uv-radio",
+ "uv-rate",
+ "uv-read-more",
+ "uv-row",
+ "uv-safe-bottom",
+ "uv-search",
+ "uv-skeleton",
+ "uv-slider",
+ "uv-status-bar",
+ "uv-steps",
+ "uv-sticky",
+ "uv-subsection",
+ "uv-swipe-action",
+ "uv-swiper",
+ "uv-switch",
+ "uv-tabbar",
+ "uv-tabs",
+ "uv-tags",
+ "uv-text",
+ "uv-textarea",
+ "uv-toast",
+ "uv-tooltip",
+ "uv-transition",
+ "uv-upload",
+ "uv-waterfall"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-ui/readme.md b/ledApp/uni_modules/uv-ui/readme.md
new file mode 100644
index 0000000..9dfe1df
--- /dev/null
+++ b/ledApp/uni_modules/uv-ui/readme.md
@@ -0,0 +1,164 @@
+<p align="center">
+ <span> </span><img alt="logo" src="https://www.uvui.cn/common/logo.png" width="120" height="120">
+</p>
+<h3 align="center">uv-ui</h3>
+<h3 align="center">兼容vue3+2多平台快速开发的UI框架</h3>
+
+[](https://gitee.com/climblee/uv-ui)
+[](https://gitee.com/climblee/uv-ui)
+[](https://github.com/climblee/uv-ui)
+[](https://github.com/climblee/uv-ui/issues)
+[](https://www.uvui.cn)
+[](https://www.uvui.cn/components/changelog.html)
+[](https://en.wikipedia.org/wiki/MIT_License)
+
+## 温馨提示:如需下载uv-ui示例项目,请不要使用【下载插件ZIP】按钮。
+
+### uvui官方群1:<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=aaoyDvfV7nSee9vzWfzyZM1vKlu3xrNZ&authKey=pdU6HDpyzGUoc1QvQvCmzCbwzsoHgBbToF%2F0ChP4lNjPIPgWHGRE4I99XYGiTcNI&noverify=0&group_code=549833913" target="_blank">549833913</a>
+### uvui官方群2:<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=2QbEeKBn6ysCyQ09V4mgst1W8onxFybQ&authKey=aMTGL5zCYwsinG%2FeH0qMlAqXAdyKr5AjXVf2oMnsmj7NCg%2F2HraMU%2FNFxELLIPvp&noverify=0&group_code=206060892" target="_blank">206060892</a>
+
+## uvui特点
+
+1. **uv-ui的前世今生**,`uv-ui` 是基于 `uview2.x` 版本改造而来。重命名也是为了避开发布冲突和很多组件 `u-`在 `nvue` 中不能使用的情况,所以这才诞生了`uv-ui`。感谢 `uview-ui` 作者的开源奉献,再次为开源点赞。 同时 `uv-ui` 也是无条件开源。
+
+2. **全端兼容**,`uv-ui`支持vue3、vue2、app-vue、app-nvue、h5、小程序等。`uv-ui`的组件都是多端自适应的,底层会抹平很多小程序平台的差异或bug。
+
+3. **扩展配置**,`uv-ui`内置的方法默认不再挂载到`uni`对象之上,也就意味着默认情况下不能在项目中直接使用`uni.$uv.xxx`使用内置方法。但是可以通过扩展可以解决,通过如下方式进行配置即可,使用方式请参考[扩展配置](https://www.uvui.cn/components/setting.html)。其中包括[ JS工具库](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-js%E5%B7%A5%E5%85%B7%E5%BA%93)、[ 自定义主题](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%BB%E9%A2%98)、[ 基础样式](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E5%9F%BA%E7%A1%80%E6%A0%B7%E5%BC%8F)、[ setconfig](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-setconfig)等。
+
+## 预览
+
+通过微信(APP下载不支持微信扫码)或浏览器扫码查看演示效果。
+
+<img src="https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-wx.png" width="200" /> <img src="https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-app.png" width="200" /> <img src="https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-h5.png" width="200" />
+
+## 链接
+
+- [官方文档](https://www.uvui.cn)
+- [演示地址](https://h5.uvui.cn)
+- [更新日志](https://www.uvui.cn/components/changelog.html)
+- [关于我们](https://www.uvui.cn/cooperation/about.html)
+- <a href="#list">组件列表</a>
+
+## 交流反馈
+
+欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uvui.cn/components/addQQGroup.html)
+
+## 快速开始
+
+方式一:`uv-ui` 强烈建议通过 `下载插件并导入HbuilderX` 导入组件。
+
+方式二:下载完整 [uv-ui项目](https://ext.dcloud.net.cn/plugin?id=12287) 将 `uni_modules` 复制到自己的项目。
+
+方式三:通过 `npm i @climblee/uv-ui` 下载,此方法需要配置 easycom,配置详情可查看[安装](https://www.uvui.cn/components/install.html)。
+
+请通过[快速上手](https://www.uvui.cn/components/quickstart.html)了解更详细的内容。
+
+**注意:导入插件后,建议`HBuilderX`重新运行项目,可能新导入的插件不能实时更新而导致不能运行。**
+
+## 使用方法
+
+组件导入 `uni_modules` 后,直接在项目中使用,无需通过import引入组件。
+
+```html
+<template>
+ <uv-icon name="baidu" size="30" color="#909399"></uv-icon>
+</template>
+```
+
+## 扩展功能
+
+`uv-ui` 内置了强大的工具函数、请求封装等,可以根据自身需求进行扩展配置,详情请查看[扩展配置](https://www.uvui.cn/components/setting.html)。
+
+**注意:只有[扩展配置](https://www.uvui.cn/components/setting.html)后才能在自己的项目页面中使用组件库内置方法和变量等**。
+
+<div id="list"></div>
+
+## 组件列表
+
+下表为 `uv-ui` 的扩展组件清单,点击每个组件**点击下载&安装**即可在详情页面导入组件到项目下,导入后建议重新运行即可直接使用,组件无需import和注册。
+
+| 组件名 | 组件说明 |
+| --- | --- |
+| uv-skeletons | [新版骨架屏(推荐)](https://www.uvui.cn/components/skeletons.html) |
+| uv-calendars | [新版日历(推荐)](https://www.uvui.cn/components/calendars.html) |
+| uv-drop-down | [下拉筛选](https://www.uvui.cn/components/dropDown.html) |
+| uv-scroll-list | [横向滚动列表](https://www.uvui.cn/components/scrollList.html) |
+| uv-vtabs | [垂直选项卡](https://www.uvui.cn/components/vtabs.html) |
+| uv-pick-color | [颜色选择器](https://www.uvui.cn/components/pickColor.html) |
+| uv-qrcode | [二维码](https://www.uvui.cn/components/qrcode.html) |
+| uv-waterfall | [瀑布流](https://www.uvui.cn/components/waterfall.html) |
+| uv-row | [Layout 布局](https://www.uvui.cn/components/layout.html) |
+| uv-icon | [图标](https://www.uvui.cn/components/icon.html) |
+| uv-button | [按钮](https://www.uvui.cn/components/button.html) |
+| uv-text | [文本](https://www.uvui.cn/components/text.html) |
+| uv-link | [超链接](https://www.uvui.cn/components/link.html) |
+| uv-image | [图片](https://www.uvui.cn/components/image.html) |
+| uv-transition | [动画](https://www.uvui.cn/components/transition.html) |
+| uv-form | [表单](https://www.uvui.cn/components/form.html) |
+| uv-input | [增强输入框](https://www.uvui.cn/components/input.html) |
+| uv-textarea | [增强文本域](https://www.uvui.cn/components/textarea.html) |
+| uv-checkbox | [复选框](https://www.uvui.cn/components/checkbox.html) |
+| uv-radio | [单选框](https://www.uvui.cn/components/radio.html) |
+| uv-switch | [开关选择器](https://www.uvui.cn/components/switch.html) |
+| uv-calendar | [日历](https://www.uvui.cn/components/calendar.html) |
+| uv-picker | [选择器](https://www.uvui.cn/components/picker.html) |
+| uv-datetime-picker | [时间选择器](https://www.uvui.cn/components/datetimePicker.html) |
+| uv-code | [验证码倒计时](https://www.uvui.cn/components/code.html) |
+| uv-keyboard | [键盘](https://www.uvui.cn/components/keyboard.html) |
+| uv-rate | [评分](https://www.uvui.cn/components/rate.html) |
+| uv-search | [多功能搜索框](https://www.uvui.cn/components/search.html) |
+| uv-number-box | [步进器](https://www.uvui.cn/components/numberBox.html) |
+| uv-upload | [上传](https://www.uvui.cn/components/upload.html) |
+| uv-slider | [滑动选择器](https://www.uvui.cn/components/slider.html) |
+| uv-list | [列表](https://www.uvui.cn/components/list.html) |
+| uv-index-list | [索引列表](https://www.uvui.cn/components/indexList.html) |
+| uv-tags | [标签](https://www.uvui.cn/components/tag.html) |
+| uv-line-progress | [线形进度条](https://www.uvui.cn/components/lineProgress.html) |
+| uv-badge | [徽标数](https://www.uvui.cn/components/badge.html) |
+| uv-count-down | [倒计时](https://www.uvui.cn/components/countDown.html) |
+| uv-count-to | [数字滚动](https://www.uvui.cn/components/countTo.html) |
+| uv-avatar | [头像](https://www.uvui.cn/components/avatar.html) |
+| uv-skeleton | [骨架屏](https://www.uvui.cn/components/skeleton.html) |
+| uv-loading-icon | [加载动画](https://www.uvui.cn/components/loadingIcon.html) |
+| uv-loading-page | [加载页](https://www.uvui.cn/components/loadingPage.html) |
+| uv-load-more | [加载更多](https://www.uvui.cn/components/loadMore.html) |
+| uv-empty | [内容为空](https://www.uvui.cn/components/empty.html) |
+| uv-tooltip | [长按提示](https://www.uvui.cn/components/tooltip.html) |
+| uv-alert | [警告提示](https://www.uvui.cn/components/alert.html) |
+| uv-toast | [消息提示](https://www.uvui.cn/components/toast.html) |
+| uv-notice-bar | [滚动通知](https://www.uvui.cn/components/noticeBar.html) |
+| uv-notify | [消息提示](https://www.uvui.cn/components/notify.html) |
+| uv-no-network | [无网络提示](https://www.uvui.cn/components/noNetwork.html) |
+| uv-popup | [弹出层](https://www.uvui.cn/components/popup.html) |
+| uv-modal | [模态框](https://www.uvui.cn/components/modal.html) |
+| uv-cell | [单元格](https://www.uvui.cn/components/cell.html) |
+| uv-swipe-action | [滑动单元格](https://www.uvui.cn/components/swipeAction.html) |
+| uv-swiper | [轮播图](https://www.uvui.cn/components/swiper.html) |
+| uv-collapse | [折叠面板](https://www.uvui.cn/components/collapse.html) |
+| uv-grid | [宫格布局](https://www.uvui.cn/components/grid.html) |
+| uv-album | [相册](https://www.uvui.cn/components/album.html) |
+| uv-tabbar | [底部导航栏](https://www.uvui.cn/components/tabbar.html) |
+| uv-back-top | [返回顶部](https://www.uvui.cn/components/backTop.html) |
+| uv-navbar | [自定义导航栏](https://www.uvui.cn/components/navbar.html) |
+| uv-action-sheet | [底部操作菜单](https://www.uvui.cn/components/actionSheet.html) |
+| uv-tabs | [标签选项卡](https://www.uvui.cn/components/tabs.html) |
+| uv-steps | [步骤条](https://www.uvui.cn/components/steps.html) |
+| uv-subsection | [分段器](https://www.uvui.cn/components/subsection.html) |
+| uv-sticky | [吸顶](https://www.uvui.cn/components/sticky.html) |
+| uv-parse | [富文本解析器](https://www.uvui.cn/components/parse.html) |
+| uv-overlay | [遮罩层](https://www.uvui.cn/components/overlay.html) |
+| uv-code-input | [验证码输入](https://www.uvui.cn/components/codeInput.html) |
+| uv-read-more | [展开阅读更多](https://www.uvui.cn/components/readMore.html) |
+| uv-line | [线条](https://www.uvui.cn/components/line.html) |
+| uv-gap | [间隔槽](https://www.uvui.cn/components/gap.html) |
+| uv-divider | [分割线](https://www.uvui.cn/components/divider.html) |
+
+## 版权信息
+uv-ui遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uv-ui应用到您的产品中。
+
+## 作者想说
+- 开源真的不易,不图大家的钱财,所以希望大家多多鼓励支持,希望不要恶意评论,有问题加群快速解决。
+- 遇到BUG,是一件很正常的事情,是程序肯定就有BUG,所以希望大家能以理解的心态去提出BUG,然后作者才有动力去努力修复。
+- 最后觉得好用的小伙伴,不要吝啬你的双手,给个好评就是给我们最大的鼓励。
+
+# 恶评者手下留情,有事加QQ群解决:549833913
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-upload/changelog.md b/ledApp/uni_modules/uv-upload/changelog.md
new file mode 100644
index 0000000..dfcefe7
--- /dev/null
+++ b/ledApp/uni_modules/uv-upload/changelog.md
@@ -0,0 +1,17 @@
+## 1.0.6(2023-12-20)
+1. 修复动态设置deletable为false不生效的BUG
+## 1.0.5(2023-08-31)
+1. 添加uv-popup依赖
+## 1.0.4(2023-08-18)
+1. 修复图片预览位置错误的BUG
+2. 修复视频预览不生效的BUG
+3. 修复改变上传视频宽高不生效的BUG
+## 1.0.3(2023-07-03)
+去除插槽判断,避免某些平台不显示的BUG
+## 1.0.2(2023-05-24)
+1. 优化fileList,watch中增加deep属性
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-upload 上传
diff --git a/ledApp/uni_modules/uv-upload/components/uv-preview-video/uv-preview-video.vue b/ledApp/uni_modules/uv-upload/components/uv-preview-video/uv-preview-video.vue
new file mode 100644
index 0000000..327c680
--- /dev/null
+++ b/ledApp/uni_modules/uv-upload/components/uv-preview-video/uv-preview-video.vue
@@ -0,0 +1,52 @@
+<template>
+ <uv-popup ref="popup" @change="change">
+ <view class="video-view" v-if="show">
+ <video class="video" :src="getSec" :autoplay="autoplay"></video>
+ </view>
+ </uv-popup>
+</template>
+<script>
+ export default {
+ props: {
+ src: {
+ type: String,
+ default: ''
+ },
+ autoplay: {
+ type: Boolean,
+ default: true
+ }
+ },
+ data() {
+ return {
+ videoSrc: '',
+ show: false
+ }
+ },
+ computed: {
+ getSec() {
+ return this.src || this.videoSrc;
+ }
+ },
+ methods: {
+ open(url) {
+ this.videoSrc = url;
+ this.$refs.popup.open();
+ },
+ close() {
+ this.$refs.popup.close();
+ },
+ change(e) {
+ this.show = e.show;
+ }
+ }
+ }
+</script>
+<style scoped lang="scss">
+ .video-view {
+ width: 750rpx;
+ .video {
+ width: 750rpx;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-upload/components/uv-upload/mixin.js b/ledApp/uni_modules/uv-upload/components/uv-upload/mixin.js
new file mode 100644
index 0000000..13ab0d2
--- /dev/null
+++ b/ledApp/uni_modules/uv-upload/components/uv-upload/mixin.js
@@ -0,0 +1,22 @@
+import { error } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
+export default {
+ watch: {
+ // 监听accept的变化,判断是否符合个平台要求
+ // 只有微信小程序才支持选择媒体,文件类型,所以这里做一个判断提示
+ accept: {
+ immediate: true,
+ handler(val) {
+ // #ifndef MP-WEIXIN
+ if (val === 'all' || val === 'media') {
+ error('只有微信小程序才支持把accept配置为all、media之一')
+ }
+ // #endif
+ // #ifndef H5 || MP-WEIXIN
+ if (val === 'file') {
+ error('只有微信小程序和H5(HX2.9.9)才支持把accept配置为file')
+ }
+ // #endif
+ }
+ }
+ }
+}
diff --git a/ledApp/uni_modules/uv-upload/components/uv-upload/props.js b/ledApp/uni_modules/uv-upload/components/uv-upload/props.js
new file mode 100644
index 0000000..2aad7b6
--- /dev/null
+++ b/ledApp/uni_modules/uv-upload/components/uv-upload/props.js
@@ -0,0 +1,130 @@
+export default {
+ props: {
+ // 接受的文件类型, 可选值为all media image file video
+ accept: {
+ type: String,
+ default: 'image'
+ },
+ // 图片或视频拾取模式,当accept为image类型时设置capture可选额外camera可以直接调起摄像头
+ capture: {
+ type: [String, Array],
+ default: () => ['album', 'camera']
+ },
+ // 当accept为video时生效,是否压缩视频,默认为true
+ compressed: {
+ type: Boolean,
+ default: true
+ },
+ // 当accept为video时生效,可选值为back或front
+ camera: {
+ type: String,
+ default: 'back'
+ },
+ // 当accept为video时生效,拍摄视频最长拍摄时间,单位秒
+ maxDuration: {
+ type: Number,
+ default: 60
+ },
+ // 上传区域的图标,只能内置图标
+ uploadIcon: {
+ type: String,
+ default: 'camera-fill'
+ },
+ // 上传区域的图标的颜色,默认
+ uploadIconColor: {
+ type: String,
+ default: '#D3D4D6'
+ },
+ // 是否开启文件读取前事件
+ useBeforeRead: {
+ type: Boolean,
+ default: false
+ },
+ // 读取后的处理函数
+ afterRead: {
+ type: Function,
+ default: null
+ },
+ // 读取前的处理函数
+ beforeRead: {
+ type: Function,
+ default: null
+ },
+ // 是否开启图片预览功能
+ previewFullImage: {
+ type: Boolean,
+ default: true
+ },
+ // 是否开启视频预览功能
+ previewFullVideo: {
+ type: Boolean,
+ default: true
+ },
+ // 最大上传数量
+ maxCount: {
+ type: [String, Number],
+ default: 52
+ },
+ // 是否禁用
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ // 预览上传的图片时的裁剪模式,和image组件mode属性一致
+ imageMode: {
+ type: String,
+ default: 'aspectFill'
+ },
+ // 标识符,可以在回调函数的第二项参数中获取
+ name: {
+ type: String,
+ default: ''
+ },
+ // 所选的图片的尺寸, 可选值为original compressed
+ sizeType: {
+ type: Array,
+ default: () => ['original', 'compressed']
+ },
+ // 是否开启图片多选,部分安卓机型不支持
+ multiple: {
+ type: Boolean,
+ default: false
+ },
+ // 是否展示删除按钮
+ deletable: {
+ type: Boolean,
+ default: true
+ },
+ // 文件大小限制,单位为byte
+ maxSize: {
+ type: [String, Number],
+ default: Number.MAX_VALUE
+ },
+ // 显示已上传的文件列表
+ fileList: {
+ type: Array,
+ default: () => []
+ },
+ // 上传区域的提示文字
+ uploadText: {
+ type: String,
+ default: ''
+ },
+ // 内部预览图片区域和选择图片按钮的区域宽度
+ width: {
+ type: [String, Number],
+ default: 80
+ },
+ // 内部预览图片区域和选择图片按钮的区域高度
+ height: {
+ type: [String, Number],
+ default: 80
+ },
+ // 是否在上传完成后展示预览图
+ previewImage: {
+ type: Boolean,
+ default: true
+ },
+ ...uni.$uv?.props?.upload
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-upload/components/uv-upload/utils.js b/ledApp/uni_modules/uv-upload/components/uv-upload/utils.js
new file mode 100644
index 0000000..88cb602
--- /dev/null
+++ b/ledApp/uni_modules/uv-upload/components/uv-upload/utils.js
@@ -0,0 +1,151 @@
+function pickExclude(obj, keys) {
+ // 某些情况下,type可能会为
+ if (!['[object Object]', '[object File]'].includes(Object.prototype.toString.call(obj))) {
+ return {}
+ }
+ return Object.keys(obj).reduce((prev, key) => {
+ if (!keys.includes(key)) {
+ prev[key] = obj[key]
+ }
+ return prev
+ }, {})
+}
+
+function formatImage(res) {
+ return res.tempFiles.map((item) => ({
+ ...pickExclude(item, ['path']),
+ type: 'image',
+ url: item.path,
+ thumb: item.path,
+ size: item.size,
+ // #ifdef H5
+ name: item.name
+ // #endif
+ }))
+}
+
+function formatVideo(res) {
+ return [
+ {
+ ...pickExclude(res, ['tempFilePath', 'thumbTempFilePath', 'errMsg']),
+ type: 'video',
+ url: res.tempFilePath,
+ thumb: res.thumbTempFilePath,
+ size: res.size,
+ // #ifdef H5
+ name: res.name
+ // #endif
+ }
+ ]
+}
+
+function formatMedia(res) {
+ return res.tempFiles.map((item) => ({
+ ...pickExclude(item, ['fileType', 'thumbTempFilePath', 'tempFilePath']),
+ type: res.type,
+ url: item.tempFilePath,
+ thumb: res.type === 'video' ? item.thumbTempFilePath : item.tempFilePath,
+ size: item.size
+ }))
+}
+
+function formatFile(res) {
+ return res.tempFiles.map((item) => ({
+ ...pickExclude(item, ['path']),
+ url: item.path,
+ size:item.size,
+ // #ifdef H5
+ name: item.name,
+ type: item.type
+ // #endif
+ }))
+}
+export function chooseFile({
+ accept,
+ multiple,
+ capture,
+ compressed,
+ maxDuration,
+ sizeType,
+ camera,
+ maxCount
+}) {
+ return new Promise((resolve, reject) => {
+ switch (accept) {
+ case 'image':
+ uni.chooseImage({
+ count: multiple ? Math.min(maxCount, 9) : 1,
+ sourceType: capture,
+ sizeType,
+ success: (res) => resolve(formatImage(res)),
+ fail: reject
+ })
+ break
+ // #ifdef MP-WEIXIN
+ // 只有微信小程序才支持chooseMedia接口
+ case 'media':
+ wx.chooseMedia({
+ count: multiple ? Math.min(maxCount, 9) : 1,
+ sourceType: capture,
+ maxDuration,
+ sizeType,
+ camera,
+ success: (res) => resolve(formatMedia(res)),
+ fail: reject
+ })
+ break
+ // #endif
+ case 'video':
+ uni.chooseVideo({
+ sourceType: capture,
+ compressed,
+ maxDuration,
+ camera,
+ success: (res) => resolve(formatVideo(res)),
+ fail: reject
+ })
+ break
+ // #ifdef MP-WEIXIN || H5
+ // 只有微信小程序才支持chooseMessageFile接口
+ case 'file':
+ // #ifdef MP-WEIXIN
+ wx.chooseMessageFile({
+ count: multiple ? maxCount : 1,
+ type: accept,
+ success: (res) => resolve(formatFile(res)),
+ fail: reject
+ })
+ // #endif
+ // #ifdef H5
+ // 需要hx2.9.9以上才支持uni.chooseFile
+ uni.chooseFile({
+ count: multiple ? maxCount : 1,
+ type: accept,
+ success: (res) => resolve(formatFile(res)),
+ fail: reject
+ })
+ // #endif
+ break
+ // #endif
+ default:
+ // 此为保底选项,在accept不为上面任意一项的时候选取全部文件
+ // #ifdef MP-WEIXIN
+ wx.chooseMessageFile({
+ count: multiple ? maxCount : 1,
+ type: 'all',
+ success: (res) => resolve(formatFile(res)),
+ fail: reject
+ })
+ // #endif
+ // #ifdef H5
+ // 需要hx2.9.9以上才支持uni.chooseFile
+ uni.chooseFile({
+ count: multiple ? maxCount : 1,
+ type: 'all',
+ success: (res) => resolve(formatFile(res)),
+ fail: reject
+ })
+ // #endif
+ }
+ })
+}
diff --git a/ledApp/uni_modules/uv-upload/components/uv-upload/uv-upload.vue b/ledApp/uni_modules/uv-upload/components/uv-upload/uv-upload.vue
new file mode 100644
index 0000000..213be0f
--- /dev/null
+++ b/ledApp/uni_modules/uv-upload/components/uv-upload/uv-upload.vue
@@ -0,0 +1,488 @@
+<template>
+ <view class="uv-upload" :style="[$uv.addStyle(customStyle)]">
+ <view class="uv-upload__wrap">
+ <template v-if="previewImage">
+ <view class="uv-upload__wrap__preview" v-for="(item, index) in lists" :key="index">
+ <image
+ v-if="item.isImage || (item.type && item.type === 'image')"
+ :src="item.thumb || item.url" :mode="imageMode"
+ class="uv-upload__wrap__preview__image"
+ @tap="onPreviewImage(item,index)"
+ :style="[{
+ width: $uv.addUnit(width),
+ height: $uv.addUnit(height)
+ }]"
+ />
+ <view
+ v-else
+ class="uv-upload__wrap__preview__other"
+ @tap="onPreviewVideo(item,index)"
+ :style="[{
+ width: $uv.addUnit(width),
+ height: $uv.addUnit(height)
+ }]"
+ >
+ <uv-icon color="#80CBF9" size="26" :name="item.isVideo || (item.type && item.type === 'video') ? 'movie' : 'folder'"></uv-icon>
+ <text class="uv-upload__wrap__preview__other__text">{{item.isVideo || (item.type && item.type === 'video') ? '视频' : '文件'}}</text>
+ </view>
+ <view class="uv-upload__status" v-if="item.status === 'uploading' || item.status === 'failed'">
+ <view class="uv-upload__status__icon">
+ <uv-icon v-if="item.status === 'failed'" name="close-circle" color="#ffffff" size="25" />
+ <uv-loading-icon size="22" mode="circle" v-else />
+ </view>
+ <text v-if="item.message" class="uv-upload__status__message">{{ item.message }}</text>
+ </view>
+ <view class="uv-upload__deletable" v-if="item.status !== 'uploading' && (deletable || item.deletable)" @tap.stop="deleteItem(index)">
+ <view class="uv-upload__deletable__icon">
+ <uv-icon name="close" color="#ffffff" size="10"></uv-icon>
+ </view>
+ </view>
+ <view class="uv-upload__success" v-if="item.status === 'success'">
+ <!-- #ifdef APP-NVUE -->
+ <image :src="successIcon" class="uv-upload__success__icon"></image>
+ <!-- #endif -->
+ <!-- #ifndef APP-NVUE -->
+ <view class="uv-upload__success__icon">
+ <uv-icon name="checkmark" color="#ffffff" size="12"></uv-icon>
+ </view>
+ <!-- #endif -->
+ </view>
+ </view>
+ </template>
+ <template v-if="isInCount">
+ <view @tap="chooseFile">
+ <slot>
+ <view class="uv-upload__button" :hover-class="!disabled ? 'uv-upload__button--hover' : ''" hover-stay-time="150" @tap.stop="chooseFile" :class="[disabled && 'uv-upload__button--disabled']" :style="[{
+ width: $uv.addUnit(width),
+ height: $uv.addUnit(height)
+ }]">
+ <uv-icon :name="uploadIcon" size="26" :color="uploadIconColor"></uv-icon>
+ <text v-if="uploadText" class="uv-upload__button__text">{{ uploadText }}</text>
+ </view>
+ </slot>
+ </view>
+ </template>
+ </view>
+ <uv-preview-video ref="previewVideo"></uv-preview-video>
+ </view>
+</template>
+
+<script>
+ import { func, image, video, array, promise } from '@/uni_modules/uv-ui-tools/libs/function/test.js';
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import { chooseFile } from './utils';
+ import mixin_accept from './mixin.js';
+ import props from './props.js';
+ /**
+ * upload 上传
+ * @description 该组件用于上传图片场景
+ * @tutorial https://www.uvui.cn/components/upload.html
+ * @property {String} accept 接受的文件类型, 可选值为all media image file video (默认 'image' )
+ * @property {String | Array} capture 图片或视频拾取模式,当accept为image类型时设置capture可选额外camera可以直接调起摄像头(默认 ['album', 'camera'] )
+ * @property {Boolean} compressed 当accept为video时生效,是否压缩视频,默认为true(默认 true )
+ * @property {String} camera 当accept为video时生效,可选值为back或front(默认 'back' )
+ * @property {Number} maxDuration 当accept为video时生效,拍摄视频最长拍摄时间,单位秒(默认 60 )
+ * @property {String} uploadIcon 上传区域的图标,只能内置图标(默认 'camera-fill' )
+ * @property {String} uploadIconColor 上传区域的图标的字体颜色,只能内置图标(默认 #D3D4D6 )
+ * @property {Boolean} useBeforeRead 是否开启文件读取前事件(默认 false )
+ * @property {Boolean} previewFullImage 是否开启图片预览功能(默认 true )
+ * @property {Boolean} previewFullVideo 是否开启视频预览功能(默认 true )
+ * @property {String | Number} maxCount 最大上传数量(默认 52 )
+ * @property {Boolean} disabled 是否启用(默认 false )
+ * @property {String} imageMode 预览上传的图片时的裁剪模式,和image组件mode属性一致(默认 'aspectFill' )
+ * @property {String} name 标识符,可以在回调函数的第二项参数中获取
+ * @property {Array} sizeType 所选的图片的尺寸, 可选值为original compressed(默认 ['original', 'compressed'] )
+ * @property {Boolean} multiple 是否开启图片多选,部分安卓机型不支持 (默认 false )
+ * @property {Boolean} deletable 是否展示删除按钮(默认 true )
+ * @property {String | Number} maxSize 文件大小限制,单位为byte (默认 Number.MAX_VALUE )
+ * @property {Array} fileList 显示已上传的文件列表
+ * @property {String} uploadText 上传区域的提示文字
+ * @property {String | Number} width 内部预览图片区域和选择图片按钮的区域宽度(默认 80 )
+ * @property {String | Number} height 内部预览图片区域和选择图片按钮的区域高度(默认 80 )
+ * @property {Object} customStyle 组件的样式,对象形式
+ * @event {Function} afterRead 读取后的处理函数
+ * @event {Function} beforeRead 读取前的处理函数
+ * @event {Function} oversize 文件超出大小限制
+ * @event {Function} clickPreview 点击预览时触发
+ * @event {Function} delete 删除图片
+ * @example <uv-upload :action="action" :fileList="fileList" ></uv-upload>
+ */
+ export default {
+ name: "uv-upload",
+ // #ifdef VUE3
+ emits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview'],
+ // #endif
+ mixins: [mpMixin, mixin, mixin_accept, props],
+ data() {
+ return {
+ // #ifdef APP-NVUE
+ successIcon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAAB65masAAACP0lEQVRYCc3YXygsURwH8K/dpcWyG3LF5u/6/+dKVylSypuUl6uUPMifKMWL8oKEB1EUT1KeUPdR3uTNUsSLxb2udG/cbvInNuvf2rVnazZ/ZndmZ87snjM1Z+Z3zpzfp9+Z5mEAhlvjRtZgCKs+gnPAOcAkkMOR4jEHfItjDvgRxxSQD8cM0BuOCaAvXNCBQrigAsXgggYUiwsK0B9cwIH+4gIKlIILGFAqLiBAOTjFgXJxigJp4BQD0sIpAqSJow6kjSNAFTnRaHJwLenD6Mud52VQAcrBfTd2oyq+HtGaGGWAcnAVcXWoM3bCZrdi+ncPfaAcXE5UKVpdW/vitGPqqAtn98d0gXJwX7Qp6MmegUYVhvmTIezdmHlxJCjpHRTCFerLkRRu4k0aqdajN3sWOo0BK//msHa+xDuPC/oNFMKRhTtM4xjIX0SCNpXL4+7VIaHuyiWEp2L7ahWLf8fejfPdqPmC3mJicORZUp1CQzm+GiphvljGk+PBvWRbxii+xVTj5M6CiZ/tsDufvaXyxEUDxeLIyvu3m0iOyEFWVAkydcVYdyFrE9tQk9iMq6f/GNlvwt3LjQfh60LUrw9/cFyyMJUW/XkLSNMV4Mi6C5ML+ui4x5ClAX9sB9w0wV6wglJwJCv5fOxcr6EstgbGiEw4XcfUry4cWrcEUW8n+ARKxXEJHhw2WG43UKSvwI/TSZgvl7kh0b3XLZaLEy0QmMgLZAVH7J+ALOE+AVnDvQOyiPMAWcW5gSzjCPAV+78S5WE0GrQAAAAASUVORK5CYII=',
+ // #endif
+ lists: [],
+ isInCount: true,
+ }
+ },
+ watch: {
+ // 监听文件列表的变化,重新整理内部数据
+ fileList: {
+ deep: true,
+ immediate: true,
+ handler() {
+ this.formatFileList()
+ }
+ },
+ deletable(newVal) {
+ if(!newVal) {
+ this.lists.map(item=>{
+ item.deletable = this.deletable;
+ })
+ }
+ }
+ },
+ methods: {
+ formatFileList() {
+ const {
+ fileList = [], maxCount
+ } = this;
+ const lists = fileList.map((item) =>
+ Object.assign(Object.assign({}, item), {
+ // 如果item.url为本地选择的blob文件的话,无法判断其为video还是image,此处优先通过accept做判断处理
+ isImage: this.accept === 'image' || image(item.url || item.thumb),
+ isVideo: this.accept === 'video' || video(item.url || item.thumb),
+ deletable: typeof(item.deletable) === 'boolean' ? item.deletable : this.deletable,
+ })
+ );
+ this.lists = lists
+ this.isInCount = lists.length < maxCount
+ },
+ chooseFile() {
+ this.timer && clearTimeout(this.timer);
+ this.timer = setTimeout(() => {
+ const {
+ maxCount,
+ multiple,
+ lists,
+ disabled
+ } = this;
+ if (disabled) return;
+ // 如果用户传入的是字符串,需要格式化成数组
+ let capture;
+ try {
+ capture = array(this.capture) ? this.capture : this.capture.split(',');
+ } catch (e) {
+ capture = [];
+ }
+ chooseFile(
+ Object.assign({
+ accept: this.accept,
+ multiple: this.multiple,
+ capture: capture,
+ compressed: this.compressed,
+ maxDuration: this.maxDuration,
+ sizeType: this.sizeType,
+ camera: this.camera,
+ }, {
+ maxCount: maxCount - lists.length,
+ })
+ )
+ .then((res) => {
+ this.onBeforeRead(multiple ? res : res[0]);
+ })
+ .catch((error) => {
+ this.$emit('error', error);
+ });
+ }, 100)
+ },
+ // 文件读取之前
+ onBeforeRead(file) {
+ const {
+ beforeRead,
+ useBeforeRead,
+ } = this;
+ let res = true
+ // beforeRead是否为一个方法
+ if (func(beforeRead)) {
+ // 如果用户定义了此方法,则去执行此方法,并传入读取的文件回调
+ res = beforeRead(file, this.getDetail());
+ }
+ if (useBeforeRead) {
+ res = new Promise((resolve, reject) => {
+ this.$emit(
+ 'beforeRead',
+ Object.assign(Object.assign({
+ file
+ }, this.getDetail()), {
+ callback: (ok) => {
+ ok ? resolve() : reject();
+ },
+ })
+ );
+ });
+ }
+ if (!res) {
+ return;
+ }
+ if (promise(res)) {
+ res.then((data) => this.onAfterRead(data || file));
+ } else {
+ this.onAfterRead(file);
+ }
+ },
+ getDetail(index) {
+ return {
+ name: this.name,
+ index: index == null ? this.fileList.length : index,
+ };
+ },
+ onAfterRead(file) {
+ const {
+ maxSize,
+ afterRead
+ } = this;
+ const oversize = Array.isArray(file) ?
+ file.some((item) => item.size > maxSize) :
+ file.size > maxSize;
+ if (oversize) {
+ this.$emit('oversize', Object.assign({
+ file
+ }, this.getDetail()));
+ return;
+ }
+ if (typeof afterRead === 'function') {
+ afterRead(file, this.getDetail());
+ }
+ this.$emit('afterRead', Object.assign({
+ file
+ }, this.getDetail()));
+ },
+ deleteItem(index) {
+ this.$emit(
+ 'delete',
+ Object.assign(Object.assign({}, this.getDetail(index)), {
+ file: this.fileList[index],
+ })
+ );
+ },
+ // 预览图片
+ onPreviewImage(item, index) {
+ const lists = this.$uv.deepClone(this.lists);
+ lists.map((i,j)=>{
+ if(j == index) {
+ i.current = true;
+ }
+ });
+ const filters = lists.filter(i=>i.isImage);
+ const findIndex = filters.findIndex(i=>i.current);
+ this.onClickPreview(item, index);
+ if (!item.isImage || !this.previewFullImage) return
+ uni.previewImage({
+ // 先filter找出为图片的item,再返回filter结果中的图片url
+ urls: this.lists.filter((item) => this.accept === 'image' || image(item.url || item.thumb)).map((item) => item.url || item.thumb),
+ current: findIndex,
+ fail() {
+ this.$uv.toast('预览图片失败')
+ },
+ });
+ },
+ onPreviewVideo(item, index) {
+ this.onClickPreview(item, index);
+ if (!this.previewFullVideo || !item.isVideo) return;
+ this.$refs.previewVideo.open(item.url);
+ },
+ onClickPreview(item, index) {
+ this.$emit(
+ 'clickPreview',
+ Object.assign(Object.assign({}, item), this.getDetail(index))
+ );
+ }
+ }
+ }
+</script>
+
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ $uv-upload-preview-border-radius: 2px !default;
+ $uv-upload-preview-margin: 0 8px 8px 0 !default;
+ $uv-upload-image-width: 80px !default;
+ $uv-upload-image-height: $uv-upload-image-width;
+ $uv-upload-other-bgColor: rgb(242, 242, 242) !default;
+ $uv-upload-other-flex: 1 !default;
+ $uv-upload-text-font-size: 11px !default;
+ $uv-upload-text-color: $uv-tips-color !default;
+ $uv-upload-text-margin-top: 2px !default;
+ $uv-upload-deletable-right: 0 !default;
+ $uv-upload-deletable-top: 0 !default;
+ $uv-upload-deletable-bgColor: rgb(55, 55, 55) !default;
+ $uv-upload-deletable-height: 14px !default;
+ $uv-upload-deletable-width: $uv-upload-deletable-height;
+ $uv-upload-deletable-boder-bottom-left-radius: 100px !default;
+ $uv-upload-deletable-zIndex: 3 !default;
+ $uv-upload-success-bottom: 0 !default;
+ $uv-upload-success-right: 0 !default;
+ $uv-upload-success-border-style: solid !default;
+ $uv-upload-success-border-top-color: transparent !default;
+ $uv-upload-success-border-left-color: transparent !default;
+ $uv-upload-success-border-bottom-color: $uv-success !default;
+ $uv-upload-success-border-right-color: $uv-upload-success-border-bottom-color;
+ $uv-upload-success-border-width: 9px !default;
+ $uv-upload-icon-top: 0px !default;
+ $uv-upload-icon-right: 0px !default;
+ $uv-upload-icon-h5-top: 1px !default;
+ $uv-upload-icon-h5-right: 0 !default;
+ $uv-upload-icon-width: 16px !default;
+ $uv-upload-icon-height: $uv-upload-icon-width;
+ $uv-upload-success-icon-bottom: -10px !default;
+ $uv-upload-success-icon-right: -10px !default;
+ $uv-upload-status-right: 0 !default;
+ $uv-upload-status-left: 0 !default;
+ $uv-upload-status-bottom: 0 !default;
+ $uv-upload-status-top: 0 !default;
+ $uv-upload-status-bgColor: rgba(0, 0, 0, 0.5) !default;
+ $uv-upload-status-icon-Zindex: 1 !default;
+ $uv-upload-message-font-size: 12px !default;
+ $uv-upload-message-color: #FFFFFF !default;
+ $uv-upload-message-margin-top: 5px !default;
+ $uv-upload-button-width: 80px !default;
+ $uv-upload-button-height: $uv-upload-button-width;
+ $uv-upload-button-bgColor: rgb(244, 245, 247) !default;
+ $uv-upload-button-border-radius: 2px !default;
+ $uv-upload-botton-margin: 0 8px 8px 0 !default;
+ $uv-upload-text-font-size: 11px !default;
+ $uv-upload-text-color: $uv-tips-color !default;
+ $uv-upload-text-margin-top: 2px !default;
+ $uv-upload-hover-bgColor: rgb(230, 231, 233) !default;
+ $uv-upload-disabled-opacity: .5 !default;
+ .uv-upload {
+ @include flex(column);
+ flex: 1;
+ &__wrap {
+ @include flex;
+ flex-wrap: wrap;
+ flex: 1;
+ &__preview {
+ border-radius: $uv-upload-preview-border-radius;
+ margin: $uv-upload-preview-margin;
+ position: relative;
+ overflow: hidden;
+ @include flex;
+ &__image {
+ width: $uv-upload-image-width;
+ height: $uv-upload-image-height;
+ }
+ &__other {
+ width: $uv-upload-image-width;
+ height: $uv-upload-image-height;
+ background-color: $uv-upload-other-bgColor;
+ flex: $uv-upload-other-flex;
+ @include flex(column);
+ justify-content: center;
+ align-items: center;
+ &__text {
+ font-size: $uv-upload-text-font-size;
+ color: $uv-upload-text-color;
+ margin-top: $uv-upload-text-margin-top;
+ }
+ }
+ }
+ }
+ &__deletable {
+ position: absolute;
+ top: $uv-upload-deletable-top;
+ right: $uv-upload-deletable-right;
+ background-color: $uv-upload-deletable-bgColor;
+ height: $uv-upload-deletable-height;
+ width: $uv-upload-deletable-width;
+ @include flex;
+ border-bottom-left-radius: $uv-upload-deletable-boder-bottom-left-radius;
+ align-items: center;
+ justify-content: center;
+ z-index: $uv-upload-deletable-zIndex;
+ &__icon {
+ position: absolute;
+ transform: scale(0.7);
+ top: $uv-upload-icon-top;
+ right: $uv-upload-icon-right;
+ /* #ifdef H5 */
+ top: $uv-upload-icon-h5-top;
+ right: $uv-upload-icon-h5-right;
+ /* #endif */
+ }
+ }
+ &__success {
+ position: absolute;
+ bottom: $uv-upload-success-bottom;
+ right: $uv-upload-success-right;
+ @include flex;
+ // 由于weex(nvue)为阿里巴巴的KPI(部门业绩考核)的laji产物,不支持css绘制三角形
+ // 所以在nvue下使用图片,非nvue下使用css实现
+ /* #ifndef APP-NVUE */
+ border-style: $uv-upload-success-border-style;
+ border-top-color: $uv-upload-success-border-top-color;
+ border-left-color: $uv-upload-success-border-left-color;
+ border-bottom-color: $uv-upload-success-border-bottom-color;
+ border-right-color: $uv-upload-success-border-right-color;
+ border-width: $uv-upload-success-border-width;
+ align-items: center;
+ justify-content: center;
+ /* #endif */
+ &__icon {
+ /* #ifndef APP-NVUE */
+ position: absolute;
+ transform: scale(0.7);
+ bottom: $uv-upload-success-icon-bottom;
+ right: $uv-upload-success-icon-right;
+ /* #endif */
+ /* #ifdef APP-NVUE */
+ width: $uv-upload-icon-width;
+ height: $uv-upload-icon-height;
+ /* #endif */
+ }
+ }
+ &__status {
+ position: absolute;
+ top: $uv-upload-status-top;
+ bottom: $uv-upload-status-bottom;
+ left: $uv-upload-status-left;
+ right: $uv-upload-status-right;
+ background-color: $uv-upload-status-bgColor;
+ @include flex(column);
+ align-items: center;
+ justify-content: center;
+ &__icon {
+ position: relative;
+ z-index: $uv-upload-status-icon-Zindex;
+ }
+ &__message {
+ font-size: $uv-upload-message-font-size;
+ color: $uv-upload-message-color;
+ margin-top: $uv-upload-message-margin-top;
+ }
+ }
+ &__button {
+ @include flex(column);
+ align-items: center;
+ justify-content: center;
+ width: $uv-upload-button-width;
+ height: $uv-upload-button-height;
+ background-color: $uv-upload-button-bgColor;
+ border-radius: $uv-upload-button-border-radius;
+ margin: $uv-upload-botton-margin;
+ /* #ifndef APP-NVUE */
+ box-sizing: border-box;
+ /* #endif */
+ &__text {
+ font-size: $uv-upload-text-font-size;
+ color: $uv-upload-text-color;
+ margin-top: $uv-upload-text-margin-top;
+ }
+ &--hover {
+ background-color: $uv-upload-hover-bgColor;
+ }
+ &--disabled {
+ opacity: $uv-upload-disabled-opacity;
+ }
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-upload/package.json b/ledApp/uni_modules/uv-upload/package.json
new file mode 100644
index 0000000..b29dbb0
--- /dev/null
+++ b/ledApp/uni_modules/uv-upload/package.json
@@ -0,0 +1,90 @@
+{
+ "id": "uv-upload",
+ "displayName": "uv-upload 上传 全面兼容小程序、nvue、vue2、vue3等多端",
+ "version": "1.0.6",
+ "description": "该组件用于上传图片等文件场景。",
+ "keywords": [
+ "uv-upload",
+ "uvui",
+ "uv-ui",
+ "upload",
+ "上传"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-icon",
+ "uv-loading-icon",
+ "uv-popup"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-upload/readme.md b/ledApp/uni_modules/uv-upload/readme.md
new file mode 100644
index 0000000..de4aedf
--- /dev/null
+++ b/ledApp/uni_modules/uv-upload/readme.md
@@ -0,0 +1,11 @@
+## Upload 上传
+
+> **组件名:uv-upload**
+
+该组件用于上传图片等文件场景。
+
+### <a href="https://www.uvui.cn/components/upload.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/uv-vtabs/changelog.md b/ledApp/uni_modules/uv-vtabs/changelog.md
new file mode 100644
index 0000000..4d73568
--- /dev/null
+++ b/ledApp/uni_modules/uv-vtabs/changelog.md
@@ -0,0 +1,20 @@
+## 1.0.8(2023-12-20)
+1. 优化
+## 1.0.7(2023-12-20)
+1. 优化
+## 1.0.6(2023-12-12)
+1. 修复设置current,快速点击菜单,导致死循环的BUG
+## 1.0.5(2023-06-27)
+修复:非联动,内容过多的情况,滚动一段距离,再切换未滚动到顶部的BUG
+## 1.0.4(2023-06-13)
+1. 增加scrolltolower回调函数
+2. 优化
+## 1.0.3(2023-06-13)
+1. 优化
+## 1.0.2(2023-06-13)
+1. 增加hdHeight参数,避免顶部有内容计算联动不准确的BUG
+2. 优化滑动触发频率,避免跳动
+## 1.0.1(2023-06-04)
+1. 文档说明
+## 1.0.0(2023-06-04)
+1. 新增垂直选项卡组件
diff --git a/ledApp/uni_modules/uv-vtabs/components/uv-vtabs-item/uv-vtabs-item.vue b/ledApp/uni_modules/uv-vtabs/components/uv-vtabs-item/uv-vtabs-item.vue
new file mode 100644
index 0000000..286dc8c
--- /dev/null
+++ b/ledApp/uni_modules/uv-vtabs/components/uv-vtabs-item/uv-vtabs-item.vue
@@ -0,0 +1,68 @@
+<template>
+ <view
+ class="uv-vtabs-item"
+ :id="`content_${index}`"
+ ref="uv-vtabs-item"
+ >
+ <slot />
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom');
+ // #endif
+ export default {
+ name: 'uv-vtabs-item',
+ mixins: [mpMixin, mixin],
+ props: {
+ index: {
+ type: [Number,String],
+ default: 0
+ }
+ },
+ data(){
+ return {
+ // 记录item的离顶部的距离
+ top: 0,
+ // 记录item的高度
+ height: 0
+ // 是否为联动
+ }
+ },
+ mounted() {
+ this.init();
+ },
+ methods: {
+ async init(){
+ this.getParentData('uv-vtabs');
+ if (!this.parent) {
+ return this.$uv.error('uv-vtabs必须要搭配uv-vtabs-item组件使用')
+ }
+ if(!this.parent.chain) return;
+ await this.$uv.sleep();
+ this.getItemRect().then(size=>{
+ // 由于对象的引用特性,此处会同时生效到父组件的children数组的本实例的top属性中,供父组件判断读取
+ this.top = size.top;
+ this.height = size.height;
+ });
+ },
+ getItemRect(){
+ return new Promise(resolve => {
+ // #ifndef APP-NVUE
+ this.$uvGetRect('.uv-vtabs-item').then(size => {
+ resolve(size)
+ })
+ // #endif
+ // #ifdef APP-NVUE
+ const ref = this.$refs['uv-vtabs-item']
+ dom.getComponentRect(ref, res => {
+ resolve(res.size)
+ })
+ // #endif
+ })
+ }
+ }
+ }
+</script>
diff --git a/ledApp/uni_modules/uv-vtabs/components/uv-vtabs/props.js b/ledApp/uni_modules/uv-vtabs/components/uv-vtabs/props.js
new file mode 100644
index 0000000..a64dce8
--- /dev/null
+++ b/ledApp/uni_modules/uv-vtabs/components/uv-vtabs/props.js
@@ -0,0 +1,79 @@
+export default {
+ props: {
+ // 列表数据
+ list: {
+ type: Array,
+ default: ()=>[]
+ },
+ // 从list元素对象中读取的键名,默认name
+ keyName: {
+ type: String,
+ default: 'name'
+ },
+ // 当前选中项
+ current: {
+ type: [Number, String],
+ default: 0
+ },
+ // 头部内容的高度
+ hdHeight: {
+ type: [Number, String],
+ default: 0
+ },
+ // 是否联动,默认开启联动
+ chain: {
+ type: Boolean,
+ default: true
+ },
+ // 整个列表的高度,默认auto屏幕高度
+ height: {
+ type: [Number, String],
+ default: 'auto'
+ },
+ // 左边列表的宽度,默认200rpx
+ barWidth: {
+ type: [Number, String],
+ default: '180rpx'
+ },
+ // 左边列表是否允许滚动
+ barScrollable: {
+ type: Boolean,
+ default: true
+ },
+ // 背景颜色 默认主题颜色 $bg-color
+ barBgColor: {
+ type: String,
+ default: ''
+ },
+ // 左边列表的自定义样式
+ barStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ // 左边列表项的自定义样式
+ barItemStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ // 左边选择项激活时的自定义样式
+ barItemActiveStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ // 左边选择项激活时的左边线条自定义样式
+ barItemActiveLineStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ // 菜单项中的徽标自定义样式,比如定位位置
+ barItemBadgeStyle: {
+ type: [Object, String],
+ default: ''
+ },
+ // 右边区域自定义样式
+ contentStyle: {
+ type: [Object, String],
+ default: ''
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-vtabs/components/uv-vtabs/uv-vtabs.vue b/ledApp/uni_modules/uv-vtabs/components/uv-vtabs/uv-vtabs.vue
new file mode 100644
index 0000000..cd23320
--- /dev/null
+++ b/ledApp/uni_modules/uv-vtabs/components/uv-vtabs/uv-vtabs.vue
@@ -0,0 +1,438 @@
+<template>
+ <view
+ class="uv-vtabs"
+ :style="[vtabsStyle]"
+ >
+ <scroll-view
+ class="uv-vtabs__bar"
+ ref="uv-vtabs__bar"
+ :style="[getBarStyle]"
+ :scroll-y="barScrollable"
+ :scroll-x="scrollX"
+ :show-scrollbar="false"
+ :scroll-with-animation="true"
+ :scroll-top="barScrollTop"
+ :scroll-into-view="barScrollToView"
+ >
+ <view
+ :class="[
+ 'uv-vtabs__bar-item',
+ `uv-vtabs__bar-item--${index}`,
+ index == activeIndex && 'uv-vtabs__bar-item-active'
+ ]"
+ :ref="`uv-vtabs__bar-item--${index}`"
+ v-for="(item,index) in list"
+ :key="index"
+ :id="`bar_${index}`"
+ :style="[itemStyle(index)]"
+ @tap.stop="clickHandler(index)"
+ >
+ <view
+ class="uv-vtabs__bar-item--line"
+ v-if="index == activeIndex"
+ :style="[$uv.addStyle(barItemActiveLineStyle)]"
+ ></view>
+ <text
+ :class="[
+ 'uv-vtabs__bar-item--value',
+ index == activeIndex && 'uv-vtabs__bar-item-active--value'
+ ]"
+ :style="[itemStyle(index),textStyle(index)]"
+ >{{item[keyName]}}</text>
+ <view
+ class="uv-vtabs__bar-item--badge"
+ :style="[$uv.addStyle(barItemBadgeStyle)]"
+ v-if="!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))"
+ >
+ <uv-badge
+ :show="!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))"
+ :isDot="item.badge && item.badge.isDot || propsBadge.isDot"
+ :value="item.badge && item.badge.value || propsBadge.value"
+ :max="item.badge && item.badge.max || propsBadge.max"
+ :type="item.badge && item.badge.type || propsBadge.type"
+ :showZero="item.badge && item.badge.showZero || propsBadge.showZero"
+ :bgColor="item.badge && item.badge.bgColor || propsBadge.bgColor"
+ :color="item.badge && item.badge.color || propsBadge.color"
+ :shape="item.badge && item.badge.shape || propsBadge.shape"
+ :numberType="item.badge && item.badge.numberType || propsBadge.numberType"
+ :inverted="item.badge && item.badge.inverted || propsBadge.inverted"
+ ></uv-badge>
+ </view>
+ </view>
+ </scroll-view>
+ <scroll-view
+ class="uv-vtabs__content"
+ :style="[getContentStyle,$uv.addStyle(contentStyle)]"
+ :scroll-y="true"
+ :scroll-x="scrollX"
+ :show-scrollbar="false"
+ :scroll-top="contentScrollTop"
+ :scroll-into-view="contentScrollTo"
+ :scroll-with-animation="true"
+ @scroll="scrollHandler"
+ @scrolltolower="scrolltolower"
+ v-if="chain"
+ >
+ <slot />
+ </scroll-view>
+ <scroll-view
+ v-else
+ class="uv-vtabs__content"
+ :style="[getContentStyle,$uv.addStyle(contentStyle)]"
+ :scroll-y="true"
+ :scroll-x="scrollX"
+ :show-scrollbar="false"
+ :scroll-top="contentScrollTop2"
+ @scrolltolower="scrolltolower"
+ >
+ <slot />
+ </scroll-view>
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import debounce from '@/uni_modules/uv-ui-tools/libs/function/debounce.js'
+ import throttle from '@/uni_modules/uv-ui-tools/libs/function/throttle.js'
+ import uvBadgeProps from '@/uni_modules/uv-badge/components/uv-badge/props.js'
+ import props from './props.js';
+ // #ifdef APP-NVUE
+ const dom = uni.requireNativePlugin('dom')
+ // #endif
+ /**
+ * 垂直选项卡
+ * @description 该组件兼容所有端,提供了分类展示和联动等功能
+ * @tutorial https://www.uvui.cn/components/vtabs.html
+ * @property {Array} list 选项数组,元素为对象,如[{name:'uv-ui'}](默认 [] )
+ * @property {String} keyName 从list元素对象中读取的键名(默认 name )
+ * @property {Number} current 当前选中项,从0开始(默认 0 )
+ * @property {Number | String} hdHeight 头部内容的高度,头部有内容必传,否则会有联动误差(默认 0 )
+ * @property {Boolean} chain 是否开启联动,开启后右边区域可以滑动查看内容(默认 true )
+ * @property {Number|String} height 整个列表的高度,默认auto或空则为屏幕高度(默认 auto屏幕高度 )
+ * @property {Number|String} barWidth 左边选项区域的宽度(默认 180rpx )
+ * @property {Boolean} barScrollable 左边选项区域是否允许滚动 (默认 true )
+ * @property {String} barBgColor 左边选项区域的背景颜色(默认$uv-bg-color)
+ * @property {Object} barStyle 左边选项区域的自定义样式 (默认{})
+ * @property {Object} barItemStyle 左边选项区域每个选项的自定义样式 (默认{})
+ * @property {Object} barItemActiveStyle 左边选项区域选中选项的自定义样式 (默认{})
+ * @property {Object} barItemActiveLineStyle 左边选项区域选中选项竖线条的自定义样式 (默认{})
+ * @property {Object} barItemBadgeStyle 左边选项区域选中选项徽标的自定义样式,主要用于设置位置 (默认{})
+ * @property {Object} contentStyle 右边区域自定义样式 (默认{})
+ * @example <uv-vtabs :list="list"><uv-vtabs-item>...</uv-vtabs-item></uv-vtabs>
+ */
+ export default {
+ name: 'uv-vtabs',
+ mixins: [mpMixin, mixin, props],
+ created() {
+ this.children = []
+ },
+ mounted() {
+ this.$nextTick(()=>{
+ this.init(this.current);
+ })
+ },
+ data() {
+ return {
+ activeIndex: 0,
+ // 微信小程序下,scroll-view的scroll-into-view属性无法对slot中的内容的id生效,只能通过设置scrollTop的形式去移动滚动条
+ contentScrollTop: 0,
+ contentScrollTop2: 0,//针对非联动
+ contentScrollTo: '',
+ scrolling: false,
+ barScrolling: false,
+ touching: false,
+ hasHeight: 0,
+ scrollViewHeight: 0,
+ barScrollTop: 0,
+ barScrollToView: '',
+ timer2: 0
+ }
+ },
+ computed: {
+ scrollX(){
+ // #ifdef APP-NVUE
+ return true;
+ // #endif
+ return false;
+ },
+ vtabsStyle() {
+ const style = {};
+ style.height = this.getHeight();
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+ },
+ getBarStyle() {
+ const style = {};
+ style.width = this.$uv.getPx(this.barWidth, true);
+ style.background = this.barBgColor;
+ style.height = this.getHeight();
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.barStyle));
+ },
+ itemStyle(){
+ return index =>{
+ const style = {};
+ let barItemInitStyle = this.barItemStyle;
+ // 避免在nvue模式下,切换时候上一个选中颜色不变
+ if(this.barItemStyle && !this.barItemStyle?.background) {
+ barItemInitStyle.background = 'transparent';
+ }
+ // 是否激活的样式
+ const customeStyle = index === this.activeIndex ? this.$uv.addStyle(this.barItemActiveStyle) : this.$uv.addStyle(barItemInitStyle);
+ if (this.list[index].disabled) {
+ style.color = '#c8c9cc'
+ }
+ return this.$uv.deepMerge(style, customeStyle);
+ }
+ },
+ // nvue设置字体样式必须要text标签上进行
+ textStyle(){
+ return index=>{
+ const style = {};
+ style.width = this.$uv.getPx(this.barWidth, true);
+ return style;
+ }
+ },
+ getContentStyle() {
+ const style = {};
+ style.height = this.getHeight();
+ return style;
+ },
+ propsBadge() {
+ return uvBadgeProps
+ }
+ },
+ watch: {
+ current(newVal){
+ if(!this.touching)
+ this.$nextTick(()=>{
+ this.init(newVal?newVal:0);
+ })
+ },
+ list(newVal) {
+ if (newVal.length) {
+ this.$uv.sleep(30).then(res => {
+ this.resize();
+ })
+ }
+ },
+ activeIndex(newVal){
+ if(!this.chain) {// 解决:非联动,内容过多的情况,滚动一段距离,再切换未滚动到顶部的BUG
+ this.contentScrollTop2 = 0 - Math.random() * 4 - 4;
+ }
+ this.$emit('change',newVal);
+ }
+ },
+ methods: {
+ init(index){
+ let num = 0;
+ clearInterval(this.timer2);
+ this.timer2 = setInterval(async ()=>{
+ num++;
+ if(num>50) clearInterval(this.timer2);
+ if(this.children.length) {
+ clearInterval(this.timer2);
+ await this.$uv.sleep(300);
+ this.clickHandler(index);
+ }
+ },100)
+ },
+ // 内容滚动到底部触发
+ scrolltolower(){
+ this.$emit('scrolltolower',this.activeIndex);
+ },
+ async resize() {
+ // 如果list数组长度为0就不处理 || 选中目标未变则不处理
+ if (this.list.length == 0 || !this.barScrollable) return;
+ // 避免滑太快,修复位置
+ Promise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => {
+ this.tabsRect = tabsRect;
+ this.scrollViewHeight = 0
+ itemRect.map((item, index) => {
+ this.scrollViewHeight += item.height;
+ this.list[index].rect = item;
+ })
+ this.setBarScrollTop();
+ })
+ },
+ // 设置左边菜单滚动条的位置,目标:将当前的选项移到中间位置
+ setBarScrollTop() {
+ const tabRect = this.list[this.activeIndex];
+ const offsetTop = this.list
+ .slice(0, this.activeIndex)
+ .reduce((total, item) => {
+ return total + item.rect.height;
+ }, 0);
+ const scrollViewHeight = this.$uv.getPx(this.getHeight());
+ let barScrollTop = tabRect.rect.height / 2 + offsetTop - scrollViewHeight / 2;
+ // 先给一点随机值,避免出现不能滚动的BUG
+ barScrollTop = Math.min(barScrollTop, this.scrollViewHeight - this.tabsRect.height);
+ this.barScrollTop = Math.max(0, barScrollTop);
+ // 已经不能滚动的时候,就使用scroll-into-view的方式进行定位,避免失效
+ if(barScrollTop>=(this.scrollViewHeight - this.tabsRect.height)) {
+ this.timer && clearTimeout(this.timer);
+ this.timer = setTimeout(()=>{
+ this.barScrollToView = `bar_${this.activeIndex}`;
+ },400)
+ }
+ },
+ // 左边菜单点击
+ async clickHandler(currentIndex) {
+ if (currentIndex == this.activeIndex) return;
+ this.touching = true;
+ this.activeIndex = currentIndex;
+ if(this.chain) {
+ // 给一点随机值,避免出现不能滚动的BUG。微信端必须用此方法
+ this.contentScrollTop = this.children[currentIndex].top - this.$uv.getPx(this.hdHeight) - Math.random() * 4 - 4;
+ // #ifndef MP-WEIXIN
+ this.contentScrollTo = `content_${currentIndex}`;
+ // #endif
+ }
+ this.timer && clearTimeout(this.timer);
+ throttle(()=>{
+ this.resize();
+ },300,false)
+ debounce(() => {
+ this.touching = false;
+ }, 900)
+ },
+ // 内容滚动
+ scrollHandler(e) {
+ if (this.touching || this.scrolling) return;
+ // 每过一定时间取样一次,减少资源损耗以及可能带来的卡顿
+ this.scrolling = true;
+ this.$uv.sleep(80).then(() => {
+ this.scrolling = false;
+ })
+ const scrollTop = e.detail.scrollTop;
+ let children = this.children;
+ const len = children.length;
+ let top = 0;
+ let activeIndex = 0;
+ children = this.children.map((item, index) => {
+ if (item.height > 0) this.hasHeight = item.height;
+ item.height = item.height > 0 ? item.height : this.hasHeight;
+ const child = {
+ height: item.height,
+ top
+ }
+ // 进行累加,给下一个item提供计算依据
+ top += item.height;
+ return child;
+ })
+ for (let i = 0; i < len; i++) {
+ const item = children[i];
+ const nextItem = children[i + 1];
+ // 如果滚动条高度小于第一个item的top值,此时无需设置任意字母为高亮
+ if (scrollTop <= children[0].top) {
+ activeIndex = 0;
+ break
+ } else if (!nextItem) {
+ // 当不存在下一个item时,意味着历遍到了最后一个
+ activeIndex = len - 1;
+ break
+ } else if (scrollTop > item.top && scrollTop < nextItem.top) {
+ activeIndex = i;
+ break
+ }
+ }
+ this.activeIndex = activeIndex;
+ // 当前选中项索引必然来源于前后两个索引,满足才执行,避免闪烁的bug
+ this.timer4 && clearTimeout(this.timer4);
+ this.timer4 = setTimeout(()=>{
+ this.resize();
+ },100)
+ },
+ // 设置高度
+ getHeight() {
+ let height = 0;
+ const isEmpty = this.$uv.test.empty(this.height);
+ if (isEmpty || this.height=='auto') height = this.$uv.addUnit(this.$uv.sys().windowHeight);
+ else height = this.$uv.getPx(this.height, true);
+ return height;
+ },
+ // 获取导航菜单的尺寸
+ getTabsRect() {
+ return new Promise(resolve => {
+ this.queryRect('uv-vtabs__bar').then(size => resolve(size))
+ })
+ },
+ // 获取所有标签的尺寸
+ getAllItemRect() {
+ return new Promise(resolve => {
+ const promiseAllArr = this.list.map((item, index) => this.queryRect(
+ `uv-vtabs__bar-item--${index}`, true))
+ Promise.all(promiseAllArr).then(sizes => resolve(sizes))
+ })
+ },
+ // 获取各个标签的尺寸
+ queryRect(el, item) {
+ // #ifndef APP-NVUE
+ // $uvGetRect为uv-ui自带的节点查询简化方法,详见文档介绍:https://www.uvui.cn/js/getRect.html
+ // 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+ return new Promise(resolve => {
+ this.$uvGetRect(`.${el}`).then(size => {
+ resolve(size)
+ })
+ })
+ // #endif
+ // #ifdef APP-NVUE
+ // nvue下,使用dom模块查询元素高度
+ // 返回一个promise,让调用此方法的主体能使用then回调
+ return new Promise(resolve => {
+ dom.getComponentRect(item ? this.$refs[el][0] : this.$refs[el], res => {
+ resolve(res.size)
+ })
+ })
+ // #endif
+ }
+ }
+ }
+</script>
+<style scoped lang="scss">
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ @import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+ .uv-vtabs {
+ @include flex;
+ &__bar {
+ background: $uv-bg-color;
+ &-item {
+ position: relative;
+ @include flex;
+ align-items: center;
+ justify-content: center;
+ padding: 35rpx 12rpx 35rpx 20rpx;
+ &--value {
+ /* #ifdef APP-NVUE */
+ padding: 0 12rpx;
+ /* #endif */
+ font-size: 14px;
+ color: $uv-content-color;
+ }
+ &-active {
+ background: #fff;
+ &--value {
+ color: $uv-primary;
+ }
+ }
+ &--line {
+ position: absolute;
+ width: 2px;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ z-index: 1;
+ background-color: $uv-primary;
+ }
+ &--badge {
+ position: absolute;
+ top: 4px;
+ right: 10px;
+ z-index: 1;
+ }
+ }
+ }
+ &__content {
+ flex: 1;
+ background: #fff;
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-vtabs/package.json b/ledApp/uni_modules/uv-vtabs/package.json
new file mode 100644
index 0000000..9848beb
--- /dev/null
+++ b/ledApp/uni_modules/uv-vtabs/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "uv-vtabs",
+ "displayName": "uv-vtabs 垂直选项卡 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.8",
+ "description": "uv-vtabs 垂直分类组件主要用于分类选择,简单配置即可使用,左右自动进行联动,不用自己再去做复杂的计算,组件内部已经完成相关计算。支持联动和不联动,商品分类,灵活配置,多端兼容开箱即用。",
+ "keywords": [
+ "uv-vtabs",
+ "uvui",
+ "uv-ui",
+ "垂直分类",
+ "垂直选项卡"
+],
+ "repository": "",
+"engines": {
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-badge"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-vtabs/readme.md b/ledApp/uni_modules/uv-vtabs/readme.md
new file mode 100644
index 0000000..812b10c
--- /dev/null
+++ b/ledApp/uni_modules/uv-vtabs/readme.md
@@ -0,0 +1,19 @@
+## Vtabs 垂直选项卡
+
+> **组件名:uv-vtabs**
+
+该组件主要用于分类选择,开箱即用,简单配置参数即可使用,左右自动进行联动,不用自己再去做复杂的计算,组件内部已经完成相关计算。联动和不联动两种可选方式,联动-左右均可滚动,不联动-右边区域只会在选中时显示。
+
+# <a href="https://www.uvui.cn/components/vtabs.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-waterfall/changelog.md b/ledApp/uni_modules/uv-waterfall/changelog.md
new file mode 100644
index 0000000..18f9162
--- /dev/null
+++ b/ledApp/uni_modules/uv-waterfall/changelog.md
@@ -0,0 +1,24 @@
+## 1.0.8(2023-08-17)
+1. 修复只有一条数据切换时可能存在位置错误的BUG
+## 1.0.7(2023-07-22)
+1. 避免快速切换报错的BUG
+## 1.0.6(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.5(2023-07-14)
+1. 优化changeList未处理数据时,正确返回对应列的数据,避免误导
+## 1.0.4(2023-05-27)
+1. 修复在百度小程序中可能存在的BUG
+2. 去掉原有的slot方式
+## 1.0.3(2023-05-23)
+1. 修复在百度/头条小程序显示异常等BUG
+2. 增加changeList回调函数处理数据
+3. 更新示例
+## 1.0.2(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.1(2023-05-12)
+1. 增加clear回调函数
+2. 增加remove回调函数
+## 1.0.0(2023-05-10)
+uv-waterfall 瀑布流
diff --git a/ledApp/uni_modules/uv-waterfall/components/uv-waterfall/props.js b/ledApp/uni_modules/uv-waterfall/components/uv-waterfall/props.js
new file mode 100644
index 0000000..de8cb8f
--- /dev/null
+++ b/ledApp/uni_modules/uv-waterfall/components/uv-waterfall/props.js
@@ -0,0 +1,69 @@
+export default {
+ props: {
+ // 瀑布流数据
+ // #ifdef VUE2
+ value: {
+ type: Array,
+ default: () => []
+ },
+ // #endif
+ // #ifdef VUE3
+ modelValue: {
+ type: Array,
+ default: () => []
+ },
+ // #endif
+ // 数据的id值,根据id值对数据执行删除操作
+ // 如数据为:{id: 1, name: 'uv-ui'},那么该值设置为id
+ idKey: {
+ type: String,
+ default: 'id'
+ },
+ // 每次插入数据的事件间隔,间隔越长能保证两列高度相近,但是用户体验不好,单位ms
+ addTime: {
+ type: Number,
+ default: 200
+ },
+ // 瀑布流的列数,默认2,最高为5
+ columnCount: {
+ type: [Number, String],
+ default: 2
+ },
+ // 列与列的间隙,默认20
+ columnGap: {
+ type: [Number, String],
+ default: 20
+ },
+ // 左边和列表的间隙
+ leftGap: {
+ type: [Number, String],
+ default: 0
+ },
+ // 右边和列表的间隙
+ rightGap: {
+ type: [Number, String],
+ default: 0
+ },
+ // 是否显示滚动条,仅nvue生效
+ showScrollbar: {
+ type: [Boolean],
+ default: false
+ },
+ // 列宽,nvue生效
+ columnWidth: {
+ type: [Number, String],
+ default: 'auto'
+ },
+ // 瀑布流的宽度,nvue生效
+ width: {
+ type: [Number, String],
+ default: ''
+ },
+ // 瀑布流的高度,nvue生效
+ height: {
+ type: [Number, String],
+ default: ''
+ },
+ ...uni.$uv?.props?.waterfall
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-waterfall/components/uv-waterfall/uv-waterfall.vue b/ledApp/uni_modules/uv-waterfall/components/uv-waterfall/uv-waterfall.vue
new file mode 100644
index 0000000..cfb3ebc
--- /dev/null
+++ b/ledApp/uni_modules/uv-waterfall/components/uv-waterfall/uv-waterfall.vue
@@ -0,0 +1,265 @@
+<template>
+ <view class="uv-waterfall">
+ <!-- #ifndef APP-NVUE -->
+ <view class="uv-waterfall__gap_left" :style="[gapLeftStyle]"></view>
+ <template v-if="columnNum>=1">
+ <view id="uv-waterfall-1" class="uv-waterfall__column">
+ <slot name="list1"></slot>
+ </view>
+ </template>
+ <template v-if="columnNum>=2">
+ <view class="uv-waterfall__gap_center" :style="[gapCenterStyle]"></view>
+ <view id="uv-waterfall-2" class="uv-waterfall__column">
+ <slot name="list2"></slot>
+ </view>
+ </template>
+ <template v-if="columnNum>=3">
+ <view class="uv-waterfall__gap_center" :style="[gapCenterStyle]"></view>
+ <view id="uv-waterfall-3" class="uv-waterfall__column">
+ <slot name="list3"></slot>
+ </view>
+ </template>
+ <template v-if="columnNum>=4">
+ <view class="uv-waterfall__gap_center" :style="[gapCenterStyle]">
+ </view>
+ <view id="uv-waterfall-4" class="uv-waterfall__column">
+ <slot name="list4"></slot>
+ </view>
+ </template>
+ <template v-if="columnNum>=5">
+ <view class="uv-waterfall__gap_center" :style="[gapCenterStyle]">
+ </view>
+ <view id="uv-waterfall-5" class="uv-waterfall__column">
+ <slot name="list5"></slot>
+ </view>
+ </template>
+ <view class="uv-waterfall__gap_right" :style="[gapRightStyle]">
+ </view>
+ <!-- #endif -->
+ <!-- #ifdef APP-NVUE -->
+ <view class="waterfall-warapper">
+ <waterfall :column-count="columnNum" :show-scrollbar="false" column-width="auto" :column-gap="columnGap" :left-gap="leftGap" :right-gap="rightGap" :always-scrollable-vertical="true" :style="[nvueWaterfallStyle]"
+ @loadmore="scrolltolower">
+ <slot></slot>
+ </waterfall>
+ </view>
+ <!-- #endif -->
+ </view>
+</template>
+<script>
+ import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+ import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+ import props from './props.js';
+ /**
+ * 瀑布流
+ * @description 该组件兼容所有端,nvue参考https://uniapp.dcloud.net.cn/component/waterfall.html
+ * @tutorial https://www.uvui.cn/components/list.html
+ * @property {Array} value/modelValue 瀑布流数组数据,非nvue生效 (默认 [] )
+ * @property {String} idKey 数据的id值,根据id值对数据执行删除操作,如数据为:{id: 1, name: 'uv-ui'},那么该值设置为id,非nvue有效 (默认 '' )
+ * @property {String | Number} addTime 每次插入数据的事件间隔,间隔越长能保证两列高度相近,但是用户体验不好,单位ms,非nvue生效(默认 200 )
+ * @property {String | Number} columnCount 瀑布流的列数(默认 2 )
+ * @property {String | Number} columnGap 列与列的间隙(默认 0 )
+ * @property {String | Number} leftGap 左边和列表的间隙(默认 0 )
+ * @property {String | Number} rightGap 右边和列表的间隙(默认 0 )
+ * @property {Boolean} showScrollbar 控制是否出现滚动条,仅nvue有效 (默认 false )
+ * @property {String | Number} columnWidth 描述瀑布流每一列的列宽,nvue生效 (默认 auto)
+ * @property {String | Number} width 瀑布流的宽度,nvue生效 (默认 屏幕宽 )
+ * @property {String | Number} height 瀑布流的高度,nvue生效 (默认 屏幕高 )
+ * @property {Object} customStyle 定义需要用到的外部样式
+ *
+ * @example <uv-waterfall v-model="list"></uv-waterfall>
+ */
+ export default {
+ name: 'uv-waterfall',
+ mixins: [mpMixin, mixin, props],
+ data() {
+ return {
+ list1: [],
+ list2: [],
+ list3: [],
+ list4: [],
+ list5: [],
+ // 临时列表
+ tempList: []
+ }
+ },
+ computed: {
+ // 破坏value变量引用,否则数据会保持不变
+ copyValue() {
+ // #ifdef VUE2
+ return this.$uv.deepClone(this.value)
+ // #endif
+ // #ifdef VUE3
+ return this.$uv.deepClone(this.modelValue)
+ // #endif
+ },
+ columnNum() {
+ return this.columnCount <= 0 ? 0 : this.columnCount >= 5 ? 5 : this.columnCount;
+ },
+ gapLeftStyle() {
+ const style = {}
+ style.width = this.$uv.addUnit(this.leftGap)
+ return style;
+ },
+ gapRightStyle() {
+ const style = {}
+ style.width = this.$uv.addUnit(this.rightGap)
+ return style;
+ },
+ gapCenterStyle() {
+ const style = {}
+ style.width = this.$uv.addUnit(this.columnGap)
+ return style;
+ },
+ nvueWaterfallStyle() {
+ const style = {};
+ if (this.width != 0) style.width = this.$uv.addUnit(this.width)
+ if (this.height != 0) style.height = this.$uv.addUnit(this.height)
+ // 如果没有定义列表高度,则默认使用屏幕高度
+ if (!style.width) style.width = this.$uv.addUnit(this.$uv.sys().windowWidth, 'px')
+ if (!style.height) style.height = this.$uv.addUnit(this.$uv.sys().windowHeight, 'px')
+ return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+ }
+ },
+ watch: {
+ copyValue(nVal, oVal) {
+ // #ifndef APP-NVUE
+ if (nVal.length != 0) {
+ // 取出数组发生变化的部分
+ let startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0
+ // 拼接原有数据
+ this.tempList = this.tempList.concat(this.$uv.deepClone(nVal.slice(startIndex)))
+ this.splitData()
+ }
+ // #endif
+ }
+ },
+ mounted() {
+ // #ifndef APP-NVUE
+ this.tempList = this.$uv.deepClone(this.copyValue)
+ this.splitData()
+ // #endif
+ },
+ methods: {
+ // 滚动到底部触发事件
+ scrolltolower(e) {
+ this.$uv.sleep(30).then(() => {
+ this.$emit('scrolltolower')
+ })
+ },
+ // 拆分数据
+ async splitData() {
+ let rectArr = [];
+ let emitList = {};
+ if (!this.tempList.length) return
+ for (let i = 1; i <= this.columnNum; i++) {
+ const rect = await this.$uvGetRect(`#uv-waterfall-${i}`);
+ rectArr.push({ ...rect, name: i });
+ }
+ let item = this.tempList[0]
+ // 因为经过上面两个await节点查询和定时器,数组有可能会变成空[],导致item的值为undefined
+ // 解决多次快速滚动会导致数据乱的问题
+ if (!item) return
+ const minCol = this.getMin(rectArr);
+ // 列宽可能使用的到
+ item.width = minCol.width;
+ this[`list${minCol.name}`].push(item);
+ emitList.name = `list${minCol.name}`;
+ emitList.value = item;
+ this.$emit('changeList', emitList);
+ // 移除临时数组中已处理的数据
+ this.tempList.splice(0, 1)
+ // 如果还有数据则继续执行
+ if (this.tempList.length) {
+ let _timeout = this.addTime;
+ // 部分平台在延时较短的情况会出现BUG
+ // #ifdef MP-BAIDU
+ _timeout = _timeout < 200 ? 200 : _timeout;
+ // #endif
+ await this.$uv.sleep(_timeout);
+ this.splitData()
+ } else {
+ this.$emit('finish')
+ }
+ },
+ getMin(arr) {
+ let result = null;
+ const filter = arr.filter(item => item.height == 0);
+ if (!filter.length) {
+ const min = Math.min.apply(Math, arr.map(item => {
+ return item.height;
+ }))
+ const [item] = arr.filter(item => item.height == min);
+ result = item;
+ } else {
+ let newArr = [];
+ arr.map((item, index) => {
+ newArr.push({ len: this[`list${index+1}`].length, item: item });
+ });
+ const minLen = Math.min.apply(Math, newArr.map(item => {
+ return item.len;
+ }))
+ try {
+ const { item } = newArr.find(item => item.len == minLen && item.item.height == 0);
+ result = item;
+ } catch (e) {
+ const { item } = newArr.find(item => item.item.height == 0);
+ result = item;
+ }
+ }
+ return result;
+ },
+ // 清空数据列表
+ async clear() {
+ // 清除数据
+ for (let i = 0; i < this.columnCount; i++) {
+ this[`list${i+1}`] = [];
+ }
+ // #ifdef VUE2
+ this.$emit('input', [])
+ // #endif
+ // #ifdef VUE3
+ this.$emit('update:modelValue', [])
+ // #endif
+ this.tempList = []
+ await this.$uv.sleep(300);
+ this.$emit('clear');
+ },
+ // 清除指定的某一条数据,根据id来实现
+ remove(id) {
+ let index = -1
+ // 删除组件数据
+ for (let i = 1; i <= this.columnCount; i++) {
+ index = this[`list${i}`].findIndex(item => item[this.idKey] == id)
+ if (index != -1) {
+ this[`list${i}`].splice(index, 1)
+ }
+ }
+ // 同时删除父组件对应的数据
+ // #ifdef VUE2
+ index = this.value.findIndex(item => item[this.idKey] == id)
+ if (index != -1) this.$emit('input', this.value.splice(index, 1))
+ // #endif
+ // #ifdef VUE3
+ index = this.modelValue.findIndex(item => item[this.idKey] == id)
+ if (index != -1) this.$emit('update:modelValue', this.modelValue.splice(index, 1))
+ // #endif
+ this.$emit('remove', id);
+ }
+ }
+ }
+</script>
+<style lang="scss" scoped>
+ @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+ .uv-waterfall {
+ @include flex(row);
+ align-items: flex-start;
+ &__column {
+ @include flex(column);
+ flex: 1;
+ // #ifndef APP-NVUE
+ height: auto;
+ // #endif
+ }
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-waterfall/package.json b/ledApp/uni_modules/uv-waterfall/package.json
new file mode 100644
index 0000000..9a2bbfc
--- /dev/null
+++ b/ledApp/uni_modules/uv-waterfall/package.json
@@ -0,0 +1,89 @@
+{
+ "id": "uv-waterfall",
+ "displayName": "uv-waterfall 瀑布流 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.0.8",
+ "description": "该组件主要用于瀑布流式布局显示,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部,同时集成nvue的原生瀑布流。",
+ "keywords": [
+ "uv-waterfall",
+ "uvui",
+ "uv-ui",
+ "waterfall",
+ "瀑布流"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-ui-tools",
+ "uv-image",
+ "uv-loading-icon"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/uv-waterfall/readme.md b/ledApp/uni_modules/uv-waterfall/readme.md
new file mode 100644
index 0000000..b043b85
--- /dev/null
+++ b/ledApp/uni_modules/uv-waterfall/readme.md
@@ -0,0 +1,19 @@
+## Waterfall 瀑布流
+
+> **组件名:uv-waterfall**
+
+该组件主要用于瀑布流式布局显示,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部,同时集成`nvue`的原生瀑布流用于`app-nvue`。常用于一些电商商品展示等,如某宝首页、x红书等。
+
+研究uniapp瀑布流多年,**该方式是目前小程序端最佳方案**,灵活配置,简单易用,开箱即用。
+
+该插件请根据文档耐心查看,`vue`的写法稍微麻烦点,但是效果是很好的,比之前上线的两个版本的瀑布流适用,更有扩展性,我自己的上线项目也是用的此插件。
+
+# <a href="https://www.uvui.cn/components/waterfall.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
diff --git a/ledApp/uni_modules/x-color-picker/changelog.md b/ledApp/uni_modules/x-color-picker/changelog.md
new file mode 100644
index 0000000..98e6b54
--- /dev/null
+++ b/ledApp/uni_modules/x-color-picker/changelog.md
@@ -0,0 +1,2 @@
+## 1.0.0(2025-08-02)
+初版
diff --git a/ledApp/uni_modules/x-color-picker/components/x-color-picker/x-color-picker.vue b/ledApp/uni_modules/x-color-picker/components/x-color-picker/x-color-picker.vue
new file mode 100644
index 0000000..ab78cad
--- /dev/null
+++ b/ledApp/uni_modules/x-color-picker/components/x-color-picker/x-color-picker.vue
@@ -0,0 +1,1242 @@
+<template>
+ <view class="x-color-picker" :class="{ 'color-picker--dark': darkMode }" :style="containerStyle">
+ <!-- 预设颜色 -->
+ <view class="preset-colors" v-if="showPresets">
+ <view class="preset-title">常用颜色</view>
+ <view class="preset-grid">
+ <view v-for="(color, index) in presetColors" :key="index" class="preset-item" :style="[{ backgroundColor: color }]" @click="selectPresetColor(color)">
+ <view v-if="color === currentHex" class="preset-selected">✓</view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 历史颜色 -->
+ <view class="history-colors" v-if="colorHistory.length > 0">
+ <view class="history-title">
+ <text>最近使用</text>
+ <text @click="clearColorHistory">清除</text>
+ </view>
+ <view class="history-grid">
+ <view v-for="(color, index) in colorHistory" :key="index" class="history-item" :style="[{ backgroundColor: color }]" @click="selectPresetColor(color)">
+ <view v-if="color === currentHex" class="history-selected">✓</view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 主色彩选择区域 -->
+ <view class="color-main">
+ <!-- 饱和度/亮度面板 -->
+ <view class="saturation-panel" :style="[panelStyle, { backgroundColor: pureColor }]" @touchstart="onSaturationTouchStart" @touchmove="onSaturationTouchMove" @touchend="onTouchEnd"
+ ref="saturationPanel">
+ <!-- 白色渐变 -->
+ <view class="saturation-white"></view>
+ <!-- 黑色渐变 -->
+ <view class="saturation-black"></view>
+ <!-- 选择器指示点 -->
+ <view class="saturation-cursor" :style="[cursorStyle, { left: saturationPosition.x + 'px', top: saturationPosition.y + 'px' }]"></view>
+ </view>
+
+ <!-- 色相滑条 -->
+ <view class="hue-slider" :style="sliderStyle" @touchstart="onHueTouchStart" @touchmove="onHueTouchMove" @touchend="onTouchEnd">
+ <view class="hue-track"></view>
+ <view class="hue-cursor" :style="[{ left: huePosition + 'px' }]"></view>
+ </view>
+
+ <!-- 透明度滑条 -->
+ <view v-if="showAlpha" class="alpha-slider" :style="sliderStyle" @touchstart="onAlphaTouchStart" @touchmove="onAlphaTouchMove" @touchend="onTouchEnd">
+ <view class="alpha-checkerboard"></view>
+ <view class="alpha-bg" :style="[{ background: alphaGradient }]"></view>
+ <view class="alpha-cursor" :style="[{ left: alphaPosition + 'px' }]"></view>
+ </view>
+ </view>
+
+ <!-- 颜色预览和信息 -->
+ <view class="color-info">
+ <!-- 颜色预览 -->
+ <view class="color-preview">
+ <view class="preview-current" :style="[{ backgroundColor: currentColorRgba }]">
+ <view class="preview-checkerboard"></view>
+ </view>
+ <view class="preview-old" :style="[{ backgroundColor: initialColor }]"></view>
+ </view>
+
+ <!-- 颜色值输入 -->
+ <view class="color-inputs">
+ <view class="input-tabs">
+ <text v-for="tab in inputTabs" :key="tab.key" class="input-tab" :class="{ active: activeInputTab === tab.key }" @click="activeInputTab = tab.key">
+ {{ tab.label }}
+ </text>
+ </view>
+
+ <!-- HEX 输入 -->
+ <view v-if="activeInputTab === 'hex'" class="input-group">
+ <input class="color-input hex-input" v-model="hexInput" @input="onHexInput" @blur="validateHexInput" maxlength="7" placeholder="#000000" />
+ </view>
+
+ <!-- RGB 输入 -->
+ <view v-if="activeInputTab === 'rgb'" class="input-row">
+ <view class="input-group">
+ <text class="input-label">R</text>
+ <input class="color-input" type="number" v-model.number="rgbInput.r" @input="onRgbInput" min="0" max="255" />
+ </view>
+ <view class="input-group">
+ <text class="input-label">G</text>
+ <input class="color-input" type="number" v-model.number="rgbInput.g" @input="onRgbInput" min="0" max="255" />
+ </view>
+ <view class="input-group">
+ <text class="input-label">B</text>
+ <input class="color-input" type="number" v-model.number="rgbInput.b" @input="onRgbInput" min="0" max="255" />
+ </view>
+ <view v-if="showAlpha" class="input-group">
+ <text class="input-label">A</text>
+ <input class="color-input" type="number" v-model.number="alphaInput" @input="onAlphaInputChange" min="0" max="100" />
+ </view>
+ </view>
+
+ <!-- HSL 输入 -->
+ <view v-if="activeInputTab === 'hsl'" class="input-row">
+ <view class="input-group">
+ <text class="input-label">H</text>
+ <input class="color-input" type="number" v-model.number="hslInput.h" @input="onHslInput" min="0" max="360" />
+ </view>
+ <view class="input-group">
+ <text class="input-label">S</text>
+ <input class="color-input" type="number" v-model.number="hslInput.s" @input="onHslInput" min="0" max="100" />
+ </view>
+ <view class="input-group">
+ <text class="input-label">L</text>
+ <input class="color-input" type="number" v-model.number="hslInput.l" @input="onHslInput" min="0" max="100" />
+ </view>
+ </view>
+ </view>
+ </view>
+
+ <!-- 操作按钮 -->
+ <view class="color-actions">
+ <button class="action-btn cancel-btn" @click="onCancel">
+ {{ cancelText }}
+ </button>
+ <button class="action-btn confirm-btn" @click="onConfirm">
+ {{ confirmText }}
+ </button>
+ </view>
+ </view>
+</template>
+
+<script>
+ const PRESET_COLORS = [
+ '#FF0000', '#FF8000', '#FFFF00', '#80FF00', '#00FF00', '#00FF80',
+ '#00FFFF', '#0080FF', '#0000FF', '#8000FF', '#FF00FF', '#FF0080',
+ '#000000', '#404040', '#808080', '#C0C0C0', '#FFFFFF', '#FF8080',
+ '#FFB366', '#FFFF80', '#B3FF80', '#80FF80', '#80FFB3', '#80FFFF',
+ '#80B3FF', '#8080FF', '#B380FF', '#FF80FF', '#FF80B3'
+ ];
+
+ const DEFAULT_OPTIONS = {
+ width: uni.upx2px(630),
+ padding: uni.upx2px(32),
+ throttleDelay: 16,
+ cursorSize: uni.upx2px(36),
+ maxHistory: 8
+ }
+
+
+ export default {
+ name: 'x-color-picker',
+ props: {
+ modelValue: {
+ type: String,
+ default: '#1B73CC'
+ },
+ showAlpha: {
+ type: Boolean,
+ default: true
+ },
+ showPresets: {
+ type: Boolean,
+ default: true
+ },
+ darkMode: {
+ type: Boolean,
+ default: false
+ },
+ saveHistory: {
+ type: Boolean,
+ default: true
+ },
+ cancelText: {
+ type: String,
+ default: '取消'
+ },
+ confirmText: {
+ type: String,
+ default: '确定'
+ },
+ options: {
+ type: Object,
+ default: () => DEFAULT_OPTIONS
+ }
+ },
+ emits: ['update:modelValue', 'cancel', 'confirm', 'change'],
+ data() {
+ return {
+ // 内部状态
+ internalHsv: { h: 0, s: 100, v: 100 },
+ internalRgb: { r: 255, g: 0, b: 0 },
+ internalAlpha: 100,
+
+ // 输入框状态
+ hexInput: '#FF0000',
+ rgbInput: { r: 255, g: 0, b: 0 },
+ hslInput: { h: 0, s: 100, l: 50 },
+ alphaInput: 100,
+
+ // UI 状态
+ activeInputTab: 'hex',
+ initialColor: '#FF0000',
+ colorHistory: [],
+
+ // 位置信息
+ saturationPosition: { x: 0, y: 0 },
+ huePosition: 0,
+ alphaPosition: 0,
+
+ // 触摸状态
+ isDragging: false,
+ dragType: '',
+ lastTouchTime: 0,
+
+ // 缓存的DOM查询结果
+ panelRect: null,
+ hueRect: null,
+ alphaRect: null
+ }
+ },
+ computed: {
+ // 合并配置选项
+ mergedOptions() {
+ return { ...DEFAULT_OPTIONS, ...this.options };
+ },
+
+ // 计算面板尺寸(内容区域宽度减去内边距)
+ computedPanelSize() {
+ const contentWidth = this.mergedOptions.width - (this.mergedOptions.padding * 2);
+ return {
+ width: contentWidth,
+ height: contentWidth * 0.714 // 保持 560:400 的宽高比
+ };
+ },
+
+ // 计算滑条宽度
+ computedSliderWidth() {
+ return this.mergedOptions.width - (this.mergedOptions.padding * 2);
+ },
+
+ // 容器样式
+ containerStyle() {
+ return {
+ width: this.mergedOptions.width + 'px',
+ padding: this.mergedOptions.padding + 'px'
+ };
+ },
+
+ // 面板样式
+ panelStyle() {
+ return {
+ width: this.computedPanelSize.width + 'px',
+ height: this.computedPanelSize.height + 'px'
+ };
+ },
+
+ // 指示点样式
+ cursorStyle() {
+ return {
+ width: this.mergedOptions.cursorSize + 'px',
+ height: this.mergedOptions.cursorSize + 'px'
+ };
+ },
+
+ // 滑条样式
+ sliderStyle() {
+ return {
+ width: this.computedSliderWidth + 'px'
+ };
+ },
+
+ // 预设颜色
+ presetColors() {
+ return PRESET_COLORS;
+ },
+
+ // 输入标签页
+ inputTabs() {
+ return [
+ { key: 'hex', label: 'HEX' },
+ { key: 'rgb', label: 'RGB' },
+ { key: 'hsl', label: 'HSL' }
+ ];
+ },
+
+ // 当前HEX颜色
+ currentHex() {
+ return this.rgbToHex(this.internalRgb.r, this.internalRgb.g, this.internalRgb.b);
+ },
+
+ // 纯色(用于饱和度面板背景)
+ pureColor() {
+ return this.hsvToRgbString(this.internalHsv.h, 100, 100);
+ },
+
+ // 当前颜色的RGBA值
+ currentColorRgba() {
+ const { r, g, b } = this.internalRgb;
+ const alpha = this.internalAlpha / 100;
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+ },
+
+ // 透明度渐变背景
+ alphaGradient() {
+ const { r, g, b } = this.internalRgb;
+ return `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0), rgba(${r}, ${g}, ${b}, 1))`;
+ }
+ },
+ watch: {
+ modelValue: {
+ handler(newVal) {
+ this.initialColor = newVal;
+ this.setColor(newVal);
+ },
+ immediate: true
+ },
+ // 监听配置变化,重新计算位置
+ 'mergedOptions.width': {
+ handler() {
+ this.$nextTick(() => {
+ this.updateAllPositions();
+ });
+ }
+ }
+ },
+ mounted() {
+ this.loadColorHistory();
+ },
+ methods: {
+ // ========== 颜色设置 ==========
+ setColor(color) {
+ const rgb = this.hexToRgb(color);
+ const hsv = this.rgbToHsv(rgb.r, rgb.g, rgb.b);
+ const hsl = this.rgbToHsl(rgb.r, rgb.g, rgb.b);
+
+ this.internalRgb = rgb;
+ this.internalHsv = hsv;
+
+ // 更新输入框
+ this.hexInput = color;
+ this.rgbInput = { ...rgb };
+ this.hslInput = hsl;
+
+ this.updateAllPositions();
+ },
+
+ // 更新所有位置
+ updateAllPositions() {
+ const { h, s, v } = this.internalHsv;
+
+ // 色相位置
+ this.huePosition = (h / 360) * this.computedSliderWidth;
+
+ // 饱和度/亮度位置
+ this.saturationPosition.x = (s / 100) * this.computedPanelSize.width;
+ this.saturationPosition.y = ((100 - v) / 100) * this.computedPanelSize.height;
+
+ // 透明度位置
+ this.alphaPosition = (this.internalAlpha / 100) * this.computedSliderWidth;
+ },
+
+ // ========== 预设颜色相关 ==========
+ selectPresetColor(color) {
+ this.setColor(color);
+ this.emitChange();
+ },
+
+ // 加载颜色历史
+ loadColorHistory() {
+ if (this.saveHistory) {
+ this.colorHistory = uni.getStorageSync('colorPickerHistory') || [];
+ }
+ },
+
+ // 清除颜色历史
+ clearColorHistory() {
+ this.colorHistory = []
+ uni.removeStorageSync('colorPickerHistory')
+ },
+
+ // 保存颜色历史
+ saveColorHistory() {
+ const color = this.currentHex;
+ let history = [...this.colorHistory];
+
+ // 移除已存在的相同颜色
+ history = history.filter(c => c !== color);
+
+ // 添加到开头
+ history.unshift(color);
+
+ // 限制历史记录数量
+ if (history.length > this.mergedOptions.maxHistory) {
+ history = history.slice(0, this.mergedOptions.maxHistory);
+ }
+
+ if (this.saveHistory) {
+ this.colorHistory = history;
+ uni.setStorageSync('colorPickerHistory', history);
+ }
+ },
+
+ // ========== 触摸事件处理 ==========
+ onSaturationTouchStart(e) {
+ this.startDragging('saturation', e);
+ this.updateSaturationFromTouch(e);
+ },
+
+ onSaturationTouchMove(e) {
+ if (this.isDragging && this.dragType === 'saturation') {
+ this.throttledUpdateSaturation(e);
+ }
+ },
+
+ onHueTouchStart(e) {
+ this.startDragging('hue', e);
+ this.updateHueFromTouch(e);
+ },
+
+ onHueTouchMove(e) {
+ if (this.isDragging && this.dragType === 'hue') {
+ this.throttledUpdateHue(e);
+ }
+ },
+
+ onAlphaTouchStart(e) {
+ this.startDragging('alpha', e);
+ this.updateAlphaFromTouch(e);
+ },
+
+ onAlphaTouchMove(e) {
+ if (this.isDragging && this.dragType === 'alpha') {
+ this.throttledUpdateAlpha(e);
+ }
+ },
+
+ onTouchEnd() {
+ this.isDragging = false;
+ this.dragType = '';
+ this.clearRectCache();
+ },
+
+ // 开始拖拽
+ startDragging(type, e) {
+ this.isDragging = true;
+ this.dragType = type;
+ },
+
+ // ========== 节流更新函数 ==========
+ throttledUpdateSaturation(e) {
+ return this.throttle(() => this.updateSaturationFromTouch(e), this.mergedOptions.throttleDelay);
+ },
+
+ throttledUpdateHue(e) {
+ return this.throttle(() => this.updateHueFromTouch(e), this.mergedOptions.throttleDelay);
+ },
+
+ throttledUpdateAlpha(e) {
+ return this.throttle(() => this.updateAlphaFromTouch(e), this.mergedOptions.throttleDelay);
+ },
+
+ // 节流函数
+ throttle(func, delay) {
+ const now = Date.now();
+ if (now - this.lastTouchTime >= delay) {
+ this.lastTouchTime = now;
+ return func();
+ }
+ },
+
+ // ========== 位置更新函数 ==========
+ updateSaturationFromTouch(e) {
+ const touch = e.touches[0] || e.changedTouches[0];
+
+ if (this.panelRect) {
+ this.updateSaturationPosition(touch, this.panelRect);
+ } else {
+ this.queryRect('.saturation-panel', (rect) => {
+ this.panelRect = rect;
+ this.updateSaturationPosition(touch, rect);
+ });
+ }
+ },
+
+ updateSaturationPosition(touch, rect) {
+ const x = Math.max(0, Math.min(touch.clientX - rect.left, this.computedPanelSize.width));
+ const y = Math.max(0, Math.min(touch.clientY - rect.top, this.computedPanelSize.height));
+
+ this.saturationPosition.x = x;
+ this.saturationPosition.y = y;
+
+ const s = (x / this.computedPanelSize.width) * 100;
+ const v = ((this.computedPanelSize.height - y) / this.computedPanelSize.height) * 100;
+
+ this.internalHsv.s = Math.round(s);
+ this.internalHsv.v = Math.round(v);
+
+ this.updateColorFromHsv();
+ },
+
+ updateHueFromTouch(e) {
+ const touch = e.touches[0] || e.changedTouches[0];
+
+ if (this.hueRect) {
+ this.updateHuePosition(touch, this.hueRect);
+ } else {
+ this.queryRect('.hue-slider', (rect) => {
+ this.hueRect = rect;
+ this.updateHuePosition(touch, rect);
+ });
+ }
+ },
+
+ updateHuePosition(touch, rect) {
+ const x = Math.max(0, Math.min(touch.clientX - rect.left, this.computedSliderWidth));
+ this.huePosition = x;
+
+ const h = (x / this.computedSliderWidth) * 360;
+ this.internalHsv.h = Math.round(h);
+
+ this.updateColorFromHsv();
+ },
+
+ updateAlphaFromTouch(e) {
+ const touch = e.touches[0] || e.changedTouches[0];
+
+ if (this.alphaRect) {
+ this.updateAlphaPosition(touch, this.alphaRect);
+ } else {
+ this.queryRect('.alpha-slider', (rect) => {
+ this.alphaRect = rect;
+ this.updateAlphaPosition(touch, rect);
+ });
+ }
+ },
+
+ updateAlphaPosition(touch, rect) {
+ const x = Math.max(0, Math.min(touch.clientX - rect.left, this.computedSliderWidth));
+ this.alphaPosition = x;
+
+ const alpha = (x / this.computedSliderWidth) * 100;
+ this.internalAlpha = Math.round(alpha);
+ this.alphaInput = this.internalAlpha;
+ },
+
+ // DOM查询封装
+ queryRect(selector, callback) {
+ uni.createSelectorQuery().in(this).select(selector).boundingClientRect(callback).exec();
+ },
+
+ // 清除DOM查询缓存
+ clearRectCache() {
+ this.panelRect = null;
+ this.hueRect = null;
+ this.alphaRect = null;
+ },
+
+ // ========== 颜色更新 ==========
+ updateColorFromHsv() {
+ const { h, s, v } = this.internalHsv;
+ this.internalRgb = this.hsvToRgb(h, s, v);
+
+ // 更新输入框
+ this.hexInput = this.currentHex;
+ this.rgbInput = { ...this.internalRgb };
+ this.hslInput = this.rgbToHsl(this.internalRgb.r, this.internalRgb.g, this.internalRgb.b);
+
+ this.emitChange();
+ },
+
+ // ========== 输入事件处理 ==========
+ onHexInput() {
+ if (this.isValidHex(this.hexInput)) {
+ const rgb = this.hexToRgb(this.hexInput);
+ const hsv = this.rgbToHsv(rgb.r, rgb.g, rgb.b);
+
+ this.internalRgb = rgb;
+ this.internalHsv = hsv;
+ this.rgbInput = { ...rgb };
+ this.hslInput = this.rgbToHsl(rgb.r, rgb.g, rgb.b);
+
+ this.updateAllPositions();
+ this.emitChange();
+ }
+ },
+
+ validateHexInput() {
+ if (!this.isValidHex(this.hexInput)) {
+ this.hexInput = this.currentHex;
+ }
+ },
+
+ onRgbInput() {
+ // 验证和限制输入值
+ this.rgbInput.r = this.clamp(this.rgbInput.r || 0, 0, 255);
+ this.rgbInput.g = this.clamp(this.rgbInput.g || 0, 0, 255);
+ this.rgbInput.b = this.clamp(this.rgbInput.b || 0, 0, 255);
+
+ this.internalRgb = { ...this.rgbInput };
+ this.internalHsv = this.rgbToHsv(this.internalRgb.r, this.internalRgb.g, this.internalRgb.b);
+
+ this.hexInput = this.currentHex;
+ this.hslInput = this.rgbToHsl(this.internalRgb.r, this.internalRgb.g, this.internalRgb.b);
+
+ this.updateAllPositions();
+ this.emitChange();
+ },
+
+ onHslInput() {
+ // 验证和限制输入值
+ this.hslInput.h = this.clamp(this.hslInput.h || 0, 0, 360);
+ this.hslInput.s = this.clamp(this.hslInput.s || 0, 0, 100);
+ this.hslInput.l = this.clamp(this.hslInput.l || 0, 0, 100);
+
+ const rgb = this.hslToRgb(this.hslInput.h, this.hslInput.s, this.hslInput.l);
+ this.internalRgb = rgb;
+ this.internalHsv = this.rgbToHsv(rgb.r, rgb.g, rgb.b);
+
+ this.hexInput = this.currentHex;
+ this.rgbInput = { ...rgb };
+
+ this.updateAllPositions();
+ this.emitChange();
+ },
+
+ onAlphaInputChange() {
+ this.alphaInput = this.clamp(this.alphaInput || 0, 0, 100);
+ this.internalAlpha = this.alphaInput;
+ this.alphaPosition = (this.internalAlpha / 100) * this.computedSliderWidth;
+ this.emitChange();
+ },
+
+ // ========== 工具函数 ==========
+ clamp(value, min, max) {
+ return Math.max(min, Math.min(max, parseInt(value) || 0));
+ },
+
+ // 发出变化事件
+ emitChange() {
+ const color = {
+ hex: this.currentHex,
+ rgb: this.internalRgb,
+ hsv: this.internalHsv,
+ hsl: this.hslInput,
+ alpha: this.internalAlpha,
+ rgba: this.currentColorRgba
+ };
+ this.$emit('change', color);
+ },
+
+ // ========== 颜色转换函数 ==========
+ hsvToRgb(h, s, v) {
+ h = h / 360;
+ s = s / 100;
+ v = v / 100;
+
+ const i = Math.floor(h * 6);
+ const f = h * 6 - i;
+ const p = v * (1 - s);
+ const q = v * (1 - f * s);
+ const t = v * (1 - (1 - f) * s);
+
+ let r, g, b;
+ switch (i % 6) {
+ case 0:
+ r = v;
+ g = t;
+ b = p;
+ break;
+ case 1:
+ r = q;
+ g = v;
+ b = p;
+ break;
+ case 2:
+ r = p;
+ g = v;
+ b = t;
+ break;
+ case 3:
+ r = p;
+ g = q;
+ b = v;
+ break;
+ case 4:
+ r = t;
+ g = p;
+ b = v;
+ break;
+ case 5:
+ r = v;
+ g = p;
+ b = q;
+ break;
+ }
+
+ return {
+ r: Math.round(r * 255),
+ g: Math.round(g * 255),
+ b: Math.round(b * 255)
+ };
+ },
+
+ hsvToRgbString(h, s, v) {
+ const rgb = this.hsvToRgb(h, s, v);
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
+ },
+
+ rgbToHsv(r, g, b) {
+ r /= 255;
+ g /= 255;
+ b /= 255;
+
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ const diff = max - min;
+
+ let h = 0;
+ const s = max === 0 ? 0 : diff / max;
+ const v = max;
+
+ if (diff !== 0) {
+ if (max === r) {
+ h = ((g - b) / diff) % 6;
+ } else if (max === g) {
+ h = (b - r) / diff + 2;
+ } else {
+ h = (r - g) / diff + 4;
+ }
+ }
+
+ return {
+ h: Math.round((h < 0 ? h + 6 : h) * 60),
+ s: Math.round(s * 100),
+ v: Math.round(v * 100)
+ };
+ },
+
+ rgbToHsl(r, g, b) {
+ r /= 255;
+ g /= 255;
+ b /= 255;
+
+ const max = Math.max(r, g, b);
+ const min = Math.min(r, g, b);
+ const diff = max - min;
+ const sum = max + min;
+
+ let h = 0;
+ let s = 0;
+ const l = sum / 2;
+
+ if (diff !== 0) {
+ s = l > 0.5 ? diff / (2 - sum) : diff / sum;
+
+ if (max === r) {
+ h = ((g - b) / diff) + (g < b ? 6 : 0);
+ } else if (max === g) {
+ h = (b - r) / diff + 2;
+ } else {
+ h = (r - g) / diff + 4;
+ }
+ h /= 6;
+ }
+
+ return {
+ h: Math.round(h * 360),
+ s: Math.round(s * 100),
+ l: Math.round(l * 100)
+ };
+ },
+
+ hslToRgb(h, s, l) {
+ h /= 360;
+ s /= 100;
+ l /= 100;
+
+ const c = (1 - Math.abs(2 * l - 1)) * s;
+ const x = c * (1 - Math.abs((h * 6) % 2 - 1));
+ const m = l - c / 2;
+
+ let r, g, b;
+
+ if (0 <= h && h < 1 / 6) {
+ r = c;
+ g = x;
+ b = 0;
+ } else if (1 / 6 <= h && h < 2 / 6) {
+ r = x;
+ g = c;
+ b = 0;
+ } else if (2 / 6 <= h && h < 3 / 6) {
+ r = 0;
+ g = c;
+ b = x;
+ } else if (3 / 6 <= h && h < 4 / 6) {
+ r = 0;
+ g = x;
+ b = c;
+ } else if (4 / 6 <= h && h < 5 / 6) {
+ r = x;
+ g = 0;
+ b = c;
+ } else {
+ r = c;
+ g = 0;
+ b = x;
+ }
+
+ return {
+ r: Math.round((r + m) * 255),
+ g: Math.round((g + m) * 255),
+ b: Math.round((b + m) * 255)
+ };
+ },
+
+ rgbToHex(r, g, b) {
+ return '#' + [r, g, b].map(x => {
+ const hex = x.toString(16);
+ return hex.length === 1 ? '0' + hex : hex;
+ }).join('').toUpperCase();
+ },
+
+ hexToRgb(hex) {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result ? {
+ r: parseInt(result[1], 16),
+ g: parseInt(result[2], 16),
+ b: parseInt(result[3], 16)
+ } : { r: 255, g: 0, b: 0 };
+ },
+
+ isValidHex(hex) {
+ return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(hex);
+ },
+
+ // ========== 按钮事件 ==========
+ onCancel() {
+ this.setColor(this.initialColor);
+ this.$emit('cancel');
+ },
+
+ onConfirm() {
+ this.saveColorHistory();
+ const color = {
+ hex: this.currentHex,
+ rgb: this.internalRgb,
+ hsv: this.internalHsv,
+ hsl: this.hslInput,
+ alpha: this.internalAlpha,
+ rgba: this.currentColorRgba
+ };
+ this.$emit('confirm', color);
+ this.$emit('update:modelValue', this.currentHex);
+ }
+ }
+ }
+</script>
+
+<style scoped>
+ .x-color-picker {
+ background: #ffffff;
+ border-radius: 24rpx;
+ box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
+ margin: 0 auto;
+ box-sizing: border-box;
+ }
+
+ .color-picker--dark {
+ background: #1a1a1a;
+ color: #ffffff;
+ }
+
+ /* ========== 预设颜色 ========== */
+ .preset-colors,
+ .history-colors {
+ margin-bottom: 40rpx;
+ }
+
+ .preset-title,
+ .history-title {
+ font-size: 28rpx;
+ color: #666666;
+ margin-bottom: 16rpx;
+ font-weight: 500;
+ }
+
+ .history-title {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .history-title text:last-child {
+ color: #ff0000;
+ font-size: 26rpx;
+ }
+
+ .color-picker--dark .preset-title,
+ .color-picker--dark .history-title {
+ color: #cccccc;
+ }
+
+ .preset-grid,
+ .history-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12rpx;
+ }
+
+ .preset-item,
+ .history-item {
+ width: 56rpx;
+ height: 56rpx;
+ border-radius: 12rpx;
+ border: 3rpx solid #ffffff;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ }
+
+ .preset-item:active,
+ .history-item:active {
+ transform: scale(0.95);
+ }
+
+ .preset-selected,
+ .history-selected {
+ color: #ffffff;
+ font-size: 24rpx;
+ font-weight: bold;
+ text-shadow: 0 1rpx 3rpx rgba(0, 0, 0, 0.5);
+ }
+
+ /* ========== 主色彩选择区域 ========== */
+ .color-main {
+ margin-bottom: 40rpx;
+ }
+
+ .saturation-panel {
+ position: relative;
+ border-radius: 16rpx;
+ overflow: hidden;
+ margin-bottom: 32rpx;
+ box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
+ cursor: crosshair;
+ }
+
+ .saturation-white {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(to right, #ffffff, transparent);
+ }
+
+ .saturation-black {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(to bottom, transparent, #000000);
+ }
+
+ .saturation-cursor {
+ position: absolute;
+ border: 4rpx solid #ffffff;
+ border-radius: 50%;
+ box-shadow: 0 0 12rpx rgba(0, 0, 0, 0.4), inset 0 0 4rpx rgba(0, 0, 0, 0.2);
+ transform: translate(-50%, -50%);
+ pointer-events: none;
+ transition: transform 0.1s ease;
+ }
+
+ .saturation-cursor:active {
+ transform: translate(-50%, -50%) scale(1.2);
+ }
+
+ /* ========== 滑条样式 ========== */
+ .hue-slider,
+ .alpha-slider {
+ height: 48rpx;
+ position: relative;
+ border-radius: 24rpx;
+ margin-bottom: 24rpx;
+ cursor: pointer;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+ }
+
+ .hue-track {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(to right,
+ #ff0000 0%,
+ #ffff00 16.66%,
+ #00ff00 33.33%,
+ #00ffff 50%,
+ #0000ff 66.66%,
+ #ff00ff 83.33%,
+ #ff0000 100%);
+ border-radius: 24rpx;
+ }
+
+ .hue-cursor,
+ .alpha-cursor {
+ position: absolute;
+ top: -8rpx;
+ width: 32rpx;
+ height: 64rpx;
+ background: #ffffff;
+ border: 3rpx solid #dddddd;
+ border-radius: 8rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
+ transform: translateX(-50%);
+ pointer-events: none;
+ transition: all 0.1s ease;
+ }
+
+ .hue-cursor:active,
+ .alpha-cursor:active {
+ transform: translateX(-50%) scale(1.1);
+ }
+
+ /* ========== 透明度滑条 ========== */
+ .alpha-checkerboard {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image:
+ linear-gradient(45deg, #cccccc 25%, transparent 25%),
+ linear-gradient(-45deg, #cccccc 25%, transparent 25%),
+ linear-gradient(45deg, transparent 75%, #cccccc 75%),
+ linear-gradient(-45deg, transparent 75%, #cccccc 75%);
+ background-size: 16rpx 16rpx;
+ background-position: 0 0, 0 8rpx, 8rpx -8rpx, -8rpx 0;
+ border-radius: 24rpx;
+ }
+
+ .alpha-bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: 24rpx;
+ }
+
+ /* ========== 颜色信息区域 ========== */
+ .color-info {
+ display: flex;
+ gap: 32rpx;
+ margin-bottom: 40rpx;
+ }
+
+ .color-preview {
+ width: 160rpx;
+ flex-shrink: 0;
+ }
+
+ .preview-current,
+ .preview-old {
+ width: 100%;
+ height: 60rpx;
+ border-radius: 12rpx;
+ position: relative;
+ overflow: hidden;
+ border: 2rpx solid #eeeeee;
+ }
+
+ .preview-current {
+ margin-bottom: 16rpx;
+ }
+
+ .preview-checkerboard {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image:
+ linear-gradient(45deg, #eeeeee 25%, transparent 25%),
+ linear-gradient(-45deg, #eeeeee 25%, transparent 25%),
+ linear-gradient(45deg, transparent 75%, #eeeeee 75%),
+ linear-gradient(-45deg, transparent 75%, #eeeeee 75%);
+ background-size: 12rpx 12rpx;
+ background-position: 0 0, 0 6rpx, 6rpx -6rpx, -6rpx 0;
+ }
+
+
+ /* ========== 颜色输入 ========== */
+ .color-inputs {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .input-tabs {
+ display: flex;
+ margin-bottom: 20rpx;
+ background: #f8f8f8;
+ border-radius: 12rpx;
+ padding: 4rpx;
+ }
+
+ .color-picker--dark .input-tabs {
+ background: #2a2a2a;
+ }
+
+ .input-tab {
+ flex: 1;
+ text-align: center;
+ padding: 12rpx 8rpx;
+ font-size: 26rpx;
+ color: #666666;
+ border-radius: 8rpx;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ }
+
+ .input-tab.active {
+ background: #ffffff;
+ color: #333333;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
+ }
+
+ .color-picker--dark .input-tab.active {
+ background: #1a1a1a;
+ color: #ffffff;
+ }
+
+ .input-group {
+ margin-bottom: 20rpx;
+ }
+
+ .input-row {
+ display: flex;
+ gap: 16rpx;
+ }
+
+ .input-row .input-group {
+ flex: 1;
+ margin-bottom: 0;
+ }
+
+ .input-label {
+ display: block;
+ font-size: 24rpx;
+ color: #666666;
+ margin-bottom: 8rpx;
+ font-weight: 500;
+ }
+
+ .color-picker--dark .input-label {
+ color: #cccccc;
+ }
+
+ .color-input {
+ width: 100%;
+ height: 64rpx;
+ padding: 0 16rpx;
+ border: 2rpx solid #eeeeee;
+ border-radius: 12rpx;
+ font-size: 28rpx;
+ text-align: center;
+ box-sizing: border-box;
+ transition: all 0.2s ease;
+ background: #ffffff;
+ }
+
+ .color-picker--dark .color-input {
+ background: #2a2a2a;
+ border-color: #444444;
+ color: #ffffff;
+ }
+
+ .color-input:focus {
+ border-color: #007aff;
+ outline: none;
+ box-shadow: 0 0 0 4rpx rgba(0, 122, 255, 0.1);
+ }
+
+ .hex-input {
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ text-transform: uppercase;
+ }
+
+ /* ========== 操作按钮 ========== */
+ .color-actions {
+ display: flex;
+ gap: 24rpx;
+ margin-top: 40rpx;
+ }
+
+ .action-btn {
+ flex: 1;
+ height: 88rpx;
+ line-height: 88rpx;
+ text-align: center;
+ border-radius: 16rpx;
+ font-size: 32rpx;
+ font-weight: 500;
+ border: none;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ }
+
+ .cancel-btn {
+ background: #f8f8f8;
+ color: #666666;
+ }
+
+ .color-picker--dark .cancel-btn {
+ background: #2a2a2a;
+ color: #cccccc;
+ }
+
+ .confirm-btn {
+ background: linear-gradient(135deg, #007aff, #0056cc);
+ color: #ffffff;
+ box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
+ }
+
+ .action-btn:active {
+ transform: scale(0.98);
+ }
+
+ .cancel-btn:active {
+ background: #eeeeee;
+ }
+
+ .color-picker--dark .cancel-btn:active {
+ background: #1a1a1a;
+ }
+
+ .confirm-btn:active {
+ box-shadow: 0 2rpx 8rpx rgba(0, 122, 255, 0.4);
+ }
+</style>
\ No newline at end of file
diff --git a/ledApp/uni_modules/x-color-picker/package.json b/ledApp/uni_modules/x-color-picker/package.json
new file mode 100644
index 0000000..c12761c
--- /dev/null
+++ b/ledApp/uni_modules/x-color-picker/package.json
@@ -0,0 +1,87 @@
+{
+ "id": "x-color-picker",
+ "displayName": "取色器,拾色器,颜色选择器、取色板",
+ "version": "1.0.0",
+ "description": "一个功能强大的颜色选择器组件。它提供了直观的颜色选择界面,支持多种颜色格式,并具有丰富的自定义选项",
+ "keywords": [
+ "取色器",
+ "拾色器",
+ "颜色选择器",
+ "取色板"
+ ],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^4.07"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y",
+ "alipay": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "u",
+ "app-uvue": "n",
+ "app-harmony": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "y",
+ "快手": "y",
+ "飞书": "y",
+ "京东": "y"
+ },
+ "快应用": {
+ "华为": "y",
+ "联盟": "y"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ledApp/uni_modules/x-color-picker/readme.md b/ledApp/uni_modules/x-color-picker/readme.md
new file mode 100644
index 0000000..0cf473f
--- /dev/null
+++ b/ledApp/uni_modules/x-color-picker/readme.md
@@ -0,0 +1,225 @@
+# x-color-picker 颜色选择器使用说明
+
+## 组件简介
+
+`x-color-picker` 一个功能强大的颜色选择器组件。它提供了直观的颜色选择界面,支持多种颜色格式,并具有丰富的自定义选项。
+
+## 特性
+
+- 🎨 **直观的颜色选择界面**:HSV 色相/饱和度/亮度面板
+- 🌈 **多种颜色格式支持**:HEX、RGB、HSL
+- 🎯 **透明度控制**:支持 Alpha 通道调节
+- 🎪 **预设颜色**:内置常用颜色面板
+- 📝 **历史记录**:自动保存最近使用的颜色
+- 🌙 **深色模式**:支持明暗主题切换
+
+
+## 基础用法
+
+### 最简单的使用方式
+```vue
+<template>
+ <x-color-picker v-model="selectedColor" />
+</template>
+
+<script>
+export default {
+ data() {
+ return {
+ selectedColor: '#1B73CC'
+ }
+ }
+}
+</script>
+```
+
+### 监听颜色变化
+```vue
+<template>
+ <x-color-picker
+ v-model="selectedColor"
+ @change="onColorChange"
+ @confirm="onColorConfirm"
+ @cancel="onColorCancel"
+ />
+</template>
+
+<script>
+export default {
+ data() {
+ return {
+ selectedColor: '#1B73CC'
+ }
+ },
+ methods: {
+ onColorChange(color) {
+ console.log('颜色实时变化:', color)
+ },
+ onColorConfirm(color) {
+ console.log('确认选择:', color)
+ },
+ onColorCancel() {
+ console.log('取消选择')
+ }
+ }
+}
+</script>
+```
+
+## Props 属性
+
+| 属性名 | 类型 | 默认值 | 说明 |
+|--------|------|--------|------|
+| `modelValue` | String | `'#1B73CC'` | 当前选中的颜色值(HEX 格式)|
+| `showAlpha` | Boolean | `true` | 是否显示透明度控制 |
+| `showPresets` | Boolean | `true` | 是否显示预设颜色面板 |
+| `darkMode` | Boolean | `false` | 是否启用深色模式 |
+| `saveHistory` | Boolean | `true` | 是否保存颜色历史记录 |
+| `cancelText` | String | `'取消'` | 取消按钮文本 |
+| `confirmText` | String | `'确定'` | 确认按钮文本 |
+| `options` | Object | 见下方 | 自定义配置选项 |
+
+### options 配置项
+
+```javascript
+{
+ width: uni.upx2px(630), // 组件宽度(px)
+ padding: uni.upx2px(32), // 内边距(px)
+ throttleDelay: 16, // 触摸事件节流延迟(ms)
+ cursorSize: uni.upx2px(36), // 光标大小(px)
+ maxHistory: 8 // 最大历史记录数量
+}
+```
+
+## Events 事件
+
+| 事件名 | 参数 | 说明 |
+|--------|------|------|
+| `update:modelValue` | `color: string` | 颜色值更新(用于 v-model) |
+| `change` | `color: object` | 颜色实时变化时触发 |
+| `confirm` | `color: object` | 点击确认按钮时触发 |
+| `cancel` | - | 点击取消按钮时触发 |
+
+### color 对象结构
+```javascript
+{
+ hex: '#FF5722', // HEX 格式
+ rgb: { r: 255, g: 87, b: 34 }, // RGB 对象
+ hsv: { h: 12, s: 87, v: 100 }, // HSV 对象
+ hsl: { h: 12, s: 100, l: 57 }, // HSL 对象
+ alpha: 100, // 透明度 (0-100)
+ rgba: 'rgba(255, 87, 34, 1)' // RGBA 字符串
+}
+```
+
+## 高级用法示例
+
+### 禁用透明度和预设颜色
+```vue
+<template>
+ <x-color-picker
+ v-model="color"
+ :show-alpha="false"
+ :show-presets="false"
+ />
+</template>
+```
+
+### 深色模式
+```vue
+<template>
+ <x-color-picker
+ v-model="color"
+ :dark-mode="true"
+ />
+</template>
+```
+
+### 自定义尺寸和配置
+```vue
+<template>
+ <x-color-picker
+ v-model="color"
+ :options="customOptions"
+ />
+</template>
+
+<script>
+export default {
+ data() {
+ return {
+ color: '#FF5722',
+ customOptions: {
+ width: uni.upx2px(700), // 更大的宽度
+ maxHistory: 12, // 更多历史记录
+ throttleDelay: 8 // 更快的响应速度
+ }
+ }
+ }
+}
+</script>
+```
+
+### 禁用历史记录
+```vue
+<template>
+ <x-color-picker
+ v-model="color"
+ :save-history="false"
+ />
+</template>
+```
+
+### 自定义按钮文本
+```vue
+<template>
+ <x-color-picker
+ v-model="color"
+ cancel-text="返回"
+ confirm-text="应用"
+ />
+</template>
+```
+
+## 颜色格式说明
+
+### HEX 格式
+- 格式:`#RRGGBB`
+- 示例:`#FF5722`、`#000000`、`#FFFFFF`
+
+### RGB 格式
+- 格式:`{ r: 0-255, g: 0-255, b: 0-255 }`
+- 示例:`{ r: 255, g: 87, b: 34 }`
+
+### HSL 格式
+- 格式:`{ h: 0-360, s: 0-100, l: 0-100 }`
+- 示例:`{ h: 12, s: 100, l: 57 }`
+
+### HSV 格式
+- 格式:`{ h: 0-360, s: 0-100, v: 0-100 }`
+- 示例:`{ h: 12, s: 87, v: 100 }`
+
+
+## 注意事项
+
+1. **性能优化**:组件内置了触摸事件节流,避免频繁更新导致的性能问题
+2. **存储限制**:历史记录使用 `uni.getStorageSync` 存储,注意存储空间限制
+3. **样式兼容**:在不同平台可能存在细微的样式差异,可通过条件编译进行调整
+4. **触摸精度**:在小屏设备上,建议适当调整组件尺寸以提供更好的触摸体验
+
+## 常见问题
+
+### Q: 如何获取不同格式的颜色值?
+A: 监听 `change` 或 `confirm` 事件,事件参数包含所有格式的颜色值。
+
+### Q: 如何自定义预设颜色?
+A: 当前版本预设颜色是固定的,如需自定义,可以修改组件内的 `PRESET_COLORS` 常量。
+
+### Q: 历史记录存储在哪里?
+A: 历史记录使用 `uni.getStorageSync('colorPickerHistory')` 存储在本地。
+
+### Q: 如何在不同页面间共享颜色选择器状态?
+A: 可以使用 Vuex 或其他状态管理工具来管理颜色状态。
+
+
+## 插件如果对你有帮助给个好评吧~
\ No newline at end of file
--
Gitblit v1.10.0