From 9709f200c2145593a497ec0b4efa71131368847a Mon Sep 17 00:00:00 2001 From: sfmind <cc130201237@163.com> Date: Wed, 6 Apr 2022 16:08:26 +0800 Subject: [PATCH] =?UTF-8?q?uni-app=E5=BC=95=E5=85=A5=E4=BD=BF=E7=94=A8uVie?= =?UTF-8?q?w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-ui-app/App.vue | 25 + yudao-ui-app/index.html | 20 + yudao-ui-app/main.js | 26 + yudao-ui-app/manifest.json | 72 + yudao-ui-app/pages.json | 64 + yudao-ui-app/pages/cart/cart.nvue | 24 + yudao-ui-app/pages/category/category.nvue | 24 + yudao-ui-app/pages/index/index.nvue | 25 + yudao-ui-app/pages/user/user.nvue | 25 + .../static/images/tabbar/cart-active.png | Bin 0 -> 6304 bytes yudao-ui-app/static/images/tabbar/cart.png | Bin 0 -> 6169 bytes .../static/images/tabbar/category-active.png | Bin 0 -> 8172 bytes .../static/images/tabbar/category.png | Bin 0 -> 7893 bytes .../static/images/tabbar/index-active.png | Bin 0 -> 6891 bytes yudao-ui-app/static/images/tabbar/index.png | Bin 0 -> 6766 bytes .../static/images/tabbar/user-active.png | Bin 0 -> 8298 bytes yudao-ui-app/static/images/tabbar/user.png | Bin 0 -> 8070 bytes yudao-ui-app/uni.scss | 79 + yudao-ui-app/uni_modules/uview-ui/LICENSE | 21 + yudao-ui-app/uni_modules/uview-ui/README.md | 104 ++ .../uni_modules/uview-ui/changelog.md | 318 ++++ .../uview-ui/components/u--form/u--form.vue | 78 + .../uview-ui/components/u--image/u--image.vue | 47 + .../uview-ui/components/u--input/u--input.vue | 72 + .../uview-ui/components/u--text/u--text.vue | 44 + .../components/u--textarea/u--textarea.vue | 47 + .../components/u-action-sheet/props.js | 54 + .../u-action-sheet/u-action-sheet.vue | 278 ++++ .../uview-ui/components/u-album/props.js | 59 + .../uview-ui/components/u-album/u-album.vue | 259 ++++ .../uview-ui/components/u-alert/props.js | 44 + .../uview-ui/components/u-alert/u-alert.vue | 243 +++ .../components/u-avatar-group/props.js | 52 + .../u-avatar-group/u-avatar-group.vue | 103 ++ .../uview-ui/components/u-avatar/props.js | 78 + .../uview-ui/components/u-avatar/u-avatar.vue | 172 +++ .../uview-ui/components/u-back-top/props.js | 54 + .../components/u-back-top/u-back-top.vue | 129 ++ .../uview-ui/components/u-badge/props.js | 72 + .../uview-ui/components/u-badge/u-badge.vue | 171 +++ .../uview-ui/components/u-button/nvue.scss | 46 + .../uview-ui/components/u-button/props.js | 161 ++ .../uview-ui/components/u-button/u-button.vue | 490 ++++++ .../uview-ui/components/u-button/vue.scss | 80 + .../uview-ui/components/u-calendar/header.vue | 99 ++ .../uview-ui/components/u-calendar/month.vue | 579 +++++++ .../uview-ui/components/u-calendar/props.js | 144 ++ .../components/u-calendar/u-calendar.vue | 383 +++++ .../uview-ui/components/u-calendar/util.js | 85 ++ .../components/u-car-keyboard/props.js | 14 + .../u-car-keyboard/u-car-keyboard.vue | 311 ++++ .../uview-ui/components/u-cell-group/props.js | 14 + .../components/u-cell-group/u-cell-group.vue | 61 + .../uview-ui/components/u-cell/props.js | 110 ++ .../uview-ui/components/u-cell/u-cell.vue | 229 +++ .../components/u-checkbox-group/props.js | 82 + .../u-checkbox-group/u-checkbox-group.vue | 103 ++ .../uview-ui/components/u-checkbox/props.js | 69 + .../components/u-checkbox/u-checkbox.vue | 344 +++++ .../components/u-circle-progress/props.js | 8 + .../u-circle-progress/u-circle-progress.vue | 198 +++ .../uview-ui/components/u-code-input/props.js | 74 + .../components/u-code-input/u-code-input.vue | 240 +++ .../uview-ui/components/u-code/props.js | 34 + .../uview-ui/components/u-code/u-code.vue | 129 ++ .../uview-ui/components/u-col/props.js | 29 + .../uview-ui/components/u-col/u-col.vue | 162 ++ .../components/u-collapse-item/props.js | 59 + .../u-collapse-item/u-collapse-item.vue | 225 +++ .../uview-ui/components/u-collapse/props.js | 19 + .../components/u-collapse/u-collapse.vue | 90 ++ .../components/u-column-notice/props.js | 55 + .../u-column-notice/u-column-notice.vue | 160 ++ .../uview-ui/components/u-count-down/props.js | 24 + .../components/u-count-down/u-count-down.vue | 163 ++ .../uview-ui/components/u-count-down/utils.js | 62 + .../uview-ui/components/u-count-to/props.js | 59 + .../components/u-count-to/u-count-to.vue | 184 +++ .../components/u-datetime-picker/props.js | 116 ++ .../u-datetime-picker/u-datetime-picker.vue | 360 +++++ .../uview-ui/components/u-divider/props.js | 44 + .../components/u-divider/u-divider.vue | 116 ++ .../components/u-dropdown-item/props.js | 36 + .../u-dropdown-item/u-dropdown-item.vue | 146 ++ .../uview-ui/components/u-dropdown/props.js | 65 + .../components/u-dropdown/u-dropdown.vue | 127 ++ .../uview-ui/components/u-empty/props.js | 59 + .../uview-ui/components/u-empty/u-empty.vue | 128 ++ .../uview-ui/components/u-form-item/props.js | 43 + .../components/u-form-item/u-form-item.vue | 235 +++ .../uview-ui/components/u-form/props.js | 45 + .../uview-ui/components/u-form/u-form.vue | 214 +++ .../uview-ui/components/u-gap/props.js | 24 + .../uview-ui/components/u-gap/u-gap.vue | 38 + .../uview-ui/components/u-grid-item/props.js | 14 + .../components/u-grid-item/u-grid-item.vue | 209 +++ .../uview-ui/components/u-grid/props.js | 19 + .../uview-ui/components/u-grid/u-grid.vue | 97 ++ .../uview-ui/components/u-icon/icons.js | 214 +++ .../uview-ui/components/u-icon/props.js | 89 ++ .../uview-ui/components/u-icon/u-icon.vue | 234 +++ .../uview-ui/components/u-image/props.js | 84 ++ .../uview-ui/components/u-image/u-image.vue | 232 +++ .../components/u-index-anchor/props.js | 29 + .../u-index-anchor/u-index-anchor.vue | 91 ++ .../uview-ui/components/u-index-item/props.js | 5 + .../components/u-index-item/u-index-item.vue | 87 ++ .../uview-ui/components/u-index-list/props.js | 29 + .../components/u-index-list/u-index-list.vue | 440 ++++++ .../uview-ui/components/u-input/props.js | 182 +++ .../uview-ui/components/u-input/u-input.vue | 353 +++++ .../uview-ui/components/u-keyboard/props.js | 84 ++ .../components/u-keyboard/u-keyboard.vue | 164 ++ .../components/u-line-progress/props.js | 28 + .../u-line-progress/u-line-progress.vue | 144 ++ .../uview-ui/components/u-line/props.js | 33 + .../uview-ui/components/u-line/u-line.vue | 62 + .../uview-ui/components/u-link/props.js | 39 + .../uview-ui/components/u-link/u-link.vue | 83 + .../uview-ui/components/u-list-item/props.js | 9 + .../components/u-list-item/u-list-item.vue | 116 ++ .../uview-ui/components/u-list/props.js | 76 + .../uview-ui/components/u-list/u-list.vue | 159 ++ .../components/u-loading-icon/props.js | 59 + .../u-loading-icon/u-loading-icon.vue | 343 +++++ .../components/u-loading-page/props.js | 44 + .../u-loading-page/u-loading-page.vue | 110 ++ .../uview-ui/components/u-loadmore/props.js | 80 + .../components/u-loadmore/u-loadmore.vue | 145 ++ .../uview-ui/components/u-modal/props.js | 84 ++ .../uview-ui/components/u-modal/u-modal.vue | 227 +++ .../uview-ui/components/u-navbar/props.js | 84 ++ .../uview-ui/components/u-navbar/u-navbar.vue | 186 +++ .../uview-ui/components/u-no-network/props.js | 19 + .../components/u-no-network/u-no-network.vue | 219 +++ .../uview-ui/components/u-notice-bar/props.js | 70 + .../components/u-notice-bar/u-notice-bar.vue | 101 ++ .../uview-ui/components/u-notify/props.js | 49 + .../uview-ui/components/u-notify/u-notify.vue | 211 +++ .../uview-ui/components/u-number-box/props.js | 109 ++ .../components/u-number-box/u-number-box.vue | 416 +++++ .../components/u-number-keyboard/props.js | 19 + .../u-number-keyboard/u-number-keyboard.vue | 196 +++ .../uview-ui/components/u-overlay/props.js | 24 + .../components/u-overlay/u-overlay.vue | 68 + .../uview-ui/components/u-parse/node/node.vue | 499 ++++++ .../uview-ui/components/u-parse/parser.js | 1075 +++++++++++++ .../uview-ui/components/u-parse/props.js | 45 + .../uview-ui/components/u-parse/u-parse.vue | 366 +++++ .../components/u-picker-column/props.js | 5 + .../u-picker-column/u-picker-column.vue | 27 + .../uview-ui/components/u-picker/props.js | 84 ++ .../uview-ui/components/u-picker/u-picker.vue | 284 ++++ .../uview-ui/components/u-popup/props.js | 79 + .../uview-ui/components/u-popup/u-popup.vue | 304 ++++ .../components/u-radio-group/props.js | 85 ++ .../u-radio-group/u-radio-group.vue | 108 ++ .../uview-ui/components/u-radio/props.js | 64 + .../uview-ui/components/u-radio/u-radio.vue | 337 +++++ .../uview-ui/components/u-rate/props.js | 69 + .../uview-ui/components/u-rate/u-rate.vue | 304 ++++ .../uview-ui/components/u-read-more/props.js | 61 + .../components/u-read-more/u-read-more.vue | 157 ++ .../uview-ui/components/u-row-notice/props.js | 39 + .../components/u-row-notice/u-row-notice.vue | 306 ++++ .../uview-ui/components/u-row/props.js | 19 + .../uview-ui/components/u-row/u-row.vue | 93 ++ .../components/u-safe-bottom/props.js | 5 + .../u-safe-bottom/u-safe-bottom.vue | 56 + .../uview-ui/components/u-scroll-list/nvue.js | 28 + .../components/u-scroll-list/other.js} | 0 .../components/u-scroll-list/props.js | 34 + .../components/u-scroll-list/scrollWxs.wxs | 50 + .../u-scroll-list/u-scroll-list.vue | 226 +++ .../uview-ui/components/u-search/props.js | 118 ++ .../uview-ui/components/u-search/u-search.vue | 303 ++++ .../uview-ui/components/u-skeleton/props.js | 59 + .../components/u-skeleton/u-skeleton.vue | 244 +++ .../uview-ui/components/u-slider/mpother.js | 113 ++ .../uview-ui/components/u-slider/mpwxs.js | 42 + .../uview-ui/components/u-slider/mpwxs.wxs | 121 ++ .../components/u-slider/nvue - 副本.js | 180 +++ .../uview-ui/components/u-slider/nvue.js | 193 +++ .../uview-ui/components/u-slider/props.js | 54 + .../uview-ui/components/u-slider/u-slider.vue | 55 + .../uview-ui/components/u-status-bar/props.js | 8 + .../components/u-status-bar/u-status-bar.vue | 46 + .../uview-ui/components/u-steps-item/props.js | 24 + .../components/u-steps-item/u-steps-item.vue | 316 ++++ .../uview-ui/components/u-steps/props.js | 39 + .../uview-ui/components/u-steps/u-steps.vue | 80 + .../uview-ui/components/u-sticky/props.js | 40 + .../uview-ui/components/u-sticky/u-sticky.vue | 212 +++ .../uview-ui/components/u-subsection/props.js | 49 + .../components/u-subsection/u-subsection.vue | 299 ++++ .../u-swipe-action-item/index - backup.wxs | 256 ++++ .../components/u-swipe-action-item/index.wxs | 225 +++ .../u-swipe-action-item/nvue - backup.js | 270 ++++ .../components/u-swipe-action-item/nvue.js | 174 +++ .../components/u-swipe-action-item/props.js | 41 + .../u-swipe-action-item.vue | 190 +++ .../components/u-swipe-action-item/wxs.js | 15 + .../components/u-swipe-action/props.js | 9 + .../u-swipe-action/u-swipe-action.vue | 67 + .../components/u-swiper-indicator/props.js | 29 + .../u-swiper-indicator/u-swiper-indicator.vue | 110 ++ .../uview-ui/components/u-swiper/props.js | 125 ++ .../uview-ui/components/u-swiper/u-swiper.vue | 255 ++++ .../uview-ui/components/u-switch/props.js | 54 + .../uview-ui/components/u-switch/u-switch.vue | 177 +++ .../components/u-tabbar-item/props.js | 35 + .../u-tabbar-item/u-tabbar-item.vue | 142 ++ .../uview-ui/components/u-tabbar/props.js | 44 + .../uview-ui/components/u-tabbar/u-tabbar.vue | 141 ++ .../uview-ui/components/u-table/props.js | 5 + .../uview-ui/components/u-table/u-table.vue | 29 + .../uview-ui/components/u-tabs-item/props.js | 5 + .../components/u-tabs-item/u-tabs-item.vue | 29 + .../uview-ui/components/u-tabs/props.js | 64 + .../uview-ui/components/u-tabs/u-tabs.vue | 354 +++++ .../uview-ui/components/u-tag/props.js | 84 ++ .../uview-ui/components/u-tag/u-tag.vue | 358 +++++ .../uview-ui/components/u-td/props.js | 5 + .../uview-ui/components/u-td/u-td.vue | 31 + .../uview-ui/components/u-text/props.js | 110 ++ .../uview-ui/components/u-text/u-text.vue | 223 +++ .../uview-ui/components/u-text/value.js | 85 ++ .../uview-ui/components/u-textarea/props.js | 114 ++ .../components/u-textarea/u-textarea.vue | 237 +++ .../uview-ui/components/u-toast/u-toast.vue | 291 ++++ .../uview-ui/components/u-toolbar/props.js | 34 + .../components/u-toolbar/u-toolbar.vue | 102 ++ .../components/u-tooltip/clipboard.min.js | 58 + .../uview-ui/components/u-tooltip/props.js | 59 + .../components/u-tooltip/u-tooltip.vue | 365 +++++ .../uview-ui/components/u-tr/props.js | 5 + .../uview-ui/components/u-tr/u-tr.vue | 31 + .../components/u-transition/nvue.ani-map.js | 68 + .../uview-ui/components/u-transition/props.js | 24 + .../components/u-transition/transition.js | 157 ++ .../components/u-transition/u-transition.vue | 92 ++ .../u-transition/vue.ani-style.scss | 113 ++ .../uview-ui/components/u-upload/mixin.js | 21 + .../uview-ui/components/u-upload/props.js | 124 ++ .../uview-ui/components/u-upload/u-upload.vue | 556 +++++++ .../uview-ui/components/u-upload/utils.js | 151 ++ .../uview-ui/components/uview-ui/uview-ui.vue | 15 + yudao-ui-app/uni_modules/uview-ui/index.js | 79 + yudao-ui-app/uni_modules/uview-ui/index.scss | 23 + .../uni_modules/uview-ui/libs/config/color.js | 17 + .../uview-ui/libs/config/config.js | 34 + .../uni_modules/uview-ui/libs/config/props.js | 190 +++ .../uview-ui/libs/config/props/actionSheet.js | 25 + .../uview-ui/libs/config/props/album.js | 25 + .../uview-ui/libs/config/props/alert.js | 22 + .../uview-ui/libs/config/props/avatar.js | 28 + .../uview-ui/libs/config/props/avatarGroup.js | 23 + .../uview-ui/libs/config/props/backtop.js | 27 + .../uview-ui/libs/config/props/badge.js | 27 + .../uview-ui/libs/config/props/button.js | 42 + .../uview-ui/libs/config/props/calendar.js | 42 + .../uview-ui/libs/config/props/carKeyboard.js | 15 + .../uview-ui/libs/config/props/cell.js | 35 + .../uview-ui/libs/config/props/cellGroup.js | 17 + .../uview-ui/libs/config/props/checkbox.js | 27 + .../libs/config/props/checkboxGroup.js | 29 + .../libs/config/props/circleProgress.js | 15 + .../uview-ui/libs/config/props/code.js | 21 + .../uview-ui/libs/config/props/codeInput.js | 28 + .../uview-ui/libs/config/props/col.js | 19 + .../uview-ui/libs/config/props/collapse.js | 17 + .../libs/config/props/collapseItem.js | 25 + .../libs/config/props/columnNotice.js | 24 + .../uview-ui/libs/config/props/countDown.js | 18 + .../uview-ui/libs/config/props/countTo.js | 25 + .../libs/config/props/datetimePicker.js | 36 + .../uview-ui/libs/config/props/divider.js | 23 + .../uview-ui/libs/config/props/empty.js | 26 + .../uview-ui/libs/config/props/form.js | 22 + .../uview-ui/libs/config/props/formItem.js | 22 + .../uview-ui/libs/config/props/gap.js | 19 + .../uview-ui/libs/config/props/grid.js | 17 + .../uview-ui/libs/config/props/gridItem.js | 16 + .../uview-ui/libs/config/props/icon.js | 36 + .../uview-ui/libs/config/props/image.js | 30 + .../uview-ui/libs/config/props/indexAnchor.js | 19 + .../uview-ui/libs/config/props/indexList.js | 19 + .../uview-ui/libs/config/props/input.js | 48 + .../uview-ui/libs/config/props/keyboard.js | 30 + .../uview-ui/libs/config/props/line.js | 20 + .../libs/config/props/lineProgress.js | 19 + .../uview-ui/libs/config/props/link.js | 26 + .../uview-ui/libs/config/props/list.js | 28 + .../uview-ui/libs/config/props/listItem.js | 15 + .../uview-ui/libs/config/props/loadingIcon.js | 30 + .../uview-ui/libs/config/props/loadingPage.js | 22 + .../uview-ui/libs/config/props/loadmore.js | 29 + .../uview-ui/libs/config/props/modal.js | 30 + .../uview-ui/libs/config/props/navbar.js | 32 + .../uview-ui/libs/config/props/noNetwork.js | 18 + .../uview-ui/libs/config/props/noticeBar.js | 27 + .../uview-ui/libs/config/props/notify.js | 22 + .../uview-ui/libs/config/props/numberBox.js | 35 + .../libs/config/props/numberKeyboard.js | 17 + .../uview-ui/libs/config/props/overlay.js | 18 + .../uview-ui/libs/config/props/parse.js | 22 + .../uview-ui/libs/config/props/picker.js | 30 + .../uview-ui/libs/config/props/popup.js | 29 + .../uview-ui/libs/config/props/radio.js | 27 + .../uview-ui/libs/config/props/radioGroup.js | 30 + .../uview-ui/libs/config/props/rate.js | 26 + .../uview-ui/libs/config/props/readMore.js | 22 + .../uview-ui/libs/config/props/row.js | 17 + .../uview-ui/libs/config/props/rowNotice.js | 21 + .../uview-ui/libs/config/props/scrollList.js | 20 + .../uview-ui/libs/config/props/search.js | 37 + .../uview-ui/libs/config/props/section.js | 24 + .../uview-ui/libs/config/props/skeleton.js | 25 + .../uview-ui/libs/config/props/slider.js | 25 + .../uview-ui/libs/config/props/statusBar.js | 15 + .../uview-ui/libs/config/props/steps.js | 21 + .../uview-ui/libs/config/props/stepsItem.js | 18 + .../uview-ui/libs/config/props/sticky.js | 20 + .../uview-ui/libs/config/props/subsection.js | 23 + .../uview-ui/libs/config/props/swipeAction.js | 15 + .../libs/config/props/swipeActionItem.js | 21 + .../uview-ui/libs/config/props/swiper.js | 39 + .../libs/config/props/swipterIndicator.js | 19 + .../uview-ui/libs/config/props/switch.js | 24 + .../uview-ui/libs/config/props/tabbar.js | 22 + .../uview-ui/libs/config/props/tabbarItem.js | 20 + .../uview-ui/libs/config/props/tabs.js | 32 + .../uview-ui/libs/config/props/tag.js | 29 + .../uview-ui/libs/config/props/text.js | 38 + .../uview-ui/libs/config/props/textarea.js | 36 + .../uview-ui/libs/config/props/toast.js | 30 + .../uview-ui/libs/config/props/toolbar.js | 21 + .../uview-ui/libs/config/props/tooltip.js | 25 + .../uview-ui/libs/config/props/transition.js | 18 + .../uview-ui/libs/config/props/upload.js | 36 + .../uview-ui/libs/config/zIndex.js | 20 + .../uni_modules/uview-ui/libs/css/color.scss | 155 ++ .../uni_modules/uview-ui/libs/css/common.scss | 97 ++ .../uview-ui/libs/css/components.scss | 15 + .../uni_modules/uview-ui/libs/css/flex.scss | 257 ++++ .../uni_modules/uview-ui/libs/css/h5.scss | 0 .../uni_modules/uview-ui/libs/css/mixin.scss | 8 + .../uni_modules/uview-ui/libs/css/mp.scss | 0 .../uni_modules/uview-ui/libs/css/nvue.scss | 0 .../uni_modules/uview-ui/libs/css/vue.scss | 27 + .../uview-ui/libs/function/colorGradient.js | 134 ++ .../uview-ui/libs/function/debounce.js | 29 + .../uview-ui/libs/function/digit.js | 167 ++ .../uview-ui/libs/function/index.js | 705 +++++++++ .../uview-ui/libs/function/platform.js | 75 + .../uview-ui/libs/function/test.js | 288 ++++ .../uview-ui/libs/function/throttle.js | 30 + .../libs/luch-request/adapters/index.js | 97 ++ .../luch-request/core/InterceptorManager.js | 50 + .../libs/luch-request/core/Request.js | 198 +++ .../libs/luch-request/core/buildFullPath.js | 20 + .../libs/luch-request/core/defaults.js | 29 + .../libs/luch-request/core/dispatchRequest.js | 3 + .../libs/luch-request/core/mergeConfig.js | 103 ++ .../uview-ui/libs/luch-request/core/settle.js | 16 + .../libs/luch-request/helpers/buildURL.js | 69 + .../libs/luch-request/helpers/combineURLs.js | 14 + .../luch-request/helpers/isAbsoluteURL.js | 14 + .../uview-ui/libs/luch-request/index.d.ts | 116 ++ .../uview-ui/libs/luch-request/index.js | 3 + .../uview-ui/libs/luch-request/utils.js | 131 ++ .../uview-ui/libs/luch-request/utils/clone.js | 264 ++++ .../uni_modules/uview-ui/libs/mixin/button.js | 13 + .../uni_modules/uview-ui/libs/mixin/mixin.js | 160 ++ .../uview-ui/libs/mixin/mpMixin.js | 8 + .../uview-ui/libs/mixin/mpShare.js | 13 + .../uview-ui/libs/mixin/openType.js | 25 + .../uni_modules/uview-ui/libs/mixin/style.js | 228 +++ .../uni_modules/uview-ui/libs/mixin/touch.js | 59 + .../uview-ui/libs/util/async-validator.js | 1343 +++++++++++++++++ .../uview-ui/libs/util/calendar.js | 546 +++++++ .../uni_modules/uview-ui/libs/util/dayjs.js | 308 ++++ .../uni_modules/uview-ui/libs/util/emitter.js | 51 + .../uni_modules/uview-ui/libs/util/route.js | 124 ++ .../uni_modules/uview-ui/package.json | 87 ++ yudao-ui-app/uni_modules/uview-ui/theme.scss | 44 + 386 files changed, 38777 insertions(+) create mode 100644 yudao-ui-app/App.vue create mode 100644 yudao-ui-app/index.html create mode 100644 yudao-ui-app/main.js create mode 100644 yudao-ui-app/manifest.json create mode 100644 yudao-ui-app/pages.json create mode 100644 yudao-ui-app/pages/cart/cart.nvue create mode 100644 yudao-ui-app/pages/category/category.nvue create mode 100644 yudao-ui-app/pages/index/index.nvue create mode 100644 yudao-ui-app/pages/user/user.nvue create mode 100644 yudao-ui-app/static/images/tabbar/cart-active.png create mode 100644 yudao-ui-app/static/images/tabbar/cart.png create mode 100644 yudao-ui-app/static/images/tabbar/category-active.png create mode 100644 yudao-ui-app/static/images/tabbar/category.png create mode 100644 yudao-ui-app/static/images/tabbar/index-active.png create mode 100644 yudao-ui-app/static/images/tabbar/index.png create mode 100644 yudao-ui-app/static/images/tabbar/user-active.png create mode 100644 yudao-ui-app/static/images/tabbar/user.png create mode 100644 yudao-ui-app/uni.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/LICENSE create mode 100644 yudao-ui-app/uni_modules/uview-ui/README.md create mode 100644 yudao-ui-app/uni_modules/uview-ui/changelog.md create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u--form/u--form.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u--image/u--image.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u--input/u--input.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u--text/u--text.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u--textarea/u--textarea.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-action-sheet/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-album/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-album/u-album.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-alert/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-alert/u-alert.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-avatar-group/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-avatar/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-avatar/u-avatar.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-back-top/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-back-top/u-back-top.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-badge/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-badge/u-badge.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-button/nvue.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-button/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-button/u-button.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-button/vue.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-calendar/header.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-calendar/month.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-calendar/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-calendar/u-calendar.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-calendar/util.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-car-keyboard/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-cell-group/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-cell/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-cell/u-cell.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-checkbox-group/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-checkbox/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-checkbox/u-checkbox.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-circle-progress/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-code-input/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-code-input/u-code-input.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-code/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-code/u-code.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-col/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-col/u-col.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-collapse-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-collapse-item/u-collapse-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-collapse/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-collapse/u-collapse.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-column-notice/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-column-notice/u-column-notice.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-count-down/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-count-down/u-count-down.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-count-down/utils.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-count-to/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-count-to/u-count-to.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-datetime-picker/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-datetime-picker/u-datetime-picker.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-divider/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-divider/u-divider.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-dropdown-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-dropdown/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-empty/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-empty/u-empty.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-form-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-form-item/u-form-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-form/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-form/u-form.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-gap/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-gap/u-gap.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-grid-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-grid-item/u-grid-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-grid/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-grid/u-grid.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-icon/icons.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-icon/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-icon/u-icon.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-image/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-image/u-image.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-index-anchor/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-index-anchor/u-index-anchor.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-index-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-index-item/u-index-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-index-list/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-index-list/u-index-list.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-input/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-input/u-input.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-keyboard/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-keyboard/u-keyboard.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-line-progress/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-line-progress/u-line-progress.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-line/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-line/u-line.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-link/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-link/u-link.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-list-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-list-item/u-list-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-list/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-list/u-list.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-loading-icon/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-loading-icon/u-loading-icon.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-loading-page/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-loading-page/u-loading-page.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-loadmore/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-loadmore/u-loadmore.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-modal/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-modal/u-modal.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-navbar/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-navbar/u-navbar.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-no-network/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-no-network/u-no-network.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-notice-bar/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-notice-bar/u-notice-bar.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-notify/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-notify/u-notify.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-number-box/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-number-box/u-number-box.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-number-keyboard/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-number-keyboard/u-number-keyboard.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-overlay/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-overlay/u-overlay.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-parse/node/node.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-parse/parser.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-parse/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-parse/u-parse.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-picker-column/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-picker-column/u-picker-column.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-picker/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-picker/u-picker.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-popup/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-popup/u-popup.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-radio-group/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-radio-group/u-radio-group.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-radio/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-radio/u-radio.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-rate/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-rate/u-rate.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-read-more/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-read-more/u-read-more.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-row-notice/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-row-notice/u-row-notice.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-row/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-row/u-row.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-safe-bottom/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-safe-bottom/u-safe-bottom.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/nvue.js rename yudao-ui-app/{.keep => uni_modules/uview-ui/components/u-scroll-list/other.js} (100%) create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/scrollWxs.wxs create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-search/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-search/u-search.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-skeleton/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-skeleton/u-skeleton.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpother.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpwxs.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpwxs.wxs create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-slider/nvue - 副本.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-slider/nvue.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-slider/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-slider/u-slider.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-status-bar/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-status-bar/u-status-bar.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-steps-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-steps-item/u-steps-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-steps/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-steps/u-steps.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-sticky/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-sticky/u-sticky.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-subsection/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-subsection/u-subsection.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/index - backup.wxs create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/index.wxs create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/nvue - backup.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/nvue.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/u-swipe-action-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/wxs.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action/u-swipe-action.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swiper-indicator/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swiper-indicator/u-swiper-indicator.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swiper/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-swiper/u-swiper.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-switch/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-switch/u-switch.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tabbar-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tabbar-item/u-tabbar-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tabbar/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tabbar/u-tabbar.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-table/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-table/u-table.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tabs-item/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tabs-item/u-tabs-item.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tabs/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tabs/u-tabs.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tag/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tag/u-tag.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-td/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-td/u-td.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-text/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-text/u-text.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-text/value.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-textarea/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-textarea/u-textarea.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-toast/u-toast.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-toolbar/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/clipboard.min.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/u-tooltip.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tr/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-tr/u-tr.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-transition/nvue.ani-map.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-transition/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-transition/transition.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-transition/u-transition.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-transition/vue.ani-style.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-upload/mixin.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-upload/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-upload/u-upload.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/u-upload/utils.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/components/uview-ui/uview-ui.vue create mode 100644 yudao-ui-app/uni_modules/uview-ui/index.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/index.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/color.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/config.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/actionSheet.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/album.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/alert.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/avatar.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/avatarGroup.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/backtop.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/badge.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/button.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/calendar.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/carKeyboard.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/cell.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/cellGroup.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/checkbox.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/checkboxGroup.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/circleProgress.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/code.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/codeInput.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/col.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/collapse.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/collapseItem.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/columnNotice.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/countDown.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/countTo.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/datetimePicker.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/divider.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/empty.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/form.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/formItem.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/gap.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/grid.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/gridItem.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/icon.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/image.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/indexAnchor.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/indexList.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/input.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/keyboard.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/line.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/lineProgress.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/link.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/list.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/listItem.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadingIcon.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadingPage.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadmore.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/modal.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/navbar.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/noNetwork.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/noticeBar.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/notify.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/numberBox.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/numberKeyboard.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/overlay.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/parse.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/picker.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/popup.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/radio.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/radioGroup.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/rate.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/readMore.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/row.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/rowNotice.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/scrollList.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/search.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/section.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/skeleton.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/slider.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/statusBar.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/steps.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/stepsItem.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/sticky.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/subsection.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipeAction.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipeActionItem.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/swiper.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipterIndicator.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/switch.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabbar.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabbarItem.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabs.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/tag.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/text.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/textarea.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/toast.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/toolbar.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/tooltip.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/transition.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/props/upload.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/config/zIndex.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/color.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/common.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/components.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/flex.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/h5.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/mixin.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/mp.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/nvue.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/css/vue.scss create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/function/colorGradient.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/function/debounce.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/function/digit.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/function/index.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/function/platform.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/function/test.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/function/throttle.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/adapters/index.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/InterceptorManager.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/Request.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/buildFullPath.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/defaults.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/dispatchRequest.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/mergeConfig.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/settle.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/buildURL.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/combineURLs.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/isAbsoluteURL.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/index.d.ts create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/index.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/utils.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/luch-request/utils/clone.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/mixin/button.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/mixin/mixin.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/mixin/mpMixin.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/mixin/mpShare.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/mixin/openType.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/mixin/style.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/mixin/touch.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/util/async-validator.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/util/calendar.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/util/dayjs.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/util/emitter.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/libs/util/route.js create mode 100644 yudao-ui-app/uni_modules/uview-ui/package.json create mode 100644 yudao-ui-app/uni_modules/uview-ui/theme.scss diff --git a/yudao-ui-app/App.vue b/yudao-ui-app/App.vue new file mode 100644 index 000000000..cb4ef8126 --- /dev/null +++ b/yudao-ui-app/App.vue @@ -0,0 +1,25 @@ +<script> + export default { + onLaunch: function() { + console.log('App Launch') + }, + onShow: function() { + console.log('App Show') + }, + onHide: function() { + console.log('App Hide') + } + } +</script> + +<style lang="scss"> + /* 引入uView基础样式 */ + @import "@/uni_modules/uview-ui/index.scss"; + + /*每个页面公共css */ + .container { + background-color: $uni-bg-color; + box-sizing: border-box; + height: 100%; + } +</style> diff --git a/yudao-ui-app/index.html b/yudao-ui-app/index.html new file mode 100644 index 000000000..c3ff205f6 --- /dev/null +++ b/yudao-ui-app/index.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <script> + var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || + CSS.supports('top: constant(a)')) + document.write( + '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + + (coverSupport ? ', viewport-fit=cover' : '') + '" />') + </script> + <title></title> + <!--preload-links--> + <!--app-context--> + </head> + <body> + <div id="app"><!--app-html--></div> + <script type="module" src="/main.js"></script> + </body> +</html> diff --git a/yudao-ui-app/main.js b/yudao-ui-app/main.js new file mode 100644 index 000000000..6b80b6348 --- /dev/null +++ b/yudao-ui-app/main.js @@ -0,0 +1,26 @@ +import App from './App' + +// #ifndef VUE3 +import Vue from 'vue' +Vue.config.productionTip = false + +//引入并使用uView的JS库 +import uView from '@/uni_modules/uview-ui' +Vue.use(uView) + +App.mpType = 'app' +const app = new Vue({ + ...App +}) +app.$mount() +// #endif + +// #ifdef VUE3 +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} +// #endif \ No newline at end of file diff --git a/yudao-ui-app/manifest.json b/yudao-ui-app/manifest.json new file mode 100644 index 000000000..7cce06e5f --- /dev/null +++ b/yudao-ui-app/manifest.json @@ -0,0 +1,72 @@ +{ + "name" : "yudao-ui-app", + "appid" : "__UNI__CA099E3", + "description" : "yudao-ui-app for ruoyi-vue-pro", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueStyleCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : {}, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", + "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", + "<uses-permission android:name=\"android.permission.VIBRATE\"/>", + "<uses-permission android:name=\"android.permission.READ_LOGS\"/>", + "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", + "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", + "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", + "<uses-permission android:name=\"android.permission.CAMERA\"/>", + "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", + "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", + "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", + "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", + "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", + "<uses-feature android:name=\"android.hardware.camera\"/>", + "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" + ] + }, + /* ios打包配置 */ + "ios" : {}, + /* SDK配置 */ + "sdkConfigs" : {} + } + }, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "2" +} diff --git a/yudao-ui-app/pages.json b/yudao-ui-app/pages.json new file mode 100644 index 000000000..02112f68e --- /dev/null +++ b/yudao-ui-app/pages.json @@ -0,0 +1,64 @@ +{ + "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "首页" + } + }, + { + "path": "pages/category/category", + "style": { + "navigationBarTitleText": "分类" + } + }, + { + "path": "pages/cart/cart", + "style": { + "navigationBarTitleText": "购物车" + } + }, + { + "path": "pages/user/user", + "style": { + "navigationBarTitleText": "我的" + } + } + ], + "tabBar": { + "selectedColor": "#333333", + "color": "#bfbfbf", + "list": [ + { + "pagePath": "pages/index/index", + "text": "首页", + "iconPath": "/static/images/tabbar/index.png", + "selectedIconPath": "/static/images/tabbar/index-active.png" + }, + { + "pagePath": "pages/category/category", + "text": "分类", + "iconPath": "/static/images/tabbar/category.png", + "selectedIconPath": "/static/images/tabbar/category-active.png" + }, + { + "pagePath": "pages/cart/cart", + "text": "购物车", + "iconPath": "/static/images/tabbar/cart.png", + "selectedIconPath": "/static/images/tabbar/cart-active.png" + }, + { + "pagePath": "pages/user/user.png", + "text": "我的", + "iconPath": "/static/images/tabbar/user.png", + "selectedIconPath": "/static/images/tabbar/user-active.png" + } + ] + }, + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "yudao-ui-app", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#FFFFFF" + } +} diff --git a/yudao-ui-app/pages/cart/cart.nvue b/yudao-ui-app/pages/cart/cart.nvue new file mode 100644 index 000000000..793da35cc --- /dev/null +++ b/yudao-ui-app/pages/cart/cart.nvue @@ -0,0 +1,24 @@ +<template> + <view class="container"> + + </view> +</template> + +<script> + export default { + data() { + return { + title: '' + } + }, + onLoad() { + + }, + methods: { + + } + } +</script> + +<style lang="scss" scoped> +</style> diff --git a/yudao-ui-app/pages/category/category.nvue b/yudao-ui-app/pages/category/category.nvue new file mode 100644 index 000000000..793da35cc --- /dev/null +++ b/yudao-ui-app/pages/category/category.nvue @@ -0,0 +1,24 @@ +<template> + <view class="container"> + + </view> +</template> + +<script> + export default { + data() { + return { + title: '' + } + }, + onLoad() { + + }, + methods: { + + } + } +</script> + +<style lang="scss" scoped> +</style> diff --git a/yudao-ui-app/pages/index/index.nvue b/yudao-ui-app/pages/index/index.nvue new file mode 100644 index 000000000..d14e16907 --- /dev/null +++ b/yudao-ui-app/pages/index/index.nvue @@ -0,0 +1,25 @@ +<template> + <view class="container"> + + </view> +</template> + +<script> + export default { + data() { + return { + title: '' + } + }, + onLoad() { + + }, + methods: { + + } + } +</script> + +<style lang="scss" scoped> + +</style> diff --git a/yudao-ui-app/pages/user/user.nvue b/yudao-ui-app/pages/user/user.nvue new file mode 100644 index 000000000..d14e16907 --- /dev/null +++ b/yudao-ui-app/pages/user/user.nvue @@ -0,0 +1,25 @@ +<template> + <view class="container"> + + </view> +</template> + +<script> + export default { + data() { + return { + title: '' + } + }, + onLoad() { + + }, + methods: { + + } + } +</script> + +<style lang="scss" scoped> + +</style> diff --git a/yudao-ui-app/static/images/tabbar/cart-active.png b/yudao-ui-app/static/images/tabbar/cart-active.png new file mode 100644 index 0000000000000000000000000000000000000000..5e9aac700ad5efad0e5b9929873cf82470aa03d9 GIT binary patch literal 6304 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 literal 0 HcmV?d00001 diff --git a/yudao-ui-app/static/images/tabbar/cart.png b/yudao-ui-app/static/images/tabbar/cart.png new file mode 100644 index 0000000000000000000000000000000000000000..7af4e0f01d10ce3d3c6e05f322e36ef361537046 GIT binary patch literal 6169 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 WJdu%}T_1;M{eYg9AylJb8~H!=pJ)L9 literal 0 HcmV?d00001 diff --git a/yudao-ui-app/static/images/tabbar/category-active.png b/yudao-ui-app/static/images/tabbar/category-active.png new file mode 100644 index 0000000000000000000000000000000000000000..d9bce99f595a0f2f5aedb89af88c2ddade95e29a GIT binary patch literal 8172 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 literal 0 HcmV?d00001 diff --git a/yudao-ui-app/static/images/tabbar/category.png b/yudao-ui-app/static/images/tabbar/category.png new file mode 100644 index 0000000000000000000000000000000000000000..38a2eafcd239844820b49324587c5bb4d6d9d18b GIT binary patch literal 7893 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 TSzie6sR1oDeX!!r{fPeq{EyYP literal 0 HcmV?d00001 diff --git a/yudao-ui-app/static/images/tabbar/index-active.png b/yudao-ui-app/static/images/tabbar/index-active.png new file mode 100644 index 0000000000000000000000000000000000000000..c5c8352c44ef1ad1a9f323f95778159d5bd8559a GIT binary patch literal 6891 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 RN$g$$bRHOL)TtpO{s&7?*!%zh literal 0 HcmV?d00001 diff --git a/yudao-ui-app/static/images/tabbar/index.png b/yudao-ui-app/static/images/tabbar/index.png new file mode 100644 index 0000000000000000000000000000000000000000..ff745e3d8c948c2ce4b34412d53461392d88767a GIT binary patch literal 6766 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 literal 0 HcmV?d00001 diff --git a/yudao-ui-app/static/images/tabbar/user-active.png b/yudao-ui-app/static/images/tabbar/user-active.png new file mode 100644 index 0000000000000000000000000000000000000000..9658357ea0e7c876f4fb088f7d4ca38427d95ca7 GIT binary patch literal 8298 zcmYMa2Q*yW8|Xd87$XLwk1kQ78zqRAArhVFJx1?AbVe90M2!$c8BwEmQG(Gs3DF{Y zM2p@_<j(v5zIE?e>zuvTS^Mm>%d?;Idv?q-EoHJhjCTM40GX<ak}kd+`0s{*@y`n5 z${c(_;H9gq2&f!m-U0yF0jf$0`u@*%gGrj04KmKI4VE~QNZ_wvF$uLqCW<eNeEd$y z?sh7{!r9@k)<Rut-+hvbi=K}Qb-i<QZ+L)C^cETmBdO(Q2U9_}w)ecQqGW)C49s-h zp9I80J@$96ewU1q^^|Yu8~7O+VUJ}4_JWuHj$MvjPX)O=+(5H+ig$-vq4J?gD}4wQ zd{&C^Df{nj1k^Q*fCJ_g00+k*M;@Tykp9r-@QAfhV;>TH1q&)iu~82w9P@wQxRIY^ zJ@gXgH>zZol$8x+PGn?ED={+@xn7Gdm)v3a7vkvnJyT9D+<mWhSjb(-ys3K8-mmqA zKSvT>BszI#@2g(FK2Q^M`~tuYguHsCG(`k_et>a+_=88rVJllR@`ydjapC7T3C?Wb z;{H&|XjV(p%|-Ji8tV`+!E4~G@~|=C2u=>Luy1zS%g>iuEUrQ3Er#!54?0FlYlWRn z3X-c$S*<*>;NB<$&Z!Q4y{$xXYNW0%;erBW+TtLbQKYu<JdO>BFy-QcdIE%;qsa}b zVf(Krua-|T+f#PRywp)zikft7tK~Y%7N;0qg7XWdnW@skATv>v8dwBKLOGyC)z>m9 zrudDti82~&nV=^CKywp@6kEMl;UV};;9UYMe0h&WBU<k%`yEWWY`_nR44rU?uuBa< zKCOl{ktifjedlq$utkmamP_`kH~(L8;2d<`q}T)6c_o1hY7|9Ou*AM1{!LnN^5VsA z(;(X@#Fkp5)@zRo1@epEa~#=FGm$Tr;VzR<=SU(uNEtpoy;@esp>rAIc@;%ourh8x z3`U{m%6LB*Y?i<=<&U%1XcBC{+5zNgK1>}%Rf$!<2{k}z0IYza72SUH6Uk1^*pl&` zD*e*CK`QYXlmn(8M}FUNfK4WTV6Xri!jA88yU>alUeZ<6qD@b)E(8tT9JLMPv23KM zt^)+OvEev5KpXXYK|xCPVqf#*lncQoXcyo~-LHb4dCJalw|2K{j@^0s_;Y`*jHvB` zMTPufYg6SLSNTU|?WYeF9}CFzPwkuj{oaX2O2+m;K%s8*V+7ykJfEXFb9eyfog3HA z4YkpA`(&r<QhT=}-iT-l@WG*RzfI0u#l5XSjICE>6>Kuq)8ZNE=1oS``&zV-dGPkx zn$(^-z>6Wh+@)1Sw(|pL&rwKhS%=nEaT$^IeD6c{cDW*ypnca=Ln*yc$lISEqz-dM z)*5fu%n?M^G3yBv@%Oe&F+E%fQ}e@@&Uc3-pSI*>+GdS!JX*ds^pGTpII4;6zKKBv z=Vk73TJR>hR!YfSB-5;0-|dq5v*yj)wv&G{$v3~aSTb)mz5E5f*Z>xNA7Wgn8&GM` z{@E+5l3GO`yVS5})&Z<`0j*lrKrFX=#OQo056+kSq_r>ln)K9!FJ8XCn)U(KxcN~+ z>Nu3n7{VU3zGD-seR%F~Ef9?(z@ZaT*1b4RzY+9Va3#UzHv)D2dB>@A>;d)Fh0 zFsriBFi9XPuiIs6BHL<Lrn3_NFR94K3^Xf~{0m%9w!E@o`UY&_DUZkH&u>ByYx$)T zJIgs7(0$>Ei(r-Oo=l&&$e;c2bXLB5yi4obQY+0?2V@U)<aueTSWw?-fid;{Z#>_n zEQ+wehE;@qBwTyXo>BqXc$F?4-ZHCv^4!Kxww^C=ijDZdY5@*%VScdkKoFZ+`DnC< zIP>04`|0bX*4N?M9b$Z`L)Z^nHZoUxKunVn=3pK6>(<k;+9C0UH2vFQ&AG{qz@}Z! zh+(^_>uZ?o{U4lDz`^yaBl+u<WGcj2>cq8MUj7VdBaGP++VV(gCY)|+*SUdl{>3^> zzUM`<GiJ6iNDxxIS=+Zv?UKjrmW}lL3R+<)_Gb*Qn)Z#^R5TF`Hkude1eegNM6~u# z>G~g2G1t9s`YqM#0!NpQp%h;3uB?d6iQB2DRv^0;<Tf94?Cno3S(3MJdi!`~S-<b( zS`b$Tp*11<Z|alM)G!)j8|MQT?{Dio(p<66+WdF3u5A$QMQ_+TQwt8R#NDV!wF{B8 zT8EjSTMXS!MDfn!{Xccqy?5Rr-6EP66|MYS(q$cAC|eC**;drvNSfZ<r^!;o%qB3Q zgC|To*ikvKoZDC@MM9=Dvqp=HyU}A4+rm$<XubQee$R88Wu5F$s%j;O`y-aj7o7<L z_2B(`Z2DNSzprkH`}S(2h*GO`@5APWZXikypNOg14>lrsmOk1{YR;Cg<*4J%y?<xD z-@U&@9lb^lm{os}4$_Z2I4Zq((Q|r6=lfZ-z~Dzbk~D%Rdv*(5sK%Z!`I)|(0+hE1 zNgFCXC-Vv4Q*z$F;F1+Adp@lGwU;GZpM*1$KTAzxW;3Zrsm^&y*8?<DuZe*DuFk-& z$XyL*w-u%E{kKNc5M?LxEn&5lNI8K55si(<;ndO4vwHh3@7k2YOH&CF!eVvy1g0$4 zNfozlNi`jLAuAEpav9&T;5%^(Q!Ibbuk<*QtZSNfmti~E%BT;5c3bq@1p6-vvb}@} z9|x$*5b#GF1j=MIe24USzPUyiI|N<j(k!NHkv}gB9$$Ym8T`9nyl>Y(L5~AA@7n?D z>fj+x+*H6zhbe=5C>gjm;hG-U_^c>gOh)~(=XmHMf?$#Z*53xheV)23WGdvzIH&OG znuh%H1M!5k+bvyrS2W%zj9O6<YR4n-AA13Bt3>NG=+#ZGibNLyGmu{dK>L!3P9@6< z19hX4)pt?q@krNxGAc0*1Fm!h%c>Y%y)xC3s|nSna=oSx$YQ4p^jn%mKk<W@up$-q zUPj!urAB%}a;>*|Ufwr?F<X{%Gmv?~HeYyEdCbvP=|#bcE+@>dP>Vqn>-g{1dFyQf z5$D$JiF71~z}JeQSE9F1)b>Gk=&n#Fs)3L#*TKC$YlNqHP+N05FWvQU%Jn^g6oY-z z|BaI~=`(?A$<Y2+)Oh1U%CTB%+UN>*^B#GA6eVZ6o*RYAO7K(KEOB#Z(CV+BR`#~~ zB&>|+pb;Dnm&>=fY$#Se8OLB27qV8-@##nb$D!zdUH1Qt_fOXK66#d{mSK3QMz`d~ zVFH-!9pX)G4f9BZEq+wR&h*`=CnA~sXhSJVz+u6AWm7|)I=YkM@XgWYI)OgIQf|;s z?g+mKK6$4maT28+emV>VbspO9f9>G6nE1qCi(|Vr7e_(knmh?AxmGel&u*$!>;GTT zAWDKI-J<Zraos>moxc0f^dgyomw=)5ZxAUn$o@4+tn%u@p!8k^=+h%n5sQ0vs={v1 z!i-hkA;XmH#8R5MZj=n$5O$O)uT?k@J59ro^gP+m<W*y-te9VK=)+AOUIGbXBM!bx zO7dA!Zh`Gt&M82Da%I(vgjGyTKgDUo5puwqST7{Hm9GO@{>kUUNph;WFLFNL_@^)F zs9t7vyUpDvK&KSbYl`gH&hp63YT7Tk^a*#r#!DOO`m+Sb`_v!rfQNj|EB+d5piL`G z+YRq8FT_o~QXmEw9Gvahj_GnItp)>j{#-Yk-Py0!B1e%1f5yw0=#qPdto>_uedzBD zA<S%~7t*`&B>=M$x9)CKqQCgHdP~hwV#$yFvi*5j8~MMmRB6n~+3)M}xYLL+T8-t0 ziYXzLHgzI@>USOv9A=&~gfl4m3=V#0Q|>4Y{u4y#{zRTp9@nK70{`0~mzty3utYO9 z)|?jVS)^r2clyV$LMX_vpWA6xe?bBG(rx~)804RFsw2>XUrBWXvK45d-H{Wd&=i3o zoVmoRr+a)`yPtft$Yszm)4MB!)#mE+L+)^mwHA}3o#s?eoM+pazDQR>=_VdrT~yF4 zwliKRDNgdh+D`K0Qgs7cqvFR-m@djjX3aI**@Wa`zJGRbl$vaE0g&P@Zi+?(`h*g* zHB2opr%z@w$IngL_p&pz=<~b_`}C<uG_#S4T-D?mIIBK%juiB!Ro9l`vmV&e<R$R0 z4oMZ@gEZEUvkPdA)+M+-nf*5K$X}qXF!V2fv|B4FQtElX5Lu#8CtfipEiQH~8b%pz zf~axo48z#cAbHy>G&yTo3U)YSMF+fO83<e`+gb`da3Z~cefMOAG`_TqU=xsgEB^8| zL0j|ceZk+B{E0P;v)_s#GdV8XAS#V9&!3i}xv3A{V)&iACQCP^-Q;(6{{><%gHS6i z2t_AIcRw#czAWGy_`vLaj*|4;ez;%D6ZVm5#@ItU-Zre`9OwVcitk}8d%H=R7uK=k z*4ZhhbfNxoq;xiZnGe9q-6xoNwfbP!bT2$I9A!lZ*HIG?ejZ3Qs(<=0lYfl+_mk2K zKtHZjB;Rpgx8%furM19Xk2fjQb>1`Fl#WPnH74Y4ncaHFd&)iboz`B+SU|@eMGq?? z%dAGi>fM2H9n<4kDdQdTRjIuy2DN?3{lxUVJD*F?#3s}w<$5=K7|C-Vr@!hk9`)sc zYJO}Beo5&<M+XazU&3v@_6~cDbLHSRmb`M=0@ptt*H5rF_$9Tsw^uB&oP@id%k01y zzt>j_{DY;^pS`Uz?&Qe0I&)`nSncHt>D}HH+;cTSOF~SqKfy2TH2sNtcYK3_)GLsT zi6*`)uCVNUO_9UkT2pzYUM)`+rNXizYwu|VycS0gT$KdE`_;Gp)|XH_d2W=@;hi}) zGY1!5xEADj)%&i@Vr$9WKzX8wg1tMewW0%$e82oCcAw{61RlKQJAO`FXM9Kdk`Nhv zJ8zBmO+azdS}Tz0L9SLqIApu5bFykpyDl;pwZ7EImCVFi9}+8l7IdMn`y<Y2<0o7A z$J3Ia(Q2nrs&|1H5CPOPVWn+Y)8UE#6>u?jrwJUfwM(a(l;}LDT99Sl>3K(4l*ha= zs83Tju)jWNY!u!h14<T@vi+tDuz8$x5k<4;Z_wJF>A$11zpM+mU-qLXE{HacKP{*H zb}tq0?i!v~<SKfkpH;eF>Rt1vI>Xlr=Dj14VeGS44o{{N7S?qAC%wa(mwaR!w9=IW zedc|lj49M{1}UAii7eXA*WF>khnF|LK1+An9>rnUzDE={1anPrnz{ab0CbA(@ni#f zAmIrl#t&u*9dO5CG{r-1SLj77X4&qahaE@o^0;UOKdao^@55A}#GJVs@mhiPY(gfF zoo{jLAw{|+po~+!xfV5cg%U^DqpotDLZ5|dvGJ7JFUIH2=@M86BCOWK*#Px{tsm}y zj&!&>&L2I<Ti`cQ<*6)~16tkbzM^#Hz1ydt8`x>8=WJ#~Qs7BHNZU?O6q~P<qTuC{ z=dC#_U-W#q)jsfm_so+W(h8V;^=m0=ND{=w_?+ZcguT;0^mWb2XD?orXOyLp4_TTP zmq@xO8%q7mc!;G3KJ9=+k3RX(hdT0Jl015EupcL7)AsM*h8yv}FbYrbycv%F*0|g> zkcKFR%G#;f{6*frgAk#D6Q#FJIMspN-7m-1o_s6W-i1g;ho=H9F2Ab$E?^?U_q)~= zQ>I+bBH0A%*9%nNJxyeMaOVBYCZ6MmG})RxZ(IIjnMtcCd|=XN(IYeh;F)!=H-L(? zW`^SZA8!h}Uc^aJt4G#q4Im_*ayV$bTh|}!+%ieRnCn7xl%o<I_E_7q;qq0K@V;<b zxGvPqtvO4kZ0v%Opw9Yb0e58at3Z(=h4}?dBhmuZdjX_MX)`6aRsRL#rYy@-297&k z1kw_HHJ0zYu2tgx`e+zg2q$sJ*p3oaLzO!2{<b7pk((|k>l*s~<~8L705J}798d3O zNbX`{@xro9W>XHRd@`R|uyTYeBsU=DbtITPfX`8V-GC)7yN4HV=vs)%mfEooM~K;; zMleYCs{4TLmP<YytkPvAkh=9VV#=w3^Pb(@)?e?~+k7N5p1M#q?qwGEa>y@ll}dY~ zHQRaL1$dbbyBS+10dRvF9{Qi^{N$Ew*Lic~smaQDxWI&QWmjt4eNLo!Ziu}nuWmS; z#(BH#h6#Vl?()`LfbMg!AN<<*&$w+~#$5{226k6X=CCauUiFp6Hpy=lzCH_6zzo@d zBMCV<n=^;nHz@L>0WCKpb}ETIz}?w8d%?qC)9Iy-o8E}a8BxC$Eq3brTFiA*hHDg7 zMd8Yg=IClWSHC@^!pfLTRG`Q8yPR{y4XwvZ$CZ@PYc%B6d5>Wb!Y}#q27N$WNVo@+ z$P4w=N{qb{&nb{$&e=1|7g%p!on0x<X18h<6iu(sm-^9LbNBCDfi63T@O(d&*FlDE zanTdgwVz)CD06HN!YDuTaJ_cS5j#B3;=_Qt;EbyQaZg`vIJO$DZAdT3i5hmT5@k&J zr93LIt1W@4B>fvYuV~gtCw{?<6s^#NA`XDj!j%TYMh%0J#;<q%@qWAv1qKnSS|e&B z9Xo`3jA~aJdYMF=Yh%`br@*tF8QmUmsdd(%oLd$ccwt@j_z{I)0kXDvm7xbs5Gpv2 z4gjr>gvGITjyHt37n_BM)vkzMG@tFeoy3G_;{p^@+#U0&KSu5GU~KDp6ZN9TiEXq# z%|%Jxd1F}#`1Y>n_T4GekaOi(%9wW`X=Iz2kA{8i8Q3eSA-ML%@1X2MiMVw$!K3jq z!?KRpMI)EMO*oz*?0xHL`t-!Im=hAaGMSHCw&q>msUT5yQLb?dl0^i91#Urbv9Nao zf(OeT=@(zL0%G<dm|5RLAS|RfrjzL&>aF&L0r-?_%K3DTJ0W$pGQ9l0k8M%7Cws-% z)kI322mt|9fRPn_-GsqlB&pR^Cdz(FC)9C#J?40!Yp4j$VPAbhcO%nkK*Q83Wij)2 z6O~QDQH12tRQ--a(!Lu1*zlt^EtxO5XHfw^Q^hf!3^-4$BdKAK`(ezhMN2`s<nK7n z!7A6Jo6InoJ1k5O4JNLruf;llXh*OAdtdOd>X7R#a8Npu8bY%}pO{;3$_^bqa@XOl zdf$U;m@i)NTk<Ot{ER*~#z}*FbV_$CAEic2OK4;v|Cp}`ApRm6V=))<7t?~jVb5IS zB&3Jz7ldJlHMuB1Z`pVpmNM@KDcjaNsh$U-s!{4}iTk30i&XSC>VKTNW%n2=zIUeK zfT<2*hEiU{st)OA;DH@4Id}=@vlDiEt6Tlb%JdzB<9B$8Lp#B0-75SI((6gYuVv4R zq)&eMaJFO2QQz*(Y*UDB@7V^WjOg#X@ao;JcJ@0;RDvQG+v8<RbY0g95J~F?PS-59 zr!#!FcHd2i9$X3xKjP3dtH^&jA#^=ET|xECu%!Y|4gL%AHW`RB#^2VWU$hEA#P+VB zaevTFP@J#Mg%uW$n_lv&vFW(Zb0?Ws3R^ma@ZMEyxW8=2ee+n7=4A}kFW<R@o(oaM z`qGY5);}#>$A*nItZtMl`AMYbKUq8}pPAaS7*z=EyAylbYdiplb-Vgte-nBaeCP%1 z#EQND*Q{1Jam3N)Mj4&W|Fc!PRb}%`%F4eHcF)^q`YQ)h_cN$%b{86$4IlD9U96CC z?z%=BUswAI(nq_|zXIj)6b`c{cgZ0BM8sQM!>hga5<BDhj8+&YBX!@mGmS}Q8K@H_ zkJ4&!6@Bd$Ch@L-6;5_(P8oAy2<C-YiC3{vr;#N6-%A%Yt#*BOO}Ud?B_6#K2n6Jc zv}l1Z>gmfVx>bvR*w$SV4%XJI8zqjMYtxz3l}@dv8jf^u0W-v%CYp3?`sC>(7{tS? zN@9+F8p5pm9LWe9EwTt#=ey(jaLKI0Sc4M%u(dHwH$h5#ug8dW1{{=I4Bkx8XAXsJ zeG!Dt`mQkLCOz265PnyCLow?6W$Qo6#RpLb7`MD5xz+t5=6lkq{pj}M-{ucSv;zXQ z@iPQ0IU%tnc}DTJ_9eFcW6P6CBD~AE07V-v6rLvuo7uA1PCryc%vf<Zu7R8D$iMn# z23%&kx8KG%V1v&r+{~VY7u~@J{ly|u-+jHfnXL>5X!B=R5dFM#aOP;+FK7Dc!b!C^ zi9dYbfHm>pLyFD*M1b%><ca0tYY{BJ-6gY|jLp{?sM3|q_BFPAiqCVpH6*dWh=lV` zY(O=?5oA9)A->*IR-BO1aDM-NmyEy(E)(JU25Gq8Fa(l8eE#P3;aZH27`w0gj>X*l zb|K!Ec_Tb7XIV$R!<Oh6Gde){lLiy9pr!a<Xf<k8EHoXnMk7C-8`(mGIH>z@kmnlx zb2+JolrwGk^vX5W+h`=*rWViQRMX($+H8a$*&5P|RZU*WYCW~4#~X@DtFI*=wAwdk zoE~{anZIjD=?V&pl`+fcQ;@E>rvfr%SK>WMSRwnyP=@5z_VpV)5kJy)DlF$j<qXOv zPn601s%P%c9>!^VkIwZkHhhrGb@?oUE(dxtpPR)!4Rbo}Ycf(39#PD@f`jMYLh<Zo zT&3iN-9HAYD()})yiBfOcp08#(6%siWo#apHh4RjQ;YsBc#Xl6(#&*vr?Jh9xb=M& zqxFSZQ1WQ=Yw9)f>_ON;G;Hx!S^_+80hLO)cG=~#j>yM{>JQ0_tno|I^vGs;zdp;F zS<p_F`Wxd+s7w^Wyl)~<VJDP^;2g5@WNef+i6$2L^!XTe(n^j;?iux>{C}-wLv307 z7zp(0?Z<i4wgOBgm;n9v@PJ1fydo{=054YAvZ%7N!C4F<(|2M+_eoZ==~JtXK~$@U z+Z1>zQODwBo=0W>$7~!akNt;S_#D;}MV+GTV9ICb)FS$d12+KN+S!@ET2OeTvfWTo zD#m>o#-|7V97WL2`@9>~M&vm5M_`?LT-i#b7TJYv10HwkzTZz=ZK~N70=u>zFu=C5 zS#8lKYT7|Ho^JLWq)OKVoGYp(r(sk*MGWBj|91HuL>SfdoA|x7JQ^+qtCK2Yyp#`L z1@Q(#5f<!rB~JHoDSr{T1RGpq>_)gT|K)3%=LMNa&NbKRY$dv!?^Yj>dXYRt_sKNH zppGI*!>5{ub2F{J_g?MAb0)!<hMq6Dt!m8+<1;puy>SFg;HU6v7RgVHjqgP7QZ7Bd zN76IaEQ#?<sjb7|T}923=8BbYj4y)l#|-mIaqTt>oA1y!P+o<7yYbMGWVf`-A1GcQ z$ddF(la3JT`ZEfjg^YSSV&a&czwtSDDPi%j>-+<P3EU-`G`e?8SsP{fz2<ypKVfHo zKSaIXo=l=1>DuZ6kAC%0%(3sNKXb~9r4Q=+7&@y}1NNvAl^GE$6c9G-t0IC5;_ZXB z!}7~`1#DW^ya}OuZWM-0G|A_u8ObwkGTN0Va`kxb7Q{<nX_RPn@`UNM1}Zv=mxl9u z!1h8}<t}a8sq0!%`2?BhT^f2|`E#n+O)3+?UR_7|5v6#|K=^=k@1%-w;jT6BmkcX7 zB$&8{#qVKg-cc^er+|t-Wdz(GFpF6-!`8C7E5FzZ4;p819mMhXk_-Uy1+tDkw3_Rl zP{b+pe=xXw@wW0Oq<j7~?-ipi`-X9&7k1j&KSkSZNC1v9Li|5G=BO$0E3N$5f4a7> zNhg0vSID^=C-Y#%t>nR7LW%!!cJrQ=lYD8gqv$6*cqXH@us8&5%cJakq`u--jFEiS zjpJshW;tg7w3bZJNbGy@U}n!!vXQ5#$#h+t<;V{QCp!^5r!SBT1EIT33gn9=rC#^H zdMc*8(C@A4jHHTQJK60$NS%}sJ<!+m589S?IEcc;i=Z~{_=RnGl|mym-I>(bo6Rj3 z9XiUlnF*&p;MuT$xERzmfk0;CIdR`$W_N|peJMT2{=bq{js=C6&8yy6rn%3lrq3!G zxxf5P!GUez&rrBAT4BJkA5AWq%B(o@;~sn4C3@_r;!Tv7Fyxp#Pc5yJ7qKkW%fx_= z1A0G**s|qSKTbX5z&!(g3s$w<MV-(Hw7e6$I1xq3L^2{Zm%ib9#nD{7_%#oE*{b_q z_<OAgsv8G}dt`Negtn-^W<CqLZ8S7HOHLRa$j_)8bpEp2B<y0>;_~y!s_<Ldu^^kN zK`5TEmv?UcsknMavq;=M^6)!L!QCVnuQ5<2Hqf`sFinoB&%|bO0jlvq-09?#NXYTd zGDQVBwT@a{B%O-8d;9p1vG%4^3s^mOQn2h*NTKexAqbnjfX(D3^w;qv$*DB00*$Te zP4!va|FCUrqs8|l;zkQJuqW6G%vwW|L-J29tW8AoAtCgS8csQ1L(Jw<#*i=dA9PZA zQ$_`0EV03=9Y<sZmXL_T7$T6lWf45V5B?ax1yMm6OO*_b-Q%;Zkck+{8|m}Y^EB+V zSP&bF3Hx1A!Kl-jSq5+l1N$=i?w1f<iCF-m78xe*Hs3}Up_eCR?Mg;iBZ{j>J8o9| z1>>*$6Q)=s<2}!xsN=l3BsdpipM(%NO6+}y%PjqILw2+#JIB41CVRom-RuIgIv z>Y86c_w9h7Ax2y&*^3?{B2(*7MA#iFPbiELo6LJ};qrsUR<r*{OxsBef-VZvVIO)y zfABgnO}Cx({)3t&g+*%BvA;l(G0<|~R`u2};=l+B%dtN&XmGwPwMR^fV?bkL=_2_X z4U%{xfzbvW9NKH(PUae*vfa3jGvnLTQH|(DeM~t^%Objce078XB@e&}0W_#ySGF}i z)|8#)tEEz+xYiG8>Yy4|zxXZdl!2V~De&|Q*xj{=M*kgk$jnT={8wTXIu{s_>DE_i z{>A^$Lxy`w0#k2OYp>BADuhpqx5P1^jYLqO34;}hcT;vah*FwJ_&y&#h2FyV)4sL^ zpN`%XwLh@v4W(2uik#OlOvC5cE&8DCWpJ_)Z+`s20Qi4r0T95gQjg5RZBY~`Z#ccv ckCyNsDceQ8B?O{8jjsbxeWImQsfY^uKc#owKmY&$ literal 0 HcmV?d00001 diff --git a/yudao-ui-app/static/images/tabbar/user.png b/yudao-ui-app/static/images/tabbar/user.png new file mode 100644 index 0000000000000000000000000000000000000000..b5367d4cd2f835025ce4e3561832343264036b9b GIT binary patch literal 8070 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 OI-stigDO?Fi1;67LscaJ literal 0 HcmV?d00001 diff --git a/yudao-ui-app/uni.scss b/yudao-ui-app/uni.scss new file mode 100644 index 000000000..b54d6b3b2 --- /dev/null +++ b/yudao-ui-app/uni.scss @@ -0,0 +1,79 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 引入uView主题样式 */ +@import '@/uni_modules/uview-ui/theme.scss'; + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/yudao-ui-app/uni_modules/uview-ui/LICENSE b/yudao-ui-app/uni_modules/uview-ui/LICENSE new file mode 100644 index 000000000..8e39eada8 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 www.uviewui.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/yudao-ui-app/uni_modules/uview-ui/README.md b/yudao-ui-app/uni_modules/uview-ui/README.md new file mode 100644 index 000000000..31bd594da --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/README.md @@ -0,0 +1,104 @@ +<p align="center"> + <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;"> +</p> +<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3> +<h3 align="center">多平台快速开发的UI框架</h3> + +## 说明 + +uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水 + +## 特性 + +- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序 +- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用 +- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨 +- 众多的常用页面和布局,让您专注逻辑,事半功倍 +- 详尽的文档支持,现代化的演示效果 +- 按需引入,精简打包体积 + + +## 安装 + +```bash +# npm方式安装,插件市场导入无需执行此命令 +npm i uview-ui +``` + +## 快速上手 + +1. `main.js`引入uView库 +```js +// main.js +import uView from 'uview-ui'; +Vue.use(uView); +``` + +2. `App.vue`引入基础样式(注意style标签需声明scss属性支持) +```css +/* App.vue */ +<style lang="scss"> +@import "uview-ui/index.scss"; +</style> +``` + +3. `uni.scss`引入全局scss变量文件 +```css +/* uni.scss */ +@import "uview-ui/theme.scss"; +``` + +4. `pages.json`配置easycom规则(按需引入) + +```js +// pages.json +{ + "easycom": { + // npm安装的方式不需要前面的"@/",下载安装的方式需要"@/" + // npm安装方式 + "^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" + // 下载安装方式 + // "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" + }, + // 此为本身已有的内容 + "pages": [ + // ...... + ] +} +``` + +请通过[快速上手](https://www.uviewui.com/components/quickstart.html)了解更详细的内容 + +## 使用方法 +配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。 + +```html +<template> + <u-button text="按钮"></u-button> +</template> +``` + +请通过[快速上手](https://www.uviewui.com/components/quickstart.html)了解更详细的内容 + +## 链接 + +- [官方文档](https://www.uviewui.com/) +- [更新日志](https://www.www.uviewui.com/components/changelog.html) +- [升级指南](https://www.uviewui.com/components/changelog.html) +- [关于我们](https://www.uviewui.com/cooperation/about.html) + +## 预览 + +您可以通过**微信**扫码,查看最佳的演示效果。 +<br> +<br> +<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" > + +## 捐赠uView的研发 + +uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。 + +<img src="https://uviewui.com/common/alipay.png" width="220" ><img style="margin-left: 100px;" src="https://uviewui.com/common/wechat.png" width="220" > + +## 版权信息 +uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。 diff --git a/yudao-ui-app/uni_modules/uview-ui/changelog.md b/yudao-ui-app/uni_modules/uview-ui/changelog.md new file mode 100644 index 000000000..7a54275a2 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/changelog.md @@ -0,0 +1,318 @@ +## 2.0.30(2022-04-04) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. `u-rate`增加`readonly`属性 +2. `tabs`滑块支持设置背景图片 +3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题 +4. `u-code-input`添加光标效果动画 +5. 修复`popup`的`open`事件不触发 +6. 修复`u-flex-column`无效的问题 +7. 修复`u-datetime-picker`索引在特定场合异常问题 +8. 修复`u-datetime-picker`最小时间字符串模板错误问题 +9. `u-swiper`添加`m3u8`验证 +10. `u-swiper`修改判断image和video逻辑 +11. 修复`swiper`无法使用本地图片问题,增加`type`参数 +12. 修复`u-row-notice`格式错误问题 +13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题 +14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题 +15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题 +16. 修复`u-checkbox-group`设置`shape`属性无效的问题 +17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题 +18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题 +19. 修复`u-list`触顶事件的触发错误的问题 +20. 修复`u-text`只有手机号可拨打的问题 +21. 修复`u-textarea`不能换行的问题 +22. 其他修复 +## 2.0.29(2022-03-13) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复`u--text`组件设置`decoration`属性未生效的问题 +2. 修复`u-datetime-picker`使用`formatter`后返回值不正确 +3. 修复`u-datetime-picker` `intercept` 可能为undefined +4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度 +5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效 +6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错,不能选中第一列时间的bug +7. 修复`u-datetime-picker`使用`formatter`后返回值不正确 +8. 修复`u-image`组件`loading`无效果的问题 +9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题 +10. 修复`u-datetime-picker`组件`itemHeight`无效问题 +11. 其他修复 +## 2.0.28(2022-02-22) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. search组件新增searchIconSize属性 +2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56 +3. 修复text value.js 判断日期出format错误问题 +4. priceFormat格式化金额出现精度错误 +5. priceFormat在部分情况下出现精度损失问题 +6. 优化表单rules提示 +7. 修复avatar组件src为空时,展示状态不对 +8. 其他修复 +## 2.0.27(2022-01-28) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1.样式修复 +## 2.0.26(2022-01-28) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1.样式修复 +## 2.0.25(2022-01-27) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复text组件mode=price时,可能会导致精度错误的问题 +2. 添加$u.setConfig()方法,可设置uView内置的config, props, zIndex, color属性,详见:[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE) +3. 优化form组件在errorType=toast时,如果输入错误页面会有抖动的问题 +4. 修复$u.addUnit()对配置默认单位可能无效的问题 +## 2.0.24(2022-01-25) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复swiper在current指定非0时缩放有误 +2. 修复u-icon添加stop属性的时候报错 +3. 优化遗留的通过正则判断rpx单位的问题 +4. 优化Layout布局 vue使用gutter时,会超出固定区域 +5. 优化search组件高度单位问题(rpx -> px) +6. 修复u-image slot 加载和错误的图片失去了高度 +7. 修复u-index-list中footer插槽与header插槽存在性判断错误 +8. 修复部分机型下u-popup关闭时会闪烁 +9. 修复u-image在nvue-app下失去宽高 +10. 修复u-popup运行报错 +11. 修复u-tooltip报错 +12. 修复box-sizing在app下的警告 +13. 修复u-navbar在小程序中报运行时错误 +14. 其他修复 +## 2.0.23(2022-01-24) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题 +2. 修复col组件gutter参数带rpx单位处理不正确的问题 +3. 修复text组件单行时无法显示省略号的问题 +4. navbar添加titleStyle参数 +5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题 +## 2.0.22(2022-01-19) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. $u.page()方法优化,避免在特殊场景可能报错的问题 +2. picker组件添加immediateChange参数 +3. 新增$u.pages()方法 +## 2.0.21(2022-01-19) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 优化:form组件在用户设置rules的时候提示用户model必传 +2. 优化遗留的通过正则判断rpx单位的问题 +3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后,placeholder高度填充不正确 +4. 修复swiper在current指定非0时缩放有误 +5. 修复u-icon添加stop属性的时候报错 +6. 修复upload组件在accept=all的时候没有作用 +7. 修复在text组件mode为phone时call属性无效的问题 +8. 处理u-form clearValidate方法 +9. 其他修复 +## 2.0.20(2022-01-14) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复calendar默认会选择一个日期,如果直接点确定的话,无法取到值的问题 +2. 修复Slider缺少disabled props 还有注释 +3. 修复u-notice-bar点击事件无法拿到index索引值的问题 +4. 修复u-collapse-item在vue文件下,app端自定义插槽不生效的问题 +5. 优化头像为空时显示默认头像 +6. 修复图片地址赋值后判断加载状态为完成问题 +7. 修复日历滚动到默认日期月份区域 +8. search组件暴露点击左边icon事件 +9. 修复u-form clearValidate方法不生效 +10. upload h5端增加返回文件参数(文件的name参数) +11. 处理upload选择文件后url为blob类型无法预览的问题 +12. u-code-input 修复输入框没有往左移出一半屏幕 +13. 修复Upload上传 disabled为true时,控制台报hoverClass类型错误 +14. 临时处理ios app下grid点击坍塌问题 +15. 其他修复 +## 2.0.19(2021-12-29) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 优化微信小程序包体积可在微信中预览,请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码” +2. 优化微信小程序setData性能,处理某些方法如$u.route()无法在模板中使用的问题 +3. navbar添加autoBack参数 +4. 允许avatar组件的事件冒泡 +5. 修复cell组件报错问题 +6. 其他修复 +## 2.0.18(2021-12-28) +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复app端编译报错问题 +2. 重新处理微信小程序端setData过大的性能问题 +3. 修复边框问题 +4. 修复最大最小月份不大于0则没有数据出现的问题 +5. 修复SwipeAction微信小程序端无法上下滑动问题 +6. 修复input的placeholder在小程序端默认显示为true问题 +7. 修复divider组件click事件无效问题 +8. 修复u-code-input maxlength 属性值为 String 类型时显示异常 +9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题 +10. 处理form-item的label为top时,取消错误提示的左边距 +11. 其他修复 +## 2.0.17(2021-12-26) +## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 解决HBuilderX3.3.3.20211225版本导致的样式问题 +2. calendar日历添加monthNum参数 +3. navbar添加center slot +## 2.0.16(2021-12-25) +## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 解决微信小程序setData性能问题 +2. 修复count-down组件change事件不触发问题 +## 2.0.15(2021-12-21) +## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复Cell单元格titleWidth无效 +2. 修复cheakbox组件ischecked不更新 +3. 修复keyboard是否显示"."按键默认值问题 +4. 修复number-keyboard是否显示键盘的"."符号问题 +5. 修复Input输入框 readonly无效 +6. 修复u-avatar 导致打包app、H5时候报错问题 +7. 修复Upload上传deletable无效 +8. 修复upload当设置maxSize时无效的问题 +9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题 +10. 修复rate组件在有padding的view内,显示的星星位置和可触摸区域不匹配,无法正常选中星星 +## 2.0.13(2021-12-14) +## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题 +## 2.0.12(2021-12-14) +## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复tabs组件在vue环境下划线消失的问题 +2. 修复upload组件在安卓小程序无法选择视频的问题 +3. 添加uni.$u.config.unit配置,用于配置参数默认单位,详见:[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE) +4. 修复textarea组件在没绑定v-model时,字符统计不生效问题 +5. 修复nvue下控制是否出现滚动条失效问题 +## 2.0.11(2021-12-13) +## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. text组件align参数无效的问题 +2. subsection组件添加keyName参数 +3. upload组件无法判断[Object file]类型的问题 +4. 处理notify层级过低问题 +5. codeInput组件添加disabledDot参数 +6. 处理actionSheet组件round参数无效的问题 +7. calendar组件添加round参数用于控制圆角值 +8. 处理swipeAction组件在vue环境下默认被打开的问题 +9. button组件的throttleTime节流参数无效的问题 +10. 解决u-notify手动关闭方法close()无效的问题 +11. input组件readonly不生效问题 +12. tag组件type参数为info不生效问题 +## 2.0.10(2021-12-08) +## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复button sendMessagePath属性不生效 +2. 修复DatetimePicker选择器title无效 +3. 修复u-toast设置loading=true不生效 +4. 修复u-text金额模式传0报错 +5. 修复u-toast组件的icon属性配置不生效 +6. button的icon在特殊场景下的颜色优化 +7. IndexList优化,增加# +## 2.0.9(2021-12-01) +## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 优化swiper的height支持100%值(仅vue有效),修复嵌入视频时click事件无法触发的问题 +2. 优化tabs组件对list值为空的判断,或者动态变化list时重新计算相关尺寸的问题 +3. 优化datetime-picker组件逻辑,让其后续打开的默认值为上一次的选中值,需要通过v-model绑定值才有效 +4. 修复upload内嵌在其他组件中,选择图片可能不会换行的问题 +## 2.0.8(2021-12-01) +## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复toast的position参数无效问题 +2. 处理input在ios nvue上无法获得焦点的问题 +3. avatar-group组件添加extraValue参数,让剩余展示数量可手动控制 +4. tabs组件添加keyName参数用于配置从对象中读取的键名 +5. 处理text组件名字脱敏默认配置无效的问题 +6. 处理picker组件item文本太长换行问题 +## 2.0.7(2021-11-30) +## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 修复radio和checkbox动态改变v-model无效的问题。 +2. 优化form规则validator在微信小程序用法 +3. 修复backtop组件mode参数在微信小程序无效的问题 +4. 处理Album的previewFullImage属性无效的问题 +5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题 +## 2.0.6(2021-11-27) +## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. 处理tag组件在vue下边框无效的问题。 +2. 处理popup组件圆角参数可能无效的问题。 +3. 处理tabs组件lineColor参数可能无效的问题。 +4. propgress组件在值很小时,显示异常的问题。 +## 2.0.5(2021-11-25) +## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. calendar在vue下显示异常问题。 +2. form组件labelPosition和errorType参数无效的问题 +3. input组件inputAlign无效的问题 +4. 其他一些修复 +## 2.0.4(2021-11-23) +## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +0. input组件缺失@confirm事件,以及subfix和prefix无效问题 +1. component.scss文件样式在vue下干扰全局布局问题 +2. 修复subsection在vue环境下表现异常的问题 +3. tag组件的bgColor等参数无效的问题 +4. upload组件不换行的问题 +5. 其他的一些修复处理 +## 2.0.3(2021-11-16) +## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. uView2.0已实现全面兼容nvue +2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升 +3. 目前uView2.0为公测阶段,相关细节可能会有变动 +4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html) +5. 处理modal的confirm回调事件拼写错误问题 +6. 处理input组件@input事件参数错误问题 +7. 其他一些修复 +## 2.0.2(2021-11-16) +## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. uView2.0已实现全面兼容nvue +2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升 +3. 目前uView2.0为公测阶段,相关细节可能会有变动 +4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html) +5. 修复input组件formatter参数缺失问题 +6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss +## 2.0.0(2020-11-15) +## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU) + +# uView2.0重磅发布,利剑出鞘,一统江湖 + +1. uView2.0已实现全面兼容nvue +2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升 +3. 目前uView2.0为公测阶段,相关细节可能会有变动 +4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html) +5. 修复input组件formatter参数缺失问题 + + diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u--form/u--form.vue b/yudao-ui-app/uni_modules/uview-ui/components/u--form/u--form.vue new file mode 100644 index 000000000..fdfc212ae --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u--form/u--form.vue @@ -0,0 +1,78 @@ +<template> + <uvForm + ref="uForm" + :model="model" + :rules="rules" + :errorType="errorType" + :borderBottom="borderBottom" + :labelPosition="labelPosition" + :labelWidth="labelWidth" + :labelAlign="labelAlign" + :labelStyle="labelStyle" + :customStyle="customStyle" + > + <slot /> + </uvForm> +</template> + +<script> + /** + * 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件 + * 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转 + */ + import uvForm from '../u-form/u-form.vue'; + import props from '../u-form/props.js' + export default { + // #ifdef MP-WEIXIN + name: 'u-form', + // #endif + // #ifndef MP-WEIXIN + name: 'u--form', + // #endif + mixins: [uni.$u.mpMixin, props, uni.$u.mixin], + components: { + uvForm + }, + created() { + this.children = [] + }, + methods: { + // 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则 + setRules(rules) { + this.$refs.uForm.setRules(rules) + }, + validate() { + /** + * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form + * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的 + * 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children + */ + // #ifdef MP-WEIXIN + this.setMpData() + // #endif + return this.$refs.uForm.validate() + }, + validateField(value, callback) { + // #ifdef MP-WEIXIN + this.setMpData() + // #endif + return this.$refs.uForm.validateField(value, callback) + }, + resetFields() { + // #ifdef MP-WEIXIN + this.setMpData() + // #endif + return this.$refs.uForm.resetFields() + }, + clearValidate(props) { + // #ifdef MP-WEIXIN + this.setMpData() + // #endif + return this.$refs.uForm.clearValidate(props) + }, + setMpData() { + this.$refs.uForm.children = this.children + } + }, + } +</script> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u--image/u--image.vue b/yudao-ui-app/uni_modules/uview-ui/components/u--image/u--image.vue new file mode 100644 index 000000000..21b7ab196 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u--image/u--image.vue @@ -0,0 +1,47 @@ +<template> + <uvImage + :src="src" + :mode="mode" + :width="width" + :height="height" + :shape="shape" + :radius="radius" + :lazyLoad="lazyLoad" + :showMenuByLongpress="showMenuByLongpress" + :loadingIcon="loadingIcon" + :errorIcon="errorIcon" + :showLoading="showLoading" + :showError="showError" + :fade="fade" + :webp="webp" + :duration="duration" + :bgColor="bgColor" + :customStyle="customStyle" + @click="$emit('click')" + @error="$emit('error')" + @load="$emit('load')" + > + <template v-slot:loading> + <slot name="loading"></slot> + </template> + <template v-slot:error> + <slot name="error"></slot> + </template> + </uvImage> +</template> + +<script> + /** + * 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件 + * 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转 + */ + import uvImage from '../u-image/u-image.vue'; + import props from '../u-image/props.js'; + export default { + name: 'u--image', + mixins: [uni.$u.mpMixin, props, uni.$u.mixin], + components: { + uvImage + }, + } +</script> \ No newline at end of file diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u--input/u--input.vue b/yudao-ui-app/uni_modules/uview-ui/components/u--input/u--input.vue new file mode 100644 index 000000000..2887e4cf9 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u--input/u--input.vue @@ -0,0 +1,72 @@ +<template> + <uvInput + :value="value" + :type="type" + :fixed="fixed" + :disabled="disabled" + :disabledColor="disabledColor" + :clearable="clearable" + :password="password" + :maxlength="maxlength" + :placeholder="placeholder" + :placeholderClass="placeholderClass" + :placeholderStyle="placeholderStyle" + :showWordLimit="showWordLimit" + :confirmType="confirmType" + :confirmHold="confirmHold" + :holdKeyboard="holdKeyboard" + :focus="focus" + :autoBlur="autoBlur" + :disableDefaultPadding="disableDefaultPadding" + :cursor="cursor" + :cursorSpacing="cursorSpacing" + :selectionStart="selectionStart" + :selectionEnd="selectionEnd" + :adjustPosition="adjustPosition" + :inputAlign="inputAlign" + :fontSize="fontSize" + :color="color" + :prefixIcon="prefixIcon" + :suffixIcon="suffixIcon" + :suffixIconStyle="suffixIconStyle" + :prefixIconStyle="prefixIconStyle" + :border="border" + :readonly="readonly" + :shape="shape" + :customStyle="customStyle" + :formatter="formatter" + @focus="$emit('focus')" + @blur="$emit('blur')" + @keyboardheightchange="$emit('keyboardheightchange')" + @change="e => $emit('change', e)" + @input="e => $emit('input', e)" + @confirm="e => $emit('confirm', e)" + @clear="$emit('clear')" + @click="$emit('click')" + > + <!-- #ifdef MP --> + <slot name="prefix"></slot> + <slot name="suffix"></slot> + <!-- #endif --> + <!-- #ifndef MP --> + <slot name="prefix" slot="prefix"></slot> + <slot name="suffix" slot="suffix"></slot> + <!-- #endif --> + </uvInput> +</template> + +<script> + /** + * 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件 + * 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转 + */ + import uvInput from '../u-input/u-input.vue'; + import props from '../u-input/props.js' + export default { + name: 'u--input', + mixins: [uni.$u.mpMixin, props, uni.$u.mixin], + components: { + uvInput + }, + } +</script> \ No newline at end of file diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u--text/u--text.vue b/yudao-ui-app/uni_modules/uview-ui/components/u--text/u--text.vue new file mode 100644 index 000000000..44ee52a37 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u--text/u--text.vue @@ -0,0 +1,44 @@ +<template> + <uvText + :type="type" + :show="show" + :text="text" + :prefixIcon="prefixIcon" + :suffixIcon="suffixIcon" + :mode="mode" + :href="href" + :format="format" + :call="call" + :openType="openType" + :bold="bold" + :block="block" + :lines="lines" + :color="color" + :decoration="decoration" + :size="size" + :iconStyle="iconStyle" + :margin="margin" + :lineHeight="lineHeight" + :align="align" + :wordWrap="wordWrap" + :customStyle="customStyle" + @click="$emit('click')" + ></uvText> +</template> + +<script> +/** + * 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件 + * 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转 + * 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法 + */ +import uvText from "../u-text/u-text.vue"; +import props from "../u-text/props.js"; +export default { + name: "u--text", + mixins: [uni.$u.mpMixin, props, uni.$u.mixin], + components: { + uvText, + }, +}; +</script> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u--textarea/u--textarea.vue b/yudao-ui-app/uni_modules/uview-ui/components/u--textarea/u--textarea.vue new file mode 100644 index 000000000..9dbcfbb32 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u--textarea/u--textarea.vue @@ -0,0 +1,47 @@ +<template> + <uvTextarea + :value="value" + :placeholder="placeholder" + :height="height" + :confirmType="confirmType" + :disabled="disabled" + :count="count" + :focus="focus" + :autoHeight="autoHeight" + :fixed="fixed" + :cursorSpacing="cursorSpacing" + :cursor="cursor" + :showConfirmBar="showConfirmBar" + :selectionStart="selectionStart" + :selectionEnd="selectionEnd" + :adjustPosition="adjustPosition" + :disableDefaultPadding="disableDefaultPadding" + :holdKeyboard="holdKeyboard" + :maxlength="maxlength" + :border="border" + :customStyle="customStyle" + :formatter="formatter" + @focus="e => $emit('focus')" + @blur="e => $emit('blur')" + @linechange="e => $emit('linechange', e)" + @confirm="e => $emit('confirm')" + @input="e => $emit('input', e)" + @keyboardheightchange="e => $emit('keyboardheightchange')" + ></uvTextarea> +</template> + +<script> + /** + * 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件 + * 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转 + */ + import uvTextarea from '../u-textarea/u-textarea.vue'; + import props from '../u-textarea/props.js' + export default { + name: 'u--textarea', + mixins: [uni.$u.mpMixin, props, uni.$u.mixin], + components: { + uvTextarea + }, + } +</script> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-action-sheet/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-action-sheet/props.js new file mode 100644 index 000000000..e96e04fd4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-action-sheet/props.js @@ -0,0 +1,54 @@ +export default { + props: { + // 操作菜单是否展示 (默认false) + show: { + type: Boolean, + default: uni.$u.props.actionSheet.show + }, + // 标题 + title: { + type: String, + default: uni.$u.props.actionSheet.title + }, + // 选项上方的描述信息 + description: { + type: String, + default: uni.$u.props.actionSheet.description + }, + // 数据 + actions: { + type: Array, + default: uni.$u.props.actionSheet.actions + }, + // 取消按钮的文字,不为空时显示按钮 + cancelText: { + type: String, + default: uni.$u.props.actionSheet.cancelText + }, + // 点击某个菜单项时是否关闭弹窗 + closeOnClickAction: { + type: Boolean, + default: uni.$u.props.actionSheet.closeOnClickAction + }, + // 处理底部安全区(默认true) + safeAreaInsetBottom: { + type: Boolean, + default: uni.$u.props.actionSheet.safeAreaInsetBottom + }, + // 小程序的打开方式 + openType: { + type: String, + default: uni.$u.props.actionSheet.openType + }, + // 点击遮罩是否允许关闭 (默认true) + closeOnClickOverlay: { + type: Boolean, + default: uni.$u.props.actionSheet.closeOnClickOverlay + }, + // 圆角值 + round: { + type: [Boolean, String, Number], + default: uni.$u.props.actionSheet.round + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue new file mode 100644 index 000000000..98a73d876 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue @@ -0,0 +1,278 @@ + +<template> + <u-popup + :show="show" + mode="bottom" + @close="closeHandler" + :safeAreaInsetBottom="safeAreaInsetBottom" + :round="round" + > + <view class="u-action-sheet"> + <view + class="u-action-sheet__header" + v-if="title" + > + <text class="u-action-sheet__header__title u-line-1">{{title}}</text> + <view + class="u-action-sheet__header__icon-wrap" + @tap.stop="close" + > + <u-icon + name="close" + size="17" + color="#c8c9cc" + bold + ></u-icon> + </view> + </view> + <text + class="u-action-sheet__description" + :style="[{ + marginTop: `${title && description ? 0 : '18px'}` + }]" + v-if="description" + >{{description}}</text> + <slot> + <u-line v-if="description"></u-line> + <view class="u-action-sheet__item-wrap"> + <template v-for="(item, index) in actions"> + <!-- #ifdef MP --> + <button + :key="index" + class="u-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 ? 'u-action-sheet--hover' : ''" + > + <!-- #endif --> + <view + class="u-action-sheet__item-wrap__item" + @tap.stop="selectHandler(index)" + :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''" + :hover-stay-time="150" + > + <template v-if="!item.loading"> + <text + class="u-action-sheet__item-wrap__item__name" + :style="[itemStyle(index)]" + >{{ item.name }}</text> + <text + v-if="item.subname" + class="u-action-sheet__item-wrap__item__subname" + >{{ item.subname }}</text> + </template> + <u-loading-icon + v-else + custom-class="van-action-sheet__loading" + size="18" + mode="circle" + /> + </view> + <!-- #ifdef MP --> + </button> + <!-- #endif --> + <u-line v-if="index !== actions.length - 1"></u-line> + </template> + </view> + </slot> + <u-gap + bgColor="#eaeaec" + height="6" + v-if="cancelText" + ></u-gap> + <view hover-class="u-action-sheet--hover"> + <text + @touchmove.stop.prevent + :hover-stay-time="150" + v-if="cancelText" + class="u-action-sheet__cancel-text" + @tap="cancel" + >{{cancelText}}</text> + </view> + </view> + </u-popup> +</template> + +<script> + import openType from '../../libs/mixin/openType' + import button from '../../libs/mixin/button' + import props from './props.js'; + /** + * ActionSheet 操作菜单 + * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。 + * @tutorial https://www.uviewui.com/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 {Number|String} round 圆角值,默认无圆角 (默认 0 ) + * @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 <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet> + */ + export default { + name: "u-action-sheet", + // 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到 + mixins: [openType, button, uni.$u.mixin, props], + data() { + return { + + } + }, + computed: { + // 操作项目的样式 + itemStyle() { + return (index) => { + let style = {}; + if (this.actions[index].color) style.color = this.actions[index].color + if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize) + // 选项被禁用的样式 + if (this.actions[index].disabled) style.color = '#c0c4cc' + return style; + } + }, + }, + methods: { + closeHandler() { + // 允许点击遮罩关闭时,才发出close事件 + if(this.closeOnClickOverlay) { + this.$emit('close') + } + }, + // 点击取消按钮 + cancel() { + this.$emit('close') + }, + selectHandler(index) { + const item = this.actions[index] + if (item && !item.disabled && !item.loading) { + this.$emit('select', item) + if (this.closeOnClickAction) { + this.$emit('close') + } + } + }, + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-action-sheet-reset-button-width:100% !default; + $u-action-sheet-title-font-size: 16px !default; + $u-action-sheet-title-padding: 12px 30px !default; + $u-action-sheet-title-color: $u-main-color !default; + $u-action-sheet-header-icon-wrap-right:15px !default; + $u-action-sheet-header-icon-wrap-top:15px !default; + $u-action-sheet-description-font-size:13px !default; + $u-action-sheet-description-color:14px !default; + $u-action-sheet-description-margin: 18px 15px !default; + $u-action-sheet-item-wrap-item-padding:15px !default; + $u-action-sheet-item-wrap-name-font-size:16px !default; + $u-action-sheet-item-wrap-subname-font-size:13px !default; + $u-action-sheet-item-wrap-subname-color: #c0c4cc !default; + $u-action-sheet-item-wrap-subname-margin-top:10px !default; + $u-action-sheet-cancel-text-font-size:16px !default; + $u-action-sheet-cancel-text-color:$u-content-color !default; + $u-action-sheet-cancel-text-font-size:15px !default; + $u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default; + + .u-reset-button { + width: $u-action-sheet-reset-button-width; + } + + .u-action-sheet { + text-align: center; + &__header { + position: relative; + padding: $u-action-sheet-title-padding; + &__title { + font-size: $u-action-sheet-title-font-size; + color: $u-action-sheet-title-color; + font-weight: bold; + text-align: center; + } + + &__icon-wrap { + position: absolute; + right: $u-action-sheet-header-icon-wrap-right; + top: $u-action-sheet-header-icon-wrap-top; + } + } + + &__description { + font-size: $u-action-sheet-description-font-size; + color: $u-tips-color; + margin: $u-action-sheet-description-margin; + text-align: center; + } + + &__item-wrap { + + &__item { + padding: $u-action-sheet-item-wrap-item-padding; + @include flex; + align-items: center; + justify-content: center; + flex-direction: column; + + &__name { + font-size: $u-action-sheet-item-wrap-name-font-size; + color: $u-main-color; + text-align: center; + } + + &__subname { + font-size: $u-action-sheet-item-wrap-subname-font-size; + color: $u-action-sheet-item-wrap-subname-color; + margin-top: $u-action-sheet-item-wrap-subname-margin-top; + text-align: center; + } + } + } + + &__cancel-text { + font-size: $u-action-sheet-cancel-text-font-size; + color: $u-action-sheet-cancel-text-color; + text-align: center; + padding: $u-action-sheet-cancel-text-font-size; + } + + &--hover { + background-color: $u-action-sheet-cancel-text-hover-background-color; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-album/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-album/props.js new file mode 100644 index 000000000..75cdb37d5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-album/props.js @@ -0,0 +1,59 @@ +export default { + props: { + // 图片地址,Array<String>|Array<Object>形式 + urls: { + type: Array, + default: uni.$u.props.album.urls + }, + // 指定从数组的对象元素中读取哪个属性作为图片地址 + keyName: { + type: String, + default: uni.$u.props.album.keyName + }, + // 单图时,图片长边的长度 + singleSize: { + type: [String, Number], + default: uni.$u.props.album.singleSize + }, + // 多图时,图片边长 + multipleSize: { + type: [String, Number], + default: uni.$u.props.album.multipleSize + }, + // 多图时,图片水平和垂直之间的间隔 + space: { + type: [String, Number], + default: uni.$u.props.album.space + }, + // 单图时,图片缩放裁剪的模式 + singleMode: { + type: String, + default: uni.$u.props.album.singleMode + }, + // 多图时,图片缩放裁剪的模式 + multipleMode: { + type: String, + default: uni.$u.props.album.multipleMode + }, + // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量 + maxCount: { + type: [String, Number], + default: uni.$u.props.album.maxCount + }, + // 是否可以预览图片 + previewFullImage: { + type: Boolean, + default: uni.$u.props.album.previewFullImage + }, + // 每行展示图片数量,如设置,singleSize和multipleSize将会无效 + rowCount: { + type: [String, Number], + default: uni.$u.props.album.rowCount + }, + // 超出maxCount时是否显示查看更多的提示 + showMore: { + type: Boolean, + default: uni.$u.props.album.showMore + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-album/u-album.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-album/u-album.vue new file mode 100644 index 000000000..687e2d52c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-album/u-album.vue @@ -0,0 +1,259 @@ +<template> + <view class="u-album"> + <view + class="u-album__row" + ref="u-album__row" + v-for="(arr, index) in showUrls" + :forComputedUse="albumWidth" + :key="index" + > + <view + class="u-album__row__wrapper" + v-for="(item, index1) in arr" + :key="index1" + :style="[imageStyle(index + 1, index1 + 1)]" + @tap="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="u-album__row__wrapper__text" + > + <u--text + :text="`+${urls.length - maxCount}`" + color="#fff" + :size="multipleSize * 0.3" + align="center" + customStyle="justify-content: center" + ></u--text> + </view> + </view> + </view> + </view> +</template> + +<script> +import props from './props.js' +// #ifdef APP-NVUE +// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度 +const dom = uni.requireNativePlugin('dom') +// #endif + +/** + * Album 相册 + * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码 + * @tutorial https://www.uviewui.com/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 <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album> + */ +export default { + name: 'u-album', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + 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, + { addUnit, addStyle } = uni.$u, + rowLen = this.showUrls.length, + allLen = this.urls.length + const style = { + marginRight: addUnit(space), + marginBottom: 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 uni.$u.addUnit( + this.urls.length === 1 ? this.singleWidth : this.multipleSize + ) + }, + imageHeight() { + return uni.$u.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.multipleSize + + 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 uni.$u.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.singleSize + this.singleHeight = !isHorizotal + ? this.singleSize + : (res.height / res.width) * this.singleWidth + }, + fail: () => { + this.getComponentWidth() + } + }) + }, + // 获取组件的宽度 + async getComponentWidth() { + // 延时一定时间,以获取dom尺寸 + await uni.$u.sleep(30) + // #ifndef APP-NVUE + this.$uGetRect('.u-album__row').then((size) => { + this.singleWidth = size.width * this.singlePercent + }) + // #endif + + // #ifdef APP-NVUE + // 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组 + const ref = this.$refs['u-album__row'][0] + ref && + dom.getComponentRect(ref, (res) => { + this.singleWidth = res.size.width * this.singlePercent + }) + // #endif + } + } +} +</script> + +<style lang="scss" scoped> +@import '../../libs/css/components.scss'; + +.u-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/yudao-ui-app/uni_modules/uview-ui/components/u-alert/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-alert/props.js new file mode 100644 index 000000000..4297e2c3d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-alert/props.js @@ -0,0 +1,44 @@ +export default { + props: { + // 显示文字 + title: { + type: String, + default: uni.$u.props.alert.title + }, + // 主题,success/warning/info/error + type: { + type: String, + default: uni.$u.props.alert.type + }, + // 辅助性文字 + description: { + type: String, + default: uni.$u.props.alert.description + }, + // 是否可关闭 + closable: { + type: Boolean, + default: uni.$u.props.alert.closable + }, + // 是否显示图标 + showIcon: { + type: Boolean, + default: uni.$u.props.alert.showIcon + }, + // 浅或深色调,light-浅色,dark-深色 + effect: { + type: String, + default: uni.$u.props.alert.effect + }, + // 文字是否居中 + center: { + type: Boolean, + default: uni.$u.props.alert.center + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.alert.fontSize + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-alert/u-alert.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-alert/u-alert.vue new file mode 100644 index 000000000..81f7d43e1 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-alert/u-alert.vue @@ -0,0 +1,243 @@ +<template> + <u-transition + mode="fade" + :show="show" + > + <view + class="u-alert" + :class="[`u-alert--${type}--${effect}`]" + @tap.stop="clickHandler" + :style="[$u.addStyle(customStyle)]" + > + <view + class="u-alert__icon" + v-if="showIcon" + > + <u-icon + :name="iconName" + size="18" + :color="iconColor" + ></u-icon> + </view> + <view + class="u-alert__content" + :style="[{ + paddingRight: closable ? '20px' : 0 + }]" + > + <text + class="u-alert__content__title" + v-if="title" + :style="[{ + fontSize: $u.addUnit(fontSize), + textAlign: center ? 'center' : 'left' + }]" + :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]" + >{{ title }}</text> + <text + class="u-alert__content__desc" + v-if="description" + :style="[{ + fontSize: $u.addUnit(fontSize), + textAlign: center ? 'center' : 'left' + }]" + :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]" + >{{ description }}</text> + </view> + <view + class="u-alert__close" + v-if="closable" + @tap.stop="closeHandler" + > + <u-icon + name="close" + :color="iconColor" + size="15" + ></u-icon> + </view> + </view> + </u-transition> +</template> + +<script> + import props from './props.js'; + /** + * Alert 警告提示 + * @description 警告提示,展现需要关注的信息。 + * @tutorial https://www.uviewui.com/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 <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert> + */ + export default { + name: 'u-alert', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + 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 "../../libs/css/components.scss"; + + .u-alert { + position: relative; + background-color: $u-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: $u-primary; + } + + &--primary--light { + background-color: #ecf5ff; + } + + &--error--dark { + background-color: $u-error; + } + + &--error--light { + background-color: #FEF0F0; + } + + &--success--dark { + background-color: $u-success; + } + + &--success--light { + background-color: #f5fff0; + } + + &--warning--dark { + background-color: $u-warning; + } + + &--warning--light { + background-color: #FDF6EC; + } + + &--info--dark { + background-color: $u-info; + } + + &--info--light { + background-color: #f4f4f5; + } + + &__icon { + margin-right: 5px; + } + + &__content { + @include flex(column); + flex: 1; + + &__title { + color: $u-main-color; + font-size: 14px; + font-weight: bold; + color: #fff; + margin-bottom: 2px; + } + + &__desc { + color: $u-main-color; + font-size: 14px; + flex-wrap: wrap; + color: #fff; + } + } + + &__title--dark, + &__desc--dark { + color: #FFFFFF; + } + + &__text--primary--light, + &__text--primary--light { + color: $u-primary; + } + + &__text--success--light, + &__text--success--light { + color: $u-success; + } + + &__text--warning--light, + &__text--warning--light { + color: $u-warning; + } + + &__text--error--light, + &__text--error--light { + color: $u-error; + } + + &__text--info--light, + &__text--info--light { + color: $u-info; + } + + &__close { + position: absolute; + top: 11px; + right: 10px; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-avatar-group/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-avatar-group/props.js new file mode 100644 index 000000000..58b42ac95 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-avatar-group/props.js @@ -0,0 +1,52 @@ +export default { + props: { + // 头像图片组 + urls: { + type: Array, + default: uni.$u.props.avatarGroup.urls + }, + // 最多展示的头像数量 + maxCount: { + type: [String, Number], + default: uni.$u.props.avatarGroup.maxCount + }, + // 头像形状 + shape: { + type: String, + default: uni.$u.props.avatarGroup.shape + }, + // 图片裁剪模式 + mode: { + type: String, + default: uni.$u.props.avatarGroup.mode + }, + // 超出maxCount时是否显示查看更多的提示 + showMore: { + type: Boolean, + default: uni.$u.props.avatarGroup.showMore + }, + // 头像大小 + size: { + type: [String, Number], + default: uni.$u.props.avatarGroup.size + }, + // 指定从数组的对象元素中读取哪个属性作为图片地址 + keyName: { + type: String, + default: uni.$u.props.avatarGroup.keyName + }, + // 头像之间的遮挡比例 + gap: { + type: [String, Number], + validator(value) { + return value >= 0 && value <= 1 + }, + default: uni.$u.props.avatarGroup.gap + }, + // 需额外显示的值 + extraValue: { + type: [Number, String], + default: uni.$u.props.avatarGroup.extraValue + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue new file mode 100644 index 000000000..7e996d770 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue @@ -0,0 +1,103 @@ +<template> + <view class="u-avatar-group"> + <view + class="u-avatar-group__item" + v-for="(item, index) in showUrl" + :key="index" + :style="{ + marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap) + }" + > + <u-avatar + :size="size" + :shape="shape" + :mode="mode" + :src="$u.test.object(item) ? keyName && item[keyName] || item.url : item" + ></u-avatar> + <view + class="u-avatar-group__item__show-more" + v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)" + @tap="clickHandler" + > + <u--text + color="#ffffff" + :size="size * 0.4" + :text="`+${extraValue || urls.length - showUrl.length}`" + align="center" + customStyle="justify-content: center" + ></u--text> + </view> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * AvatarGroup 头像组 + * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 + * @tutorial https://www.uviewui.com/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 <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=> + */ + export default { + name: 'u-avatar-group', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + + } + }, + computed: { + showUrl() { + return this.urls.slice(0, this.maxCount) + } + }, + methods: { + clickHandler() { + this.$emit('showMore') + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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/yudao-ui-app/uni_modules/uview-ui/components/u-avatar/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-avatar/props.js new file mode 100644 index 000000000..34ca0f282 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-avatar/props.js @@ -0,0 +1,78 @@ +export default { + props: { + // 头像图片路径(不能为相对路径) + src: { + type: String, + default: uni.$u.props.avatar.src + }, + // 头像形状,circle-圆形,square-方形 + shape: { + type: String, + default: uni.$u.props.avatar.shape + }, + // 头像尺寸 + size: { + type: [String, Number], + default: uni.$u.props.avatar.size + }, + // 裁剪模式 + mode: { + type: String, + default: uni.$u.props.avatar.mode + }, + // 显示的文字 + text: { + type: String, + default: uni.$u.props.avatar.text + }, + // 背景色 + bgColor: { + type: String, + default: uni.$u.props.avatar.bgColor + }, + // 文字颜色 + color: { + type: String, + default: uni.$u.props.avatar.color + }, + // 文字大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.avatar.fontSize + }, + // 显示的图标 + icon: { + type: String, + default: uni.$u.props.avatar.icon + }, + // 显示小程序头像,只对百度,微信,QQ小程序有效 + mpAvatar: { + type: Boolean, + default: uni.$u.props.avatar.mpAvatar + }, + // 是否使用随机背景色 + randomBgColor: { + type: Boolean, + default: uni.$u.props.avatar.randomBgColor + }, + // 加载失败的默认头像(组件有内置默认图片) + defaultUrl: { + type: String, + default: uni.$u.props.avatar.defaultUrl + }, + // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间 + colorIndex: { + type: [String, Number], + // 校验参数规则,索引在0-19之间 + validator(n) { + return uni.$u.test.range(n, [0, 19]) || n === '' + }, + default: uni.$u.props.avatar.colorIndex + }, + // 组件标识符 + name: { + type: String, + default: uni.$u.props.avatar.name + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-avatar/u-avatar.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-avatar/u-avatar.vue new file mode 100644 index 000000000..3319be581 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-avatar/u-avatar.vue @@ -0,0 +1,172 @@ +<template> + <view + class="u-avatar" + :class="[`u-avatar--${shape}`]" + :style="[{ + backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $u.random(0, 19)] : bgColor) : 'transparent', + width: $u.addUnit(size), + height: $u.addUnit(size), + }, $u.addStyle(customStyle)]" + @tap="clickHandler" + > + <slot> + <!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU --> + <open-data + v-if="mpAvatar && allowMp" + type="userAvatarUrl" + :style="[{ + width: $u.addUnit(size), + height: $u.addUnit(size) + }]" + /> + <!-- #endif --> + <!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU --> + <template v-if="mpAvatar && allowMp"></template> + <!-- #endif --> + <u-icon + v-else-if="icon" + :name="icon" + :size="fontSize" + :color="color" + ></u-icon> + <u--text + v-else-if="text" + :text="text" + :size="fontSize" + :color="color" + align="center" + customStyle="justify-content: center" + ></u--text> + <image + class="u-avatar__image" + v-else + :class="[`u-avatar__image--${shape}`]" + :src="avatarUrl || defaultUrl" + :mode="mode" + @error="errorHandler" + :style="[{ + width: $u.addUnit(size), + height: $u.addUnit(size) + }]" + ></image> + </slot> + </view> +</template> + +<script> + import props from './props.js'; + const base64Avatar = + "data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYG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"; + /** + * Avatar 头像 + * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 + * @tutorial https://www.uviewui.com/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 <u-avatar :src="src" mode="square"></u-avatar> + */ + export default { + name: 'u-avatar', + mixins: [uni.$u.mpMixin, uni.$u.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 "../../libs/css/components.scss"; + + .u-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/yudao-ui-app/uni_modules/uview-ui/components/u-back-top/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-back-top/props.js new file mode 100644 index 000000000..6c702c286 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-back-top/props.js @@ -0,0 +1,54 @@ +export default { + props: { + // 返回顶部的形状,circle-圆形,square-方形 + mode: { + type: String, + default: uni.$u.props.backtop.mode + }, + // 自定义图标 + icon: { + type: String, + default: uni.$u.props.backtop.icon + }, + // 提示文字 + text: { + type: String, + default: uni.$u.props.backtop.text + }, + // 返回顶部滚动时间 + duration: { + type: [String, Number], + default: uni.$u.props.backtop.duration + }, + // 滚动距离 + scrollTop: { + type: [String, Number], + default: uni.$u.props.backtop.scrollTop + }, + // 距离顶部多少距离显示,单位px + top: { + type: [String, Number], + default: uni.$u.props.backtop.top + }, + // 返回顶部按钮到底部的距离,单位px + bottom: { + type: [String, Number], + default: uni.$u.props.backtop.bottom + }, + // 返回顶部按钮到右边的距离,单位px + right: { + type: [String, Number], + default: uni.$u.props.backtop.right + }, + // 层级 + zIndex: { + type: [String, Number], + default: uni.$u.props.backtop.zIndex + }, + // 图标的样式,对象形式 + iconStyle: { + type: Object, + default: uni.$u.props.backtop.iconStyle + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-back-top/u-back-top.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-back-top/u-back-top.vue new file mode 100644 index 000000000..2d075667f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-back-top/u-back-top.vue @@ -0,0 +1,129 @@ +<template> + <u-transition + mode="fade" + :customStyle="backTopStyle" + :show="show" + > + <view + class="u-back-top" + :style="[contentStyle]" + v-if="!$slots.default && !$slots.$default" + @click="backToTop" + > + <u-icon + :name="icon" + :custom-style="iconStyle" + ></u-icon> + <text + v-if="text" + class="u-back-top__text" + >{{text}}</text> + </view> + <slot v-else /> + </u-transition> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const dom = weex.requireModule('dom') + // #endif + /** + * backTop 返回顶部 + * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。 + * @tutorial https://uviewui.com/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 <u-back-top :scrollTop="scrollTop"></u-back-top> + */ + export default { + name: 'u-back-top', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + computed: { + backTopStyle() { + // 动画组件样式 + const style = { + bottom: uni.$u.addUnit(this.bottom), + right: uni.$u.addUnit(this.right), + width: '40px', + height: '40px', + position: 'fixed', + zIndex: 10, + } + return style + }, + show() { + return uni.$u.getPx(this.scrollTop) > uni.$u.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 uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + methods: { + backToTop() { + // #ifdef APP-NVUE + if (!this.$parent.$refs['u-back-top']) { + uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`) + } + dom.scrollToElement(this.$parent.$refs['u-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 '../../libs/css/components.scss'; + $u-back-top-flex:1 !default; + $u-back-top-height:100% !default; + $u-back-top-background-color:#E1E1E1 !default; + $u-back-top-tips-font-size:12px !default; + .u-back-top { + @include flex; + flex-direction: column; + align-items: center; + flex:$u-back-top-flex; + height: $u-back-top-height; + justify-content: center; + background-color: $u-back-top-background-color; + + &__tips { + font-size:$u-back-top-tips-font-size; + transform: scale(0.8); + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-badge/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-badge/props.js new file mode 100644 index 000000000..74c032c39 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-badge/props.js @@ -0,0 +1,72 @@ +export default { + props: { + // 是否显示圆点 + isDot: { + type: Boolean, + default: uni.$u.props.badge.isDot + }, + // 显示的内容 + value: { + type: [Number, String], + default: uni.$u.props.badge.value + }, + // 是否显示 + show: { + type: Boolean, + default: uni.$u.props.badge.show + }, + // 最大值,超过最大值会显示 '{max}+' + max: { + type: [Number, String], + default: uni.$u.props.badge.max + }, + // 主题类型,error|warning|success|primary + type: { + type: String, + default: uni.$u.props.badge.type + }, + // 当数值为 0 时,是否展示 Badge + showZero: { + type: Boolean, + default: uni.$u.props.badge.showZero + }, + // 背景颜色,优先级比type高,如设置,type参数会失效 + bgColor: { + type: [String, null], + default: uni.$u.props.badge.bgColor + }, + // 字体颜色 + color: { + type: [String, null], + default: uni.$u.props.badge.color + }, + // 徽标形状,circle-四角均为圆角,horn-左下角为直角 + shape: { + type: String, + default: uni.$u.props.badge.shape + }, + // 设置数字的显示方式,overflow|ellipsis|limit + // overflow会根据max字段判断,超出显示`${max}+` + // ellipsis会根据max判断,超出显示`${max}...` + // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数 + numberType: { + type: String, + default: uni.$u.props.badge.numberType + }, + // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效 + offset: { + type: Array, + default: uni.$u.props.badge.offset + }, + // 是否反转背景和字体颜色 + inverted: { + type: Boolean, + default: uni.$u.props.badge.inverted + }, + // 是否绝对定位 + absolute: { + type: Boolean, + default: uni.$u.props.badge.absolute + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-badge/u-badge.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-badge/u-badge.vue new file mode 100644 index 000000000..53cfc8115 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-badge/u-badge.vue @@ -0,0 +1,171 @@ +<template> + <text + v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)" + :class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]" + :style="[$u.addStyle(customStyle), badgeStyle]" + class="u-badge" + >{{ isDot ? '' :showValue }}</text> +</template> + +<script> + import props from './props.js'; + /** + * badge 徽标数 + * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。 + * @tutorial https://uviewui.com/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 <u-badge :type="type" :count="count"></u-badge> + */ + export default { + name: 'u-badge', + mixins: [uni.$u.mpMixin, props, uni.$u.mixin], + 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 = uni.$u.addUnit(top) + style.right = uni.$u.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) + } + }, + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + $u-badge-primary: $u-primary !default; + $u-badge-error: $u-error !default; + $u-badge-success: $u-success !default; + $u-badge-info: $u-info !default; + $u-badge-warning: $u-warning !default; + $u-badge-dot-radius: 100px !default; + $u-badge-dot-size: 8px !default; + $u-badge-dot-right: 4px !default; + $u-badge-dot-top: 0 !default; + $u-badge-text-font-size: 11px !default; + $u-badge-text-right: 10px !default; + $u-badge-text-padding: 2px 5px !default; + $u-badge-text-align: center !default; + $u-badge-text-color: #FFFFFF !default; + + .u-badge { + border-top-right-radius: $u-badge-dot-radius; + border-top-left-radius: $u-badge-dot-radius; + border-bottom-left-radius: $u-badge-dot-radius; + border-bottom-right-radius: $u-badge-dot-radius; + @include flex; + line-height: $u-badge-text-font-size; + text-align: $u-badge-text-align; + font-size: $u-badge-text-font-size; + color: $u-badge-text-color; + + &--dot { + height: $u-badge-dot-size; + width: $u-badge-dot-size; + } + + &--inverted { + font-size: 13px; + } + + &--not-dot { + padding: $u-badge-text-padding; + } + + &--horn { + border-bottom-left-radius: 0; + } + + &--primary { + background-color: $u-badge-primary; + } + + &--primary--inverted { + color: $u-badge-primary; + } + + &--error { + background-color: $u-badge-error; + } + + &--error--inverted { + color: $u-badge-error; + } + + &--success { + background-color: $u-badge-success; + } + + &--success--inverted { + color: $u-badge-success; + } + + &--info { + background-color: $u-badge-info; + } + + &--info--inverted { + color: $u-badge-info; + } + + &--warning { + background-color: $u-badge-warning; + } + + &--warning--inverted { + color: $u-badge-warning; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-button/nvue.scss b/yudao-ui-app/uni_modules/uview-ui/components/u-button/nvue.scss new file mode 100644 index 000000000..490db7d99 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-button/nvue.scss @@ -0,0 +1,46 @@ +$u-button-active-opacity:0.75 !default; +$u-button-loading-text-margin-left:4px !default; +$u-button-text-color: #FFFFFF !default; +$u-button-text-plain-error-color:$u-error !default; +$u-button-text-plain-warning-color:$u-warning !default; +$u-button-text-plain-success-color:$u-success !default; +$u-button-text-plain-info-color:$u-info !default; +$u-button-text-plain-primary-color:$u-primary !default; +.u-button { + &--active { + opacity: $u-button-active-opacity; + } + + &--active--plain { + background-color: rgb(217, 217, 217); + } + + &__loading-text { + margin-left:$u-button-loading-text-margin-left; + } + + &__text, + &__loading-text { + color:$u-button-text-color; + } + + &__text--plain--error { + color:$u-button-text-plain-error-color; + } + + &__text--plain--warning { + color:$u-button-text-plain-warning-color; + } + + &__text--plain--success{ + color:$u-button-text-plain-success-color; + } + + &__text--plain--info { + color:$u-button-text-plain-info-color; + } + + &__text--plain--primary { + color:$u-button-text-plain-primary-color; + } +} \ No newline at end of file diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-button/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-button/props.js new file mode 100644 index 000000000..07fd84467 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-button/props.js @@ -0,0 +1,161 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-16 10:04:04 + * @LastAuthor : LQ + * @lastTime : 2021-08-16 10:04:24 + * @FilePath : /u-view2.0/uview-ui/components/u-button/props.js + */ +export default { + props: { + // 是否细边框 + hairline: { + type: Boolean, + default: uni.$u.props.button.hairline + }, + // 按钮的预置样式,info,primary,error,warning,success + type: { + type: String, + default: uni.$u.props.button.type + }, + // 按钮尺寸,large,normal,small,mini + size: { + type: String, + default: uni.$u.props.button.size + }, + // 按钮形状,circle(两边为半圆),square(带圆角) + shape: { + type: String, + default: uni.$u.props.button.shape + }, + // 按钮是否镂空 + plain: { + type: Boolean, + default: uni.$u.props.button.plain + }, + // 是否禁止状态 + disabled: { + type: Boolean, + default: uni.$u.props.button.disabled + }, + // 是否加载中 + loading: { + type: Boolean, + default: uni.$u.props.button.loading + }, + // 加载中提示文字 + loadingText: { + type: [String, Number], + default: uni.$u.props.button.loadingText + }, + // 加载状态图标类型 + loadingMode: { + type: String, + default: uni.$u.props.button.loadingMode + }, + // 加载图标大小 + loadingSize: { + type: [String, Number], + default: uni.$u.props.button.loadingSize + }, + // 开放能力,具体请看uniapp稳定关于button组件部分说明 + // https://uniapp.dcloud.io/component/button + openType: { + type: String, + default: uni.$u.props.button.openType + }, + // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 + // 取值为submit(提交表单),reset(重置表单) + formType: { + type: String, + default: uni.$u.props.button.formType + }, + // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 + // 只微信小程序、QQ小程序有效 + appParameter: { + type: String, + default: uni.$u.props.button.appParameter + }, + // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效 + hoverStopPropagation: { + type: Boolean, + default: uni.$u.props.button.hoverStopPropagation + }, + // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效 + lang: { + type: String, + default: uni.$u.props.button.lang + }, + // 会话来源,open-type="contact"时有效。只微信小程序有效 + sessionFrom: { + type: String, + default: uni.$u.props.button.sessionFrom + }, + // 会话内消息卡片标题,open-type="contact"时有效 + // 默认当前标题,只微信小程序有效 + sendMessageTitle: { + type: String, + default: uni.$u.props.button.sendMessageTitle + }, + // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效 + // 默认当前分享路径,只微信小程序有效 + sendMessagePath: { + type: String, + default: uni.$u.props.button.sendMessagePath + }, + // 会话内消息卡片图片,open-type="contact"时有效 + // 默认当前页面截图,只微信小程序有效 + sendMessageImg: { + type: String, + default: uni.$u.props.button.sendMessageImg + }, + // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示, + // 用户点击后可以快速发送小程序消息,open-type="contact"时有效 + showMessageCard: { + type: Boolean, + default: uni.$u.props.button.showMessageCard + }, + // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 + dataName: { + type: String, + default: uni.$u.props.button.dataName + }, + // 节流,一定时间内只能触发一次 + throttleTime: { + type: [String, Number], + default: uni.$u.props.button.throttleTime + }, + // 按住后多久出现点击态,单位毫秒 + hoverStartTime: { + type: [String, Number], + default: uni.$u.props.button.hoverStartTime + }, + // 手指松开后点击态保留时间,单位毫秒 + hoverStayTime: { + type: [String, Number], + default: uni.$u.props.button.hoverStayTime + }, + // 按钮文字,之所以通过props传入,是因为slot传入的话 + // nvue中无法控制文字的样式 + text: { + type: [String, Number], + default: uni.$u.props.button.text + }, + // 按钮图标 + icon: { + type: String, + default: uni.$u.props.button.icon + }, + // 按钮图标 + iconColor: { + type: String, + default: uni.$u.props.button.icon + }, + // 按钮颜色,支持传入linear-gradient渐变色 + color: { + type: String, + default: uni.$u.props.button.color + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-button/u-button.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-button/u-button.vue new file mode 100644 index 000000000..29001b097 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-button/u-button.vue @@ -0,0 +1,490 @@ +<template> + <!-- #ifndef APP-NVUE --> + <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="getphonenumber" + @getuserinfo="getuserinfo" + @error="error" + @opensetting="opensetting" + @launchapp="launchapp" + :hover-class="!disabled && !loading ? 'u-button--active' : ''" + class="u-button u-reset-button" + :style="[baseColor, $u.addStyle(customStyle)]" + @tap="clickHandler" + :class="bemClass" + > + <template v-if="loading"> + <u-loading-icon + :mode="loadingMode" + :size="textSize * 1.15" + :color="loadingColor" + ></u-loading-icon> + <text + class="u-button__loading-text" + :style="[{ fontSize: textSize + 'px' }]" + >{{ loadingText || text }}</text + > + </template> + <template v-else> + <u-icon + v-if="icon" + :name="icon" + :color="iconColorCom" + :size="textSize * 1.35" + :customStyle="{ marginRight: '2px' }" + ></u-icon> + <slot> + <text + class="u-button__text" + :style="[{ fontSize: textSize + 'px' }]" + >{{ text }}</text + > + </slot> + </template> + </button> + <!-- #endif --> + + <!-- #ifdef APP-NVUE --> + <view + :hover-start-time="Number(hoverStartTime)" + :hover-stay-time="Number(hoverStayTime)" + class="u-button" + :hover-class=" + !disabled && !loading && !color && (plain || type === 'info') + ? 'u-button--active--plain' + : !disabled && !loading && !plain + ? 'u-button--active' + : '' + " + @tap="clickHandler" + :class="bemClass" + :style="[baseColor, $u.addStyle(customStyle)]" + > + <template v-if="loading"> + <u-loading-icon + :mode="loadingMode" + :size="textSize * 1.15" + :color="loadingColor" + ></u-loading-icon> + <text + class="u-button__loading-text" + :style="[nvueTextStyle]" + :class="[plain && `u-button__text--plain--${type}`]" + >{{ loadingText || text }}</text + > + </template> + <template v-else> + <u-icon + v-if="icon" + :name="icon" + :color="iconColorCom" + :size="textSize * 1.35" + ></u-icon> + <text + class="u-button__text" + :style="[ + { + marginLeft: icon ? '2px' : 0, + }, + nvueTextStyle, + ]" + :class="[plain && `u-button__text--plain--${type}`]" + >{{ text }}</text + > + </template> + </view> + <!-- #endif --> +</template> + +<script> +import button from "../../libs/mixin/button.js"; +import openType from "../../libs/mixin/openType.js"; +import props from "./props.js"; +/** + * button 按钮 + * @description Button 按钮 + * @tutorial https://www.uviewui.com/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 <u-button>月落</u-button> + */ +export default { + name: "u-button", + // #ifdef MP + mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props], + // #endif + // #ifndef MP + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + // #endif + 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 + : uni.$u.config.color[`u-${this.type}`]; + } + if (this.type === "info") { + return "#c9c9c9"; + } + return "rgb(200, 200, 200)"; + }, + iconColorCom() { + // 如果是镂空状态,设置了color就用color值,否则使用主题颜色, + // u-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; + }, + }, + methods: { + clickHandler() { + // 非禁止并且非加载中,才能点击 + if (!this.disabled && !this.loading) { + // 进行节流控制,每this.throttle毫秒内,只在开始处执行 + uni.$u.throttle(() => { + this.$emit("click"); + }, this.throttleTime); + } + }, + // 下面为对接uniapp官方按钮开放能力事件回调的对接 + getphonenumber(res) { + this.$emit("getphonenumber", res); + }, + getuserinfo(res) { + this.$emit("getuserinfo", res); + }, + error(res) { + this.$emit("error", res); + }, + opensetting(res) { + this.$emit("opensetting", res); + }, + launchapp(res) { + this.$emit("launchapp", res); + }, + }, +}; +</script> + +<style lang="scss" scoped> +@import "../../libs/css/components.scss"; + +/* #ifndef APP-NVUE */ +@import "./vue.scss"; +/* #endif */ + +/* #ifdef APP-NVUE */ +@import "./nvue.scss"; +/* #endif */ + +$u-button-u-button-height: 40px !default; +$u-button-text-font-size: 15px !default; +$u-button-loading-text-font-size: 15px !default; +$u-button-loading-text-margin-left: 4px !default; +$u-button-large-width: 100% !default; +$u-button-large-height: 50px !default; +$u-button-normal-padding: 0 12px !default; +$u-button-large-padding: 0 15px !default; +$u-button-normal-font-size: 14px !default; +$u-button-small-min-width: 60px !default; +$u-button-small-height: 30px !default; +$u-button-small-padding: 0px 8px !default; +$u-button-mini-padding: 0px 8px !default; +$u-button-small-font-size: 12px !default; +$u-button-mini-height: 22px !default; +$u-button-mini-font-size: 10px !default; +$u-button-mini-min-width: 50px !default; +$u-button-disabled-opacity: 0.5 !default; +$u-button-info-color: #323233 !default; +$u-button-info-background-color: #fff !default; +$u-button-info-border-color: #ebedf0 !default; +$u-button-info-border-width: 1px !default; +$u-button-info-border-style: solid !default; +$u-button-success-color: #fff !default; +$u-button-success-background-color: $u-success !default; +$u-button-success-border-color: $u-button-success-background-color !default; +$u-button-success-border-width: 1px !default; +$u-button-success-border-style: solid !default; +$u-button-primary-color: #fff !default; +$u-button-primary-background-color: $u-primary !default; +$u-button-primary-border-color: $u-button-primary-background-color !default; +$u-button-primary-border-width: 1px !default; +$u-button-primary-border-style: solid !default; +$u-button-error-color: #fff !default; +$u-button-error-background-color: $u-error !default; +$u-button-error-border-color: $u-button-error-background-color !default; +$u-button-error-border-width: 1px !default; +$u-button-error-border-style: solid !default; +$u-button-warning-color: #fff !default; +$u-button-warning-background-color: $u-warning !default; +$u-button-warning-border-color: $u-button-warning-background-color !default; +$u-button-warning-border-width: 1px !default; +$u-button-warning-border-style: solid !default; +$u-button-block-width: 100% !default; +$u-button-circle-border-top-right-radius: 100px !default; +$u-button-circle-border-top-left-radius: 100px !default; +$u-button-circle-border-bottom-left-radius: 100px !default; +$u-button-circle-border-bottom-right-radius: 100px !default; +$u-button-square-border-top-right-radius: 3px !default; +$u-button-square-border-top-left-radius: 3px !default; +$u-button-square-border-bottom-left-radius: 3px !default; +$u-button-square-border-bottom-right-radius: 3px !default; +$u-button-icon-min-width: 1em !default; +$u-button-plain-background-color: #fff !default; +$u-button-hairline-border-width: 0.5px !default; + +.u-button { + height: $u-button-u-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: $u-button-text-font-size; + } + + &__loading-text { + font-size: $u-button-loading-text-font-size; + margin-left: $u-button-loading-text-margin-left; + } + + &--large { + /* #ifndef APP-NVUE */ + width: $u-button-large-width; + /* #endif */ + height: $u-button-large-height; + padding: $u-button-large-padding; + } + + &--normal { + padding: $u-button-normal-padding; + font-size: $u-button-normal-font-size; + } + + &--small { + /* #ifndef APP-NVUE */ + min-width: $u-button-small-min-width; + /* #endif */ + height: $u-button-small-height; + padding: $u-button-small-padding; + font-size: $u-button-small-font-size; + } + + &--mini { + height: $u-button-mini-height; + font-size: $u-button-mini-font-size; + /* #ifndef APP-NVUE */ + min-width: $u-button-mini-min-width; + /* #endif */ + padding: $u-button-mini-padding; + } + + &--disabled { + opacity: $u-button-disabled-opacity; + } + + &--info { + color: $u-button-info-color; + background-color: $u-button-info-background-color; + border-color: $u-button-info-border-color; + border-width: $u-button-info-border-width; + border-style: $u-button-info-border-style; + } + + &--success { + color: $u-button-success-color; + background-color: $u-button-success-background-color; + border-color: $u-button-success-border-color; + border-width: $u-button-success-border-width; + border-style: $u-button-success-border-style; + } + + &--primary { + color: $u-button-primary-color; + background-color: $u-button-primary-background-color; + border-color: $u-button-primary-border-color; + border-width: $u-button-primary-border-width; + border-style: $u-button-primary-border-style; + } + + &--error { + color: $u-button-error-color; + background-color: $u-button-error-background-color; + border-color: $u-button-error-border-color; + border-width: $u-button-error-border-width; + border-style: $u-button-error-border-style; + } + + &--warning { + color: $u-button-warning-color; + background-color: $u-button-warning-background-color; + border-color: $u-button-warning-border-color; + border-width: $u-button-warning-border-width; + border-style: $u-button-warning-border-style; + } + + &--block { + @include flex; + width: $u-button-block-width; + } + + &--circle { + border-top-right-radius: $u-button-circle-border-top-right-radius; + border-top-left-radius: $u-button-circle-border-top-left-radius; + border-bottom-left-radius: $u-button-circle-border-bottom-left-radius; + border-bottom-right-radius: $u-button-circle-border-bottom-right-radius; + } + + &--square { + border-bottom-left-radius: $u-button-square-border-top-right-radius; + border-bottom-right-radius: $u-button-square-border-top-left-radius; + border-top-left-radius: $u-button-square-border-bottom-left-radius; + border-top-right-radius: $u-button-square-border-bottom-right-radius; + } + + &__icon { + /* #ifndef APP-NVUE */ + min-width: $u-button-icon-min-width; + line-height: inherit !important; + vertical-align: top; + /* #endif */ + } + + &--plain { + background-color: $u-button-plain-background-color; + } + + &--hairline { + border-width: $u-button-hairline-border-width !important; + } +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-button/vue.scss b/yudao-ui-app/uni_modules/uview-ui/components/u-button/vue.scss new file mode 100644 index 000000000..32019b278 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-button/vue.scss @@ -0,0 +1,80 @@ +// nvue下hover-class无效 +$u-button-before-top:50% !default; +$u-button-before-left:50% !default; +$u-button-before-width:100% !default; +$u-button-before-height:100% !default; +$u-button-before-transform:translate(-50%, -50%) !default; +$u-button-before-opacity:0 !default; +$u-button-before-background-color:#000 !default; +$u-button-before-border-color:#000 !default; +$u-button-active-before-opacity:.15 !default; +$u-button-icon-margin-left:4px !default; +$u-button-plain-u-button-info-color:$u-info; +$u-button-plain-u-button-success-color:$u-success; +$u-button-plain-u-button-error-color:$u-error; +$u-button-plain-u-button-warning-color:$u-error; + +.u-button { + width: 100%; + + &__text { + white-space: nowrap; + line-height: 1; + } + + &:before { + position: absolute; + top:$u-button-before-top; + left:$u-button-before-left; + width:$u-button-before-width; + height:$u-button-before-height; + border: inherit; + border-radius: inherit; + transform:$u-button-before-transform; + opacity:$u-button-before-opacity; + content: " "; + background-color:$u-button-before-background-color; + border-color:$u-button-before-border-color; + } + + &--active { + &:before { + opacity: .15 + } + } + + &__icon+&__text:not(:empty), + &__loading-text { + margin-left:$u-button-icon-margin-left; + } + + &--plain { + &.u-button--primary { + color: $u-primary; + } + } + + &--plain { + &.u-button--info { + color:$u-button-plain-u-button-info-color; + } + } + + &--plain { + &.u-button--success { + color:$u-button-plain-u-button-success-color; + } + } + + &--plain { + &.u-button--error { + color:$u-button-plain-u-button-error-color; + } + } + + &--plain { + &.u-button--warning { + color:$u-button-plain-u-button-warning-color; + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/header.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/header.vue new file mode 100644 index 000000000..dc4f7d054 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/header.vue @@ -0,0 +1,99 @@ +<template> + <view class="u-calendar-header u-border-bottom"> + <text + class="u-calendar-header__title" + v-if="showTitle" + >{{ title }}</text> + <text + class="u-calendar-header__subtitle" + v-if="showSubtitle" + >{{ subtitle }}</text> + <view class="u-calendar-header__weekdays"> + <text class="u-calendar-header__weekdays__weekday">一</text> + <text class="u-calendar-header__weekdays__weekday">二</text> + <text class="u-calendar-header__weekdays__weekday">三</text> + <text class="u-calendar-header__weekdays__weekday">四</text> + <text class="u-calendar-header__weekdays__weekday">五</text> + <text class="u-calendar-header__weekdays__weekday">六</text> + <text class="u-calendar-header__weekdays__weekday">日</text> + </view> + </view> +</template> + +<script> + export default { + name: 'u-calendar-header', + mixins: [uni.$u.mpMixin, uni.$u.mixin], + props: { + // 标题 + title: { + type: String, + default: '' + }, + // 副标题 + subtitle: { + type: String, + default: '' + }, + // 是否显示标题 + showTitle: { + type: Boolean, + default: true + }, + // 是否显示副标题 + showSubtitle: { + type: Boolean, + default: true + }, + }, + data() { + return { + + } + }, + methods: { + name() { + + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-calendar-header { + padding-bottom: 4px; + + &__title { + font-size: 16px; + color: $u-main-color; + text-align: center; + height: 42px; + line-height: 42px; + font-weight: bold; + } + + &__subtitle { + font-size: 14px; + color: $u-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: $u-main-color; + line-height: 30px; + flex: 1; + text-align: center; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/month.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/month.vue new file mode 100644 index 000000000..c20937fee --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/month.vue @@ -0,0 +1,579 @@ +<template> + <view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper"> + <view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]" + :ref="`u-calendar-month-${index}`" :id="`month-${index}`"> + <text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text> + <view class="u-calendar-month__days"> + <view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper"> + <text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text> + </view> + <view class="u-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 && 'u-calendar-month__days__day__select--selected']"> + <view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]"> + <text class="u-calendar-month__days__day__select__info" + :class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']" + :style="[textStyle(item1)]">{{ item1.day }}</text> + <text v-if="getBottomInfo(index, index1, item1)" + class="u-calendar-month__days__day__select__buttom-info" + :class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']" + :style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text> + <text v-if="item1.dot" class="u-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 dayjs from '../../libs/util/dayjs.js'; + export default { + name: 'u-calendar-month', + mixins: [uni.$u.mpMixin, uni.$u.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: uni.$u.props.calendar.readonly + }, + // 日期区间最多可选天数,默认无限制,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 = uni.$u.addUnit(dayWidth) + // #endif + style.height = uni.$u.addUnit(this.rowHeight) + if (index2 === 0) { + // 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数 + week = (week === 0 ? 7 : week) - 1 + style.marginLeft = uni.$u.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 = uni.$u.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 + } + }, + // 获取底部的提示文字 + 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%可靠的 + uni.$u.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['u-calendar-month-wrapper'], res => { + this.width = res.size.width + }) + // #endif + // #ifndef APP-NVUE + this.$uGetRect('.u-calendar-month-wrapper').then(size => { + this.width = size.width + }) + // #endif + }, + getMonthRect() { + // 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份 + const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise( + `u-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 + // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html + // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同 + return new Promise(resolve => { + this.$uGetRect(`.${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 = uni.$u.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 => item === date) + 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) { + uni.$u.toast(this.rangePrompt) + } else { + uni.$u.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) + }, + // 设置默认日期 + 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 (!uni.$u.test.array(this.defaultDate)) { + defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")] + } else { + defaultDate = [this.defaultDate[0]] + } + } else { + // 如果为非数组,则不执行 + if (!uni.$u.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 "../../libs/css/components.scss"; + + .u-calendar-month-wrapper { + margin-top: 4px; + } + + .u-calendar-month { + + &__title { + font-size: 14px; + line-height: 42px; + height: 42px; + color: $u-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: $u-error; + position: absolute; + top: 12px; + right: 7px; + } + + &__buttom-info { + color: $u-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: $u-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/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/props.js new file mode 100644 index 000000000..2ad7bc769 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/props.js @@ -0,0 +1,144 @@ +export default { + props: { + // 日历顶部标题 + title: { + type: String, + default: uni.$u.props.calendar.title + }, + // 是否显示标题 + showTitle: { + type: Boolean, + default: uni.$u.props.calendar.showTitle + }, + // 是否显示副标题 + showSubtitle: { + type: Boolean, + default: uni.$u.props.calendar.showSubtitle + }, + // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 + mode: { + type: String, + default: uni.$u.props.calendar.mode + }, + // mode=range时,第一个日期底部的提示文字 + startText: { + type: String, + default: uni.$u.props.calendar.startText + }, + // mode=range时,最后一个日期底部的提示文字 + endText: { + type: String, + default: uni.$u.props.calendar.endText + }, + // 自定义列表 + customList: { + type: Array, + default: uni.$u.props.calendar.customList + }, + // 主题色,对底部按钮和选中日期有效 + color: { + type: String, + default: uni.$u.props.calendar.color + }, + // 最小的可选日期 + minDate: { + type: [String, Number], + default: uni.$u.props.calendar.minDate + }, + // 最大可选日期 + maxDate: { + type: [String, Number], + default: uni.$u.props.calendar.maxDate + }, + // 默认选中的日期,mode为multiple或range是必须为数组格式 + defaultDate: { + type: [Array, String, Date, null], + default: uni.$u.props.calendar.defaultDate + }, + // mode=multiple时,最多可选多少个日期 + maxCount: { + type: [String, Number], + default: uni.$u.props.calendar.maxCount + }, + // 日期行高 + rowHeight: { + type: [String, Number], + default: uni.$u.props.calendar.rowHeight + }, + // 日期格式化函数 + formatter: { + type: [Function, null], + default: uni.$u.props.calendar.formatter + }, + // 是否显示农历 + showLunar: { + type: Boolean, + default: uni.$u.props.calendar.showLunar + }, + // 是否显示月份背景色 + showMark: { + type: Boolean, + default: uni.$u.props.calendar.showMark + }, + // 确定按钮的文字 + confirmText: { + type: String, + default: uni.$u.props.calendar.confirmText + }, + // 确认按钮处于禁用状态时的文字 + confirmDisabledText: { + type: String, + default: uni.$u.props.calendar.confirmDisabledText + }, + // 是否显示日历弹窗 + show: { + type: Boolean, + default: uni.$u.props.calendar.show + }, + // 是否允许点击遮罩关闭日历 + closeOnClickOverlay: { + type: Boolean, + default: uni.$u.props.calendar.closeOnClickOverlay + }, + // 是否为只读状态,只读状态下禁止选择日期 + readonly: { + type: Boolean, + default: uni.$u.props.calendar.readonly + }, + // 是否展示确认按钮 + showConfirm: { + type: Boolean, + default: uni.$u.props.calendar.showConfirm + }, + // 日期区间最多可选天数,默认无限制,mode = range时有效 + maxRange: { + type: [Number, String], + default: uni.$u.props.calendar.maxRange + }, + // 范围选择超过最多可选天数时的提示文案,mode = range时有效 + rangePrompt: { + type: String, + default: uni.$u.props.calendar.rangePrompt + }, + // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 + showRangePrompt: { + type: Boolean, + default: uni.$u.props.calendar.showRangePrompt + }, + // 是否允许日期范围的起止时间为同一天,mode = range时有效 + allowSameDay: { + type: Boolean, + default: uni.$u.props.calendar.allowSameDay + }, + // 圆角值 + round: { + type: [Boolean, String, Number], + default: uni.$u.props.calendar.round + }, + // 最多展示月份数量 + monthNum: { + type: [Number, String], + default: 3 + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/u-calendar.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/u-calendar.vue new file mode 100644 index 000000000..ad892ffa5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/u-calendar.vue @@ -0,0 +1,383 @@ +<template> + <u-popup + :show="show" + mode="bottom" + closeable + @close="close" + :round="round" + :closeOnClickOverlay="closeOnClickOverlay" + > + <view class="u-calendar"> + <uHeader + :title="title" + :subtitle="subtitle" + :showSubtitle="showSubtitle" + :showTitle="showTitle" + ></uHeader> + <scroll-view + :style="{ + height: $u.addUnit(listHeight) + }" + scroll-y + @scroll="onScroll" + :scroll-top="scrollTop" + :scrollIntoView="scrollIntoView" + > + <uMonth + :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" + ></uMonth> + </scroll-view> + <slot name="footer" v-if="showConfirm"> + <view class="u-calendar__confirm"> + <u-button + shape="circle" + :text=" + buttonDisabled ? confirmDisabledText : confirmText + " + :color="color" + @click="confirm" + :disabled="buttonDisabled" + ></u-button> + </view> + </slot> + </view> + </u-popup> +</template> + +<script> +import uHeader from './header.vue' +import uMonth from './month.vue' +import props from './props.js' +import util from './util.js' +import dayjs from '../../libs/util/dayjs.js' +import Calendar from '../../libs/util/calendar.js' +/** + * Calendar 日历 + * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中. + * @tutorial https://www.uviewui.com/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} 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 <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm"> + </u-calendar> + * */ +export default { + name: 'u-calendar', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + components: { + uHeader, + uMonth + }, + data() { + return { + // 需要显示的月份的数组 + months: [], + // 在月份滚动区域中,当前视图中月份的index索引 + monthIndex: 0, + // 月份滚动区域的高度 + listHeight: 0, + // month组件中选择的日期数组 + selected: [], + scrollIntoView: '', + scrollTop:0, + // 过滤处理方法 + innerFormatter: (value) => value + } + }, + watch: { + selectedChange: { + immediate: true, + handler(n) { + this.setMonth() + } + }, + // 打开弹窗时,设置月份数据 + show: { + immediate: true, + handler(n) { + this.setMonth() + } + } + }, + computed: { + // 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理 + innerMaxDate() { + return uni.$u.test.number(this.maxDate) + ? Number(this.maxDate) + : this.maxDate + }, + innerMinDate() { + return uni.$u.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 + }, + // 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,不能小于当前时间 + if ( + this.innerMaxDate && + new Date(this.innerMaxDate).getTime() <= Date.now() + ) { + return uni.$u.error('maxDate不能小于当前时间') + } + // 滚动区域的高度 + this.listHeight = this.rowHeight * 5 + 30 + this.setMonth() + }, + close() { + this.$emit('close') + }, + // 点击确定按钮 + confirm() { + if (!this.buttonDisabled) { + this.$emit('confirm', this.selected) + } + }, + // 获得两个日期之间的月份数 + 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 = uni.$u.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 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), + 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 = uni.$u.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 (!uni.$u.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> +@import '../../libs/css/components.scss'; + +.u-calendar { + &__confirm { + padding: 7px 18px; + } +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/util.js b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/util.js new file mode 100644 index 000000000..ca4736bb2 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-calendar/util.js @@ -0,0 +1,85 @@ +export default { + methods: { + // 设置月份数据 + setMonth() { + // 月初是周几 + const day = dayjs(this.date).date(1).day() + const start = day == 0 ? 6 : day - 1 + + // 本月天数 + const days = dayjs(this.date).endOf('month').format('D') + + // 上个月天数 + const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D') + + // 日期数据 + const arr = [] + // 清空表格 + this.month = [] + + // 添加上月数据 + arr.push( + ...new Array(start).fill(1).map((e, i) => { + const day = prevDays - start + i + 1 + + return { + value: day, + disabled: true, + date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD') + } + }) + ) + + // 添加本月数据 + arr.push( + ...new Array(days - 0).fill(1).map((e, i) => { + const day = i + 1 + + return { + value: day, + date: dayjs(this.date).date(day).format('YYYY-MM-DD') + } + }) + ) + + // 添加下个月 + arr.push( + ...new Array(42 - days - start).fill(1).map((e, i) => { + const day = i + 1 + + return { + value: day, + disabled: true, + date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD') + } + }) + ) + + // 分割数组 + for (let n = 0; n < arr.length; n += 7) { + this.month.push( + arr.slice(n, n + 7).map((e, i) => { + e.index = i + n + + // 自定义信息 + const custom = this.customList.find((c) => c.date == e.date) + + // 农历 + if (this.lunar) { + const { + IDayCn, + IMonthCn + } = this.getLunar(e.date) + e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn + } + + return { + ...e, + ...custom + } + }) + ) + } + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-car-keyboard/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-car-keyboard/props.js new file mode 100644 index 000000000..3553647a9 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-car-keyboard/props.js @@ -0,0 +1,14 @@ +export default { + props: { + // 是否打乱键盘按键的顺序 + random: { + type: Boolean, + default: false + }, + // 输入一个中文后,是否自动切换到英文 + autoChange: { + type: Boolean, + default: false + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue new file mode 100644 index 000000000..51175b54c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue @@ -0,0 +1,311 @@ +<template> + <view + class="u-keyboard" + @touchmove.stop.prevent="noop" + > + <view + v-for="(group, i) in abc ? engKeyBoardList : areaList" + :key="i" + class="u-keyboard__button" + :index="i" + :class="[i + 1 === 4 && 'u-keyboard__button--center']" + > + <view + v-if="i === 3" + class="u-keyboard__button__inner-wrapper" + > + <view + class="u-keyboard__button__inner-wrapper__left" + hover-class="u-hover-class" + :hover-stay-time="200" + @tap="changeCarInputMode" + > + <text + class="u-keyboard__button__inner-wrapper__left__lang" + :class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']" + >中</text> + <text class="u-keyboard__button__inner-wrapper__left__line">/</text> + <text + class="u-keyboard__button__inner-wrapper__left__lang" + :class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']" + >英</text> + </view> + </view> + <view + class="u-keyboard__button__inner-wrapper" + v-for="(item, j) in group" + :key="j" + > + <view + class="u-keyboard__button__inner-wrapper__inner" + :hover-stay-time="200" + @tap="carInputClick(i, j)" + hover-class="u-hover-class" + > + <text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text> + </view> + </view> + <view + v-if="i === 3" + @touchstart="backspaceClick" + @touchend="clearTimer" + class="u-keyboard__button__inner-wrapper" + > + <view + class="u-keyboard__button__inner-wrapper__right" + hover-class="u-hover-class" + :hover-stay-time="200" + > + <u-icon + size="28" + name="backspace" + color="#303133" + ></u-icon> + </view> + </view> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * keyboard 键盘组件 + * @description 此为uView自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。 + * @tutorial https://uviewui.com/components/keyboard.html + * @property {Boolean} random 是否打乱键盘的顺序 + * @event {Function} change 点击键盘触发 + * @event {Function} backspace 点击退格键触发 + * @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard> + */ + export default { + name: "u-keyboard", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称 + abc: false + }; + }, + computed: { + areaList() { + let data = [ + '京', + '沪', + '粤', + '津', + '冀', + '豫', + '云', + '辽', + '黑', + '湘', + '皖', + '鲁', + '苏', + '浙', + '赣', + '鄂', + '桂', + '甘', + '晋', + '陕', + '蒙', + '吉', + '闽', + '贵', + '渝', + '川', + '青', + '琼', + '宁', + '挂', + '藏', + '港', + '澳', + '新', + '使', + '学' + ]; + let tmp = []; + // 打乱顺序 + if (this.random) data = uni.$u.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 = uni.$u.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; + } + }, + methods: { + // 点击键盘按钮 + carInputClick(i, j) { + let value = ''; + // 不同模式,获取不同数组的值 + if (this.abc) value = this.engKeyBoardList[i][j]; + else value = this.areaList[i][j]; + // 如果允许自动切换,则将中文状态切换为英文 + if (!this.abc && this.autoChange) uni.$u.sleep(200).then(() => this.abc = true) + this.$emit('change', value); + }, + // 修改汽车牌键盘的输入模式,中文|英文 + changeCarInputMode() { + this.abc = !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> + @import "../../libs/css/components.scss"; + $u-car-keyboard-background-color: rgb(224, 228, 230) !default; + $u-car-keyboard-padding:6px 0 6px !default; + $u-car-keyboard-button-inner-width:64rpx !default; + $u-car-keyboard-button-inner-background-color:#FFFFFF !default; + $u-car-keyboard-button-height:80rpx !default; + $u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default; + $u-car-keyboard-button-border-radius:4px !default; + $u-car-keyboard-button-inner-margin:8rpx 5rpx !default; + $u-car-keyboard-button-text-font-size:16px !default; + $u-car-keyboard-button-text-color:$u-main-color !default; + $u-car-keyboard-center-inner-margin: 0 4rpx !default; + $u-car-keyboard-special-button-width:134rpx !default; + $u-car-keyboard-lang-font-size:16px !default; + $u-car-keyboard-lang-color:$u-main-color !default; + $u-car-keyboard-active-color:$u-primary !default; + $u-car-keyboard-line-font-size:15px !default; + $u-car-keyboard-line-color:$u-main-color !default; + $u-car-keyboard-line-margin:0 1px !default; + $u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default; + + .u-keyboard { + @include flex(column); + justify-content: space-around; + background-color: $u-car-keyboard-background-color; + align-items: stretch; + padding: $u-car-keyboard-padding; + + &__button { + @include flex; + justify-content: center; + flex: 1; + /* #ifndef APP-NVUE */ + /* #endif */ + + &__inner-wrapper { + box-shadow: $u-car-keyboard-button-inner-box-shadow; + margin: $u-car-keyboard-button-inner-margin; + border-radius: $u-car-keyboard-button-border-radius; + + &__inner { + @include flex; + justify-content: center; + align-items: center; + width: $u-car-keyboard-button-inner-width; + background-color: $u-car-keyboard-button-inner-background-color; + height: $u-car-keyboard-button-height; + border-radius: $u-car-keyboard-button-border-radius; + + &__text { + font-size: $u-car-keyboard-button-text-font-size; + color: $u-car-keyboard-button-text-color; + } + } + + &__left, + &__right { + border-radius: $u-car-keyboard-button-border-radius; + width: $u-car-keyboard-special-button-width; + height: $u-car-keyboard-button-height; + background-color: $u-car-keyboard-u-hover-class-background-color; + @include flex; + justify-content: center; + align-items: center; + box-shadow: $u-car-keyboard-button-inner-box-shadow; + } + + &__left { + &__line { + font-size: $u-car-keyboard-line-font-size; + color: $u-car-keyboard-line-color; + margin: $u-car-keyboard-line-margin; + } + + &__lang { + font-size: $u-car-keyboard-lang-font-size; + color: $u-car-keyboard-lang-color; + + &--active { + color: $u-car-keyboard-active-color; + } + } + } + } + } + } + + .u-hover-class { + background-color: $u-car-keyboard-u-hover-class-background-color; + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-cell-group/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-cell-group/props.js new file mode 100644 index 000000000..350ef403e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-cell-group/props.js @@ -0,0 +1,14 @@ +export default { + props: { + // 分组标题 + title: { + type: String, + default: uni.$u.props.cellGroup.title + }, + // 是否显示外边框 + border: { + type: Boolean, + default: uni.$u.props.cellGroup.border + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue new file mode 100644 index 000000000..a9508c0c7 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue @@ -0,0 +1,61 @@ +<template> + <view :style="[$u.addStyle(customStyle)]" :class="[customClass]" class="u-cell-group"> + <view v-if="title" class="u-cell-group__title"> + <slot name="title"> + <text class="u-cell-group__title__text">{{ title }}</text> + </slot> + </view> + <view class="u-cell-group__wrapper"> + <u-line v-if="border"></u-line> + <slot /> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * cellGroup 单元格 + * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。 + * @tutorial https://uviewui.com/components/cell.html + * + * @property {String} title 分组标题 + * @property {Boolean} border 是否显示外边框 (默认 true ) + * @property {Object} customStyle 定义需要用到的外部样式 + * + * @event {Function} click 点击cell列表时触发 + * @example <u-cell-group title="设置喜好"> + */ + export default { + name: 'u-cell-group', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + $u-cell-group-title-padding: 16px 16px 8px !default; + $u-cell-group-title-font-size: 15px !default; + $u-cell-group-title-line-height: 16px !default; + $u-cell-group-title-color: $u-main-color !default; + + .u-cell-group { + flex: 1; + + &__title { + padding: $u-cell-group-title-padding; + + &__text { + font-size: $u-cell-group-title-font-size; + line-height: $u-cell-group-title-line-height; + color: $u-cell-group-title-color; + } + } + + &__wrapper { + position: relative; + } + } +</style> + diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-cell/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-cell/props.js new file mode 100644 index 000000000..da0333032 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-cell/props.js @@ -0,0 +1,110 @@ +export default { + props: { + // 标题 + title: { + type: [String, Number], + default: uni.$u.props.cell.title + }, + // 标题下方的描述信息 + label: { + type: [String, Number], + default: uni.$u.props.cell.label + }, + // 右侧的内容 + value: { + type: [String, Number], + default: uni.$u.props.cell.value + }, + // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址) + icon: { + type: String, + default: uni.$u.props.cell.icon + }, + // 是否禁用cell + disabled: { + type: Boolean, + default: uni.$u.props.cell.disabled + }, + // 是否显示下边框 + border: { + type: Boolean, + default: uni.$u.props.cell.border + }, + // 内容是否垂直居中(主要是针对右侧的value部分) + center: { + type: Boolean, + default: uni.$u.props.cell.center + }, + // 点击后跳转的URL地址 + url: { + type: String, + default: uni.$u.props.cell.url + }, + // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 + linkType: { + type: String, + default: uni.$u.props.cell.linkType + }, + // 是否开启点击反馈(表现为点击时加上灰色背景) + clickable: { + type: Boolean, + default: uni.$u.props.cell.clickable + }, + // 是否展示右侧箭头并开启点击反馈 + isLink: { + type: Boolean, + default: uni.$u.props.cell.isLink + }, + // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) + required: { + type: Boolean, + default: uni.$u.props.cell.required + }, + // 右侧的图标箭头 + rightIcon: { + type: String, + default: uni.$u.props.cell.rightIcon + }, + // 右侧箭头的方向,可选值为:left,up,down + arrowDirection: { + type: String, + default: uni.$u.props.cell.arrowDirection + }, + // 左侧图标样式 + iconStyle: { + type: [Object, String], + default: () => { + return uni.$u.props.cell.iconStyle + } + }, + // 右侧箭头图标的样式 + rightIconStyle: { + type: [Object, String], + default: () => { + return uni.$u.props.cell.rightIconStyle + } + }, + // 标题的样式 + titleStyle: { + type: [Object, String], + default: () => { + return uni.$u.props.cell.titleStyle + } + }, + // 单位元的大小,可选值为large + size: { + type: String, + default: uni.$u.props.cell.size + }, + // 点击cell是否阻止事件传播 + stop: { + type: Boolean, + default: uni.$u.props.cell.stop + }, + // 标识符,cell被点击时返回 + name: { + type: [Number, String], + default: uni.$u.props.cell.name + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-cell/u-cell.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-cell/u-cell.vue new file mode 100644 index 000000000..b099c9070 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-cell/u-cell.vue @@ -0,0 +1,229 @@ +<template> + <view class="u-cell" :class="[customClass]" :style="[$u.addStyle(customStyle)]" + :hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250" + @tap="clickHandler"> + <view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']"> + <view class="u-cell__body__content"> + <view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon"> + <slot name="icon" v-if="$slots.icon"> + </slot> + <u-icon v-else :name="icon" :custom-style="iconStyle" :size="size === 'large' ? 22 : 18"></u-icon> + </view> + <view class="u-cell__title"> + <slot name="title"> + <text v-if="title" class="u-cell__title-text" :style="[titleTextStyle]" + :class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text> + </slot> + <slot name="label"> + <text class="u-cell__label" v-if="label" + :class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text> + </slot> + </view> + </view> + <slot name="value"> + <text class="u-cell__value" + :class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']" + v-if="!$u.test.empty(value)">{{ value }}</text> + </slot> + <view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink" + :class="[`u-cell__right-icon-wrap--${arrowDirection}`]"> + <slot name="right-icon" v-if="$slots['right-icon']"> + </slot> + <u-icon v-else :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'" + :size="size === 'large' ? 18 : 16"></u-icon> + </view> + </view> + <u-line v-if="border"></u-line> + </view> +</template> + +<script> + import props from './props.js'; + /** + * cell 单元格 + * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。 + * @tutorial https://uviewui.com/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 链接跳转的方式,内部使用的是uView封装的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} customStyle 定义需要用到的外部样式 + * + * @event {Function} click 点击cell列表时触发 + * @example 该组件需要搭配cell-group组件使用,见官方文档示例 + */ + export default { + name: 'u-cell', + data() { + return { + + } + }, + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + computed: { + titleTextStyle() { + return uni.$u.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 "../../libs/css/components.scss"; + + $u-cell-padding: 10px 15px !default; + $u-cell-font-size: 15px !default; + $u-cell-line-height: 24px !default; + $u-cell-color: $u-main-color !default; + $u-cell-icon-size: 16px !default; + $u-cell-title-font-size: 15px !default; + $u-cell-title-line-height: 22px !default; + $u-cell-title-color: $u-main-color !default; + $u-cell-label-font-size: 12px !default; + $u-cell-label-color: $u-tips-color !default; + $u-cell-label-line-height: 18px !default; + $u-cell-value-font-size: 14px !default; + $u-cell-value-color: $u-content-color !default; + $u-cell-clickable-color: $u-bg-color !default; + $u-cell-disabled-color: #c8c9cc !default; + $u-cell-padding-top-large: 13px !default; + $u-cell-padding-bottom-large: 13px !default; + $u-cell-value-font-size-large: 15px !default; + $u-cell-label-font-size-large: 14px !default; + $u-cell-title-font-size-large: 16px !default; + $u-cell-left-icon-wrap-margin-right: 4px !default; + $u-cell-right-icon-wrap-margin-left: 4px !default; + $u-cell-title-flex:1 !default; + $u-cell-label-margin-top:5px !default; + + + .u-cell { + &__body { + @include flex(); + /* #ifndef APP-NVUE */ + box-sizing: border-box; + /* #endif */ + padding: $u-cell-padding; + font-size: $u-cell-font-size; + color: $u-cell-color; + // line-height: $u-cell-line-height; + align-items: center; + + &__content { + @include flex(row); + align-items: center; + flex: 1; + } + + &--large { + padding-top: $u-cell-padding-top-large; + padding-bottom: $u-cell-padding-bottom-large; + } + } + + &__left-icon-wrap, + &__right-icon-wrap { + @include flex(); + align-items: center; + // height: $u-cell-line-height; + font-size: $u-cell-icon-size; + } + + &__left-icon-wrap { + margin-right: $u-cell-left-icon-wrap-margin-right; + } + + &__right-icon-wrap { + margin-left: $u-cell-right-icon-wrap-margin-left; + transition: transform 0.3s; + + &--up { + transform: rotate(-90deg); + } + + &--down { + transform: rotate(90deg); + } + } + + &__title { + flex: $u-cell-title-flex; + + &-text { + font-size: $u-cell-title-font-size; + line-height: $u-cell-title-line-height; + color: $u-cell-title-color; + + &--large { + font-size: $u-cell-title-font-size-large; + } + } + + } + + &__label { + margin-top: $u-cell-label-margin-top; + font-size: $u-cell-label-font-size; + color: $u-cell-label-color; + line-height: $u-cell-label-line-height; + + &--large { + font-size: $u-cell-label-font-size-large; + } + } + + &__value { + text-align: right; + font-size: $u-cell-value-font-size; + line-height: $u-cell-line-height; + color: $u-cell-value-color; + + &--large { + font-size: $u-cell-value-font-size-large; + } + } + + &--clickable { + background-color: $u-cell-clickable-color; + } + + &--disabled { + color: $u-cell-disabled-color; + /* #ifndef APP-NVUE */ + cursor: not-allowed; + /* #endif */ + } + + &--center { + align-items: center; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox-group/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox-group/props.js new file mode 100644 index 000000000..2f818a10c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox-group/props.js @@ -0,0 +1,82 @@ +export default { + props: { + // 标识符 + name: { + type: String, + default: uni.$u.props.checkboxGroup.name + }, + // 绑定的值 + value: { + type: Array, + default: uni.$u.props.checkboxGroup.value + }, + // 形状,circle-圆形,square-方形 + shape: { + type: String, + default: uni.$u.props.checkboxGroup.shape + }, + // 是否禁用全部checkbox + disabled: { + type: Boolean, + default: uni.$u.props.checkboxGroup.disabled + }, + + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 + activeColor: { + type: String, + default: uni.$u.props.checkboxGroup.activeColor + }, + // 未选中的颜色 + inactiveColor: { + type: String, + default: uni.$u.props.checkboxGroup.inactiveColor + }, + + // 整个组件的尺寸,默认px + size: { + type: [String, Number], + default: uni.$u.props.checkboxGroup.size + }, + // 布局方式,row-横向,column-纵向 + placement: { + type: String, + default: uni.$u.props.checkboxGroup.placement + }, + // label的字体大小,px单位 + labelSize: { + type: [String, Number], + default: uni.$u.props.checkboxGroup.labelSize + }, + // label的字体颜色 + labelColor: { + type: [String], + default: uni.$u.props.checkboxGroup.labelColor + }, + // 是否禁止点击文本操作 + labelDisabled: { + type: Boolean, + default: uni.$u.props.checkboxGroup.labelDisabled + }, + // 图标颜色 + iconColor: { + type: String, + default: uni.$u.props.checkboxGroup.iconColor + }, + // 图标的大小,单位px + iconSize: { + type: [String, Number], + default: uni.$u.props.checkboxGroup.iconSize + }, + // 勾选图标的对齐方式,left-左边,right-右边 + iconPlacement: { + type: String, + default: uni.$u.props.checkboxGroup.iconPlacement + }, + // 竖向配列时,是否显示下划线 + borderBottom: { + type: Boolean, + default: uni.$u.props.checkboxGroup.borderBottom + } + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue new file mode 100644 index 000000000..7a6b4fa3c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue @@ -0,0 +1,103 @@ +<template> + <view + class="u-checkbox-group" + :class="bemClass" + > + <slot></slot> + </view> +</template> + +<script> + import props from './props.js'; + /** + * checkboxGroup 复选框组 + * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便 + * @tutorial https://www.uviewui.com/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 <u-checkbox-group></u-checkbox-group> + */ + export default { + name: 'u-checkbox-group', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + computed: { + // 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化 + // 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group) + // 拉取父组件新的变化后的参数 + parentData() { + return [this.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('checkbox-group', ['placement']) + }, + }, + watch: { + // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件 + parentData() { + if (this.children.length) { + this.children.map(child => { + // 判断子组件(u-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) + } + }) + // 发出事件 + this.$emit('change', values) + // 修改通过v-model绑定的值 + this.$emit('input', values) + }, + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-checkbox-group { + + &--row { + @include flex; + } + + &--column { + @include flex(column); + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox/props.js new file mode 100644 index 000000000..93f4fd9f2 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox/props.js @@ -0,0 +1,69 @@ +export default { + props: { + // checkbox的名称 + name: { + type: [String, Number, Boolean], + default: uni.$u.props.checkbox.name + }, + // 形状,square为方形,circle为圆型 + shape: { + type: String, + default: uni.$u.props.checkbox.shape + }, + // 整体的大小 + size: { + type: [String, Number], + default: uni.$u.props.checkbox.size + }, + // 是否默认选中 + checked: { + type: Boolean, + default: uni.$u.props.checkbox.checked + }, + // 是否禁用 + disabled: { + type: [String, Boolean], + default: uni.$u.props.checkbox.disabled + }, + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 + activeColor: { + type: String, + default: uni.$u.props.checkbox.activeColor + }, + // 未选中的颜色 + inactiveColor: { + type: String, + default: uni.$u.props.checkbox.inactiveColor + }, + // 图标的大小,单位px + iconSize: { + type: [String, Number], + default: uni.$u.props.checkbox.iconSize + }, + // 图标颜色 + iconColor: { + type: String, + default: uni.$u.props.checkbox.iconColor + }, + // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式 + label: { + type: [String, Number], + default: uni.$u.props.checkbox.label + }, + // label的字体大小,px单位 + labelSize: { + type: [String, Number], + default: uni.$u.props.checkbox.labelSize + }, + // label的颜色 + labelColor: { + type: String, + default: uni.$u.props.checkbox.labelColor + }, + // 是否禁止点击提示语选中复选框 + labelDisabled: { + type: [String, Boolean], + default: uni.$u.props.checkbox.labelDisabled + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox/u-checkbox.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox/u-checkbox.vue new file mode 100644 index 000000000..6429cca94 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-checkbox/u-checkbox.vue @@ -0,0 +1,344 @@ +<template> + <view + class="u-checkbox" + :style="[checkboxStyle]" + @tap.stop="wrapperClickHandler" + :class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']" + > + <view + class="u-checkbox__icon-wrap" + @tap.stop="iconClickHandler" + :class="iconClasses" + :style="[iconWrapStyle]" + > + <slot name="icon"> + <u-icon + class="u-checkbox__icon-wrap__icon" + name="checkbox-mark" + :size="elIconSize" + :color="elIconColor" + /> + </slot> + </view> + <text + @tap.stop="labelClickHandler" + :style="{ + color: elDisabled ? elInactiveColor : elLabelColor, + fontSize: elLabelSize, + lineHeight: elLabelSize + }" + >{{label}}</text> + </view> +</template> + +<script> + import props from './props.js'; + /** + * checkbox 复选框 + * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便 + * @tutorial https://uviewui.com/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 <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox> + */ + export default { + name: "u-checkbox", + mixins: [uni.$u.mpMixin, uni.$u.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, + iconColor: null, + placement: 'row', + borderBottom: false, + iconPlacement: 'left' + } + } + }, + computed: { + // 是否禁用,如果父组件u-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 uni.$u.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('u-checkbox__icon-wrap--' + this.elShape) + if (this.elDisabled) { + classes.push('u-checkbox__icon-wrap--disabled') + } + if (this.isChecked && this.elDisabled) { + classes.push('u-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 = uni.$u.addUnit(this.elSize) + style.height = uni.$u.addUnit(this.elSize) + // 如果是图标在右边的话,移除它的右边距 + if (this.parentData.iconPlacement === 'right') { + style.marginRight = 0 + } + return style + }, + checkboxStyle() { + const style = {} + if (this.parentData.borderBottom && this.parentData.placement === 'row') { + uni.$u.error('检测到您将borderBottom设置为true,需要同时将u-checkbox-group的placement设置为column才有效') + } + // 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔 + if (this.parentData.borderBottom && this.parentData.placement === 'column') { + style.paddingBottom = '8px' + } + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + mounted() { + this.init() + }, + methods: { + init() { + // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用 + this.updateParentData() + if (!this.parent) { + uni.$u.error('u-checkbox必须搭配u-checkbox-group组件使用') + } + // 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断 + if (this.checked) { + this.isChecked = true + } else if (uni.$u.test.array(this.parentData.value)) { + // 查找数组是是否存在this.name元素值 + this.isChecked = this.parentData.value.some(item => { + return item === this.name + }) + } + }, + updateParentData() { + this.getParentData('u-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) + // 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时 + this.$nextTick(() => { + uni.$u.formValidate(this, 'change') + }) + }, + // 改变组件选中状态 + // 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-checkbox实例 + // 将本组件外的其他u-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> + @import "../../libs/css/components.scss"; + $u-checkbox-icon-wrap-margin-right:6px !default; + $u-checkbox-icon-wrap-font-size:6px !default; + $u-checkbox-icon-wrap-border-width:1px !default; + $u-checkbox-icon-wrap-border-color:#c8c9cc !default; + $u-checkbox-icon-wrap-icon-line-height:0 !default; + $u-checkbox-icon-wrap-circle-border-radius:100% !default; + $u-checkbox-icon-wrap-square-border-radius:3px !default; + $u-checkbox-icon-wrap-checked-color:#fff !default; + $u-checkbox-icon-wrap-checked-background-color:red !default; + $u-checkbox-icon-wrap-checked-border-color:#2979ff !default; + $u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default; + $u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default; + $u-checkbox-label-margin-left:5px !default; + $u-checkbox-label-margin-right:12px !default; + $u-checkbox-label-color:$u-content-color !default; + $u-checkbox-label-font-size:15px !default; + $u-checkbox-label-disabled-color:#c8c9cc !default; + + .u-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: $u-content-color; + @include flex; + align-items: center; + justify-content: center; + color: transparent; + text-align: center; + margin-right: $u-checkbox-icon-wrap-margin-right; + + font-size: $u-checkbox-icon-wrap-font-size; + border-width: $u-checkbox-icon-wrap-border-width; + border-color: $u-checkbox-icon-wrap-border-color; + border-style: solid; + + /* #ifdef MP-TOUTIAO */ + // 头条小程序兼容性问题,需要设置行高为0,否则图标偏下 + &__icon { + line-height: $u-checkbox-icon-wrap-icon-line-height; + } + + /* #endif */ + + &--circle { + border-radius: $u-checkbox-icon-wrap-circle-border-radius; + } + + &--square { + border-radius: $u-checkbox-icon-wrap-square-border-radius; + } + + &--checked { + color: $u-checkbox-icon-wrap-checked-color; + background-color: $u-checkbox-icon-wrap-checked-background-color; + border-color: $u-checkbox-icon-wrap-checked-border-color; + } + + &--disabled { + background-color: $u-checkbox-icon-wrap-disabled-background-color !important; + } + + &--disabled--checked { + color: $u-checkbox-icon-wrap-disabled-checked-color !important; + } + } + + &__label { + /* #ifndef APP-NVUE */ + word-wrap: break-word; + /* #endif */ + margin-left: $u-checkbox-label-margin-left; + margin-right: $u-checkbox-label-margin-right; + color: $u-checkbox-label-color; + font-size: $u-checkbox-label-font-size; + + &--disabled { + color: $u-checkbox-label-disabled-color; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-circle-progress/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-circle-progress/props.js new file mode 100644 index 000000000..d776cfb61 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-circle-progress/props.js @@ -0,0 +1,8 @@ +export default { + props: { + percentage: { + type: [String, Number], + default: uni.$u.props.circleProgress.percentage + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue new file mode 100644 index 000000000..d1ee2867b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue @@ -0,0 +1,198 @@ +<template> + <view class="u-circle-progress"> + <view class="u-circle-progress__left"> + <view + class="u-circle-progress__left__circle" + :style="[leftSyle]" + ref="left-circle" + > + + </view> + </view> + <view + class="u-circle-progress__right" + > + <view + class="u-circle-progress__right__circle" + ref="right-circle" + :style="[rightSyle]" + > + + </view> + </view> + <view class="u-circle-progress__circle"> + + </view> + </view> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const animation = uni.requireNativePlugin('animation') + // #endif + /** + * CircleProgress 圆形进度条 TODO: 待完善 + * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。 + * @tutorial https://www.uviewui.com/components/circleProgress.html + * @property {String | Number} percentage 圆环进度百分比值,为数值类型,0-100 (默认 30 ) + * @example + */ + export default { + name: 'u-circle-progress', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + leftBorderColor: 'rgb(200, 200, 200)', + rightBorderColor: 'rgb(200, 200, 200)', + } + }, + computed: { + leftSyle() { + const style = {} + style.borderTopColor = this.leftBorderColor + style.borderRightColor = this.leftBorderColor + return style + }, + rightSyle() { + const style = {} + style.borderLeftColor = this.rightBorderColor + style.borderBottomColor = this.rightBorderColor + return style + } + }, + mounted() { + uni.$u.sleep().then(() => { + this.rightBorderColor = 'rgb(66, 185, 131)' + // this.init() + }) + }, + methods: { + init() { + animation.transition(this.$refs['right-circle'].ref, { + styles: { + transform: 'rotate(45deg)', + transformOrigin: 'center center' + }, + }, () => { + this.rightBorderColor = 'rgb(66, 185, 131)' + // animation.transition(this.$refs['right-circle'].ref, { + // styles: { + // transform: 'rotate(225deg)', + // transformOrigin: 'center center' + // }, + // duration: 3000, + // }, () => { + // animation.transition(this.$refs['left-circle'].ref, { + // styles: { + // transform: 'rotate(45deg)', + // transformOrigin: 'center center' + // }, + // }, () => { + // this.leftBorderColor = 'rgb(66, 185, 131)' + // animation.transition(this.$refs['left-circle'].ref, { + // styles: { + // transform: 'rotate(225deg)', + // transformOrigin: 'center center' + // }, + // duration: 1500, + // }, () => { + + // }) + // }) + // }) + }) + + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-circle-progress { + @include flex(row); + position: relative; + border-radius: 100px; + height: 100px; + width: 100px; + // transform: rotate(0deg); + // background-color: rgb(66, 185, 131); + background-color: rgb(200, 200, 200); + overflow: hidden; + justify-content: space-between; + + &__circle { + border-radius: 100px; + height: 90px; + width: 90px; + transform: translate(-50%, -50%); + background-color: rgb(255, 255, 255); + left: 50px; + top: 50px; + position: absolute; + } + + &__left { + position: absolute; + left: 0; + width: 50px; + height: 100px; + overflow: hidden; + box-sizing: border-box; + // background-color: rgb(66, 185, 131); + // background-color: rgb(200, 200, 200); + // transform-origin: left center; + + &__circle { + box-sizing: border-box; + // background-color: red; + border-left-color: transparent; + border-bottom-color: transparent; + border-top-left-radius: 50px; + border-top-right-radius: 50px; + border-bottom-right-radius: 50px; + // border-left-color: rgb(66, 185, 131); + // border-bottom-color: rgb(66, 185, 131); + border-top-color: rgb(66, 185, 131); + border-right-color: rgb(66, 185, 131); + border-width: 5px; + width: 100px; + height: 100px; + transform: rotate(225deg); + // border-radius: 100px; + } + } + + &__right { + position: absolute; + right: 0; + width: 50px; + height: 100px; + overflow: hidden; + + &__circle { + position: absolute; + right: 0; + box-sizing: border-box; + // background-color: red; + border-top-color: transparent; + border-right-color: transparent; + border-top-left-radius: 50px; + border-bottom-left-radius: 50px; + border-bottom-right-radius: 50px; + // border-left-color: rgb(66, 185, 131); + // border-bottom-color: rgb(66, 185, 131); + border-left-color: rgb(200, 200, 200); + border-bottom-color: rgb(200, 200, 200); + border-width: 5px; + width: 100px; + height: 100px; + transform: rotate(45deg); + transform-origin: center center; + // border-radius: 100px; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-code-input/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-code-input/props.js new file mode 100644 index 000000000..eeb58a029 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-code-input/props.js @@ -0,0 +1,74 @@ +export default { + props: { + // 最大输入长度 + maxlength: { + type: [String, Number], + default: uni.$u.props.codeInput.maxlength + }, + // 是否用圆点填充 + dot: { + type: Boolean, + default: uni.$u.props.codeInput.dot + }, + // 显示模式,box-盒子模式,line-底部横线模式 + mode: { + type: String, + default: uni.$u.props.codeInput.mode + }, + // 是否细边框 + hairline: { + type: Boolean, + default: uni.$u.props.codeInput.hairline + }, + // 字符间的距离 + space: { + type: [String, Number], + default: uni.$u.props.codeInput.space + }, + // 预置值 + value: { + type: [String, Number], + default: uni.$u.props.codeInput.value + }, + // 是否自动获取焦点 + focus: { + type: Boolean, + default: uni.$u.props.codeInput.focus + }, + // 字体是否加粗 + bold: { + type: Boolean, + default: uni.$u.props.codeInput.bold + }, + // 字体颜色 + color: { + type: String, + default: uni.$u.props.codeInput.color + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.codeInput.fontSize + }, + // 输入框的大小,宽等于高 + size: { + type: [String, Number], + default: uni.$u.props.codeInput.size + }, + // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true + disabledKeyboard: { + type: Boolean, + default: uni.$u.props.codeInput.disabledKeyboard + }, + // 边框和线条颜色 + borderColor: { + type: String, + default: uni.$u.props.codeInput.borderColor + }, + // 是否禁止输入"."符号 + disabledDot: { + type: Boolean, + default: uni.$u.props.codeInput.disabledDot + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-code-input/u-code-input.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-code-input/u-code-input.vue new file mode 100644 index 000000000..14b2a4bf4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-code-input/u-code-input.vue @@ -0,0 +1,240 @@ +<template> + <view class="u-code-input"> + <view + class="u-code-input__item" + :style="[itemStyle(index)]" + v-for="(item, index) in codeLength" + :key="index" + > + <view + class="u-code-input__item__dot" + v-if="dot && codeArray.length > index" + ></view> + <text + v-else + :style="{ + fontSize: $u.addUnit(fontSize), + fontWeight: bold ? 'bold' : 'normal', + color: color + }" + >{{codeArray[index]}}</text> + <view + class="u-code-input__item__line" + v-if="mode === 'line'" + :style="[lineStyle]" + ></view> + <view v-if="codeArray.length === index" :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view> + </view> + <input + :disabled="disabledKeyboard" + type="number" + :focus="focus" + :value="inputValue" + :maxlength="maxlength" + class="u-code-input__input" + @input="inputHandler" + :style="{ + height: $u.addUnit(size) + }" + /> + </view> +</template> + +<script> + import props from './props.js'; + /** + * CodeInput 验证码输入 + * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用 + * @tutorial https://www.uviewui.com/components/codeInput.html + * @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 {String | Number} value 预置值 + * @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 <u-code-input v-model="value4" :focus="true"></u-code-input> + */ + export default { + name: 'u-code-input', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + inputValue: '' + } + }, + watch: { + value: { + immediate: true, + handler(val) { + // 转为字符串,超出部分截掉 + this.inputValue = String(val).substring(0, this.maxlength) + } + }, + }, + computed: { + // 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for + codeLength() { + return new Array(Number(this.maxlength)) + }, + // 循环item的样式 + itemStyle() { + return index => { + const addUnit = uni.$u.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 (uni.$u.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 ? '2px' : '4px' + style.width = uni.$u.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) + // 达到用户指定输入长度时,发出完成事件 + if (String(value).length >= Number(this.maxlength)) { + this.$emit('finish', value) + } + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-code-input-cursor-width: 1px; + $u-code-input-cursor-height: 40%; + $u-code-input-cursor-animation-duration: 1s; + $u-code-input-cursor-animation-name: u-cursor-flicker; + + .u-code-input { + @include flex; + position: relative; + overflow: hidden; + + &__item { + @include flex; + justify-content: center; + align-items: center; + position: relative; + + &__text { + font-size: 15px; + color: $u-content-color; + } + + &__dot { + width: 7px; + height: 7px; + border-radius: 100px; + background-color: $u-content-color; + } + + &__line { + position: absolute; + bottom: 0; + height: 4px; + border-radius: 100px; + width: 40px; + background-color: $u-content-color; + } + &__cursor { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); + width: $u-code-input-cursor-width; + height: $u-code-input-cursor-height; + animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite; + } + } + + &__input { + // 之所以需要input输入框,是因为有它才能唤起键盘 + // 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容 + position: absolute; + left: -750rpx; + width: 1500rpx; + top: 0; + background-color: transparent; + text-align: left; + } + } + + @keyframes u-cursor-flicker { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-code/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-code/props.js new file mode 100644 index 000000000..eaf80d02f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-code/props.js @@ -0,0 +1,34 @@ +export default { + props: { + // 倒计时总秒数 + seconds: { + type: [String, Number], + default: uni.$u.props.code.seconds + }, + // 尚未开始时提示 + startText: { + type: String, + default: uni.$u.props.code.startText + }, + // 正在倒计时中的提示 + changeText: { + type: String, + default: uni.$u.props.code.changeText + }, + // 倒计时结束时的提示 + endText: { + type: String, + default: uni.$u.props.code.endText + }, + // 是否在H5刷新或各端返回再进入时继续倒计时 + keepRunning: { + type: Boolean, + default: uni.$u.props.code.keepRunning + }, + // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了 + uniqueKey: { + type: String, + default: uni.$u.props.code.uniqueKey + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-code/u-code.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-code/u-code.vue new file mode 100644 index 000000000..cdf9f825a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-code/u-code.vue @@ -0,0 +1,129 @@ +<template> + <view class="u-code"> + <!-- 此组件功能由js完成,无需写html逻辑 --> + </view> +</template> + +<script> + import props from './props.js'; + /** + * Code 验证码输入框 + * @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景 + * @tutorial https://www.uviewui.com/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 <u-code ref="uCode" @change="codeChange" seconds="20"></u-code> + */ + export default { + name: "u-code", + mixins: [uni.$u.mpMixin, uni.$u.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.setTimeToStorage() + 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) + }, + // 重置,可以让用户再次获取验证码 + 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) + }) + } + } + }, + // 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除 + beforeDestroy() { + this.setTimeToStorage() + clearTimeout(this.timer) + this.timer = null + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-col/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-col/props.js new file mode 100644 index 000000000..06222517e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-col/props.js @@ -0,0 +1,29 @@ +export default { + props: { + // 占父容器宽度的多少等分,总分为12份 + span: { + type: [String, Number], + default: uni.$u.props.col.span + }, + // 指定栅格左侧的间隔数(总12栏) + offset: { + type: [String, Number], + default: uni.$u.props.col.offset + }, + // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) + justify: { + type: String, + default: uni.$u.props.col.justify + }, + // 垂直对齐方式,可选值为top、center、bottom、stretch + align: { + type: String, + default: uni.$u.props.col.align + }, + // 文字对齐方式 + textAlign: { + type: String, + default: uni.$u.props.col.textAlign + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-col/u-col.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-col/u-col.vue new file mode 100644 index 000000000..8be151742 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-col/u-col.vue @@ -0,0 +1,162 @@ +<template> + <view + class="u-col" + ref="u-col" + :class="[ + 'u-col-' + span + ]" + :style="[colStyle]" + @tap="clickHandler" + > + <slot></slot> + </view> +</template> + +<script> + import props from './props.js'; + /** + * CodeInput 栅格系统的列 + * @description 该组件一般用于Layout 布局 通过基础的 12 分栏,迅速简便地创建布局 + * @tutorial https://www.uviewui.com/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 <u-col span="3" offset="3" > <view class="demo-layout bg-purple"></view> </u-col> + */ + export default { + name: 'u-col', + mixins: [uni.$u.mpMixin, uni.$u.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: uni.$u.addUnit(uni.$u.getPx(this.parentData.gutter)/2), + paddingRight: uni.$u.addUnit(uni.$u.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: uni.$u.addUnit(Math.floor(this.width / this.gridNum * Number(this.span))), + marginLeft: uni.$u.addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))), + // #endif + } + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + mounted() { + this.init() + }, + methods: { + async init() { + // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用 + this.updateParentData() + this.width = await this.parent.getComponentWidth() + }, + updateParentData() { + this.getParentData('u-row') + }, + clickHandler(e) { + this.$emit('click'); + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-col { + padding: 0; + /* #ifndef APP-NVUE */ + box-sizing:border-box; + /* #endif */ + /* #ifdef MP */ + display: block; + /* #endif */ + } + + // nvue下百分比无效 + /* #ifndef APP-NVUE */ + .u-col-0 { + width: 0; + } + + .u-col-1 { + width: calc(100%/12); + } + + .u-col-2 { + width: calc(100%/12 * 2); + } + + .u-col-3 { + width: calc(100%/12 * 3); + } + + .u-col-4 { + width: calc(100%/12 * 4); + } + + .u-col-5 { + width: calc(100%/12 * 5); + } + + .u-col-6 { + width: calc(100%/12 * 6); + } + + .u-col-7 { + width: calc(100%/12 * 7); + } + + .u-col-8 { + width: calc(100%/12 * 8); + } + + .u-col-9 { + width: calc(100%/12 * 9); + } + + .u-col-10 { + width: calc(100%/12 * 10); + } + + .u-col-11 { + width: calc(100%/12 * 11); + } + + .u-col-12 { + width: calc(100%/12 * 12); + } + + /* #endif */ +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-collapse-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-collapse-item/props.js new file mode 100644 index 000000000..bd5749b62 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-collapse-item/props.js @@ -0,0 +1,59 @@ +export default { + props: { + // 标题 + title: { + type: String, + default: uni.$u.props.collapseItem.title + }, + // 标题右侧内容 + value: { + type: String, + default: uni.$u.props.collapseItem.value + }, + // 标题下方的描述信息 + label: { + type: String, + default: uni.$u.props.collapseItem.label + }, + // 是否禁用折叠面板 + disabled: { + type: Boolean, + default: uni.$u.props.collapseItem.disabled + }, + // 是否展示右侧箭头并开启点击反馈 + isLink: { + type: Boolean, + default: uni.$u.props.collapseItem.isLink + }, + // 是否开启点击反馈 + clickable: { + type: Boolean, + default: uni.$u.props.collapseItem.clickable + }, + // 是否显示内边框 + border: { + type: Boolean, + default: uni.$u.props.collapseItem.border + }, + // 标题的对齐方式 + align: { + type: String, + default: uni.$u.props.collapseItem.align + }, + // 唯一标识符 + name: { + type: [String, Number], + default: uni.$u.props.collapseItem.name + }, + // 标题左侧图片,可为绝对路径的图片或内置图标 + icon: { + type: String, + default: uni.$u.props.collapseItem.icon + }, + // 面板展开收起的过渡时间,单位ms + duration: { + type: Number, + default: uni.$u.props.collapseItem.duration + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-collapse-item/u-collapse-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-collapse-item/u-collapse-item.vue new file mode 100644 index 000000000..0e1b7039e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-collapse-item/u-collapse-item.vue @@ -0,0 +1,225 @@ +<template> + <view class="u-collapse-item"> + <u-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 --> + </u-cell> + <view + class="u-collapse-item__content" + :animation="animationData" + ref="animation" + > + <view + class="u-collapse-item__content__text content-class" + :id="elId" + :ref="elId" + ><slot /></view> + </view> + <u-line v-if="parentData.border"></u-line> + </view> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const animation = uni.requireNativePlugin('animation') + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * collapseItem 折叠面板Item + * @description 通过折叠面板收纳内容区域(搭配u-collapse使用) + * @tutorial https://www.uviewui.com/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 <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item> + */ + export default { + name: "u-collapse-item", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + elId: uni.$u.guid(), + // uni.createAnimation的导出数据 + animationData: {}, + // 是否展开状态 + expanded: false, + // 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时 + showBorder: false, + // 是否动画中,如果是则不允许继续触发点击 + animating: false, + // 父组件u-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) + } + }, + mounted() { + this.init() + }, + methods: { + // 异步获取内容,或者动态修改了内容时,需要重新初始化 + init() { + // 初始化数据 + this.updateParentData() + if (!this.parent) { + return uni.$u.error('u-collapse-item必须要搭配u-collapse组件使用') + } + const { + value, + accordion, + children = [] + } = this.parent + + if (accordion) { + if (uni.$u.test.array(value)) { + return uni.$u.error('手风琴模式下,u-collapse组件的value参数不能为数组') + } + this.expanded = this.name == value + } else { + if (!uni.$u.test.array(value) && value !== null) { + return uni.$u.error('非手风琴模式下,u-collapse组件的value参数必须为数组') + } + this.expanded = (value || []).some(item => item == this.name) + } + // 设置组件的展开或收起状态 + this.$nextTick(function() { + this.setContentAnimate() + }) + }, + updateParentData() { + // 此方法在mixin中 + this.getParentData('u-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() + // 标识动画结束 + uni.$u.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 + // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html + // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同 + return new Promise(resolve => { + this.$uGetRect(`#${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 "../../libs/css/components.scss"; + + .u-collapse-item { + + &__content { + overflow: hidden; + height: 0; + + &__text { + padding: 12px 15px; + color: $u-content-color; + font-size: 14px; + line-height: 18px; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-collapse/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-collapse/props.js new file mode 100644 index 000000000..7ee6d3198 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-collapse/props.js @@ -0,0 +1,19 @@ +export default { + props: { + // 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number + value: { + type: [String, Number, Array, null], + default: uni.$u.props.collapse.value + }, + // 是否手风琴模式 + accordion: { + type: Boolean, + default: uni.$u.props.collapse.accordion + }, + // 是否显示外边框 + border: { + type: Boolean, + default: uni.$u.props.collapse.border + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-collapse/u-collapse.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-collapse/u-collapse.vue new file mode 100644 index 000000000..fc188a2ea --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-collapse/u-collapse.vue @@ -0,0 +1,90 @@ +<template> + <view class="u-collapse"> + <u-line v-if="border"></u-line> + <slot /> + </view> +</template> + +<script> + import props from './props.js'; + /** + * collapse 折叠面板 + * @description 通过折叠面板收纳内容区域 + * @tutorial https://www.uviewui.com/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 <u-collapse></u-collapse> + */ + export default { + name: "u-collapse", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + watch: { + needInit() { + this.init() + } + }, + created() { + this.children = [] + }, + computed: { + needInit() { + // 通过computed,同时监听accordion和value值的变化 + // 再通过watch去执行init()方法,进行再一次的初始化 + return [this.accordion, this.value] + } + }, + watch: { + // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件 + parentData() { + if (this.children.length) { + this.children.map(child => { + // 判断子组件(u-checkbox)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值) + typeof(child.updateParentData) === 'function' && child.updateParentData() + }) + } + }, + }, + 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> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-column-notice/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-column-notice/props.js new file mode 100644 index 000000000..48091549f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-column-notice/props.js @@ -0,0 +1,55 @@ +export default { + props: { + // 显示的内容,字符串 + text: { + type: [Array], + default: uni.$u.props.columnNotice.text + }, + // 是否显示左侧的音量图标 + icon: { + type: String, + default: uni.$u.props.columnNotice.icon + }, + // 通告模式,link-显示右箭头,closable-显示右侧关闭图标 + mode: { + type: String, + default: uni.$u.props.columnNotice.mode + }, + // 文字颜色,各图标也会使用文字颜色 + color: { + type: String, + default: uni.$u.props.columnNotice.color + }, + // 背景颜色 + bgColor: { + type: String, + default: uni.$u.props.columnNotice.bgColor + }, + // 字体大小,单位px + fontSize: { + type: [String, Number], + default: uni.$u.props.columnNotice.fontSize + }, + // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度 + speed: { + type: [String, Number], + default: uni.$u.props.columnNotice.speed + }, + // direction = row时,是否使用步进形式滚动 + step: { + type: Boolean, + default: uni.$u.props.columnNotice.step + }, + // 滚动一个周期的时间长,单位ms + duration: { + type: [String, Number], + default: uni.$u.props.columnNotice.duration + }, + // 是否禁止用手滑动切换 + // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 + disableTouch: { + type: Boolean, + default: uni.$u.props.columnNotice.disableTouch + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-column-notice/u-column-notice.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-column-notice/u-column-notice.vue new file mode 100644 index 000000000..fc395321e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-column-notice/u-column-notice.vue @@ -0,0 +1,160 @@ +<template> + <view + class="u-notice" + @tap="clickHandler" + > + <slot name="icon"> + <view + class="u-notice__left-icon" + v-if="icon" + > + <u-icon + :name="icon" + :color="color" + size="19" + ></u-icon> + </view> + </slot> + <swiper + :disable-touch="disableTouch" + :vertical="step ? false : true" + circular + :interval="duration" + :autoplay="true" + class="u-notice__swiper" + @change="noticeChange" + > + <swiper-item + v-for="(item, index) in text" + :key="index" + class="u-notice__swiper__item" + > + <text + class="u-notice__swiper__item__text u-line-1" + :style="[textStyle]" + >{{ item }}</text> + </swiper-item> + </swiper> + <view + class="u-notice__right-icon" + v-if="['link', 'closable'].includes(mode)" + > + <u-icon + v-if="mode === 'link'" + name="arrow-right" + :size="17" + :color="color" + ></u-icon> + <u-icon + v-if="mode === 'closable'" + name="close" + :size="16" + :color="color" + @click="close" + ></u-icon> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * ColumnNotice 滚动通知中的垂直滚动 内部组件 + * @description 该组件用于滚动通告场景,是其中的垂直滚动方式 + * @tutorial https://www.uviewui.com/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 { + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + watch: { + text: { + immediate: true, + handler(newValue, oldValue) { + if(!uni.$u.test.array(newValue)) { + uni.$u.error('noticebar组件direction为column时,要求text参数为数组形式') + } + } + } + }, + computed: { + // 文字内容的样式 + textStyle() { + let style = {} + style.color = this.color + style.fontSize = uni.$u.addUnit(this.fontSize) + return style + }, + // 垂直或者水平滚动 + vertical() { + if (this.mode == 'horizontal') return false + else return true + }, + }, + data() { + return { + index:0 + } + }, + methods: { + noticeChange(e){ + this.index = e.detail.current + }, + // 点击通告栏 + clickHandler() { + this.$emit('click', this.index) + }, + // 点击关闭按钮 + close() { + this.$emit('close') + } + } + }; +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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: $u-warning; + } + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-count-down/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-count-down/props.js new file mode 100644 index 000000000..d62f02551 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-count-down/props.js @@ -0,0 +1,24 @@ +export default { + props: { + // 倒计时时长,单位ms + time: { + type: [String, Number], + default: uni.$u.props.countDown.time + }, + // 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒 + format: { + type: String, + default: uni.$u.props.countDown.format + }, + // 是否自动开始倒计时 + autoStart: { + type: Boolean, + default: uni.$u.props.countDown.autoStart + }, + // 是否展示毫秒倒计时 + millisecond: { + type: Boolean, + default: uni.$u.props.countDown.millisecond + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-count-down/u-count-down.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-count-down/u-count-down.vue new file mode 100644 index 000000000..b5e85a635 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-count-down/u-count-down.vue @@ -0,0 +1,163 @@ +<template> + <view class="u-count-down"> + <slot> + <text class="u-count-down__text">{{ formattedTime }}</text> + </slot> + </view> +</template> + +<script> + import props from './props.js'; + import { + isSameSecond, + parseFormat, + parseTimeData + } from './utils'; + /** + * u-count-down 倒计时 + * @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。 + * @tutorial https://uviewui.com/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 <u-count-down :time="time"></u-count-down> + */ + export default { + name: 'u-count-down', + mixins: [uni.$u.mpMixin, uni.$u.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 + } + }, + beforeDestroy() { + this.clearTimeout() + } + } +</script> + +<style + lang="scss" + scoped +> + @import "../../libs/css/components.scss"; + $u-count-down-text-color:$u-content-color !default; + $u-count-down-text-font-size:15px !default; + $u-count-down-text-line-height:22px !default; + + .u-count-down { + &__text { + color: $u-count-down-text-color; + font-size: $u-count-down-text-font-size; + line-height: $u-count-down-text-line-height; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-count-down/utils.js b/yudao-ui-app/uni_modules/uview-ui/components/u-count-down/utils.js new file mode 100644 index 000000000..8c7500501 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-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/yudao-ui-app/uni_modules/uview-ui/components/u-count-to/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-count-to/props.js new file mode 100644 index 000000000..86873c184 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-count-to/props.js @@ -0,0 +1,59 @@ +export default { + props: { + // 开始的数值,默认从0增长到某一个数 + startVal: { + type: [String, Number], + default: uni.$u.props.countTo.startVal + }, + // 要滚动的目标数值,必须 + endVal: { + type: [String, Number], + default: uni.$u.props.countTo.endVal + }, + // 滚动到目标数值的动画持续时间,单位为毫秒(ms) + duration: { + type: [String, Number], + default: uni.$u.props.countTo.duration + }, + // 设置数值后是否自动开始滚动 + autoplay: { + type: Boolean, + default: uni.$u.props.countTo.autoplay + }, + // 要显示的小数位数 + decimals: { + type: [String, Number], + default: uni.$u.props.countTo.decimals + }, + // 是否在即将到达目标数值的时候,使用缓慢滚动的效果 + useEasing: { + type: Boolean, + default: uni.$u.props.countTo.useEasing + }, + // 十进制分割 + decimal: { + type: [String, Number], + default: uni.$u.props.countTo.decimal + }, + // 字体颜色 + color: { + type: String, + default: uni.$u.props.countTo.color + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.countTo.fontSize + }, + // 是否加粗字体 + bold: { + type: Boolean, + default: uni.$u.props.countTo.bold + }, + // 千位分隔符,类似金额的分割(¥23,321.05中的",") + separator: { + type: String, + default: uni.$u.props.countTo.separator + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-count-to/u-count-to.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-count-to/u-count-to.vue new file mode 100644 index 000000000..417b7327b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-count-to/u-count-to.vue @@ -0,0 +1,184 @@ +<template> + <text + class="u-count-num" + :style="{ + fontSize: $u.addUnit(fontSize), + fontWeight: bold ? 'bold' : 'normal', + color: color + }" + >{{ displayValue }}</text> +</template> + +<script> + import props from './props.js'; +/** + * countTo 数字滚动 + * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。 + * @tutorial https://www.uviewui.com/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 <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to> + */ +export default { + name: 'u-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: [uni.$u.mpMixin, uni.$u.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); + }, + // 重新开始(暂停的情况下) + 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 "../../libs/css/components.scss"; + +.u-count-num { + /* #ifndef APP-NVUE */ + display: inline-flex; + /* #endif */ + text-align: center; +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-datetime-picker/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-datetime-picker/props.js new file mode 100644 index 000000000..f44c0f99a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-datetime-picker/props.js @@ -0,0 +1,116 @@ +export default { + props: { + // 是否打开组件 + show: { + type: Boolean, + default: uni.$u.props.datetimePicker.show + }, + // 是否展示顶部的操作栏 + showToolbar: { + type: Boolean, + default: uni.$u.props.datetimePicker.showToolbar + }, + // 绑定值 + value: { + type: [String, Number], + default: uni.$u.props.datetimePicker.value + }, + // 顶部标题 + title: { + type: String, + default: uni.$u.props.datetimePicker.title + }, + // 展示格式,mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择 + mode: { + type: String, + default: uni.$u.props.datetimePicker.mode + }, + // 可选的最大时间 + maxDate: { + type: Number, + // 最大默认值为后10年 + default: uni.$u.props.datetimePicker.maxDate + }, + // 可选的最小时间 + minDate: { + type: Number, + // 最小默认值为前10年 + default: uni.$u.props.datetimePicker.minDate + }, + // 可选的最小小时,仅mode=time有效 + minHour: { + type: Number, + default: uni.$u.props.datetimePicker.minHour + }, + // 可选的最大小时,仅mode=time有效 + maxHour: { + type: Number, + default: uni.$u.props.datetimePicker.maxHour + }, + // 可选的最小分钟,仅mode=time有效 + minMinute: { + type: Number, + default: uni.$u.props.datetimePicker.minMinute + }, + // 可选的最大分钟,仅mode=time有效 + maxMinute: { + type: Number, + default: uni.$u.props.datetimePicker.maxMinute + }, + // 选项过滤函数 + filter: { + type: [Function, null], + default: uni.$u.props.datetimePicker.filter + }, + // 选项格式化函数 + formatter: { + type: [Function, null], + default: uni.$u.props.datetimePicker.formatter + }, + // 是否显示加载中状态 + loading: { + type: Boolean, + default: uni.$u.props.datetimePicker.loading + }, + // 各列中,单个选项的高度 + itemHeight: { + type: [String, Number], + default: uni.$u.props.datetimePicker.itemHeight + }, + // 取消按钮的文字 + cancelText: { + type: String, + default: uni.$u.props.datetimePicker.cancelText + }, + // 确认按钮的文字 + confirmText: { + type: String, + default: uni.$u.props.datetimePicker.confirmText + }, + // 取消按钮的颜色 + cancelColor: { + type: String, + default: uni.$u.props.datetimePicker.cancelColor + }, + // 确认按钮的颜色 + confirmColor: { + type: String, + default: uni.$u.props.datetimePicker.confirmColor + }, + // 每列中可见选项的数量 + visibleItemCount: { + type: [String, Number], + default: uni.$u.props.datetimePicker.visibleItemCount + }, + // 是否允许点击遮罩关闭选择器 + closeOnClickOverlay: { + type: Boolean, + default: uni.$u.props.datetimePicker.closeOnClickOverlay + }, + // 各列的默认索引 + defaultIndex: { + type: Array, + default: uni.$u.props.datetimePicker.defaultIndex + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-datetime-picker/u-datetime-picker.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-datetime-picker/u-datetime-picker.vue new file mode 100644 index 000000000..18d8dcc67 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-datetime-picker/u-datetime-picker.vue @@ -0,0 +1,360 @@ +<template> + <u-picker + ref="picker" + :show="show" + :closeOnClickOverlay="closeOnClickOverlay" + :columns="columns" + :title="title" + :itemHeight="itemHeight" + :showToolbar="showToolbar" + :visibleItemCount="visibleItemCount" + :defaultIndex="innerDefaultIndex" + :cancelText="cancelText" + :confirmText="confirmText" + :cancelColor="cancelColor" + :confirmColor="confirmColor" + @close="close" + @cancel="cancel" + @confirm="confirm" + @change="change" + > + </u-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 props from './props.js'; + import dayjs from '../../libs/util/dayjs.js'; + /** + * DatetimePicker 时间日期选择器 + * @description 此选择器用于时间日期 + * @tutorial https://www.uviewui.com/components/datetimePicker.html + * @property {Boolean} show 用于控制选择器的弹出与收起 ( 默认 false ) + * @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 是否允许点击遮罩关闭选择器 ( 默认 false ) + * @property {Array} defaultIndex 各列的默认索引 + * @event {Function} close 关闭选择器时触发 + * @event {Function} confirm 点击确定按钮,返回当前选择的值 + * @event {Function} change 当选择值变化时触发 + * @event {Function} cancel 点击取消按钮 + * @example <u-datetime-picker :show="show" :value="value1" mode="datetime" ></u-datetime-picker> + */ + export default { + name: 'datetime-picker', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + columns: [], + innerDefaultIndex: [], + innerFormatter: (type, value) => value + } + }, + watch: { + show(newValue, oldValue) { + if (newValue) { + this.updateColumnValue(this.innerValue) + } + }, + propsChange() { + this.init() + } + }, + computed: { + // 如果以下这些变量发生了变化,意味着需要重新初始化各列的值 + propsChange() { + return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, ] + } + }, + mounted() { + this.init() + }, + methods: { + init() { + this.innerValue = this.correctValue(this.value) + this.updateColumnValue(this.innerValue) + }, + // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用 + setFormatter(e) { + this.innerFormatter = e + }, + // 关闭选择器 + close() { + if (this.closeOnClickOverlay) { + this.$emit('close') + } + }, + // 点击工具栏的取消按钮 + cancel() { + this.$emit('cancel') + }, + // 点击工具栏的确定按钮 + confirm() { + this.$emit('confirm', { + value: this.innerValue, + mode: this.mode + }) + this.$emit('input', this.innerValue) + }, + //用正则截取输出值,当出现多组数字时,抛出错误 + intercept(e,type){ + let judge = e.match(/\d+/g) + //判断是否掺杂数字 + if(judge.length>1){ + uni.$u.error("请勿在过滤或格式化函数时添加数字") + return 0 + }else if(type&&judge[0].length==4){//判断是否是年份 + return judge[0] + }else if(judge[0].length>2){ + uni.$u.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 { + // 将选择的值转为数值,比如'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, + // #ifndef MP-WEIXIN + // 微信小程序不能传递this实例,会因为循环引用而报错 + picker: this.$refs.picker, + // #endif + mode: this.mode + }) + }, + // 更新各列的值,进行补0、格式化等操作 + updateColumnValue(value) { + this.innerValue = value + this.updateColumns() + this.updateIndexs(value) + }, + // 更新索引 + updateIndexs(value) { + let values = [] + const formatter = this.formatter || this.innerFormatter + const padZero = uni.$u.padZero + 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', padZero(dayjs(value).month() + 1)) + ] + if (this.mode === 'date') { + // date模式,需要添加天列 + values.push(formatter('day', padZero(dayjs(value).date()))) + } + if (this.mode === 'datetime') { + // 数组的push方法,可以写入多个参数 + values.push(formatter('day', padZero(dayjs(value).date())), formatter('hour', padZero(dayjs(value).hour())), formatter('minute', padZero(dayjs(value).minute()))) + } + } + + // 根据当前各列的所有值,从各列默认值中找到默认值在各列中的索引 + const indexs = this.columns.map((column, index) => { + // 通过取大值,可以保证不会出现找不到索引的-1情况 + return Math.max(0, column.findIndex(item => item === values[index])) + }) + this.innerDefaultIndex = indexs + }, + // 更新各列的值 + 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}` : uni.$u.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 && !uni.$u.test.date(value)) { + // 如果是日期类型,但是又没有设置合法的当前时间的话,使用最小时间为当前时间 + value = this.minDate + } else if (!isDateMode && !value) { + // 如果是时间类型,而又没有默认值的话,就用最小时间 + value = `${uni.$u.padZero(this.minHour)}:${uni.$u.padZero(this.minMinute)}` + } + // 时间类型 + if (!isDateMode) { + if (String(value).indexOf(':') === -1) return uni.$u.error('时间错误,请传递如12:24的格式') + let [hour, minute] = value.split(':') + // 对时间补零,同时控制在最小值和最大值之间 + hour = uni.$u.padZero(uni.$u.range(this.minHour, this.maxHour, Number(hour))) + minute = uni.$u.padZero(uni.$u.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); + 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> + +<style lang="scss" scoped> + @import '../../libs/css/components.scss'; +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-divider/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-divider/props.js new file mode 100644 index 000000000..1fa8359fc --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-divider/props.js @@ -0,0 +1,44 @@ +export default { + props: { + // 是否虚线 + dashed: { + type: Boolean, + default: uni.$u.props.divider.dashed + }, + // 是否细线 + hairline: { + type: Boolean, + default: uni.$u.props.divider.hairline + }, + // 是否以点替代文字,优先于text字段起作用 + dot: { + type: Boolean, + default: uni.$u.props.divider.dot + }, + // 内容文本的位置,left-左边,center-中间,right-右边 + textPosition: { + type: String, + default: uni.$u.props.divider.textPosition + }, + // 文本内容 + text: { + type: [String, Number], + default: uni.$u.props.divider.text + }, + // 文本大小 + textSize: { + type: [String, Number], + default: uni.$u.props.divider.textSize + }, + // 文本颜色 + textColor: { + type: String, + default: uni.$u.props.divider.textColor + }, + // 线条颜色 + lineColor: { + type: String, + default: uni.$u.props.divider.lineColor + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-divider/u-divider.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-divider/u-divider.vue new file mode 100644 index 000000000..b629da64a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-divider/u-divider.vue @@ -0,0 +1,116 @@ +<template> + <view + class="u-divider" + :style="[$u.addStyle(customStyle)]" + @tap="click" + > + <u-line + :color="lineColor" + :customStyle="leftLineStyle" + :hairline="hairline" + :dashed="dashed" + ></u-line> + <text + v-if="dot" + class="u-divider__dot" + >●</text> + <text + v-else-if="text" + class="u-divider__text" + :style="[textStyle]" + >{{text}}</text> + <u-line + :color="lineColor" + :customStyle="rightLineStyle" + :hairline="hairline" + :dashed="dashed" + ></u-line> + </view> +</template> + +<script> + import props from './props.js'; + /** + * divider 分割线 + * @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。 + * @tutorial https://www.uviewui.com/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 <u-divider :color="color">锦瑟无端五十弦</u-divider> + */ + export default { + name:'u-divider', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + computed: { + textStyle() { + const style = {} + style.fontSize = uni.$u.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 '../../libs/css/components.scss'; + $u-divider-margin:15px 0 !default; + $u-divider-text-margin:0 15px !default; + $u-divider-dot-font-size:12px !default; + $u-divider-dot-margin:0 12px !default; + $u-divider-dot-color: #c0c4cc !default; + + .u-divider { + @include flex; + flex-direction: row; + align-items: center; + margin: $u-divider-margin; + + &__text { + margin: $u-divider-text-margin; + } + + &__dot { + font-size: $u-divider-dot-font-size; + margin: $u-divider-dot-margin; + color: $u-divider-dot-color; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown-item/props.js new file mode 100644 index 000000000..501a1f058 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown-item/props.js @@ -0,0 +1,36 @@ +export default { + props: { + // 当前选中项的value值 + value: { + type: [Number, String, Array], + default: '' + }, + // 菜单项标题 + title: { + type: [String, Number], + default: '' + }, + // 选项数据,如果传入了默认slot,此参数无效 + options: { + type: Array, + default() { + return [] + } + }, + // 是否禁用此菜单项 + disabled: { + type: Boolean, + default: false + }, + // 下拉弹窗的高度 + height: { + type: [Number, String], + default: 'auto' + }, + // 点击遮罩是否可以收起弹窗 + closeOnClickOverlay: { + type: Boolean, + default: true + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue new file mode 100644 index 000000000..07de583d6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue @@ -0,0 +1,146 @@ +<template> + <view class="u-drawdown-item"> + <u-overlay + customStyle="top: 126px" + :show="show" + :closeOnClickOverlay="closeOnClickOverlay" + @click="overlayClick" + ></u-overlay> + <view + class="u-drawdown-item__content" + :style="[style]" + :animation="animationData" + ref="animation" + > + <slot /> + </view> + </view> +</template> + +<script> + // #ifdef APP-NVUE + const animation = uni.requireNativePlugin('animation') + const dom = uni.requireNativePlugin('dom') + // #endif + import props from './props.js'; + /** + * Drawdownitem + * @description + * @tutorial url + * @property {String} + * @event {Function} + * @example + */ + export default { + name: 'u-drawdown-item', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + show: false, + top: '126px', + // uni.createAnimation的导出数据 + animationData: {}, + } + }, + mounted() { + this.init() + }, + watch: { + // 发生变化时,需要去更新父组件对应的值 + dataChange(newValue, oldValue) { + this.updateParentData() + } + }, + computed: { + // 监听对应变量的变化 + dataChange() { + return [this.title, this.disabled] + }, + style() { + const style = { + zIndex: 10071, + position: 'fixed', + display: 'flex', + left: 0, + right: 0 + } + style.top = uni.$u.addUnit(this.top) + return style + } + }, + methods: { + init() { + this.updateParentData() + }, + // 更新父组件所需的数据 + updateParentData() { + // 获取父组件u-dropdown + this.getParentData('u-dropdown') + if (!this.parent) uni.$u.error('u-dropdown-item必须配合u-dropdown使用') + // 查找父组件menuList数组中对应的标题数据 + const menuIndex = this.parent.menuList.findIndex(item => item.title === this.title) + const menuContent = { + title: this.title, + disabled: this.disabled + } + if (menuIndex >= 0) { + // 如果能找到,则直接修改 + this.parent.menuList[menuIndex] = menuContent; + } else { + // 如果无法找到,则为第一次添加,直接push即可 + this.parent.menuList.push(menuContent); + } + }, + async setContentAnimate(height) { + this.animating = true + // #ifdef APP-NVUE + const ref = this.$refs['animation'].ref + animation.transition(ref, { + styles: { + height: uni.$u.addUnit(height) + }, + duration: this.duration, + 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() + // 标识动画结束 + uni.$u.sleep(this.duration).then(() => { + this.animating = false + }) + // #endif + }, + overlayClick() { + this.show = false + this.setContentAnimate(0) + } + }, + } +</script> + +<style lang="scss" scoped> + @import '../../libs/css/components.scss'; + + .u-drawdown-item { + + &__content { + background-color: #FFFFFF; + overflow: hidden; + height: 0; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown/props.js new file mode 100644 index 000000000..5f8465efe --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown/props.js @@ -0,0 +1,65 @@ +export default { + props: { + // 标题选中时的样式 + activeStyle: { + type: [String, Object], + default: () => ({ + color: '#2979ff', + fontSize: '14px' + }) + }, + // 标题未选中时的样式 + inactiveStyle: { + type: [String, Object], + default: () => ({ + color: '#606266', + fontSize: '14px' + }) + }, + // 点击遮罩是否关闭菜单 + closeOnClickMask: { + type: Boolean, + default: true + }, + // 点击当前激活项标题是否关闭菜单 + closeOnClickSelf: { + type: Boolean, + default: true + }, + // 过渡时间 + duration: { + type: [Number, String], + default: 300 + }, + // 标题菜单的高度 + height: { + type: [Number, String], + default: 40 + }, + // 是否显示下边框 + borderBottom: { + type: Boolean, + default: false + }, + // 标题的字体大小 + titleSize: { + type: [Number, String], + default: 14 + }, + // 下拉出来的内容部分的圆角值 + borderRadius: { + type: [Number, String], + default: 0 + }, + // 菜单右侧的icon图标 + menuIcon: { + type: String, + default: 'arrow-down' + }, + // 菜单右侧图标的大小 + menuIconSize: { + type: [Number, String], + default: 14 + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue new file mode 100644 index 000000000..9c50bfe71 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue @@ -0,0 +1,127 @@ +<template> + <view class="u-drawdown"> + <view + class="u-dropdown__menu" + :style="{ + height: $u.addUnit(height) + }" + ref="u-dropdown__menu" + > + <view + class="u-dropdown__menu__item" + v-for="(item, index) in menuList" + :key="index" + @tap.stop="clickHandler(item, index)" + > + <view class="u-dropdown__menu__item__content"> + <text + class="u-dropdown__menu__item__content__text" + :style="[index === current ? activeStyle : inactiveStyle]" + >{{item.title}}</text> + <view + class="u-dropdown__menu__item__content__arrow" + :class="[index === current && 'u-dropdown__menu__item__content__arrow--rotate']" + > + <u-icon + :name="menuIcon" + :size="$u.addUnit(menuIconSize)" + ></u-icon> + </view> + </view> + </view> + </view> + <view class="u-dropdown__content"> + <slot /> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * Dropdown + * @description + * @tutorial url + * @property {String} + * @event {Function} + * @example + */ + export default { + name: 'u-dropdown', + mixins: [uni.$u.mixin, props], + data() { + return { + // �˵����� + menuList: [], + current: 0 + } + }, + computed: { + + }, + created() { + // �������������(u-dropdown-item)��this��������data��������������������С��������ѭ�����ö����� + this.children = []; + }, + methods: { + clickHandler(item, index) { + this.children.map(child => { + if(child.title === item.title) { + // this.queryRect('u-dropdown__menu').then(size => { + child.$emit('click') + child.setContentAnimate(child.show ? 0 : 300) + child.show = !child.show + // }) + } else { + child.show = false + child.setContentAnimate(0) + } + }) + }, + // ��ȡ��ǩ�ijߴ�λ�� + queryRect(el) { + // #ifndef APP-NVUE + // $uGetRectΪuView�Դ��Ľڵ��ѯ����������ĵ����ܣ�https://www.uviewui.com/js/getRect.html + // ����ڲ�һ����this.$uGetRect�������Ϊthis.$u.getRect�����߹���һ�£����Ʋ�ͬ + return new Promise(resolve => { + this.$uGetRect(`.${el}`).then(size => { + resolve(size) + }) + }) + // #endif + + // #ifdef APP-NVUE + // nvue�£�ʹ��domģ���ѯԪ�ظ߶� + // ����һ��promise���õ��ô˷�����������ʹ��then�ص� + return new Promise(resolve => { + dom.getComponentRect(this.$refs[el], res => { + resolve(res.size) + }) + }) + // #endif + }, + }, + } +</script> + +<style lang="scss"> + @import '../../libs/css/components.scss'; + + .u-dropdown { + + &__menu { + @include flex; + + &__item { + flex: 1; + @include flex; + justify-content: center; + + &__content { + @include flex; + align-items: center; + } + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-empty/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-empty/props.js new file mode 100644 index 000000000..78662f8fd --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-empty/props.js @@ -0,0 +1,59 @@ +export default { + props: { + // 内置图标名称,或图片路径,建议绝对路径 + icon: { + type: String, + default: uni.$u.props.empty.icon + }, + // 提示文字 + text: { + type: String, + default: uni.$u.props.empty.text + }, + // 文字颜色 + textColor: { + type: String, + default: uni.$u.props.empty.textColor + }, + // 文字大小 + textSize: { + type: [String, Number], + default: uni.$u.props.empty.textSize + }, + // 图标的颜色 + iconColor: { + type: String, + default: uni.$u.props.empty.iconColor + }, + // 图标的大小 + iconSize: { + type: [String, Number], + default: uni.$u.props.empty.iconSize + }, + // 选择预置的图标类型 + mode: { + type: String, + default: uni.$u.props.empty.mode + }, + // 图标宽度,单位px + width: { + type: [String, Number], + default: uni.$u.props.empty.width + }, + // 图标高度,单位px + height: { + type: [String, Number], + default: uni.$u.props.empty.height + }, + // 是否显示组件 + show: { + type: Boolean, + default: uni.$u.props.empty.show + }, + // 组件距离上一个元素之间的距离,默认px单位 + marginTop: { + type: [String, Number], + default: uni.$u.props.empty.marginTop + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-empty/u-empty.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-empty/u-empty.vue new file mode 100644 index 000000000..03d6a2736 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-empty/u-empty.vue @@ -0,0 +1,128 @@ +<template> + <view + class="u-empty" + :style="[emptyStyle]" + v-if="show" + > + <u-icon + v-if="!isSrc" + :name="mode === 'message' ? 'chat' : `empty-${mode}`" + :size="iconSize" + :color="iconColor" + margin-top="14" + ></u-icon> + <image + v-else + :style="{ + width: $u.addUnit(width), + height: $u.addUnit(height), + }" + :src="icon" + mode="widthFix" + ></image> + <text + class="u-empty__text" + :style="[textStyle]" + >{{text ? text : icons[mode]}}</text> + <view class="u-empty__wrap" v-if="$slots.default || $slots.$default"> + <slot /> + </view> + </view> +</template> + +<script> + import props from './props.js'; + + /** + * empty 内容为空 + * @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。 + * @tutorial https://www.uviewui.com/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 <u-empty text="所谓伊人,在水一方" mode="list"></u-empty> + */ + export default { + name: "u-empty", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + icons: { + car: '购物车为空', + page: '页面不存在', + search: '没有搜索结果', + address: '没有收货地址', + wifi: '没有WiFi', + order: '订单为空', + coupon: '没有优惠券', + favor: '暂无收藏', + permission: '无权限', + history: '无历史记录', + news: '无新闻列表', + message: '消息列表为空', + list: '列表为空', + data: '数据为空', + comment: '暂无评论', + } + } + }, + computed: { + // 组件样式 + emptyStyle() { + const style = {} + style.marginTop = uni.$u.addUnit(this.marginTop) + // 合并customStyle样式,此参数通过mixin中的props传递 + return uni.$u.deepMerge(uni.$u.addStyle(this.customStyle), style) + }, + // 文本样式 + textStyle() { + const style = {} + style.color = this.textColor + style.fontSize = uni.$u.addUnit(this.textSize) + return style + }, + // 判断icon是否图片路径 + isSrc() { + return this.icon.indexOf('/') >= 0 + } + } + } +</script> + +<style lang="scss" scoped> + @import '../../libs/css/components.scss'; + $u-empty-text-margin-top:20rpx !default; + $u-empty-slot-margin-top:20rpx !default; + + .u-empty { + @include flex; + flex-direction: column; + justify-content: center; + align-items: center; + + &__text { + @include flex; + justify-content: center; + align-items: center; + margin-top: $u-empty-text-margin-top; + } + } + .u-slot-wrap { + @include flex; + justify-content: center; + align-items: center; + margin-top:$u-empty-slot-margin-top; + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-form-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-form-item/props.js new file mode 100644 index 000000000..53a019155 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-form-item/props.js @@ -0,0 +1,43 @@ +export default { + props: { + // input的label提示语 + label: { + type: String, + default: uni.$u.props.formItem.label + }, + // 绑定的值 + prop: { + type: String, + default: uni.$u.props.formItem.prop + }, + // 是否显示表单域的下划线边框 + borderBottom: { + type: [String, Boolean], + default: uni.$u.props.formItem.borderBottom + }, + // label的宽度,单位px + labelWidth: { + type: [String, Number], + default: uni.$u.props.formItem.labelWidth + }, + // 右侧图标 + rightIcon: { + type: String, + default: uni.$u.props.formItem.rightIcon + }, + // 左侧图标 + leftIcon: { + type: String, + default: uni.$u.props.formItem.leftIcon + }, + // 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置 + required: { + type: Boolean, + default: uni.$u.props.formItem.required + }, + leftIconStyle: { + type: [String, Object], + default: uni.$u.props.formItem.leftIconStyle, + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-form-item/u-form-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-form-item/u-form-item.vue new file mode 100644 index 000000000..701d7cc0b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-form-item/u-form-item.vue @@ -0,0 +1,235 @@ +<template> + <view class="u-form-item"> + <view + class="u-form-item__body" + @tap="clickHandler" + :style="[$u.addStyle(customStyle), { + flexDirection: parentData.labelPosition === 'left' ? 'row' : 'column' + }]" + > + <!-- 微信小程序中,将一个参数设置空字符串,结果会变成字符串"true" --> + <slot name="label"> + <!-- {{required}} --> + <view + class="u-form-item__body__left" + v-if="required || leftIcon || label" + :style="{ + width: $u.addUnit(labelWidth || parentData.labelWidth), + marginBottom: parentData.labelPosition === 'left' ? 0 : '5px', + }" + > + <!-- 为了块对齐 --> + <view class="u-form-item__body__left__content"> + <!-- nvue不支持伪元素before --> + <text + v-if="required" + class="u-form-item__body__left__content__required" + >*</text> + <view + class="u-form-item__body__left__content__icon" + v-if="leftIcon" + > + <u-icon + :name="leftIcon" + :custom-style="leftIconStyle" + ></u-icon> + </view> + <text + class="u-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="u-form-item__body__right"> + <view class="u-form-item__body__right__content"> + <view class="u-form-item__body__right__content__slot"> + <slot /> + </view> + <view + class="item__body__right__content__icon" + v-if="$slots.right" + > + <slot name="right" /> + </view> + </view> + </view> + </view> + <slot name="error"> + <text + v-if="!!message && parentData.errorType === 'message'" + class="u-form-item__body__right__message" + :style="{ + marginLeft: $u.addUnit(parentData.labelPosition === 'top' ? 0 : (labelWidth || parentData.labelWidth)) + }" + >{{ message }}</text> + </slot> + <u-line + v-if="borderBottom" + :color="message && parentData.errorType === 'border-bottom' ? $u.color.error : propsLine.color" + :customStyle="`margin-top: ${message && parentData.errorType === 'message' ? '5px' : 0}`" + ></u-line> + </view> +</template> + +<script> + import props from './props.js'; + /** + * Form 表单 + * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。 + * @tutorial https://www.uviewui.com/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 <u-form-item label="姓名" prop="userInfo.name" borderBottom ref="item1"></u-form-item> + */ + export default { + name: 'u-form-item', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 错误提示语 + message: '', + parentData: { + // 提示文本的位置 + labelPosition: 'left', + // 提示文本对齐方式 + labelAlign: 'left', + // 提示文本的样式 + labelStyle: {}, + // 提示文本的宽度 + labelWidth: 45, + // 错误提示方式 + errorType: 'message' + } + } + }, + // 组件创建完成时,将当前实例保存到u-form中 + computed: { + propsLine() { + return uni.$u.props.line + } + }, + mounted() { + this.init() + }, + methods: { + init() { + // 父组件的实例 + this.updateParentData() + if (!this.parent) { + uni.$u.error('u-form-item需要结合u-form组件使用') + } + }, + // 获取父组件的参数 + updateParentData() { + // 此方法写在mixin中 + this.getParentData('u-form'); + }, + // 移除u-form-item的校验结果 + clearValidate() { + this.message = null + }, + // 清空当前的组件的校验结果,并重置为初始值 + resetField() { + // 找到原始值 + const value = uni.$u.getProperty(this.parent.originalModel, this.prop) + // 将u-form的model的prop属性链还原原始值 + uni.$u.setProperty(this.parent.model, this.prop, value) + // 移除校验结果 + this.message = null + }, + // 点击组件 + clickHandler() { + this.$emit('click') + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-form-item { + @include flex(column); + font-size: 14px; + color: $u-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: $u-error; + line-height: 20px; + font-size: 20px; + top: 3px; + } + + &__label { + @include flex; + align-items: center; + flex: 1; + color: $u-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: $u-light-color; + font-size: 30rpx; + } + } + + &__message { + font-size: 12px; + line-height: 12px; + color: $u-error; + } + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-form/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-form/props.js new file mode 100644 index 000000000..f2a629c0e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-form/props.js @@ -0,0 +1,45 @@ +export default { + props: { + // 当前form的需要验证字段的集合 + model: { + type: Object, + default: uni.$u.props.form.model + }, + // 验证规则 + rules: { + type: [Object, Function, Array], + default: uni.$u.props.form.rules + }, + // 有错误时的提示方式,message-提示信息,toast-进行toast提示 + // border-bottom-下边框呈现红色,none-无提示 + errorType: { + type: String, + default: uni.$u.props.form.errorType + }, + // 是否显示表单域的下划线边框 + borderBottom: { + type: Boolean, + default: uni.$u.props.form.borderBottom + }, + // label的位置,left-左边,top-上边 + labelPosition: { + type: String, + default: uni.$u.props.form.labelPosition + }, + // label的宽度,单位px + labelWidth: { + type: [String, Number], + default: uni.$u.props.form.labelWidth + }, + // lable字体的对齐方式 + labelAlign: { + type: String, + default: uni.$u.props.form.labelAlign + }, + // lable的样式,对象形式 + labelStyle: { + type: Object, + default: uni.$u.props.form.labelStyle + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-form/u-form.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-form/u-form.vue new file mode 100644 index 000000000..fe2dde2d3 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-form/u-form.vue @@ -0,0 +1,214 @@ +<template> + <view class="u-form"> + <slot /> + </view> +</template> + +<script> + import props from "./props.js"; + import Schema from "../../libs/util/async-validator"; + // 去除警告信息 + Schema.warning = function() {}; + /** + * Form 表单 + * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。 + * @tutorial https://www.uviewui.com/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 <u--formlabelPosition="left" :model="model1" :rules="rules" ref="form1"></u--form> + */ + export default { + name: "u-form", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + provide() { + return { + uForm: this, + }; + }, + data() { + return { + formRules: {}, + // 规则校验器 + validator: {}, + // 原始的model快照,用于resetFields方法重置表单时使用 + originalModel: null, + }; + }, + watch: { + // 监听规则的变化 + rules: { + immediate: true, + handler(n) { + this.setRules(n); + }, + }, + // 监听属性的变化,通知子组件u-form-item重新获取信息 + propsChange(n) { + if (this.children?.length) { + this.children.map((child) => { + // 判断子组件(u-form-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值) + typeof child.updateParentData == "function" && + child.updateParentData(); + }); + } + }, + // 监听model的初始值作为重置表单的快照 + model: { + immediate: true, + handler(n) { + if (!this.originalModel) { + this.originalModel = uni.$u.deepClone(n); + } + }, + }, + }, + computed: { + propsChange() { + return [ + this.errorType, + this.borderBottom, + this.labelPosition, + this.labelWidth, + this.labelAlign, + this.labelStyle, + ]; + }, + }, + created() { + // 存储当前form下的所有u-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) { + uni.$u.error('设置rules,model必须设置!如果已经设置,请刷新页面。'); + return; + }; + this.formRules = rules; + // 重新将规则赋予Validator + this.validator = new Schema(rules); + }, + // 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法 + resetFields() { + this.resetModel(); + }, + // 重置model为初始值的快照 + resetModel(obj) { + // 历遍所有u-form-item,根据其prop属性,还原model的原始快照 + this.children.map((child) => { + const prop = child?.prop; + const value = uni.$u.getProperty(this.originalModel, prop); + uni.$u.setProperty(this.model, prop, value); + }); + }, + // 清空校验结果 + clearValidate(props) { + props = [].concat(props); + this.children.map((child) => { + // 如果u-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 = uni.$u.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]; + // 将u-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 (uni.$u.test.array(errors)) { + errorsRes.push(...errors); + childErrors.push(...errors); + } + child.message = + childErrors[0]?.message ?? null; + } + ); + } + } + }); + // 执行回调函数 + typeof callback === "function" && callback(errorsRes); + }); + }, + // 校验全部数据 + validate(callback) { + // 开发环境才提示,生产环境不会提示 + if (process.env.NODE_ENV === 'development' && Object.keys(this.formRules).length === 0) { + uni.$u.error('未设置rules,请看文档说明!如果已经设置,请刷新页面。'); + return; + } + 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' && uni.$u.toast(errors[0].message) + reject(errors) + } else { + resolve(true) + } + }); + }); + }); + }, + }, + }; +</script> + +<style lang="scss" scoped> +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-gap/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-gap/props.js new file mode 100644 index 000000000..89953e32a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-gap/props.js @@ -0,0 +1,24 @@ +export default { + props: { + // 背景颜色(默认transparent) + bgColor: { + type: String, + default: uni.$u.props.gap.bgColor + }, + // 分割槽高度,单位px(默认30) + height: { + type: [String, Number], + default: uni.$u.props.gap.height + }, + // 与上一个组件的距离 + marginTop: { + type: [String, Number], + default: uni.$u.props.gap.marginTop + }, + // 与下一个组件的距离 + marginBottom: { + type: [String, Number], + default: uni.$u.props.gap.marginBottom + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-gap/u-gap.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-gap/u-gap.vue new file mode 100644 index 000000000..e4429f0e4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-gap/u-gap.vue @@ -0,0 +1,38 @@ +<template> + <view class="u-gap" :style="[gapStyle]"></view> +</template> + +<script> + import props from './props.js'; + /** + * gap 间隔槽 + * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量 + * @tutorial https://www.uviewui.com/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 <u-gap height="80" bg-color="#bbb"></u-gap> + */ + export default { + name: "u-gap", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + computed: { + gapStyle() { + const style = { + backgroundColor: this.bgColor, + height: uni.$u.addUnit(this.height), + marginTop: uni.$u.addUnit(this.marginTop), + marginBottom: uni.$u.addUnit(this.marginBottom), + } + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + } + }; +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-grid-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-grid-item/props.js new file mode 100644 index 000000000..06c3c6637 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-grid-item/props.js @@ -0,0 +1,14 @@ +export default { + props: { + // 宫格的name + name: { + type: [String, Number, null], + default: uni.$u.props.gridItem.name + }, + // 背景颜色 + bgColor: { + type: String, + default: uni.$u.props.gridItem.bgColor + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-grid-item/u-grid-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-grid-item/u-grid-item.vue new file mode 100644 index 000000000..fc0c7cf1d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-grid-item/u-grid-item.vue @@ -0,0 +1,209 @@ +<template> + <!-- #ifndef APP-NVUE --> + <view + class="u-grid-item" + hover-class="u-grid-item--hover-class" + :hover-stay-time="200" + @tap="clickHandler" + :class="classes" + :style="[itemStyle]" + > + <slot /> + </view> + <!-- #endif --> + <!-- #ifdef APP-NVUE --> + <view + class="u-grid-item" + :hover-stay-time="200" + @tap="clickHandler" + :class="classes" + :style="[itemStyle]" + > + <slot /> + </view> + <!-- #endif --> +</template> + +<script> + import props from './props.js'; + /** + * gridItem 提示 + * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配u-grid使用 + * @tutorial https://www.uviewui.com/components/grid.html + * @property {String | Number} name 宫格的name ( 默认 null ) + * @property {String} bgColor 宫格的背景颜色 (默认 'transparent' ) + * @property {Object} customStyle 自定义样式,对象形式 + * @event {Function} click 点击宫格触发 + * @example <u-grid-item></u-grid-item> + */ + export default { + name: "u-grid-item", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + parentData: { + col: 3, // 父组件划分的宫格数 + border: true, // 是否显示边框,根据父组件决定 + }, + // #ifdef APP-NVUE + width: 0, // nvue下才这么计算,vue下放到computed中,否则会因为延时造成闪烁 + // #endif + classes: [], // 类名集合,用于判断是否显示右边和下边框 + }; + }, + 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 uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + methods: { + init() { + // 用于在父组件u-grid的children中被添加入子组件时, + // 重新计算item的边框 + uni.$on('$uGridItem', () => { + this.gridItemClasses() + }) + // 父组件的实例 + this.updateParentData() + // #ifdef APP-NVUE + // 获取元素该有的长度,nvue下要延时才准确 + this.$nextTick(function(){ + this.getItemWidth() + }) + // #endif + // 发出事件,通知所有的grid-item都重新计算自己的边框 + uni.$emit('$uGridItem') + this.gridItemClasses() + }, + // 获取父组件的参数 + updateParentData() { + // 此方法写在mixin中 + this.getParentData('u-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['u-grid'], res => { + resolve(res.size.width) + }) + }) + // #endif + }, + gridItemClasses() { + if(this.parentData.border) { + const 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('u-border-right') + } + // 总的宫格数量对列数取余的值 + // 如果取余后,值为0,则意味着要将最后一排的宫格,都不需要下边框 + const lessNum = len % this.parentData.col === 0 ? this.parentData.col : len % this.parentData.col + // 最下面的一排child,无需下边框 + if(index < len - lessNum) { + classes.push('u-border-bottom') + } + } + }) + // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效 + // #ifdef MP-ALIPAY || MP-TOUTIAO + classes = classes.join(' ') + // #endif + this.classes = classes + } + } + }, + beforeDestroy() { + // 移除事件监听,释放性能 + uni.$off('$uGridItem') + } + }; +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-grid-item-hover-class-opcatiy:.5 !default; + $u-grid-item-margin-top:1rpx !default; + $u-grid-item-border-right-width:0.5px !default; + $u-grid-item-border-bottom-width:0.5px !default; + $u-grid-item-border-right-color:$u-border-color !default; + $u-grid-item-border-bottom-color:$u-border-color !default; + .u-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:$u-grid-item-margin-top; + /* #endif */ + + &--hover-class { + opacity:$u-grid-item-hover-class-opcatiy; + } + } + + /* #ifdef APP-NVUE */ + // 由于nvue不支持组件内引入app.vue中再引入的样式,所以需要写在这里 + .u-border-right { + border-right-width:$u-grid-item-border-right-width; + border-color: $u-grid-item-border-right-color; + } + + .u-border-bottom { + border-bottom-width:$u-grid-item-border-bottom-width; + border-color:$u-grid-item-border-bottom-color; + } + + /* #endif */ +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-grid/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-grid/props.js new file mode 100644 index 000000000..87b0f6a74 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-grid/props.js @@ -0,0 +1,19 @@ +export default { + props: { + // 分成几列 + col: { + type: [String, Number], + default: uni.$u.props.grid.col + }, + // 是否显示边框 + border: { + type: Boolean, + default: uni.$u.props.grid.border + }, + // 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右 + align: { + type: String, + default: uni.$u.props.grid.align + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-grid/u-grid.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-grid/u-grid.vue new file mode 100644 index 000000000..b43cc270a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-grid/u-grid.vue @@ -0,0 +1,97 @@ +<template> + <view + class="u-grid" + ref='u-grid' + :style="[gridStyle]" + > + <slot /> + </view> +</template> + +<script> + import props from './props.js'; + /** + * grid 宫格布局 + * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。 + * @tutorial https://www.uviewui.com/components/grid.html + * @property {String | Number} col 宫格的列数(默认 3 ) + * @property {Boolean} border 是否显示宫格的边框(默认 false ) + * @property {String} align 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右 (默认 'left' ) + * @property {Object} customStyle 定义需要用到的外部样式 + * @event {Function} click 点击宫格触发 + * @example <u-grid :col="3" @click="click"></u-grid> + */ + export default { + name: 'u-grid', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + index: 0, + width: 0 + } + }, + watch: { + // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件 + parentData() { + if (this.children.length) { + this.children.map(child => { + // 判断子组件(u-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 uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)); + } + }, + methods: { + // 此方法由u-grid-item触发,用于在u-grid发出事件 + childClick(name) { + this.$emit('click', name) + } + } + }; +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-grid-width:100% !default; + .u-grid { + /* #ifdef MP */ + width: $u-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/yudao-ui-app/uni_modules/uview-ui/components/u-icon/icons.js b/yudao-ui-app/uni_modules/uview-ui/components/u-icon/icons.js new file mode 100644 index 000000000..f4d0fe293 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-icon/icons.js @@ -0,0 +1,214 @@ +export default { + 'uicon-level': '\ue693', + 'uicon-column-line': '\ue68e', + 'uicon-checkbox-mark': '\ue807', + 'uicon-folder': '\ue7f5', + 'uicon-movie': '\ue7f6', + 'uicon-star-fill': '\ue669', + 'uicon-star': '\ue65f', + 'uicon-phone-fill': '\ue64f', + 'uicon-phone': '\ue622', + 'uicon-apple-fill': '\ue881', + 'uicon-chrome-circle-fill': '\ue885', + 'uicon-backspace': '\ue67b', + 'uicon-attach': '\ue632', + 'uicon-cut': '\ue948', + 'uicon-empty-car': '\ue602', + 'uicon-empty-coupon': '\ue682', + 'uicon-empty-address': '\ue646', + 'uicon-empty-favor': '\ue67c', + 'uicon-empty-permission': '\ue686', + 'uicon-empty-news': '\ue687', + 'uicon-empty-search': '\ue664', + 'uicon-github-circle-fill': '\ue887', + 'uicon-rmb': '\ue608', + 'uicon-person-delete-fill': '\ue66a', + 'uicon-reload': '\ue788', + 'uicon-order': '\ue68f', + 'uicon-server-man': '\ue6bc', + 'uicon-search': '\ue62a', + 'uicon-fingerprint': '\ue955', + 'uicon-more-dot-fill': '\ue630', + 'uicon-scan': '\ue662', + 'uicon-share-square': '\ue60b', + 'uicon-map': '\ue61d', + 'uicon-map-fill': '\ue64e', + 'uicon-tags': '\ue629', + 'uicon-tags-fill': '\ue651', + 'uicon-bookmark-fill': '\ue63b', + 'uicon-bookmark': '\ue60a', + 'uicon-eye': '\ue613', + 'uicon-eye-fill': '\ue641', + 'uicon-mic': '\ue64a', + 'uicon-mic-off': '\ue649', + 'uicon-calendar': '\ue66e', + 'uicon-calendar-fill': '\ue634', + 'uicon-trash': '\ue623', + 'uicon-trash-fill': '\ue658', + 'uicon-play-left': '\ue66d', + 'uicon-play-right': '\ue610', + 'uicon-minus': '\ue618', + 'uicon-plus': '\ue62d', + 'uicon-info': '\ue653', + 'uicon-info-circle': '\ue7d2', + 'uicon-info-circle-fill': '\ue64b', + 'uicon-question': '\ue715', + 'uicon-error': '\ue6d3', + 'uicon-close': '\ue685', + 'uicon-checkmark': '\ue6a8', + 'uicon-android-circle-fill': '\ue67e', + 'uicon-android-fill': '\ue67d', + 'uicon-ie': '\ue87b', + 'uicon-IE-circle-fill': '\ue889', + 'uicon-google': '\ue87a', + 'uicon-google-circle-fill': '\ue88a', + 'uicon-setting-fill': '\ue872', + 'uicon-setting': '\ue61f', + 'uicon-minus-square-fill': '\ue855', + 'uicon-plus-square-fill': '\ue856', + 'uicon-heart': '\ue7df', + 'uicon-heart-fill': '\ue851', + 'uicon-camera': '\ue7d7', + 'uicon-camera-fill': '\ue870', + 'uicon-more-circle': '\ue63e', + 'uicon-more-circle-fill': '\ue645', + 'uicon-chat': '\ue620', + 'uicon-chat-fill': '\ue61e', + 'uicon-bag-fill': '\ue617', + 'uicon-bag': '\ue619', + 'uicon-error-circle-fill': '\ue62c', + 'uicon-error-circle': '\ue624', + 'uicon-close-circle': '\ue63f', + 'uicon-close-circle-fill': '\ue637', + 'uicon-checkmark-circle': '\ue63d', + 'uicon-checkmark-circle-fill': '\ue635', + 'uicon-question-circle-fill': '\ue666', + 'uicon-question-circle': '\ue625', + 'uicon-share': '\ue631', + 'uicon-share-fill': '\ue65e', + 'uicon-shopping-cart': '\ue621', + 'uicon-shopping-cart-fill': '\ue65d', + 'uicon-bell': '\ue609', + 'uicon-bell-fill': '\ue640', + 'uicon-list': '\ue650', + 'uicon-list-dot': '\ue616', + 'uicon-zhihu': '\ue6ba', + 'uicon-zhihu-circle-fill': '\ue709', + 'uicon-zhifubao': '\ue6b9', + 'uicon-zhifubao-circle-fill': '\ue6b8', + 'uicon-weixin-circle-fill': '\ue6b1', + 'uicon-weixin-fill': '\ue6b2', + 'uicon-twitter-circle-fill': '\ue6ab', + 'uicon-twitter': '\ue6aa', + 'uicon-taobao-circle-fill': '\ue6a7', + 'uicon-taobao': '\ue6a6', + 'uicon-weibo-circle-fill': '\ue6a5', + 'uicon-weibo': '\ue6a4', + 'uicon-qq-fill': '\ue6a1', + 'uicon-qq-circle-fill': '\ue6a0', + 'uicon-moments-circel-fill': '\ue69a', + 'uicon-moments': '\ue69b', + 'uicon-qzone': '\ue695', + 'uicon-qzone-circle-fill': '\ue696', + 'uicon-baidu-circle-fill': '\ue680', + 'uicon-baidu': '\ue681', + 'uicon-facebook-circle-fill': '\ue68a', + 'uicon-facebook': '\ue689', + 'uicon-car': '\ue60c', + 'uicon-car-fill': '\ue636', + 'uicon-warning-fill': '\ue64d', + 'uicon-warning': '\ue694', + 'uicon-clock-fill': '\ue638', + 'uicon-clock': '\ue60f', + 'uicon-edit-pen': '\ue612', + 'uicon-edit-pen-fill': '\ue66b', + 'uicon-email': '\ue611', + 'uicon-email-fill': '\ue642', + 'uicon-minus-circle': '\ue61b', + 'uicon-minus-circle-fill': '\ue652', + 'uicon-plus-circle': '\ue62e', + 'uicon-plus-circle-fill': '\ue661', + 'uicon-file-text': '\ue663', + 'uicon-file-text-fill': '\ue665', + 'uicon-pushpin': '\ue7e3', + 'uicon-pushpin-fill': '\ue86e', + 'uicon-grid': '\ue673', + 'uicon-grid-fill': '\ue678', + 'uicon-play-circle': '\ue647', + 'uicon-play-circle-fill': '\ue655', + 'uicon-pause-circle-fill': '\ue654', + 'uicon-pause': '\ue8fa', + 'uicon-pause-circle': '\ue643', + 'uicon-eye-off': '\ue648', + 'uicon-eye-off-outline': '\ue62b', + 'uicon-gift-fill': '\ue65c', + 'uicon-gift': '\ue65b', + 'uicon-rmb-circle-fill': '\ue657', + 'uicon-rmb-circle': '\ue677', + 'uicon-kefu-ermai': '\ue656', + 'uicon-server-fill': '\ue751', + 'uicon-coupon-fill': '\ue8c4', + 'uicon-coupon': '\ue8ae', + 'uicon-integral': '\ue704', + 'uicon-integral-fill': '\ue703', + 'uicon-home-fill': '\ue964', + 'uicon-home': '\ue965', + 'uicon-hourglass-half-fill': '\ue966', + 'uicon-hourglass': '\ue967', + 'uicon-account': '\ue628', + 'uicon-plus-people-fill': '\ue626', + 'uicon-minus-people-fill': '\ue615', + 'uicon-account-fill': '\ue614', + 'uicon-thumb-down-fill': '\ue726', + 'uicon-thumb-down': '\ue727', + 'uicon-thumb-up': '\ue733', + 'uicon-thumb-up-fill': '\ue72f', + 'uicon-lock-fill': '\ue979', + 'uicon-lock-open': '\ue973', + 'uicon-lock-opened-fill': '\ue974', + 'uicon-lock': '\ue97a', + 'uicon-red-packet-fill': '\ue690', + 'uicon-photo-fill': '\ue98b', + 'uicon-photo': '\ue98d', + 'uicon-volume-off-fill': '\ue659', + 'uicon-volume-off': '\ue644', + 'uicon-volume-fill': '\ue670', + 'uicon-volume': '\ue633', + 'uicon-red-packet': '\ue691', + 'uicon-download': '\ue63c', + 'uicon-arrow-up-fill': '\ue6b0', + 'uicon-arrow-down-fill': '\ue600', + 'uicon-play-left-fill': '\ue675', + 'uicon-play-right-fill': '\ue676', + 'uicon-rewind-left-fill': '\ue679', + 'uicon-rewind-right-fill': '\ue67a', + 'uicon-arrow-downward': '\ue604', + 'uicon-arrow-leftward': '\ue601', + 'uicon-arrow-rightward': '\ue603', + 'uicon-arrow-upward': '\ue607', + 'uicon-arrow-down': '\ue60d', + 'uicon-arrow-right': '\ue605', + 'uicon-arrow-left': '\ue60e', + 'uicon-arrow-up': '\ue606', + 'uicon-skip-back-left': '\ue674', + 'uicon-skip-forward-right': '\ue672', + 'uicon-rewind-right': '\ue66f', + 'uicon-rewind-left': '\ue671', + 'uicon-arrow-right-double': '\ue68d', + 'uicon-arrow-left-double': '\ue68c', + 'uicon-wifi-off': '\ue668', + 'uicon-wifi': '\ue667', + 'uicon-empty-data': '\ue62f', + 'uicon-empty-history': '\ue684', + 'uicon-empty-list': '\ue68b', + 'uicon-empty-page': '\ue627', + 'uicon-empty-order': '\ue639', + 'uicon-man': '\ue697', + 'uicon-woman': '\ue69c', + 'uicon-man-add': '\ue61c', + 'uicon-man-add-fill': '\ue64c', + 'uicon-man-delete': '\ue61a', + 'uicon-man-delete-fill': '\ue66a', + 'uicon-zh': '\ue70a', + 'uicon-en': '\ue692' +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-icon/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-icon/props.js new file mode 100644 index 000000000..71845b7ca --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-icon/props.js @@ -0,0 +1,89 @@ +export default { + props: { + // 图标类名 + name: { + type: String, + default: uni.$u.props.icon.name + }, + // 图标颜色,可接受主题色 + color: { + type: String, + default: uni.$u.props.icon.color + }, + // 字体大小,单位px + size: { + type: [String, Number], + default: uni.$u.props.icon.size + }, + // 是否显示粗体 + bold: { + type: Boolean, + default: uni.$u.props.icon.bold + }, + // 点击图标的时候传递事件出去的index(用于区分点击了哪一个) + index: { + type: [String, Number], + default: uni.$u.props.icon.index + }, + // 触摸图标时的类名 + hoverClass: { + type: String, + default: uni.$u.props.icon.hoverClass + }, + // 自定义扩展前缀,方便用户扩展自己的图标库 + customPrefix: { + type: String, + default: uni.$u.props.icon.customPrefix + }, + // 图标右边或者下面的文字 + label: { + type: [String, Number], + default: uni.$u.props.icon.label + }, + // label的位置,只能右边或者下边 + labelPos: { + type: String, + default: uni.$u.props.icon.labelPos + }, + // label的大小 + labelSize: { + type: [String, Number], + default: uni.$u.props.icon.labelSize + }, + // label的颜色 + labelColor: { + type: String, + default: uni.$u.props.icon.labelColor + }, + // label与图标的距离 + space: { + type: [String, Number], + default: uni.$u.props.icon.space + }, + // 图片的mode + imgMode: { + type: String, + default: uni.$u.props.icon.imgMode + }, + // 用于显示图片小图标时,图片的宽度 + width: { + type: [String, Number], + default: uni.$u.props.icon.width + }, + // 用于显示图片小图标时,图片的高度 + height: { + type: [String, Number], + default: uni.$u.props.icon.height + }, + // 用于解决某些情况下,让图标垂直居中的用途 + top: { + type: [String, Number], + default: uni.$u.props.icon.top + }, + // 是否阻止事件传播 + stop: { + type: Boolean, + default: uni.$u.props.icon.stop + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-icon/u-icon.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-icon/u-icon.vue new file mode 100644 index 000000000..9340328ee --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-icon/u-icon.vue @@ -0,0 +1,234 @@ +<template> + <view + class="u-icon" + @tap="clickHandler" + :class="['u-icon--' + labelPos]" + > + <image + class="u-icon__img" + v-if="isImg" + :src="name" + :mode="imgMode" + :style="[imgStyle, $u.addStyle(customStyle)]" + ></image> + <text + v-else + class="u-icon__icon" + :class="uClasses" + :style="[iconStyle, $u.addStyle(customStyle)]" + :hover-class="hoverClass" + >{{icon}}</text> + <!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 --> + <text + v-if="label !== ''" + class="u-icon__label" + :style="{ + color: labelColor, + fontSize: $u.addUnit(labelSize), + marginLeft: labelPos == 'right' ? $u.addUnit(space) : 0, + marginTop: labelPos == 'bottom' ? $u.addUnit(space) : 0, + marginRight: labelPos == 'left' ? $u.addUnit(space) : 0, + marginBottom: labelPos == 'top' ? $u.addUnit(space) : 0, + }" + >{{ label }}</text> + </view> +</template> + +<script> + // #ifdef APP-NVUE + // nvue通过weex的dom模块引入字体,相关文档地址如下: + // https://weex.apache.org/zh/docs/modules/dom.html#addrule + const fontUrl = 'https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf' + const domModule = weex.requireModule('dom') + domModule.addRule('fontFace', { + 'fontFamily': "uicon-iconfont", + 'src': `url('${fontUrl}')` + }) + // #endif + + // 引入图标名称,已经对应的unicode + import icons from './icons' + + import props from './props.js';; + + /** + * icon 图标 + * @description 基于字体的图标集,包含了大多数常见场景的图标。 + * @tutorial https://www.uviewui.com/components/icon.html + * @property {String} name 图标名称,见示例图标集 + * @property {String} color 图标颜色,可接受主题色 (默认 color['u-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['u-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 <u-icon name="photo" color="#2979ff" size="28"></u-icon> + */ + export default { + name: 'u-icon', + data() { + return { + + } + }, + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + computed: { + uClasses() { + let classes = [] + classes.push(this.customPrefix + '-' + this.name) + // // uView的自定义图标类名为u-iconfont + // if (this.customPrefix == 'uicon') { + // classes.push('u-iconfont') + // } else { + // classes.push(this.customPrefix) + // } + // 主题色,通过类配置 + if (this.color && uni.$u.config.type.includes(this.color)) classes.push('u-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: uni.$u.addUnit(this.size), + lineHeight: uni.$u.addUnit(this.size), + fontWeight: this.bold ? 'bold' : 'normal', + // 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中 + top: uni.$u.addUnit(this.top) + } + // 非主题色值时,才当作颜色值 + if (this.color && !uni.$u.config.type.includes(this.color)) style.color = this.color + + return style + }, + // 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式 + isImg() { + return this.name.indexOf('/') !== -1 + }, + imgStyle() { + let style = {} + // 如果设置width和height属性,则优先使用,否则使用size属性 + style.width = this.width ? uni.$u.addUnit(this.width) : uni.$u.addUnit(this.size) + style.height = this.height ? uni.$u.addUnit(this.height) : uni.$u.addUnit(this.size) + return style + }, + // 通过图标名,查找对应的图标 + icon() { + // 如果内置的图标中找不到对应的图标,就直接返回name值,因为用户可能传入的是unicode代码 + return icons['uicon-' + this.name] || this.name + } + }, + methods: { + clickHandler(e) { + this.$emit('click', this.index) + // 是否阻止事件冒泡 + this.stop && this.preventEvent(e) + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + // 变量定义 + $u-icon-primary: $u-primary !default; + $u-icon-success: $u-success !default; + $u-icon-info: $u-info !default; + $u-icon-warning: $u-warning !default; + $u-icon-error: $u-error !default; + $u-icon-label-line-height:1 !default; + + /* #ifndef APP-NVUE */ + // 非nvue下加载字体 + @font-face { + font-family: 'uicon-iconfont'; + src: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype'); + } + + /* #endif */ + + .u-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: uicon-iconfont; + position: relative; + @include flex; + align-items: center; + + &--primary { + color: $u-icon-primary; + } + + &--success { + color: $u-icon-success; + } + + &--error { + color: $u-icon-error; + } + + &--warning { + color: $u-icon-warning; + } + + &--info { + color: $u-icon-info; + } + } + + &__img { + /* #ifndef APP-NVUE */ + height: auto; + will-change: transform; + /* #endif */ + } + + &__label { + /* #ifndef APP-NVUE */ + line-height: $u-icon-label-line-height; + /* #endif */ + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-image/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-image/props.js new file mode 100644 index 000000000..2eabb746b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-image/props.js @@ -0,0 +1,84 @@ +export default { + props: { + // 图片地址 + src: { + type: String, + default: uni.$u.props.image.src + }, + // 裁剪模式 + mode: { + type: String, + default: uni.$u.props.image.mode + }, + // 宽度,单位任意 + width: { + type: [String, Number], + default: uni.$u.props.image.width + }, + // 高度,单位任意 + height: { + type: [String, Number], + default: uni.$u.props.image.height + }, + // 图片形状,circle-圆形,square-方形 + shape: { + type: String, + default: uni.$u.props.image.shape + }, + // 圆角,单位任意 + radius: { + type: [String, Number], + default: uni.$u.props.image.radius + }, + // 是否懒加载,微信小程序、App、百度小程序、字节跳动小程序 + lazyLoad: { + type: Boolean, + default: uni.$u.props.image.lazyLoad + }, + // 开启长按图片显示识别微信小程序码菜单 + showMenuByLongpress: { + type: Boolean, + default: uni.$u.props.image.showMenuByLongpress + }, + // 加载中的图标,或者小图片 + loadingIcon: { + type: String, + default: uni.$u.props.image.loadingIcon + }, + // 加载失败的图标,或者小图片 + errorIcon: { + type: String, + default: uni.$u.props.image.errorIcon + }, + // 是否显示加载中的图标或者自定义的slot + showLoading: { + type: Boolean, + default: uni.$u.props.image.showLoading + }, + // 是否显示加载错误的图标或者自定义的slot + showError: { + type: Boolean, + default: uni.$u.props.image.showError + }, + // 是否需要淡入效果 + fade: { + type: Boolean, + default: uni.$u.props.image.fade + }, + // 只支持网络资源,只对微信小程序有效 + webp: { + type: Boolean, + default: uni.$u.props.image.webp + }, + // 过渡时间,单位ms + duration: { + type: [String, Number], + default: uni.$u.props.image.duration + }, + // 背景颜色,用于深色页面加载图片时,为了和背景色融合 + bgColor: { + type: String, + default: uni.$u.props.image.bgColor + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-image/u-image.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-image/u-image.vue new file mode 100644 index 000000000..d7105c2fc --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-image/u-image.vue @@ -0,0 +1,232 @@ +<template> + <u-transition + mode="fade" + :show="show" + :duration="fade ? 1000 : 0" + > + <view + class="u-image" + @tap="onClick" + :style="[wrapStyle, backgroundStyle]" + > + <image + v-if="!isError" + :src="src" + :mode="mode" + @error="onErrorHandler" + @load="onLoadHandler" + :show-menu-by-longpress="showMenuByLongpress" + :lazy-load="lazyLoad" + class="u-image__image" + :style="{ + borderRadius: shape == 'circle' ? '10000px' : $u.addUnit(radius), + width: $u.addUnit(width), + height: $u.addUnit(height) + }" + ></image> + <view + v-if="showLoading && loading" + class="u-image__loading" + :style="{ + borderRadius: shape == 'circle' ? '50%' : $u.addUnit(radius), + backgroundColor: this.bgColor, + width: $u.addUnit(width), + height: $u.addUnit(height) + }" + > + <slot name="loading"> + <u-icon + :name="loadingIcon" + :width="width" + :height="height" + ></u-icon> + </slot> + </view> + <view + v-if="showError && isError && !loading" + class="u-image__error" + :style="{ + borderRadius: shape == 'circle' ? '50%' : $u.addUnit(radius), + width: $u.addUnit(width), + height: $u.addUnit(height) + }" + > + <slot name="error"> + <u-icon + :name="errorIcon" + :width="width" + :height="height" + ></u-icon> + </slot> + </view> + </view> + </u-transition> +</template> + +<script> + import props from './props.js'; + /** + * Image 图片 + * @description 此组件为uni-app的image组件的加强版,在继承了原有功能外,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。 + * @tutorial https://uviewui.com/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 <u-image width="100%" height="300px" :src="src"></u-image> + */ + export default { + name: 'u-image', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 图片是否加载错误,如果是,则显示错误占位图 + isError: false, + // 初始化组件时,默认为加载中状态 + loading: true, + // 不透明度,为了实现淡入淡出的效果 + opacity: 1, + // 过渡时间,因为props的值无法修改,故需要一个中间值 + durationTime: this.duration, + // 图片加载完成时,去掉背景颜色,因为如果是png图片,就会显示灰色的背景 + backgroundStyle: {}, + // 用于fade模式的控制组件显示与否 + show: false + }; + }, + watch: { + src: { + immediate: true, + handler(n) { + if (!n) { + // 如果传入null或者'',或者false,或者undefined,标记为错误状态 + this.isError = true + + } else { + this.isError = false; + this.loading = true; + } + } + } + }, + computed: { + wrapStyle() { + let style = {}; + // 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位 + style.width = this.$u.addUnit(this.width); + style.height = this.$u.addUnit(this.height); + // 如果是显示圆形,设置一个很多的半径值即可 + style.borderRadius = this.shape == 'circle' ? '10000px' : uni.$u.addUnit(this.radius) + // 如果设置圆角,必须要有hidden,否则可能圆角无效 + style.overflow = this.borderRadius > 0 ? 'hidden' : 'visible' + // if (this.fade) { + // style.opacity = this.opacity + // // nvue下,这几个属性必须要分开写 + // style.transitionDuration = `${this.durationTime}ms` + // style.transitionTimingFunction = 'ease-in-out' + // style.transitionProperty = 'opacity' + // } + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)); + + } + }, + mounted() { + this.show = true + }, + methods: { + // 点击图片 + onClick() { + this.$emit('click') + }, + // 图片加载失败 + onErrorHandler(err) { + this.loading = false + this.isError = true + this.$emit('error', err) + }, + // 图片加载完成,标记loading结束 + onLoadHandler() { + this.loading = false + this.isError = false + this.$emit('load') + this.removeBgColor() + // 如果不需要动画效果,就不执行下方代码,同时移除加载时的背景颜色 + // 否则无需fade效果时,png图片依然能看到下方的背景色 + // if (!this.fade) return this.removeBgColor(); + // // 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的灰色),再改成1,是为了获得过渡效果 + // this.opacity = 0; + // // 这里设置为0,是为了图片展示到背景全透明这个过程时间为0,延时之后延时之后重新设置为duration,是为了获得背景透明(灰色) + // // 到图片展示的过程中的淡入效果 + // this.durationTime = 0; + // // 延时50ms,否则在浏览器H5,过渡效果无效 + // setTimeout(() => { + // this.durationTime = this.duration; + // this.opacity = 1; + // setTimeout(() => { + // this.removeBgColor(); + // }, this.durationTime); + // }, 50); + }, + // 移除图片的背景色 + removeBgColor() { + // 淡入动画过渡完成后,将背景设置为透明色,否则png图片会看到灰色的背景 + this.backgroundStyle = { + backgroundColor: 'transparent' + }; + } + } + }; +</script> + +<style lang="scss" scoped> + @import '../../libs/css/components.scss'; + + $u-image-error-top:0px !default; + $u-image-error-left:0px !default; + $u-image-error-width:100% !default; + $u-image-error-hight:100% !default; + $u-image-error-background-color:$u-bg-color !default; + $u-image-error-color:$u-tips-color !default; + $u-image-error-font-size: 46rpx !default; + + .u-image { + position: relative; + transition: opacity 0.5s ease-in-out; + + &__image { + width: 100%; + height: 100%; + } + + &__loading, + &__error { + position: absolute; + top: $u-image-error-top; + left: $u-image-error-left; + width: $u-image-error-width; + height: $u-image-error-hight; + @include flex; + align-items: center; + justify-content: center; + background-color: $u-image-error-background-color; + color: $u-image-error-color; + font-size: $u-image-error-font-size; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-index-anchor/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-index-anchor/props.js new file mode 100644 index 000000000..6d8b59a72 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-index-anchor/props.js @@ -0,0 +1,29 @@ +export default { + props: { + // 列表锚点文本内容 + text: { + type: [String, Number], + default: uni.$u.props.indexAnchor.text + }, + // 列表锚点文字颜色 + color: { + type: String, + default: uni.$u.props.indexAnchor.color + }, + // 列表锚点文字大小,单位默认px + size: { + type: [String, Number], + default: uni.$u.props.indexAnchor.size + }, + // 列表锚点背景颜色 + bgColor: { + type: String, + default: uni.$u.props.indexAnchor.bgColor + }, + // 列表锚点高度,单位默认px + height: { + type: [String, Number], + default: uni.$u.props.indexAnchor.height + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-index-anchor/u-index-anchor.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-index-anchor/u-index-anchor.vue new file mode 100644 index 000000000..b95ddef9e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-index-anchor/u-index-anchor.vue @@ -0,0 +1,91 @@ +<template> + <!-- #ifdef APP-NVUE --> + <header> + <!-- #endif --> + <view + class="u-index-anchor u-border-bottom" + :ref="`u-index-anchor-${text}`" + :style="{ + height: $u.addUnit(height), + backgroundColor: bgColor + }" + > + <text + class="u-index-anchor__text" + :style="{ + fontSize: $u.addUnit(size), + color: color + }" + >{{ text }}</text> + </view> + <!-- #ifdef APP-NVUE --> + </header> + <!-- #endif --> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * IndexAnchor 列表锚点 + * @description + * @tutorial https://uviewui.com/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 <u-index-anchor :text="indexList[index]"></u-index-anchor> + */ + export default { + name: 'u-index-anchor', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + } + }, + mounted() { + this.init() + }, + methods: { + init() { + // 此处会活动父组件实例,并赋值给实例的parent属性 + const indexList = uni.$u.$parent.call(this, 'u-index-list') + if (!indexList) { + return uni.$u.error('u-index-anchor必须要搭配u-index-list组件使用') + } + // 将当前实例放入到u-index-list中 + indexList.anchors.push(this) + const indexListItem = uni.$u.$parent.call(this, 'u-index-item') + // #ifndef APP-NVUE + // 只有在非nvue下,u-index-anchor才是嵌套在u-index-item中的 + if (!indexListItem) { + return uni.$u.error('u-index-anchor必须要搭配u-index-item组件使用') + } + // 设置u-index-item的id为anchor的text标识符,因为非nvue下滚动列表需要依赖scroll-view滚动到元素的特性 + indexListItem.id = this.text.charCodeAt(0) + // #endif + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-index-anchor { + position: sticky; + top: 0; + @include flex; + align-items: center; + padding-left: 15px; + z-index: 1; + + &__text { + @include flex; + align-items: center; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-index-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-index-item/props.js new file mode 100644 index 000000000..7c1133117 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-index-item/props.js @@ -0,0 +1,5 @@ +export default { + props: { + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-index-item/u-index-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-index-item/u-index-item.vue new file mode 100644 index 000000000..0bc7fb3cd --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-index-item/u-index-item.vue @@ -0,0 +1,87 @@ +<template> + <!-- #ifdef APP-NVUE --> + <cell ref="u-index-item"> + <!-- #endif --> + <view + class="u-index-item" + :id="`u-index-item-${id}`" + :class="[`u-index-item-${id}`]" + > + <slot /> + </view> + <!-- #ifdef APP-NVUE --> + </cell> + <!-- #endif --> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度 + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * IndexItem + * @description + * @tutorial https://uviewui.com/components/indexList.html + * @property {String} + * @event {Function} + * @example + */ + export default { + name: 'u-index-item', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + // 本组件到滚动条顶部的距离 + top: 0, + height: 0, + id: '' + } + }, + created() { + // 子组件u-index-anchor的实例 + this.anchor = {} + }, + mounted() { + this.init() + }, + methods: { + init() { + // 此处会活动父组件实例,并赋值给实例的parent属性 + this.getParentData('u-index-list') + if (!this.parent) { + return uni.$u.error('u-index-item必须要搭配u-index-list组件使用') + } + uni.$u.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.$uGetRect('.u-index-item').then(size => { + resolve(size) + }) + // #endif + + // #ifdef APP-NVUE + const ref = this.$refs['u-index-item'] + dom.getComponentRect(ref, res => { + resolve(res.size) + }) + // #endif + }) + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-index-list/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-index-list/props.js new file mode 100644 index 000000000..354d45914 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-index-list/props.js @@ -0,0 +1,29 @@ +export default { + props: { + // 右边锚点非激活的颜色 + inactiveColor: { + type: String, + default: uni.$u.props.indexList.inactiveColor + }, + // 右边锚点激活的颜色 + activeColor: { + type: String, + default: uni.$u.props.indexList.activeColor + }, + // 索引字符列表,数组形式 + indexList: { + type: Array, + default: uni.$u.props.indexList.indexList + }, + // 是否开启锚点自动吸顶 + sticky: { + type: Boolean, + default: uni.$u.props.indexList.sticky + }, + // 自定义导航栏的高度 + customNavHeight: { + type: [String, Number], + default: uni.$u.props.indexList.customNavHeight + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-index-list/u-index-list.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-index-list/u-index-list.vue new file mode 100644 index 000000000..d71261853 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-index-list/u-index-list.vue @@ -0,0 +1,440 @@ +<template> + <view class="u-index-list"> + <!-- #ifdef APP-NVUE --> + <list + :scrollTop="scrollTop" + enable-back-to-top + :offset-accuracy="1" + :style="{ + maxHeight: $u.addUnit(scrollViewHeight) + }" + @scroll="scrollHandler" + ref="uList" + > + <cell + v-if="$slots.header" + ref="header" + > + <slot name="header" /> + </cell> + <slot /> + <cell v-if="$slots.footer"> + <slot name="footer" /> + </cell> + </list> + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <scroll-view + :scrollTop="scrollTop" + :scrollIntoView="scrollIntoView" + :offset-accuracy="1" + :style="{ + maxHeight: $u.addUnit(scrollViewHeight) + }" + scroll-y + @scroll="scrollHandler" + ref="uList" + > + <view v-if="$slots.header"> + <slot name="header" /> + </view> + <slot /> + <view v-if="$slots.footer"> + <slot name="footer" /> + </view> + </scroll-view> + <!-- #endif --> + <view + class="u-index-list__letter" + ref="u-index-list__letter" + :style="{ top: $u.addUnit(letterInfo.top || 100) }" + @touchstart="touchStart" + @touchmove.stop.prevent="touchMove" + @touchend.stop.prevent="touchEnd" + @touchcancel.stop.prevent="touchEnd" + > + <view + class="u-index-list__letter__item" + v-for="(item, index) in uIndexList" + :key="index" + :style="{ + backgroundColor: activeIndex === index ? activeColor : 'transparent' + }" + > + <text + class="u-index-list__letter__item__index" + :style="{color: activeIndex === index ? '#fff' : inactiveColor}" + >{{ item }}</text> + </view> + </view> + <u-transition + mode="fade" + :show="touching" + :customStyle="{ + position: 'fixed', + right: '50px', + top: $u.addUnit(indicatorTop), + zIndex: 2 + }" + > + <view + class="u-index-list__indicator" + :class="['u-index-list__indicator--show']" + :style="{ + height: $u.addUnit(indicatorHeight), + width: $u.addUnit(indicatorHeight) + }" + > + <text class="u-index-list__indicator__text">{{ uIndexList[activeIndex] }}</text> + </view> + </u-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 props from './props.js'; + // #ifdef APP-NVUE + // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度 + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * IndexList 索引列表 + * @description 通过折叠面板收纳内容区域 + * @tutorial https://uviewui.com/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: 'u-index-list', + mixins: [uni.$u.mpMixin, uni.$u.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: uni.$u.sys(), + scrolling: false, + scrollIntoView: '', + } + }, + 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) + } + }, + watch: { + // 监听字母索引的变化,重新设置尺寸 + uIndexList: { + immediate: true, + handler() { + uni.$u.sleep().then(() => { + this.setIndexListLetterInfo() + }) + } + } + }, + created() { + this.children = [] + this.anchors = [] + this.init() + }, + mounted() { + this.setIndexListLetterInfo() + }, + methods: { + init() { + // 设置列表的高度为整个屏幕的高度 + //减去this.customNavHeight,并将this.scrollViewHeight设置为maxHeight + //解决当u-index-list组件放在tabbar页面时,scroll-view内容较少时,还能滚动 + this.scrollViewHeight = this.sys.windowHeight - this.customNavHeight + }, + // 索引列表被触摸 + touchStart(e) { + // 获取触摸点信息 + const touchStart = e.changedTouches[0] + if (!touchStart) return + this.touching = true + const { + pageY + } = touchStart + // 根据当前触摸点的坐标,获取当前触摸的为第几个字母 + const currentIndex = this.getIndexListLetter(pageY) + this.setValueForTouch(currentIndex) + }, + // 索引字母列表被触摸滑动中 + touchMove(e) { + // 获取触摸点信息 + let touchMove = e.changedTouches[0] + if (!touchMove) return; + + // 滑动结束后迅速开始第二次滑动时候 touching 为 false 造成不显示 indicator 问题 + if (!this.touching) { + this.touching = true + } + const { + pageY + } = touchMove + const currentIndex = this.getIndexListLetter(pageY) + this.setValueForTouch(currentIndex) + }, + // 触摸结束 + touchEnd(e) { + // 延时一定时间后再隐藏指示器,为了让用户看的更直观,同时也是为了消除快速切换u-transition的show带来的影响 + uni.$u.sleep(300).then(() => { + this.touching = false + }) + }, + // 获取索引列表的尺寸以及单个字符的尺寸信息 + getIndexListLetterRect() { + return new Promise(resolve => { + // 延时一定时间,以获取dom尺寸 + // #ifndef APP-NVUE + this.$uGetRect('.u-index-list__letter').then(size => { + resolve(size) + }) + // #endif + + // #ifdef APP-NVUE + const ref = this.$refs['u-index-list__letter'] + dom.getComponentRect(ref, res => { + resolve(res.size) + }) + // #endif + }) + }, + // 设置indexList索引的尺寸信息 + setIndexListLetterInfo() { + this.getIndexListLetterRect().then(size => { + const { + height + } = size + const sys = uni.$u.sys() + const windowHeight = sys.windowHeight + let customNavHeight = 0 + // 消除各端导航栏非原生和原生导致的差异,让索引列表字母对屏幕垂直居中 + if (this.customNavHeight == 0) { + // #ifdef H5 + customNavHeight = sys.windowTop + // #endif + // #ifndef H5 + // 在非H5中,为原生导航栏,其高度不算在windowHeight内,这里设置为负值,后面相加时变成减去其高度的一半 + customNavHeight = -(sys.statusBarHeight + 44) + // #endif + } else { + customNavHeight = uni.$u.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 += uni.$u.sys().windowTop + // #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都在u-index-item中,所以需要对index-item进行偏移 + this.scrollIntoView = `u-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 = `u-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 + uni.$u.sleep(10).then(() => { + this.scrolling = false + }) + let scrollTop = 0 + const len = this.children.length + let children = this.children + const anchors = this.anchors + // #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) => { + 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 <= 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 > item.top && scrollTop < nextItem.top) { + this.activeIndex = i + break + } + } + }, + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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: $u-primary; + } + + &__index { + font-size: 12px; + text-align: center; + line-height: 12px; + } + } + } + + &__indicator { + width: 50px; + height: 50px; + border-radius: 100px 100px 0 100px; + text-align: center; + color: #ffffff; + background-color: #c9c9c9; + transform: rotate(-45deg); + @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/yudao-ui-app/uni_modules/uview-ui/components/u-input/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-input/props.js new file mode 100644 index 000000000..88917c353 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-input/props.js @@ -0,0 +1,182 @@ +export default { + props: { + // 输入的值 + value: { + type: [String, Number], + default: uni.$u.props.input.value + }, + // 输入框类型 + // number-数字输入键盘,app-vue下可以输入浮点数,app-nvue和小程序平台下只能输入整数 + // idcard-身份证输入键盘,微信、支付宝、百度、QQ小程序 + // digit-带小数点的数字键盘,App的nvue页面、微信、支付宝、百度、头条、QQ小程序 + // text-文本输入键盘 + type: { + type: String, + default: uni.$u.props.input.type + }, + // 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true, + // 兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序 + fixed: { + type: Boolean, + default: uni.$u.props.input.fixed + }, + // 是否禁用输入框 + disabled: { + type: Boolean, + default: uni.$u.props.input.disabled + }, + // 禁用状态时的背景色 + disabledColor: { + type: String, + default: uni.$u.props.input.disabledColor + }, + // 是否显示清除控件 + clearable: { + type: Boolean, + default: uni.$u.props.input.clearable + }, + // 是否密码类型 + password: { + type: Boolean, + default: uni.$u.props.input.password + }, + // 最大输入长度,设置为 -1 的时候不限制最大长度 + maxlength: { + type: [String, Number], + default: uni.$u.props.input.maxlength + }, + // 输入框为空时的占位符 + placeholder: { + type: String, + default: uni.$u.props.input.placeholder + }, + // 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ + placeholderClass: { + type: String, + default: uni.$u.props.input.placeholderClass + }, + // 指定placeholder的样式 + placeholderStyle: { + type: [String, Object], + default: uni.$u.props.input.placeholderStyle + }, + // 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效 + showWordLimit: { + type: Boolean, + default: uni.$u.props.input.showWordLimit + }, + // 设置右下角按钮的文字,有效值:send|search|next|go|done,兼容性详见uni-app文档 + // https://uniapp.dcloud.io/component/input + // https://uniapp.dcloud.io/component/textarea + confirmType: { + type: String, + default: uni.$u.props.input.confirmType + }, + // 点击键盘右下角按钮时是否保持键盘不收起,H5无效 + confirmHold: { + type: Boolean, + default: uni.$u.props.input.confirmHold + }, + // focus时,点击页面的时候不收起键盘,微信小程序有效 + holdKeyboard: { + type: Boolean, + default: uni.$u.props.input.holdKeyboard + }, + // 自动获取焦点 + // 在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 + focus: { + type: Boolean, + default: uni.$u.props.input.focus + }, + // 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 + autoBlur: { + type: Boolean, + default: uni.$u.props.input.autoBlur + }, + // 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 + disableDefaultPadding: { + type: Boolean, + default: uni.$u.props.input.disableDefaultPadding + }, + // 指定focus时光标的位置 + cursor: { + type: [String, Number], + default: uni.$u.props.input.cursor + }, + // 输入框聚焦时底部与键盘的距离 + cursorSpacing: { + type: [String, Number], + default: uni.$u.props.input.cursorSpacing + }, + // 光标起始位置,自动聚集时有效,需与selection-end搭配使用 + selectionStart: { + type: [String, Number], + default: uni.$u.props.input.selectionStart + }, + // 光标结束位置,自动聚集时有效,需与selection-start搭配使用 + selectionEnd: { + type: [String, Number], + default: uni.$u.props.input.selectionEnd + }, + // 键盘弹起时,是否自动上推页面 + adjustPosition: { + type: Boolean, + default: uni.$u.props.input.adjustPosition + }, + // 输入框内容对齐方式,可选值为:left|center|right + inputAlign: { + type: String, + default: uni.$u.props.input.inputAlign + }, + // 输入框字体的大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.input.fontSize + }, + // 输入框字体颜色 + color: { + type: String, + default: uni.$u.props.input.color + }, + // 输入框前置图标 + prefixIcon: { + type: String, + default: uni.$u.props.input.prefixIcon + }, + // 前置图标样式,对象或字符串 + prefixIconStyle: { + type: [String, Object], + default: uni.$u.props.input.prefixIconStyle + }, + // 输入框后置图标 + suffixIcon: { + type: String, + default: uni.$u.props.input.suffixIcon + }, + // 后置图标样式,对象或字符串 + suffixIconStyle: { + type: [String, Object], + default: uni.$u.props.input.suffixIconStyle + }, + // 边框类型,surround-四周边框,bottom-底部边框,none-无边框 + border: { + type: String, + default: uni.$u.props.input.border + }, + // 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 + readonly: { + type: Boolean, + default: uni.$u.props.input.readonly + }, + // 输入框形状,circle-圆形,square-方形 + shape: { + type: String, + default: uni.$u.props.input.shape + }, + // 用于处理或者过滤输入框内容的方法 + formatter: { + type: [Function, null], + default: uni.$u.props.input.formatter + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-input/u-input.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-input/u-input.vue new file mode 100644 index 000000000..c755390aa --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-input/u-input.vue @@ -0,0 +1,353 @@ +<template> + <view class="u-input" :class="inputClass" :style="[wrapperStyle]"> + <view class="u-input__content"> + <view + class="u-input__content__prefix-icon" + v-if="prefixIcon || $slots.prefix" + > + <slot name="prefix"> + <u-icon + :name="prefixIcon" + size="18" + :customStyle="prefixIconStyle" + ></u-icon> + </slot> + </view> + <view class="u-input__content__field-wrapper" @tap="clickHandler"> + <!-- 根据uni-app的input组件文档,H5和APP中只要声明了password参数(无论true还是false),type均失效,此时 + 为了防止type=number时,又存在password属性,type无效,此时需要设置password为undefined + --> + <input + class="u-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" + @input="onInput" + @blur="onBlur" + @focus="onFocus" + @confirm="onConfirm" + @keyboardheightchange="onkeyboardheightchange" + /> + </view> + <view + class="u-input__content__clear" + v-if="isShowClear" + @tap="onClear" + > + <u-icon + name="close" + size="11" + color="#ffffff" + customStyle="line-height: 12px" + ></u-icon> + </view> + <view + class="u-input__content__subfix-icon" + v-if="suffixIcon || $slots.suffix" + > + <slot name="suffix"> + <u-icon + :name="suffixIcon" + size="18" + :customStyle="suffixIconStyle" + ></u-icon> + </slot> + </view> + </view> + </view> +</template> + +<script> +import props from "./props.js"; +/** + * Input 输入框 + * @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。 + * @tutorial https://uviewui.com/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 定义需要用到的外部样式 + * + * @example <u-input v-model="value" :password="true" suffix-icon="lock-fill" /> + */ +export default { + name: "u-input", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 输入框的值 + innerValue: "", + // 是否处于获得焦点状态 + focused: false, + // value是否第一次变化,在watch中,由于加入immediate属性,会在第一次触发,此时不应该认为value发生了变化 + firstChange: true, + // value绑定值的变化是由内部还是外部引起的 + changeFromInner: false, + // 过滤处理方法 + innerFormatter: value => value + }; + }, + watch: { + value: { + immediate: true, + handler(newVal, oldVal) { + this.innerValue = newVal; + /* #ifdef H5 */ + // 在H5中,外部value变化后,修改input中的值,不会触发@input事件,此时手动调用值变化方法 + if ( + this.firstChange === false && + this.changeFromInner === false + ) { + this.valueChange(); + } + /* #endif */ + this.firstChange = false; + // 重置changeFromInner的值为false,标识下一次引起默认为外部引起的 + this.changeFromInner = false; + }, + }, + }, + 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(["u-border", "u-input--radius"])); + classes.push(`u-input--${shape}`); + border === "bottom" && + (classes = classes.concat([ + "u-border-bottom", + "u-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 uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)); + }, + // 输入框的样式 + inputStyle() { + const style = { + color: this.color, + fontSize: uni.$u.addUnit(this.fontSize), + textAlign: this.inputAlign + }; + 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,从而隐藏了清除控件而无法被点击到 + uni.$u.sleep(50).then(() => { + this.focused = false; + }); + // 尝试调用u-form的验证方法 + uni.$u.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() { + this.$emit("keyboardheightchange"); + }, + // 内容发生变化,进行处理 + valueChange() { + const value = this.innerValue; + this.$nextTick(() => { + this.$emit("input", value); + // 标识value值的变化是由内部引起的 + this.changeFromInner = true; + this.$emit("change", value); + // 尝试调用u-form的验证方法 + uni.$u.formValidate(this, "change"); + }); + }, + // 点击清除控件 + onClear() { + this.innerValue = ""; + this.$nextTick(() => { + this.valueChange(); + this.$emit("clear"); + }); + }, + /** + * 在安卓nvue上,事件无法冒泡 + * 在某些时间,我们希望监听u-from-item的点击事件,此时会导致点击u-form-item内的u-input后 + * 无法触发u-form-item的点击事件,这里通过手动调用u-form-item的方法进行触发 + */ + clickHandler() { + // #ifdef APP-NVUE + if (uni.$u.os() === "android") { + const formItem = uni.$u.$parent.call(this, "u-form-item"); + if (formItem) { + formItem.clickHandler(); + } + } + // #endif + }, + }, +}; +</script> + +<style lang="scss" scoped> +@import "../../libs/css/components.scss"; + +.u-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: $u-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: 4px; + } + + &__subfix-icon { + margin-left: 4px; + } + + &__prefix-icon { + margin-right: 4px; + } + } +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-keyboard/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-keyboard/props.js new file mode 100644 index 000000000..cfdb00aca --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-keyboard/props.js @@ -0,0 +1,84 @@ +export default { + props: { + // 键盘的类型,number-数字键盘,card-身份证键盘,car-车牌号键盘 + mode: { + type: String, + default: uni.$u.props.keyboard.mode + }, + // 是否显示键盘的"."符号 + dotDisabled: { + type: Boolean, + default: uni.$u.props.keyboard.dotDisabled + }, + // 是否显示顶部工具条 + tooltip: { + type: Boolean, + default: uni.$u.props.keyboard.tooltip + }, + // 是否显示工具条中间的提示 + showTips: { + type: Boolean, + default: uni.$u.props.keyboard.showTips + }, + // 工具条中间的提示文字 + tips: { + type: String, + default: uni.$u.props.keyboard.tips + }, + // 是否显示工具条左边的"取消"按钮 + showCancel: { + type: Boolean, + default: uni.$u.props.keyboard.showCancel + }, + // 是否显示工具条右边的"完成"按钮 + showConfirm: { + type: Boolean, + default: uni.$u.props.keyboard.showConfirm + }, + // 是否打乱键盘按键的顺序 + random: { + type: Boolean, + default: uni.$u.props.keyboard.random + }, + // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 + safeAreaInsetBottom: { + type: Boolean, + default: uni.$u.props.keyboard.safeAreaInsetBottom + }, + // 是否允许通过点击遮罩关闭键盘 + closeOnClickOverlay: { + type: Boolean, + default: uni.$u.props.keyboard.closeOnClickOverlay + }, + // 控制键盘的弹出与收起 + show: { + type: Boolean, + default: uni.$u.props.keyboard.show + }, + // 是否显示遮罩,某些时候数字键盘时,用户希望看到自己的数值,所以可能不想要遮罩 + overlay: { + type: Boolean, + default: uni.$u.props.keyboard.overlay + }, + // z-index值 + zIndex: { + type: [String, Number], + default: uni.$u.props.keyboard.zIndex + }, + // 取消按钮的文字 + cancelText: { + type: String, + default: uni.$u.props.keyboard.cancelText + }, + // 确认按钮的文字 + confirmText: { + type: String, + default: uni.$u.props.keyboard.confirmText + }, + // 输入一个中文后,是否自动切换到英文 + autoChange: { + type: Boolean, + default: uni.$u.props.keyboard.autoChange + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-keyboard/u-keyboard.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-keyboard/u-keyboard.vue new file mode 100644 index 000000000..14228cbf5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-keyboard/u-keyboard.vue @@ -0,0 +1,164 @@ +<template> + <u-popup + :overlay="overlay" + :closeOnClickOverlay="closeOnClickOverlay" + mode="bottom" + :popup="false" + :show="show" + :safeAreaInsetBottom="safeAreaInsetBottom" + @close="popupClose" + :zIndex="zIndex" + :customStyle="{ + backgroundColor: 'rgb(214, 218, 220)' + }" + > + <view class="u-keyboard"> + <slot /> + <view + class="u-keyboard__tooltip" + v-if="tooltip" + > + <view + hover-class="u-hover-class" + :hover-stay-time="100" + > + <text + class="u-keyboard__tooltip__item u-keyboard__tooltip__cancel" + v-if="showCancel" + @tap="onCancel" + >{{showCancel && cancelText}}</text> + </view> + <view> + <text + v-if="showTips" + class="u-keyboard__tooltip__item u-keyboard__tooltip__tips" + >{{tips ? tips : mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}}</text> + </view> + <view + hover-class="u-hover-class" + :hover-stay-time="100" + > + <text + v-if="showConfirm" + @tap="onConfirm" + class="u-keyboard__tooltip__item u-keyboard__tooltip__submit" + hover-class="u-hover-class" + >{{showConfirm && confirmText}}</text> + </view> + </view> + <template v-if="mode == 'number' || mode == 'card'"> + <u-number-keyboard + :random="random" + @backspace="backspace" + @change="change" + :mode="mode" + :dotDisabled="dotDisabled" + ></u-number-keyboard> + </template> + <template v-else> + <u-car-keyboard + :random="random" + :autoChange="autoChange" + @backspace="backspace" + @change="change" + ></u-car-keyboard> + </template> + </view> + </u-popup> +</template> + +<script> + import props from './props.js'; + + /** + * keyboard 键盘 + * @description 此为uViw自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3中模式,都有可以打乱按键顺序的选项。 + * @tutorial https://www.uviewui.com/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 <u-keyboard mode="number" v-model="show"></u-keyboard> + */ + export default { + name: "u-keyboard", + data() { + return { + + } + }, + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + methods: { + change(e) { + this.$emit('change', e); + }, + // 键盘关闭 + popupClose() { + this.$emit('close'); + }, + // 输入完成 + onConfirm() { + this.$emit('confirm'); + }, + // 取消输入 + onCancel() { + this.$emit('cancel'); + }, + // 退格键 + backspace() { + this.$emit('backspace'); + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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: $u-primary; + } + + &__cancel { + text-align: left; + color: #888888; + } + + &__tips { + color: $u-tips-color; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-line-progress/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-line-progress/props.js new file mode 100644 index 000000000..a4210bdfa --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-line-progress/props.js @@ -0,0 +1,28 @@ +export default { + props: { + // 激活部分的颜色 + activeColor: { + type: String, + default: uni.$u.props.lineProgress.activeColor + }, + inactiveColor: { + type: String, + default: uni.$u.props.lineProgress.color + }, + // 进度百分比,数值 + percentage: { + type: [String, Number], + default: uni.$u.props.lineProgress.inactiveColor + }, + // 是否在进度条内部显示百分比的值 + showText: { + type: Boolean, + default: uni.$u.props.lineProgress.showText + }, + // 进度条的高度,单位px + height: { + type: [String, Number], + default: uni.$u.props.lineProgress.height + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-line-progress/u-line-progress.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-line-progress/u-line-progress.vue new file mode 100644 index 000000000..4e2793101 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-line-progress/u-line-progress.vue @@ -0,0 +1,144 @@ +<template> + <view + class="u-line-progress" + :style="[$u.addStyle(customStyle)]" + > + <view + class="u-line-progress__background" + ref="u-line-progress__background" + :style="[{ + backgroundColor: inactiveColor, + height: $u.addUnit(height), + }]" + > + </view> + <view + class="u-line-progress__line" + :style="[progressStyle]" + > + <slot> + <text v-if="showText && percentage >= 10" class="u-line-progress__text">{{innserPercentage + '%'}}</text> + </slot> + </view> + </view> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * lineProgress 线型进度条 + * @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。 + * @tutorial https://www.uviewui.com/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 <u-line-progress :percent="70" :show-percent="true"></u-line-progress> + */ + export default { + name: "u-line-progress", + mixins: [uni.$u.mpMixin, uni.$u.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 = uni.$u.addUnit(this.height) + return style + }, + innserPercentage() { + // 控制范围在0-100之间 + return uni.$u.range(0, 100, this.percentage) + } + }, + mounted() { + this.init() + }, + methods: { + init() { + uni.$u.sleep(20).then(() => { + this.resizeProgressWidth() + }) + }, + getProgressWidth() { + // #ifndef APP-NVUE + return this.$uGetRect('.u-line-progress__background') + // #endif + + // #ifdef APP-NVUE + // 返回一个promise + return new Promise(resolve => { + dom.getComponentRect(this.$refs['u-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 "../../libs/css/components.scss"; + + .u-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/yudao-ui-app/uni_modules/uview-ui/components/u-line/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-line/props.js new file mode 100644 index 000000000..866bade95 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-line/props.js @@ -0,0 +1,33 @@ +export default { + props: { + color: { + type: String, + default: uni.$u.props.line.color + }, + // 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等 + length: { + type: [String, Number], + default: uni.$u.props.line.length + }, + // 线条方向,col-竖向,row-横向 + direction: { + type: String, + default: uni.$u.props.line.direction + }, + // 是否显示细边框 + hairline: { + type: Boolean, + default: uni.$u.props.line.hairline + }, + // 线条与上下左右元素的间距,字符串形式,如"30px"、"20px 30px" + margin: { + type: [String, Number], + default: uni.$u.props.line.margin + }, + // 是否虚线,true-实线,false-虚线 + dashed: { + type: Boolean, + default: uni.$u.props.line.dashed + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-line/u-line.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-line/u-line.vue new file mode 100644 index 000000000..e0a6d9255 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-line/u-line.vue @@ -0,0 +1,62 @@ +<template> + <view + class="u-line" + :style="[lineStyle]" + > + + </view> +</template> + +<script> + import props from './props.js'; + /** + * line 线条 + * @description 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单 + * @tutorial https://www.uviewui.com/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 <u-line color="red"></u-line> + */ + export default { + name: 'u-line', + mixins: [uni.$u.mpMixin, uni.$u.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 = uni.$u.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 = uni.$u.addUnit(this.length) + if (this.hairline) style.transform = 'scaleX(0.5)' + } + + style.borderColor = this.color + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-line { + /* #ifndef APP-NVUE */ + vertical-align: middle; + /* #endif */ + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-link/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-link/props.js new file mode 100644 index 000000000..d39353f18 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-link/props.js @@ -0,0 +1,39 @@ +export default { + props: { + // 文字颜色 + color: { + type: String, + default: uni.$u.props.link.color + }, + // 字体大小,单位px + fontSize: { + type: [String, Number], + default: uni.$u.props.link.fontSize + }, + // 是否显示下划线 + underLine: { + type: Boolean, + default: uni.$u.props.link.underLine + }, + // 要跳转的链接 + href: { + type: String, + default: uni.$u.props.link.href + }, + // 小程序中复制到粘贴板的提示语 + mpTips: { + type: String, + default: uni.$u.props.link.mpTips + }, + // 下划线颜色 + lineColor: { + type: String, + default: uni.$u.props.link.lineColor + }, + // 超链接的问题,不使用slot形式传入,是因为nvue下无法修改颜色 + text: { + type: String, + default: uni.$u.props.link.text + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-link/u-link.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-link/u-link.vue new file mode 100644 index 000000000..c6802a560 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-link/u-link.vue @@ -0,0 +1,83 @@ +<template> + <text + class="u-link" + @tap.stop="openLink" + :style="[linkStyle, $u.addStyle(customStyle)]" + >{{text}}</text> +</template> + +<script> + import props from './props.js'; + + /** + * link 超链接 + * @description 该组件为超链接组件,在不同平台有不同表现形式:在APP平台会通过plus环境打开内置浏览器,在小程序中把链接复制到粘贴板,同时提示信息,在H5中通过window.open打开链接。 + * @tutorial https://www.uviewui.com/components/link.html + * @property {String} color 文字颜色 (默认 color['u-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 <u-link href="http://www.uviewui.com">蜀道难,难于上青天</u-link> + */ + export default { + name: "u-link", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + computed: { + linkStyle() { + const style = { + color: this.color, + fontSize: uni.$u.addUnit(this.fontSize), + // line-height设置为比字体大小多2px + lineHeight: uni.$u.addUnit(uni.$u.getPx(this.fontSize) + 2), + textDecoration: this.underLine ? 'underline' : 'none' + } + // if (this.underLine) { + // style.borderBottomColor = this.lineColor || this.color + // style.borderBottomWidth = '1px' + // } + 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(() => { + uni.$u.toast(this.mpTips); + }) + } + }); + // #endif + this.$emit('click') + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-link-line-height:1 !default; + + .u-link { + /* #ifndef APP-NVUE */ + line-height: $u-link-line-height; + /* #endif */ + @include flex; + flex-wrap: wrap; + flex: 1; + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-list-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-list-item/props.js new file mode 100644 index 000000000..58ddc493c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-list-item/props.js @@ -0,0 +1,9 @@ +export default { + props: { + // 用于滚动到指定item + anchor: { + type: [String, Number], + default: uni.$u.props.listItem.anchor + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-list-item/u-list-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-list-item/u-list-item.vue new file mode 100644 index 000000000..1a25db65e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-list-item/u-list-item.vue @@ -0,0 +1,116 @@ +<template> + <!-- #ifdef APP-NVUE --> + <cell> + <!-- #endif --> + <view + class="u-list-item" + :ref="`u-list-item-${anchor}`" + :anchor="`u-list-item-${anchor}`" + :class="[`u-list-item-${anchor}`]" + > + <slot /> + </view> + <!-- #ifdef APP-NVUE --> + </cell> + <!-- #endif --> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * List 列表 + * @description 该组件为高性能列表组件 + * @tutorial https://www.uviewui.com/components/list.html + * @property {String | Number} anchor 用于滚动到指定item + * @example <u-list-ite v-for="(item, index) in indexList" :key="index" ></u-list-item> + */ + export default { + name: 'u-list-item', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + // 节点信息 + rect: {}, + index: 0, + show: true, + sys: uni.$u.sys() + } + }, + computed: { + + }, + inject: ['uList'], + watch: { + // #ifndef APP-NVUE + 'uList.innerScrollTop'(n) { + const preLoadScreen = this.uList.preLoadScreen + const windowHeight = this.sys.windowHeight + if(n <= windowHeight * preLoadScreen) { + this.parent.updateOffsetFromChild(0) + } else if (this.rect.top <= n - windowHeight * preLoadScreen) { + this.parent.updateOffsetFromChild(this.rect.top) + } + } + // #endif + }, + created() { + this.parent = {} + }, + mounted() { + this.init() + }, + methods: { + init() { + // 初始化数据 + this.updateParentData() + this.index = this.parent.children.indexOf(this) + this.resize() + }, + updateParentData() { + // 此方法在mixin中 + this.getParentData('u-list') + }, + resize() { + this.queryRect(`u-list-item-${this.anchor}`).then(size => { + const lastChild = this.parent.children[this.index - 1] + this.rect = size + const preLoadScreen = this.uList.preLoadScreen + const windowHeight = this.sys.windowHeight + // #ifndef APP-NVUE + if (lastChild) { + this.rect.top = lastChild.rect.top + lastChild.rect.height + } + if (size.top >= this.uList.innerScrollTop + (1 + preLoadScreen) * windowHeight) this.show = + false + // #endif + }) + }, + // 查询元素尺寸 + queryRect(el) { + return new Promise(resolve => { + // #ifndef APP-NVUE + this.$uGetRect(`.${el}`).then(size => { + resolve(size) + }) + // #endif + + // #ifdef APP-NVUE + const ref = this.$refs[el] + dom.getComponentRect(ref, res => { + resolve(res.size) + }) + // #endif + }) + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-list-item {} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-list/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-list/props.js new file mode 100644 index 000000000..25406f4e0 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-list/props.js @@ -0,0 +1,76 @@ +export default { + props: { + // 控制是否出现滚动条,仅nvue有效 + showScrollbar: { + type: Boolean, + default: uni.$u.props.list.showScrollbar + }, + // 距底部多少时触发scrolltolower事件 + lowerThreshold: { + type: [String, Number], + default: uni.$u.props.list.lowerThreshold + }, + // 距顶部多少时触发scrolltoupper事件,非nvue有效 + upperThreshold: { + type: [String, Number], + default: uni.$u.props.list.upperThreshold + }, + // 设置竖向滚动条位置 + scrollTop: { + type: [String, Number], + default: uni.$u.props.list.scrollTop + }, + // 控制 onscroll 事件触发的频率,仅nvue有效 + offsetAccuracy: { + type: [String, Number], + default: uni.$u.props.list.offsetAccuracy + }, + // 启用 flexbox 布局。开启后,当前节点声明了display: flex就会成为flex container,并作用于其孩子节点,仅微信小程序有效 + enableFlex: { + type: Boolean, + default: uni.$u.props.list.enableFlex + }, + // 是否按分页模式显示List,默认值false + pagingEnabled: { + type: Boolean, + default: uni.$u.props.list.pagingEnabled + }, + // 是否允许List滚动 + scrollable: { + type: Boolean, + default: uni.$u.props.list.scrollable + }, + // 值应为某子元素id(id不能以数字开头) + scrollIntoView: { + type: String, + default: uni.$u.props.list.scrollIntoView + }, + // 在设置滚动条位置时使用动画过渡 + scrollWithAnimation: { + type: Boolean, + default: uni.$u.props.list.scrollWithAnimation + }, + // iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只对微信小程序有效 + enableBackToTop: { + type: Boolean, + default: uni.$u.props.list.enableBackToTop + }, + // 列表的高度 + height: { + type: [String, Number], + default: uni.$u.props.list.height + }, + // 列表宽度 + width: { + type: [String, Number], + default: uni.$u.props.list.width + }, + // 列表前后预渲染的屏数,1代表一个屏幕的高度,1.5代表1个半屏幕高度 + preLoadScreen: { + type: [String, Number], + default: uni.$u.props.list.preLoadScreen + } + // vue下,是否开启虚拟列表 + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-list/u-list.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-list/u-list.vue new file mode 100644 index 000000000..513decc78 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-list/u-list.vue @@ -0,0 +1,159 @@ +<template> + <!-- #ifdef APP-NVUE --> + <list + class="u-list" + :enableBackToTop="enableBackToTop" + :loadmoreoffset="lowerThreshold" + :showScrollbar="showScrollbar" + :style="[listStyle]" + :offset-accuracy="Number(offsetAccuracy)" + @scroll="onScroll" + @loadmore="scrolltolower" + > + <slot /> + </list> + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <scroll-view + class="u-list" + :scroll-into-view="scrollIntoView" + :style="[listStyle]" + scroll-y + :scroll-top="Number(scrollTop)" + :lower-threshold="Number(lowerThreshold)" + :upper-threshold="Number(upperThreshold)" + :show-scrollbar="showScrollbar" + :enable-back-to-top="enableBackToTop" + :scroll-with-animation="scrollWithAnimation" + @scroll="onScroll" + @scrolltolower="scrolltolower" + @scrolltoupper="scrolltoupper" + > + <view :style="{ + paddingTop: `${offset}px` + }"> + <slot /> + </view> + </scroll-view> + <!-- #endif --> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * List 列表 + * @description 该组件为高性能列表组件 + * @tutorial https://www.uviewui.com/components/list.html + * @property {Boolean} showScrollbar 控制是否出现滚动条,仅nvue有效 (默认 false ) + * @property {String | Number} lowerThreshold 距底部多少时触发scrolltolower事件 (默认 50 ) + * @property {String | Number} upperThreshold 距顶部多少时触发scrolltoupper事件,非nvue有效 (默认 0 ) + * @property {String | Number} scrollTop 设置竖向滚动条位置(默认 0 ) + * @property {String | Number} offsetAccuracy 控制 onscroll 事件触发的频率,仅nvue有效(默认 10 ) + * @property {Boolean} enableFlex 启用 flexbox 布局。开启后,当前节点声明了display: flex就会成为flex container,并作用于其孩子节点,仅微信小程序有效(默认 false ) + * @property {Boolean} pagingEnabled 是否按分页模式显示List,(默认 false ) + * @property {Boolean} scrollable 是否允许List滚动(默认 true ) + * @property {String} scrollIntoView 值应为某子元素id(id不能以数字开头) + * @property {Boolean} scrollWithAnimation 在设置滚动条位置时使用动画过渡 (默认 false ) + * @property {Boolean} enableBackToTop iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只对微信小程序有效 (默认 false ) + * @property {String | Number} height 列表的高度 (默认 0 ) + * @property {String | Number} width 列表宽度 (默认 0 ) + * @property {String | Number} preLoadScreen 列表前后预渲染的屏数,1代表一个屏幕的高度,1.5代表1个半屏幕高度 (默认 1 ) + * @property {Object} customStyle 定义需要用到的外部样式 + * + * @example <u-list @scrolltolower="scrolltolower"></u-list> + */ + export default { + name: 'u-list', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + watch: { + scrollIntoView(n) { + this.scrollIntoViewById(n) + } + }, + data() { + return { + // 记录内部滚动的距离 + innerScrollTop: 0, + // vue下,scroll-view在上拉加载时的偏移值 + offset: 0, + sys: uni.$u.sys() + } + }, + computed: { + listStyle() { + const style = {}, + addUnit = uni.$u.addUnit + if (this.width != 0) style.width = addUnit(this.width) + if (this.height != 0) style.height = addUnit(this.height) + // 如果没有定义列表高度,则默认使用屏幕高度 + if (!style.height) style.height = addUnit(this.sys.windowHeight, 'px') + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + provide() { + return { + uList: this + } + }, + created() { + this.refs = [] + this.children = [] + this.anchors = [] + }, + mounted() {}, + methods: { + updateOffsetFromChild(top) { + this.offset = top + }, + onScroll(e) { + let scrollTop = 0 + // #ifdef APP-NVUE + scrollTop = e.contentOffset.y + // #endif + // #ifndef APP-NVUE + scrollTop = e.detail.scrollTop + // #endif + this.innerScrollTop = scrollTop + this.$emit('scroll', Math.abs(scrollTop)) + }, + scrollIntoViewById(id) { + // #ifdef APP-NVUE + // 根据id参数,找到所有u-list-item中匹配的节点,再通过dom模块滚动到对应的位置 + const item = this.refs.find(item => item.$refs[id] ? true : false) + dom.scrollToElement(item.$refs[id], { + // 是否需要滚动动画 + animated: this.scrollWithAnimation + }) + // #endif + }, + // 滚动到底部触发事件 + scrolltolower(e) { + uni.$u.sleep(30).then(() => { + this.$emit('scrolltolower') + }) + }, + // #ifndef APP-NVUE + // 滚动到底部时触发,非nvue有效 + scrolltoupper(e) { + uni.$u.sleep(30).then(() => { + this.$emit('scrolltoupper') + // 这一句很重要,能绝对保证在性功能障碍的webview,滚动条到顶时,取消偏移值,让页面置顶 + this.offset = 0 + }) + } + // #endif + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-list { + @include flex(column); + + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-loading-icon/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-loading-icon/props.js new file mode 100644 index 000000000..c35524e2a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-loading-icon/props.js @@ -0,0 +1,59 @@ +export default { + props: { + // 是否显示组件 + show: { + type: Boolean, + default: uni.$u.props.loadingIcon.show + }, + // 颜色 + color: { + type: String, + default: uni.$u.props.loadingIcon.color + }, + // 提示文字颜色 + textColor: { + type: String, + default: uni.$u.props.loadingIcon.textColor + }, + // 文字和图标是否垂直排列 + vertical: { + type: Boolean, + default: uni.$u.props.loadingIcon.vertical + }, + // 模式选择,circle-圆形,spinner-花朵形,semicircle-半圆形 + mode: { + type: String, + default: uni.$u.props.loadingIcon.mode + }, + // 图标大小,单位默认px + size: { + type: [String, Number], + default: uni.$u.props.loadingIcon.size + }, + // 文字大小 + textSize: { + type: [String, Number], + default: uni.$u.props.loadingIcon.textSize + }, + // 文字内容 + text: { + type: [String, Number], + default: uni.$u.props.loadingIcon.text + }, + // 动画模式 + timingFunction: { + type: String, + default: uni.$u.props.loadingIcon.timingFunction + }, + // 动画执行周期时间 + duration: { + type: [String, Number], + default: uni.$u.props.loadingIcon.duration + }, + // mode=circle时的暗边颜色 + inactiveColor: { + type: String, + default: uni.$u.props.loadingIcon.inactiveColor + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-loading-icon/u-loading-icon.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-loading-icon/u-loading-icon.vue new file mode 100644 index 000000000..2ede5c3a3 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-loading-icon/u-loading-icon.vue @@ -0,0 +1,343 @@ +<template> + <view + class="u-loading-icon" + :style="[$u.addStyle(customStyle)]" + :class="[vertical && 'u-loading-icon--vertical']" + v-if="show" + > + <view + v-if="!webviewHide" + class="u-loading-icon__spinner" + :class="[`u-loading-icon__spinner--${mode}`]" + ref="ani" + :style="{ + color: color, + width: $u.addUnit(size), + height: $u.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="u-loading-icon__dot" + > + </view> + <!-- #endif --> + <!-- #ifdef APP-NVUE --> + <!-- 此组件内部图标部分无法设置宽高,即使通过width和height配置了也无效 --> + <loading-indicator + v-if="!webviewHide" + class="u-loading-indicator" + :animating="true" + :style="{ + color: color, + width: $u.addUnit(size), + height: $u.addUnit(size) + }" + /> + <!-- #endif --> + </block> + </view> + <text + v-if="text" + class="u-loading-icon__text" + :style="{ + fontSize: $u.addUnit(textSize), + color: textColor, + }" + >{{text}}</text> + </view> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const animation = weex.requireModule('animation'); + // #endif + /** + * loading 加载动画 + * @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。 + * @tutorial https://www.uviewui.com/components/loading.html + * @property {Boolean} show 是否显示组件 (默认 true) + * @property {String} color 动画活动区域的颜色,只对 mode = flower 模式有效(默认color['u-tips-color']) + * @property {String} textColor 提示文本的颜色(默认color['u-tips-color']) + * @property {Boolean} vertical 文字和图标是否垂直排列 (默认 false ) + * @property {String} mode 模式选择,见官网说明(默认 'circle' ) + * @property {String | Number} size 加载图标的大小,单位px (默认 24 ) + * @property {String | Number} textSize 文字大小(默认 15 ) + * @property {String | Number} text 文字内容 + * @property {String} timingFunction 动画模式 (默认 'ease-in-out' ) + * @property {String | Number} duration 动画执行周期时间(默认 1200) + * @property {String} inactiveColor mode=circle时的暗边颜色 + * @property {Object} customStyle 定义需要用到的外部样式 + * @example <u-loading mode="circle"></u-loading> + */ + export default { + name: 'u-loading-icon', + mixins: [uni.$u.mpMixin, uni.$u.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 = uni.$u.colorGradient(this.color, '#ffffff', 100)[80] + if (this.mode === 'circle') { + return this.inactiveColor ? this.inactiveColor : lightColor + } else { + return 'transparent' + } + // return this.mode === 'circle' ? this.inactiveColor ? this.inactiveColor : lightColor : '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 "../../libs/css/components.scss"; + $u-loading-icon-color: #c8c9cc !default; + $u-loading-icon-text-margin-left:4px !default; + $u-loading-icon-text-color:$u-content-color !default; + $u-loading-icon-text-font-size:14px !default; + $u-loading-icon-text-line-height:20px !default; + $u-loading-width:30px !default; + $u-loading-height:30px !default; + $u-loading-max-width:100% !default; + $u-loading-max-height:100% !default; + $u-loading-semicircle-border-width: 2px !default; + $u-loading-semicircle-border-color:transparent !default; + $u-loading-semicircle-border-top-right-radius: 100px !default; + $u-loading-semicircle-border-top-left-radius: 100px !default; + $u-loading-semicircle-border-bottom-left-radius: 100px !default; + $u-loading-semicircle-border-bottom-right-radiu: 100px !default; + $u-loading-semicircle-border-style: solid !default; + $u-loading-circle-border-top-right-radius: 100px !default; + $u-loading-circle-border-top-left-radius: 100px !default; + $u-loading-circle-border-bottom-left-radius: 100px !default; + $u-loading-circle-border-bottom-right-radiu: 100px !default; + $u-loading-circle-border-width:2px !default; + $u-loading-circle-border-top-color:#e5e5e5 !default; + $u-loading-circle-border-right-color:$u-loading-circle-border-top-color !default; + $u-loading-circle-border-bottom-color:$u-loading-circle-border-top-color !default; + $u-loading-circle-border-left-color:$u-loading-circle-border-top-color !default; + $u-loading-circle-border-style:solid !default; + $u-loading-icon-host-font-size:0px !default; + $u-loading-icon-host-line-height:1 !default; + $u-loading-icon-vertical-margin:6px 0 0 !default; + $u-loading-icon-dot-top:0 !default; + $u-loading-icon-dot-left:0 !default; + $u-loading-icon-dot-width:100% !default; + $u-loading-icon-dot-height:100% !default; + $u-loading-icon-dot-before-width:2px !default; + $u-loading-icon-dot-before-height:25% !default; + $u-loading-icon-dot-before-margin:0 auto !default; + $u-loading-icon-dot-before-background-color:currentColor !default; + $u-loading-icon-dot-before-border-radius:40% !default; + + .u-loading-icon { + /* #ifndef APP-NVUE */ + // display: inline-flex; + /* #endif */ + flex-direction: row; + align-items: center; + justify-content: center; + color: $u-loading-icon-color; + + &__text { + margin-left: $u-loading-icon-text-margin-left; + color: $u-loading-icon-text-color; + font-size: $u-loading-icon-text-font-size; + line-height: $u-loading-icon-text-line-height; + } + + &__spinner { + width: $u-loading-width; + height: $u-loading-height; + position: relative; + /* #ifndef APP-NVUE */ + box-sizing: border-box; + max-width: $u-loading-max-width; + max-height: $u-loading-max-height; + animation: u-rotate 1s linear infinite; + /* #endif */ + } + + &__spinner--semicircle { + border-width: $u-loading-semicircle-border-width; + border-color: $u-loading-semicircle-border-color; + border-top-right-radius: $u-loading-semicircle-border-top-right-radius; + border-top-left-radius: $u-loading-semicircle-border-top-left-radius; + border-bottom-left-radius: $u-loading-semicircle-border-bottom-left-radius; + border-bottom-right-radius: $u-loading-semicircle-border-bottom-right-radiu; + border-style: $u-loading-semicircle-border-style; + } + + &__spinner--circle { + border-top-right-radius: $u-loading-circle-border-top-right-radius; + border-top-left-radius: $u-loading-circle-border-top-left-radius; + border-bottom-left-radius: $u-loading-circle-border-bottom-left-radius; + border-bottom-right-radius: $u-loading-circle-border-bottom-right-radiu; + border-width: $u-loading-circle-border-width; + border-top-color: $u-loading-circle-border-top-color; + border-right-color: $u-loading-circle-border-right-color; + border-bottom-color: $u-loading-circle-border-bottom-color; + border-left-color: $u-loading-circle-border-left-color; + border-style: $u-loading-circle-border-style; + } + + &--vertical { + flex-direction: column + } + } + + /* #ifndef APP-NVUE */ + :host { + font-size: $u-loading-icon-host-font-size; + line-height: $u-loading-icon-host-line-height; + } + + .u-loading-icon { + &__spinner--spinner { + animation-timing-function: steps(12) + } + + &__text:empty { + display: none + } + + &--vertical &__text { + margin: $u-loading-icon-vertical-margin; + color: $u-content-color; + } + + &__dot { + position: absolute; + top: $u-loading-icon-dot-top; + left: $u-loading-icon-dot-left; + width: $u-loading-icon-dot-width; + height: $u-loading-icon-dot-height; + + &:before { + display: block; + width: $u-loading-icon-dot-before-width; + height: $u-loading-icon-dot-before-height; + margin: $u-loading-icon-dot-before-margin; + background-color: $u-loading-icon-dot-before-background-color; + border-radius: $u-loading-icon-dot-before-border-radius; + content: " " + } + } + } + + @for $i from 1 through 12 { + .u-loading-icon__dot:nth-of-type(#{$i}) { + transform: rotate($i * 30deg); + opacity: 1 - 0.0625 * ($i - 1); + } + } + + @keyframes u-rotate { + 0% { + transform: rotate(0deg) + } + + to { + transform: rotate(1turn) + } + } + + /* #endif */ +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-loading-page/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-loading-page/props.js new file mode 100644 index 000000000..438ab6437 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-loading-page/props.js @@ -0,0 +1,44 @@ +export default { + props: { + // 提示内容 + loadingText: { + type: [String, Number], + default: uni.$u.props.loadingPage.loadingText + }, + // 文字上方用于替换loading动画的图片 + image: { + type: String, + default: uni.$u.props.loadingPage.image + }, + // 加载动画的模式,circle-圆形,spinner-花朵形,semicircle-半圆形 + loadingMode: { + type: String, + default: uni.$u.props.loadingPage.loadingMode + }, + // 是否加载中 + loading: { + type: Boolean, + default: uni.$u.props.loadingPage.loading + }, + // 背景色 + bgColor: { + type: String, + default: uni.$u.props.loadingPage.bgColor + }, + // 文字颜色 + color: { + type: String, + default: uni.$u.props.loadingPage.color + }, + // 文字大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.loadingPage.fontSize + }, + // 加载中图标的颜色,只能rgb或者十六进制颜色值 + loadingColor: { + type: String, + default: uni.$u.props.loadingPage.loadingColor + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-loading-page/u-loading-page.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-loading-page/u-loading-page.vue new file mode 100644 index 000000000..c6b51b973 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-loading-page/u-loading-page.vue @@ -0,0 +1,110 @@ +<template> + <u-transition + :show="loading" + :custom-style="{ + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: bgColor, + display: 'flex', + }" + > + <view class="u-loading-page"> + <view class="u-loading-page__warpper"> + <view class="u-loading-page__warpper__loading-icon"> + <image + v-if="image" + :src="image" + class="u-loading-page__warpper__loading-icon__img" + mode="widthFit" + ></image> + <u-loading-icon + v-else + :mode="loadingMode" + size="28" + :color="loadingColor" + ></u-loading-icon> + </view> + <slot> + <text + class="u-loading-page__warpper__text" + :style="{ + fontSize: $u.addUnit(fontSize), + color: color, + }" + >{{ loadingText }}</text + > + </slot> + </view> + </view> + </u-transition> +</template> + +<script> +import props from "./props.js"; +/** + * loadingPage 加载动画 + * @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。 + * @tutorial https://www.uviewui.com/components/loading.html + * @property {String | Number} loadingText 提示内容 (默认 '正在加载' ) + * @property {String} image 文字上方用于替换loading动画的图片 + * @property {String} loadingMode 加载动画的模式,circle-圆形,spinner-花朵形,semicircle-半圆形 (默认 'circle' ) + * @property {Boolean} loading 是否加载中 (默认 false ) + * @property {String} bgColor 背景色 (默认 '#ffffff' ) + * @property {String} color 文字颜色 (默认 '#C8C8C8' ) + * @property {String | Number} fontSize 文字大小 (默认 19 ) + * @property {String} loadingColor 加载中图标的颜色,只能rgb或者十六进制颜色值 (默认 '#C8C8C8' ) + * @property {Object} customStyle 自定义样式 + * @example <u-loading mode="circle"></u-loading> + */ +export default { + name: "u-loading-page", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return {}; + }, + methods: {}, +}; +</script> + +<style lang="scss" scoped> +@import "../../libs/css/components.scss"; + +$text-color: rgb(200, 200, 200) !default; +$text-size: 19px !default; +$u-loading-icon-margin-bottom: 10px !default; + +.u-loading-page { + @include flex(column); + flex: 1; + align-items: center; + justify-content: center; + + &__warpper { + margin-top: -150px; + justify-content: center; + align-items: center; + /* #ifndef APP-NVUE */ + color: $text-color; + font-size: $text-size; + /* #endif */ + @include flex(column); + + &__loading-icon { + margin-bottom: $u-loading-icon-margin-bottom; + + &__img { + width: 40px; + height: 40px; + } + } + + &__text { + font-size: $text-size; + color: $text-color; + } + } +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-loadmore/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-loadmore/props.js new file mode 100644 index 000000000..125ad3eb7 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-loadmore/props.js @@ -0,0 +1,80 @@ +export default { + props: { + // 组件状态,loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态 + status: { + type: String, + default: uni.$u.props.loadmore.status + }, + // 组件背景色 + bgColor: { + type: String, + default: uni.$u.props.loadmore.bgColor + }, + // 是否显示加载中的图标 + icon: { + type: Boolean, + default: uni.$u.props.loadmore.icon + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.loadmore.fontSize + }, + // 字体颜色 + color: { + type: String, + default: uni.$u.props.loadmore.color + }, + + // 加载中状态的图标,spinner-花朵状图标,circle-圆圈状,semicircle-半圆 + loadingIcon: { + type: String, + default: uni.$u.props.loadmore.loadingIcon + }, + // 加载前的提示语 + loadmoreText: { + type: String, + default: uni.$u.props.loadmore.loadmoreText + }, + // 加载中提示语 + loadingText: { + type: String, + default: uni.$u.props.loadmore.loadingText + }, + // 没有更多的提示语 + nomoreText: { + type: String, + default: uni.$u.props.loadmore.nomoreText + }, + // 在“没有更多”状态下,是否显示粗点 + isDot: { + type: Boolean, + default: uni.$u.props.loadmore.isDot + }, + // 加载中图标的颜色 + iconColor: { + type: String, + default: uni.$u.props.loadmore.iconColor + }, + // 上边距 + marginTop: { + type: [String, Number], + default: uni.$u.props.loadmore.marginTop + }, + // 下边距 + marginBottom: { + type: [String, Number], + default: uni.$u.props.loadmore.marginBottom + }, + // 高度,单位px + height: { + type: [String, Number], + default: uni.$u.props.loadmore.height + }, + // 是否显示左边分割线 + line: { + type: Boolean, + default: uni.$u.props.loadmore.line + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-loadmore/u-loadmore.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-loadmore/u-loadmore.vue new file mode 100644 index 000000000..24fb4b6a0 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-loadmore/u-loadmore.vue @@ -0,0 +1,145 @@ +<template> + <view + class="u-loadmore" + :style="[ + $u.addStyle(customStyle), + { + backgroundColor: bgColor, + marginBottom: $u.addUnit(marginBottom), + marginTop: $u.addUnit(marginTop), + height: $u.addUnit(height), + }, + ]" + > + <u-line + length="140rpx" + color="#E6E8EB" + :hairline="false" + v-if="line" + ></u-line> + <!-- 加载中和没有更多的状态才显示两边的横线 --> + <view + :class="status == 'loadmore' || status == 'nomore' ? 'u-more' : ''" + class="u-loadmore__content" + > + <view + class="u-loadmore__content__icon-wrap" + v-if="status === 'loading' && icon" + > + <u-loading-icon + :color="iconColor" + size="17" + :mode="loadingIcon" + ></u-loading-icon> + </view> + <!-- 如果没有更多的状态下,显示内容为dot(粗点),加载特定样式 --> + <text + class="u-line-1" + :style="[loadTextStyle]" + :class="[(status == 'nomore' && isDot == true) ? 'u-loadmore__content__dot-text' : 'u-loadmore__content__text']" + @tap="loadMore" + >{{ showText }}</text> + </view> + <u-line + length="140rpx" + color="#E6E8EB" + :hairline="false" + v-if="line" + ></u-line> + </view> +</template> + +<script> + import props from './props.js'; + + /** + * loadmore 加载更多 + * @description 此组件一般用于标识页面底部加载数据时的状态。 + * @tutorial https://www.uviewui.com/components/loadMore.html + * @property {String} status 组件状态(默认 'loadmore' ) + * @property {String} bgColor 组件背景颜色,在页面是非白色时会用到(默认 'transparent' ) + * @property {Boolean} icon 加载中时是否显示图标(默认 true ) + * @property {String | Number} fontSize 字体大小(默认 14 ) + * @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 | Number} marginTop 上边距 (默认 10 ) + * @property {String | Number} marginBottom 下边距 (默认 10 ) + * @property {String | Number} height 高度,单位px (默认 'auto' ) + * @property {Boolean} line 是否显示左边分割线 (默认 false ) + * @event {Function} loadmore status为loadmore时,点击组件会发出此事件 + * @example <u-loadmore :status="status" icon-type="iconType" load-text="loadText" /> + */ + export default { + name: "u-loadmore", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + // 粗点 + dotText: "●" + } + }, + computed: { + // 加载的文字显示的样式 + loadTextStyle() { + return { + color: this.color, + fontSize: uni.$u.addUnit(this.fontSize), + lineHeight: uni.$u.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> + @import "../../libs/css/components.scss"; + + .u-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: $u-content-color; + } + + &__dot-text { + font-size: 15px; + color: $u-tips-color; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-modal/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-modal/props.js new file mode 100644 index 000000000..f76672c81 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-modal/props.js @@ -0,0 +1,84 @@ +export default { + props: { + // 是否展示modal + show: { + type: Boolean, + default: uni.$u.props.modal.show + }, + // 标题 + title: { + type: [String], + default: uni.$u.props.modal.title + }, + // 弹窗内容 + content: { + type: String, + default: uni.$u.props.modal.content + }, + // 确认文案 + confirmText: { + type: String, + default: uni.$u.props.modal.confirmText + }, + // 取消文案 + cancelText: { + type: String, + default: uni.$u.props.modal.cancelText + }, + // 是否显示确认按钮 + showConfirmButton: { + type: Boolean, + default: uni.$u.props.modal.showConfirmButton + }, + // 是否显示取消按钮 + showCancelButton: { + type: Boolean, + default: uni.$u.props.modal.showCancelButton + }, + // 确认按钮颜色 + confirmColor: { + type: String, + default: uni.$u.props.modal.confirmColor + }, + // 取消文字颜色 + cancelColor: { + type: String, + default: uni.$u.props.modal.cancelColor + }, + // 对调确认和取消的位置 + buttonReverse: { + type: Boolean, + default: uni.$u.props.modal.buttonReverse + }, + // 是否开启缩放效果 + zoom: { + type: Boolean, + default: uni.$u.props.modal.zoom + }, + // 是否异步关闭,只对确定按钮有效 + asyncClose: { + type: Boolean, + default: uni.$u.props.modal.asyncClose + }, + // 是否允许点击遮罩关闭modal + closeOnClickOverlay: { + type: Boolean, + default: uni.$u.props.modal.closeOnClickOverlay + }, + // 给一个负的margin-top,往上偏移,避免和键盘重合的情况 + negativeTop: { + type: [String, Number], + default: uni.$u.props.modal.negativeTop + }, + // modal宽度,不支持百分比,可以数值,px,rpx单位 + width: { + type: [String, Number], + default: uni.$u.props.modal.width + }, + // 确认按钮的样式,circle-圆形,square-方形,如设置,将不会显示取消按钮 + confirmButtonShape: { + type: String, + default: uni.$u.props.modal.confirmButtonShape + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-modal/u-modal.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-modal/u-modal.vue new file mode 100644 index 000000000..4c37ae2ed --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-modal/u-modal.vue @@ -0,0 +1,227 @@ +<template> + <u-popup + mode="center" + :zoom="zoom" + :show="show" + :customStyle="{ + borderRadius: '6px', + overflow: 'hidden', + marginTop: `-${$u.addUnit(negativeTop)}` + }" + :closeOnClickOverlay="closeOnClickOverlay" + :safeAreaInsetBottom="false" + :duration="400" + @click="clickHandler" + > + <view + class="u-modal" + :style="{ + width: $u.addUnit(width), + }" + > + <text + class="u-modal__title" + v-if="title" + >{{ title }}</text> + <view + class="u-modal__content" + :style="{ + paddingTop: `${title ? 12 : 25}px` + }" + > + <slot> + <text class="u-modal__content__text">{{ content }}</text> + </slot> + </view> + <view + class="u-modal__button-group--confirm-button" + v-if="$slots.confirmButton" + > + <slot name="confirmButton"></slot> + </view> + <template v-else> + <u-line></u-line> + <view + class="u-modal__button-group" + :style="{ + flexDirection: buttonReverse ? 'row-reverse' : 'row' + }" + > + <view + class="u-modal__button-group__wrapper u-modal__button-group__wrapper--cancel" + :hover-stay-time="150" + hover-class="u-modal__button-group__wrapper--hover" + :class="[showCancelButton && !showConfirmButton && 'u-modal__button-group__wrapper--only-cancel']" + v-if="showCancelButton" + @tap="cancelHandler" + > + <text + class="u-modal__button-group__wrapper__text" + :style="{ + color: cancelColor + }" + >{{ cancelText }}</text> + </view> + <u-line + direction="column" + v-if="showConfirmButton && showCancelButton" + ></u-line> + <view + class="u-modal__button-group__wrapper u-modal__button-group__wrapper--confirm" + :hover-stay-time="150" + hover-class="u-modal__button-group__wrapper--hover" + :class="[!showCancelButton && showConfirmButton && 'u-modal__button-group__wrapper--only-confirm']" + v-if="showConfirmButton" + @tap="confirmHandler" + > + <u-loading-icon v-if="loading"></u-loading-icon> + <text + v-else + class="u-modal__button-group__wrapper__text" + :style="{ + color: confirmColor + }" + >{{ confirmText }}</text> + </view> + </view> + </template> + </view> + </u-popup> +</template> + +<script> + import props from './props.js'; + /** + * Modal 模态框 + * @description 弹出模态框,常用于消息提示、消息确认、在当前页面内完成特定的交互操作。 + * @tutorial https://www.uviewui.com/components/modul.html + * @property {Boolean} show 是否显示模态框,请赋值给show (默认 false ) + * @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 是否允许点击遮罩关闭Modal (默认 false ) + * @property {String | Number} negativeTop 往上偏移的值,给一个负的margin-top,往上偏移,避免和键盘重合的情况,单位任意,数值则默认为px单位 (默认 0 ) + * @property {String | Number} width modal宽度,不支持百分比,可以数值,px,rpx单位 (默认 '650rpx' ) + * @property {String} confirmButtonShape 确认按钮的样式,如设置,将不会显示取消按钮 + * @event {Function} confirm 点击确认按钮时触发 + * @event {Function} cancel 点击取消按钮时触发 + * @event {Function} close 点击遮罩关闭出发,closeOnClickOverlay为true有效 + * @example <u-loadmore :status="status" icon-type="iconType" load-text="loadText" /> + */ + export default { + name: 'u-modal', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + loading: false + } + }, + watch: { + show(n) { + // 为了避免第一次打开modal,又使用了异步关闭的loading + // 第二次打开modal时,loading依然存在的情况 + if (n && this.loading) this.loading = false + } + }, + methods: { + // 点击确定按钮 + confirmHandler() { + // 如果配置了异步关闭,将按钮值为loading状态 + if (this.asyncClose) { + this.loading = true; + } + this.$emit('confirm') + }, + // 点击取消按钮 + cancelHandler() { + this.$emit('cancel') + }, + // 点击遮罩 + // 从原理上来说,modal的遮罩点击,并不是真的点击到了遮罩 + // 因为modal依赖于popup的中部弹窗类型,中部弹窗比较特殊,虽有然遮罩,但是为了让弹窗内容能flex居中 + // 多了一个透明的遮罩,此透明的遮罩会覆盖在灰色的遮罩上,所以实际上是点击不到灰色遮罩的,popup内部在 + // 透明遮罩的子元素做了.stop处理,所以点击内容区,也不会导致误触发 + clickHandler() { + if (this.closeOnClickOverlay) { + this.$emit('close') + } + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-modal-border-radius: 6px; + + .u-modal { + width: 650rpx; + border-radius: $u-modal-border-radius; + overflow: hidden; + + &__title { + font-size: 16px; + font-weight: bold; + color: $u-content-color; + text-align: center; + padding-top: 25px; + } + + &__content { + padding: 12px 25px 25px 25px; + @include flex; + justify-content: center; + + &__text { + font-size: 15px; + color: $u-content-color; + flex: 1; + } + } + + &__button-group { + @include flex; + + &--confirm-button { + flex-direction: column; + padding: 0px 25px 15px 25px; + } + + &__wrapper { + flex: 1; + @include flex; + justify-content: center; + align-items: center; + height: 48px; + + &--confirm, + &--only-cancel { + border-bottom-right-radius: $u-modal-border-radius; + } + + &--cancel, + &--only-confirm { + border-bottom-left-radius: $u-modal-border-radius; + } + + &--hover { + background-color: $u-bg-color; + } + + &__text { + color: $u-content-color; + font-size: 16px; + text-align: center; + } + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-navbar/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-navbar/props.js new file mode 100644 index 000000000..5398de287 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-navbar/props.js @@ -0,0 +1,84 @@ +export default { + props: { + // 是否开启顶部安全区适配 + safeAreaInsetTop: { + type: Boolean, + default: uni.$u.props.navbar.safeAreaInsetTop + }, + // 固定在顶部时,是否生成一个等高元素,以防止塌陷 + placeholder: { + type: Boolean, + default: uni.$u.props.navbar.placeholder + }, + // 是否固定在顶部 + fixed: { + type: Boolean, + default: uni.$u.props.navbar.fixed + }, + // 是否显示下边框 + border: { + type: Boolean, + default: uni.$u.props.navbar.border + }, + // 左边的图标 + leftIcon: { + type: String, + default: uni.$u.props.navbar.leftIcon + }, + // 左边的提示文字 + leftText: { + type: String, + default: uni.$u.props.navbar.leftText + }, + // 左右的提示文字 + rightText: { + type: String, + default: uni.$u.props.navbar.rightText + }, + // 右边的图标 + rightIcon: { + type: String, + default: uni.$u.props.navbar.rightIcon + }, + // 标题 + title: { + type: [String, Number], + default: uni.$u.props.navbar.title + }, + // 背景颜色 + bgColor: { + type: String, + default: uni.$u.props.navbar.bgColor + }, + // 标题的宽度 + titleWidth: { + type: [String, Number], + default: uni.$u.props.navbar.titleWidth + }, + // 导航栏高度 + height: { + type: [String, Number], + default: uni.$u.props.navbar.height + }, + // 左侧返回图标的大小 + leftIconSize: { + type: [String, Number], + default: uni.$u.props.navbar.leftIconSize + }, + // 左侧返回图标的颜色 + leftIconColor: { + type: String, + default: uni.$u.props.navbar.leftIconColor + }, + // 点击左侧区域(返回图标),是否自动返回上一页 + autoBack: { + type: Boolean, + default: uni.$u.props.navbar.autoBack + }, + // 标题的样式,对象或字符串 + titleStyle: { + type: [String, Object], + default: uni.$u.props.navbar.titleStyle + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-navbar/u-navbar.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-navbar/u-navbar.vue new file mode 100644 index 000000000..2b206b7db --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-navbar/u-navbar.vue @@ -0,0 +1,186 @@ +<template> + <view class="u-navbar"> + <view + class="u-navbar__placeholder" + v-if="fixed && placeholder" + :style="{ + height: $u.addUnit($u.getPx(height) + $u.sys().statusBarHeight,'px'), + }" + ></view> + <view :class="[fixed && 'u-navbar--fixed']"> + <u-status-bar + v-if="safeAreaInsetTop" + :bgColor="bgColor" + ></u-status-bar> + <view + class="u-navbar__content" + :class="[border && 'u-border-bottom']" + :style="{ + height: $u.addUnit(height), + backgroundColor: bgColor, + }" + > + <view + class="u-navbar__content__left" + hover-class="u-navbar__content__left--hover" + hover-start-time="150" + @tap="leftClick" + > + <slot name="left"> + <u-icon + v-if="leftIcon" + :name="leftIcon" + :size="leftIconSize" + :color="leftIconColor" + ></u-icon> + <text + v-if="leftText" + :style="{ + color: leftIconColor + }" + class="u-navbar__content__left__text" + >{{ leftText }}</text> + </slot> + </view> + <slot name="center"> + <text + class="u-line-1 u-navbar__content__title" + :style="[{ + width: $u.addUnit(titleWidth), + }, $u.addStyle(titleStyle)]" + >{{ title }}</text> + </slot> + <view + class="u-navbar__content__right" + v-if="$slots.right || rightIcon || rightText" + @tap="rightClick" + > + <slot name="right"> + <u-icon + v-if="rightIcon" + :name="rightIcon" + size="20" + ></u-icon> + <text + v-if="rightText" + class="u-navbar__content__right__text" + >{{ rightText }}</text> + </slot> + </view> + </view> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * Navbar 自定义导航栏 + * @description 此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用uni-app带的导航栏。 + * @tutorial https://www.uviewui.com/components/navbar.html + * @property {Boolean} safeAreaInsetTop 是否开启顶部安全区适配 (默认 true ) + * @property {Boolean} placeholder 固定在顶部时,是否生成一个等高元素,以防止塌陷 (默认 false ) + * @property {Boolean} fixed 导航栏是否固定在顶部 (默认 false ) + * @property {Boolean} border 导航栏底部是否显示下边框 (默认 false ) + * @property {String} leftIcon 左边返回图标的名称,只能为uView自带的图标 (默认 'arrow-left' ) + * @property {String} leftText 左边的提示文字 + * @property {String} rightText 右边的提示文字 + * @property {String} rightIcon 右边返回图标的名称,只能为uView自带的图标 + * @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 <u-navbar title="剑未配妥,出门已是江湖" left-text="返回" right-text="帮助" @click-left="onClickBack" @click-right="onClickRight"></u-navbar> + */ + export default { + name: 'u-navbar', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + + } + }, + methods: { + // 点击左侧区域 + leftClick() { + // 如果配置了autoBack,自动返回上一页 + this.$emit('leftClick') + if(this.autoBack) { + uni.navigateBack() + } + }, + // 点击右侧区域 + rightClick() { + this.$emit('rightClick') + }, + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-navbar { + + &--fixed { + position: fixed; + left: 0; + right: 0; + top: 0; + z-index: 11; + } + + &__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: $u-main-color; + } + + &__right { + right: 0; + + &__text { + font-size: 15px; + margin-left: 3px; + } + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-no-network/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-no-network/props.js new file mode 100644 index 000000000..9f3af62ca --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-no-network/props.js @@ -0,0 +1,19 @@ +export default { + props: { + // 页面文字提示 + tips: { + type: String, + default: uni.$u.props.noNetwork.tips + }, + // 一个z-index值,用于设置没有网络这个组件的层次,因为页面可能会有其他定位的元素层级过高,导致此组件被覆盖 + zIndex: { + type: [String, Number], + default: uni.$u.props.noNetwork.zIndex + }, + // image 没有网络的图片提示 + image: { + type: String, + default: uni.$u.props.noNetwork.image + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-no-network/u-no-network.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-no-network/u-no-network.vue new file mode 100644 index 000000000..53db90588 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-no-network/u-no-network.vue @@ -0,0 +1,219 @@ +<template> + <u-overlay + :show="!isConnected" + @touchmove.stop.prevent="noop" + :customStyle="{ + backgroundColor: '#fff', + display: 'flex', + justifyContent: 'center', + }" + > + <view + class="u-no-network" + > + <u-icon + :name="image" + size="150" + imgMode="widthFit" + class="u-no-network__error-icon" + ></u-icon> + <text class="u-no-network__tips">{{tips}}</text> + <!-- 只有APP平台,才能跳转设置页,因为需要调用plus环境 --> + <!-- #ifdef APP-PLUS --> + <view class="u-no-network__app"> + <text class="u-no-network__app__setting">请检查网络,或前往</text> + <text + class="u-no-network__app__to-setting" + @tap="openSettings" + >设置</text> + </view> + <!-- #endif --> + <view class="u-no-network__retry"> + <u-button + size="mini" + text="重试" + type="primary" + plain + @click="retry" + ></u-button> + </view> + </view> + </u-overlay> +</template> + +<script> + import props from './props.js'; + + /** + * noNetwork 无网络提示 + * @description 该组件无需任何配置,引入即可,内部自动处理所有功能和事件。 + * @tutorial https://www.uviewui.com/components/noNetwork.html + * @property {String} tips 没有网络时的提示语 (默认:'哎呀,网络信号丢失' ) + * @property {String | Number} zIndex 组件的z-index值 + * @property {String} image 无网络的图片提示,可用的src地址或base64图片 + * @event {Function} retry 用户点击页面的"重试"按钮时触发 + * @example <u-no-network></u-no-network> + */ + export default { + name: "u-no-network", + mixins: [uni.$u.mpMixin, uni.$u.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') { + uni.$u.toast('无网络连接') + this.isConnected = false + } else { + uni.$u.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 "../../libs/css/components.scss"; + + .u-no-network { + @include flex(column); + justify-content: center; + align-items: center; + margin-top: -100px; + + &__tips { + color: $u-tips-color; + font-size: 14px; + margin-top: 15px; + } + + &__app { + @include flex(row); + margin-top: 6px; + + &__setting { + color: $u-light-color; + font-size: 13px; + } + + &__to-setting { + font-size: 13px; + color: $u-primary; + margin-left: 3px; + } + } + + &__retry { + @include flex(row); + justify-content: center; + margin-top: 15px; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-notice-bar/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-notice-bar/props.js new file mode 100644 index 000000000..7040c29a5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-notice-bar/props.js @@ -0,0 +1,70 @@ +export default { + props: { + // 显示的内容,数组 + text: { + type: [Array, String], + default: uni.$u.props.noticeBar.text + }, + // 通告滚动模式,row-横向滚动,column-竖向滚动 + direction: { + type: String, + default: uni.$u.props.noticeBar.direction + }, + // direction = row时,是否使用步进形式滚动 + step: { + type: Boolean, + default: uni.$u.props.noticeBar.step + }, + // 是否显示左侧的音量图标 + icon: { + type: String, + default: uni.$u.props.noticeBar.icon + }, + // 通告模式,link-显示右箭头,closable-显示右侧关闭图标 + mode: { + type: String, + default: uni.$u.props.noticeBar.mode + }, + // 文字颜色,各图标也会使用文字颜色 + color: { + type: String, + default: uni.$u.props.noticeBar.color + }, + // 背景颜色 + bgColor: { + type: String, + default: uni.$u.props.noticeBar.bgColor + }, + // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度 + speed: { + type: [String, Number], + default: uni.$u.props.noticeBar.speed + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.noticeBar.fontSize + }, + // 滚动一个周期的时间长,单位ms + duration: { + type: [String, Number], + default: uni.$u.props.noticeBar.duration + }, + // 是否禁止用手滑动切换 + // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 + disableTouch: { + type: Boolean, + default: uni.$u.props.noticeBar.disableTouch + }, + // 跳转的页面路径 + url: { + type: String, + default: uni.$u.props.noticeBar.url + }, + // 页面跳转的类型 + linkType: { + type: String, + default: uni.$u.props.noticeBar.linkType + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-notice-bar/u-notice-bar.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-notice-bar/u-notice-bar.vue new file mode 100644 index 000000000..a06eb39ad --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-notice-bar/u-notice-bar.vue @@ -0,0 +1,101 @@ +<template> + <view + class="u-notice-bar" + v-if="show" + :style="[{ + backgroundColor: bgColor + }, $u.addStyle(customStyle)]" + > + <template v-if="direction === 'column' || (direction === 'row' && step)"> + <u-column-notice + :color="color" + :bgColor="bgColor" + :text="text" + :mode="mode" + :step="step" + :icon="icon" + :disable-touch="disableTouch" + :fontSize="fontSize" + :duration="duration" + @close="close" + @click="click" + ></u-column-notice> + </template> + <template v-else> + <u-row-notice + :color="color" + :bgColor="bgColor" + :text="text" + :mode="mode" + :fontSize="fontSize" + :speed="speed" + :url="url" + :linkType="linkType" + :icon="icon" + @close="close" + @click="click" + ></u-row-notice> + </template> + </view> +</template> +<script> + import props from './props.js'; + + /** + * noticeBar 滚动通知 + * @description 该组件用于滚动通告场景,有多种模式可供选择 + * @tutorial https://www.uviewui.com/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 <u-notice-bar :more-icon="true" :list="list"></u-notice-bar> + */ + export default { + name: "u-notice-bar", + mixins: [uni.$u.mpMixin, uni.$u.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') + } + } + }; +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-notice-bar { + overflow: hidden; + padding: 9px 12px; + flex: 1; + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-notify/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-notify/props.js new file mode 100644 index 000000000..57a9d71e9 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-notify/props.js @@ -0,0 +1,49 @@ +export default { + props: { + // 到顶部的距离 + top: { + type: [String, Number], + default: uni.$u.props.notify.top + }, + // 是否展示组件 + // show: { + // type: Boolean, + // default: uni.$u.props.notify.show + // }, + // type主题,primary,success,warning,error + type: { + type: String, + default: uni.$u.props.notify.type + }, + // 字体颜色 + color: { + type: String, + default: uni.$u.props.notify.color + }, + // 背景颜色 + bgColor: { + type: String, + default: uni.$u.props.notify.bgColor + }, + // 展示的文字内容 + message: { + type: String, + default: uni.$u.props.notify.message + }, + // 展示时长,为0时不消失,单位ms + duration: { + type: [String, Number], + default: uni.$u.props.notify.duration + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.notify.fontSize + }, + // 是否留出顶部安全距离(状态栏高度) + safeAreaInsetTop: { + type: Boolean, + default: uni.$u.props.notify.safeAreaInsetTop + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-notify/u-notify.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-notify/u-notify.vue new file mode 100644 index 000000000..30adb72fa --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-notify/u-notify.vue @@ -0,0 +1,211 @@ +<template> + <u-transition + mode="slide-down" + :customStyle="containerStyle" + :show="open" + > + <view + class="u-notify" + :class="[`u-notify--${tmpConfig.type}`]" + :style="[backgroundColor, $u.addStyle(customStyle)]" + > + <u-status-bar v-if="tmpConfig.safeAreaInsetTop"></u-status-bar> + <view class="u-notify__warpper"> + <slot name="icon"> + <u-icon + v-if="['success', 'warning', 'error'].includes(tmpConfig.type)" + :name="tmpConfig.icon" + :color="tmpConfig.color" + :size="1.3 * tmpConfig.fontSize" + :customStyle="{marginRight: '4px'}" + ></u-icon> + </slot> + <text + class="u-notify__warpper__text" + :style="{ + fontSize: $u.addUnit(tmpConfig.fontSize), + color: tmpConfig.color + }" + >{{ tmpConfig.message }}</text> + </view> + </view> + </u-transition> +</template> + +<script> + 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 <u-notify message="Hi uView"></u-notify> + */ + export default { + name: 'u-notify', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + // 是否展示组件 + open: false, + timer: null, + config: { + // 到顶部的距离 + top: uni.$u.props.notify.top, + // type主题,primary,success,warning,error + type: uni.$u.props.notify.type, + // 字体颜色 + color: uni.$u.props.notify.color, + // 背景颜色 + bgColor: uni.$u.props.notify.bgColor, + // 展示的文字内容 + message: uni.$u.props.notify.message, + // 展示时长,为0时不消失,单位ms + duration: uni.$u.props.notify.duration, + // 字体大小 + fontSize: uni.$u.props.notify.fontSize, + // 是否留出顶部安全距离(状态栏高度) + safeAreaInsetTop: uni.$u.props.notify.safeAreaInsetTop + }, + // 合并后的配置,避免多次调用组件后,可能会复用之前使用的配置参数 + tmpConfig: {} + } + }, + computed: { + containerStyle() { + let top = 0 + if (this.tmpConfig.top === 0) { + // #ifdef H5 + // H5端,导航栏为普通元素,需要将组件移动到导航栏的下边沿 + // H5的导航栏高度为44px + top = 44 + // #endif + } + const style = { + top: uni.$u.addUnit(this.tmpConfig.top === 0 ? top : this.tmpConfig.top), + // 因为组件底层为u-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变量,避免多次调用u-toast,前后的配置造成混乱 + this.tmpConfig = uni.$u.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 + } + }, + beforeDestroy() { + this.clearTimer() + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + $u-notify-padding: 8px 10px !default; + $u-notify-text-font-size: 15px !default; + $u-notify-primary-bgColor: $u-primary !default; + $u-notify-success-bgColor: $u-success !default; + $u-notify-error-bgColor: $u-error !default; + $u-notify-warning-bgColor: $u-warning !default; + + + .u-notify { + padding: $u-notify-padding; + + &__warpper { + @include flex; + align-items: center; + text-align: center; + justify-content: center; + + &__text { + font-size: $u-notify-text-font-size; + text-align: center; + } + } + + &--primary { + background-color: $u-notify-primary-bgColor; + } + + &--success { + background-color: $u-notify-success-bgColor; + } + + &--error { + background-color: $u-notify-error-bgColor; + } + + &--warning { + background-color: $u-notify-warning-bgColor; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-number-box/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-number-box/props.js new file mode 100644 index 000000000..fb0fa947f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-number-box/props.js @@ -0,0 +1,109 @@ +export default { + props: { + // 步进器标识符,在change回调返回 + name: { + type: [String, Number], + default: uni.$u.props.numberBox.name + }, + // 用于双向绑定的值,初始化时设置设为默认min值(最小值) + value: { + type: [String, Number], + default: uni.$u.props.numberBox.value + }, + // 最小值 + min: { + type: [String, Number], + default: uni.$u.props.numberBox.min + }, + // 最大值 + max: { + type: [String, Number], + default: uni.$u.props.numberBox.max + }, + // 加减的步长,可为小数 + step: { + type: [String, Number], + default: uni.$u.props.numberBox.step + }, + // 是否只允许输入整数 + integer: { + type: Boolean, + default: uni.$u.props.numberBox.integer + }, + // 是否禁用,包括输入框,加减按钮 + disabled: { + type: Boolean, + default: uni.$u.props.numberBox.disabled + }, + // 是否禁用输入框 + disabledInput: { + type: Boolean, + default: uni.$u.props.numberBox.disabledInput + }, + // 是否开启异步变更,开启后需要手动控制输入值 + asyncChange: { + type: Boolean, + default: uni.$u.props.numberBox.asyncChange + }, + // 输入框宽度,单位为px + inputWidth: { + type: [String, Number], + default: uni.$u.props.numberBox.inputWidth + }, + // 是否显示减少按钮 + showMinus: { + type: Boolean, + default: uni.$u.props.numberBox.showMinus + }, + // 是否显示增加按钮 + showPlus: { + type: Boolean, + default: uni.$u.props.numberBox.showPlus + }, + // 显示的小数位数 + decimalLength: { + type: [String, Number, null], + default: uni.$u.props.numberBox.decimalLength + }, + // 是否开启长按加减手势 + longPress: { + type: Boolean, + default: uni.$u.props.numberBox.longPress + }, + // 输入框文字和加减按钮图标的颜色 + color: { + type: String, + default: uni.$u.props.numberBox.color + }, + // 按钮大小,宽高等于此值,单位px,输入框高度和此值保持一致 + buttonSize: { + type: [String, Number], + default: uni.$u.props.numberBox.buttonSize + }, + // 输入框和按钮的背景颜色 + bgColor: { + type: String, + default: uni.$u.props.numberBox.bgColor + }, + // 指定光标于键盘的距离,避免键盘遮挡输入框,单位px + cursorSpacing: { + type: [String, Number], + default: uni.$u.props.numberBox.cursorSpacing + }, + // 是否禁用增加按钮 + disablePlus: { + type: Boolean, + default: uni.$u.props.numberBox.disablePlus + }, + // 是否禁用减少按钮 + disableMinus: { + type: Boolean, + default: uni.$u.props.numberBox.disableMinus + }, + // 加减按钮图标的样式 + iconStyle: { + type: [Object, String], + default: uni.$u.props.numberBox.iconStyle + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-number-box/u-number-box.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-number-box/u-number-box.vue new file mode 100644 index 000000000..69211c540 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-number-box/u-number-box.vue @@ -0,0 +1,416 @@ +<template> + <view class="u-number-box"> + <view + class="u-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="u-number-box__minus" + @tap.stop="clickHandler('minus')" + @touchstart="onTouchStart('minus')" + @touchend.stop="clearTimeout" + hover-class="u-number-box__minus--hover" + hover-stay-time="150" + :class="{ 'u-number-box__minus--disabled': isDisabled('minus') }" + :style="[buttonStyle('minus')]" + > + <u-icon + name="minus" + :color="isDisabled('minus') ? '#c8c9cc' : '#323233'" + size="15" + bold + :customStyle="iconStyle" + ></u-icon> + </view> + + <slot name="input"> + <input + :disabled="disabledInput || disabled" + :cursor-spacing="getCursorSpacing" + :class="{ 'u-number-box__input--disabled': disabled || disabledInput }" + v-model="currentValue" + class="u-number-box__input" + @blur="onBlur" + @focus="onFocus" + @input="onInput" + type="number" + :style="[inputStyle]" + /> + </slot> + <view + class="u-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="u-number-box__plus" + @tap.stop="clickHandler('plus')" + @touchstart="onTouchStart('plus')" + @touchend.stop="clearTimeout" + hover-class="u-number-box__plus--hover" + hover-stay-time="150" + :class="{ 'u-number-box__minus--disabled': isDisabled('plus') }" + :style="[buttonStyle('plus')]" + > + <u-icon + name="plus" + :color="isDisabled('plus') ? '#c8c9cc' : '#323233'" + size="15" + bold + :customStyle="iconStyle" + ></u-icon> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * numberBox 步进器 + * @description 该组件一般用于商城购物选择物品数量的场景。 + * @tutorial https://uviewui.com/components/numberBox.html + * @property {String | Number} name 步进器标识符,在change回调返回 + * @property {String | Number} value 用于双向绑定的值,初始化时设置设为默认min值(最小值) (默认 0 ) + * @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 <u-number-box v-model="value" @change="valChange"></u-number-box> + */ + export default { + name: 'u-number-box', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 输入框实际操作的值 + currentValue: '', + // 定时器 + longPressTimer: null + } + }, + watch: { + // 多个值之间,只要一个值发生变化,都要重新检查check()函数 + watchChange(n) { + this.check() + }, + // 监听v-mode的变化,重新初始化内部的值 + value(n) { + if (n !== this.currentValue) { + this.currentValue = this.format(this.value) + } + } + }, + computed: { + getCursorSpacing() { + // 判断传入的单位,如果为px单位,需要转成px + return uni.$u.getPx(this.cursorSpacing) + }, + // 按钮的样式 + buttonStyle() { + return (type) => { + const style = { + backgroundColor: this.bgColor, + height: uni.$u.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: uni.$u.addUnit(this.buttonSize), + width: uni.$u.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 + ) + } + }, + }, + mounted() { + this.init() + }, + methods: { + init() { + this.currentValue = this.format(this.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 + } + }, + // 判断是否出于禁止操作状态 + // isDisabled(type) { + // if (type === 'plus') { + // // 在点击增加按钮情况下,判断整体的disabled,是否单独禁用增加按钮,以及当前值是否大于最大的允许值 + // return ( + // this.disabled || + // this.disablePlus || + // this.currentValue >= this.max + // ) + // } + // // 点击减少按钮同理 + // return ( + // this.disabled || + // this.disableMinus || + // this.currentValue <= this.min + // ) + // }, + // 输入框活动焦点 + 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.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 '../../libs/css/components.scss'; + + $u-numberBox-hover-bgColor: #E6E6E6 !default; + $u-numberBox-disabled-color: #c8c9cc !default; + $u-numberBox-disabled-bgColor: #f7f8fa !default; + $u-numberBox-plus-radius: 4px !default; + $u-numberBox-minus-radius: 4px !default; + $u-numberBox-input-text-align: center !default; + $u-numberBox-input-font-size: 15px !default; + $u-numberBox-input-padding: 0 !default; + $u-numberBox-input-margin: 0 2px !default; + $u-numberBox-input-disabled-color: #c8c9cc !default; + $u-numberBox-input-disabled-bgColor: #f2f3f5 !default; + + .u-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: $u-numberBox-hover-bgColor !important; + } + + &--disabled { + color: $u-numberBox-disabled-color; + background-color: $u-numberBox-disabled-bgColor; + } + } + + &__plus { + border-top-right-radius: $u-numberBox-plus-radius; + border-bottom-right-radius: $u-numberBox-plus-radius; + } + + &__minus { + border-top-left-radius: $u-numberBox-minus-radius; + border-bottom-left-radius: $u-numberBox-minus-radius; + } + + &__input { + position: relative; + text-align: $u-numberBox-input-text-align; + font-size: $u-numberBox-input-font-size; + padding: $u-numberBox-input-padding; + margin: $u-numberBox-input-margin; + @include flex; + align-items: center; + justify-content: center; + + &--disabled { + color: $u-numberBox-input-disabled-color; + background-color: $u-numberBox-input-disabled-bgColor; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-number-keyboard/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-number-keyboard/props.js new file mode 100644 index 000000000..5e3bf556b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-number-keyboard/props.js @@ -0,0 +1,19 @@ +export default { + props: { + // 键盘的类型,number-数字键盘,card-身份证键盘 + mode: { + type: String, + default: uni.$u.props.numberKeyboard.value + }, + // 是否显示键盘的"."符号 + dotDisabled: { + type: Boolean, + default: uni.$u.props.numberKeyboard.dotDisabled + }, + // 是否打乱键盘按键的顺序 + random: { + type: Boolean, + default: uni.$u.props.numberKeyboard.random + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-number-keyboard/u-number-keyboard.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-number-keyboard/u-number-keyboard.vue new file mode 100644 index 000000000..4f505c6e4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-number-keyboard/u-number-keyboard.vue @@ -0,0 +1,196 @@ +<template> + <view + class="u-keyboard" + @touchmove.stop.prevent="noop" + > + <view + class="u-keyboard__button-wrapper" + v-for="(item, index) in numList" + :key="index" + > + <view + class="u-keyboard__button-wrapper__button" + :style="[itemStyle(index)]" + @tap="keyboardClick(item)" + hover-class="u-hover-class" + :hover-stay-time="200" + > + <text class="u-keyboard__button-wrapper__button__text">{{ item }}</text> + </view> + </view> + <view + class="u-keyboard__button-wrapper" + > + <view + class="u-keyboard__button-wrapper__button u-keyboard__button-wrapper__button--gray" + hover-class="u-hover-class" + :hover-stay-time="200" + @touchstart.stop="backspaceClick" + @touchend="clearTimer" + > + <u-icon + name="backspace" + color="#303133" + size="28" + ></u-icon> + </view> + </view> + </view> +</template> + +<script> + 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: [uni.$u.mpMixin, uni.$u.mixin, props], + 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 uni.$u.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 uni.$u.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 uni.$u.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> + @import "../../libs/css/components.scss"; + $u-number-keyboard-background-color:rgb(224, 228, 230) !default; + $u-number-keyboard-padding:8px 10rpx 8px 10rpx !default; + $u-number-keyboard-button-width:222rpx !default; + $u-number-keyboard-button-margin:4px 6rpx !default; + $u-number-keyboard-button-border-top-left-radius:4px !default; + $u-number-keyboard-button-border-top-right-radius:4px !default; + $u-number-keyboard-button-border-bottom-left-radius:4px !default; + $u-number-keyboard-button-border-bottom-right-radius:4px !default; + $u-number-keyboard-button-height: 90rpx!default; + $u-number-keyboard-button-background-color:#FFFFFF !default; + $u-number-keyboard-button-box-shadow:0 2px 0px #BBBCBE !default; + $u-number-keyboard-text-font-size:20px !default; + $u-number-keyboard-text-font-weight:500 !default; + $u-number-keyboard-text-color:$u-main-color !default; + $u-number-keyboard-gray-background-color:rgb(200, 202, 210) !default; + $u-number-keyboard-u-hover-class-background-color: #BBBCC6 !default; + + .u-keyboard { + @include flex; + flex-direction: row; + justify-content: space-around; + background-color: $u-number-keyboard-background-color; + flex-wrap: wrap; + padding: $u-number-keyboard-padding; + + &__button-wrapper { + box-shadow: $u-number-keyboard-button-box-shadow; + margin: $u-number-keyboard-button-margin; + border-top-left-radius: $u-number-keyboard-button-border-top-left-radius; + border-top-right-radius: $u-number-keyboard-button-border-top-right-radius; + border-bottom-left-radius: $u-number-keyboard-button-border-bottom-left-radius; + border-bottom-right-radius: $u-number-keyboard-button-border-bottom-right-radius; + + &__button { + width: $u-number-keyboard-button-width; + height: $u-number-keyboard-button-height; + background-color: $u-number-keyboard-button-background-color; + @include flex; + justify-content: center; + align-items: center; + border-top-left-radius: $u-number-keyboard-button-border-top-left-radius; + border-top-right-radius: $u-number-keyboard-button-border-top-right-radius; + border-bottom-left-radius: $u-number-keyboard-button-border-bottom-left-radius; + border-bottom-right-radius: $u-number-keyboard-button-border-bottom-right-radius; + + &__text { + font-size: $u-number-keyboard-text-font-size; + font-weight: $u-number-keyboard-text-font-weight; + color: $u-number-keyboard-text-color; + } + + &--gray { + background-color: $u-number-keyboard-gray-background-color; + } + } + } + } + + .u-hover-class { + background-color: $u-number-keyboard-u-hover-class-background-color; + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-overlay/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-overlay/props.js new file mode 100644 index 000000000..e6974df9b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-overlay/props.js @@ -0,0 +1,24 @@ +export default { + props: { + // 是否显示遮罩 + show: { + type: Boolean, + default: uni.$u.props.overlay.show + }, + // 层级z-index + zIndex: { + type: [String, Number], + default: uni.$u.props.overlay.zIndex + }, + // 遮罩的过渡时间,单位为ms + duration: { + type: [String, Number], + default: uni.$u.props.overlay.duration + }, + // 不透明度值,当做rgba的第四个参数 + opacity: { + type: [String, Number], + default: uni.$u.props.overlay.opacity + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-overlay/u-overlay.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-overlay/u-overlay.vue new file mode 100644 index 000000000..92de4e9fd --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-overlay/u-overlay.vue @@ -0,0 +1,68 @@ +<template> + <u-transition + :show="show" + custom-class="u-overlay" + :duration="duration" + :custom-style="overlayStyle" + @click="clickHandler" + > + <slot /> + </u-transition> +</template> + +<script> + import props from './props.js'; + + /** + * overlay 遮罩 + * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景 + * @tutorial https://www.uviewui.com/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 <u-overlay :show="show" @click="show = false"></u-overlay> + */ + export default { + name: "u-overlay", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + 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 uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + methods: { + clickHandler() { + this.$emit('click') + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-overlay-top:0 !default; + $u-overlay-left:0 !default; + $u-overlay-width:100% !default; + $u-overlay-height:100% !default; + $u-overlay-background-color:rgba(0, 0, 0, .7) !default; + .u-overlay { + position: fixed; + top:$u-overlay-top; + left:$u-overlay-left; + width: $u-overlay-width; + height:$u-overlay-height; + background-color:$u-overlay-background-color; + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-parse/node/node.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-parse/node/node.vue new file mode 100644 index 000000000..e2a9f7bef --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-parse/node/node.vue @@ -0,0 +1,499 @@ +<template> + <view :id="attrs.id" :class="'_'+name+' '+attrs.class" :style="attrs.style"> + <block v-for="(n, i) in childs" v-bind:key="i"> + <!-- 图片 --> + <!-- 占位图 --> + <image v-if="n.name=='img'&&((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 --> + <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 --> + <image v-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'" :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 --> + <!-- 文本 --> + <!-- #ifndef MP-BAIDU --> + <text v-else-if="n.type=='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" /> + <!-- #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" :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 --> + <!-- 音频 --> + <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 --> + <rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :style="n.f" :nodes="[n]" /> + <!-- #endif --> + <!-- #ifndef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 --> + <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :nodes="[n]" /> + <!-- #endif --> + <!-- 继续递归 --> + <view v-else-if="n.c==2" :id="n.attrs.id" :class="'_'+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 是否使用 rich-text 显示剩余内容 + */ +module.exports = { + use: function (item) { + // 微信和 QQ 的 rich-text inline 布局无效 + if (inlineTags[item.name] || (item.attrs.style || '').indexOf('display:inline') != -1) + return false + return !item.c + } +} +</script> +<script> + +import node from './node' +export default { + name: 'node', + // #ifdef MP-WEIXIN + options: { + virtualHost: true + }, + // #endif + data() { + return { + ctrl: {} + } + }, + props: { + name: String, + attrs: { + type: Object, + default() { + return {} + } + }, + childs: Array, + opts: Array + }, + components: { + + node + }, + mounted() { + for (this.root = this.$parent; this.root.$options.name != 'mp-html'; this.root = this.root.$parent); + // #ifdef H5 || APP-PLUS + if (this.opts[0]) { + for (var 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() { }, + // #endif + /** + * @description 播放视频事件 + * @param {Event} e + */ + play(e) { + // #ifndef APP-PLUS + if (this.root.pauseVideo) { + var flag = false, id = e.target.id + for (var i = this.root._videos.length; i--;) { + if (this.root._videos[i].id == id) + flag = true + else + this.root._videos[i].pause() // 自动暂停其他视频 + } + // 将自己加入列表 + if (!flag) { + var ctx = uni.createVideoContext(id + // #ifndef MP-BAIDU + , this + // #endif + ) + ctx.id = id + this.root._videos.push(ctx) + } + } + // #endif + }, + + /** + * @description 图片点击事件 + * @param {Event} e + */ + imgTap(e) { + var node = this.childs[e.currentTarget.dataset.i] + if (node.a) + return this.linkTap(node.a) + 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({ + current: parseInt(node.attrs.i), + urls: this.root.imgList + }) + }, + + /** + * @description 图片长按 + */ + imgLongTap(e) { + // #ifdef APP-PLUS + var attrs = this.childs[e.currentTarget.dataset.i].attrs + if (!attrs.ignore) + uni.showActionSheet({ + itemList: ['保存图片'], + success: () => { + uni.downloadFile({ + url: this.root.imgList[attrs.i], + success: res => { + uni.saveImageToPhotosAlbum({ + filePath: res.tempFilePath, + success() { + uni.showToast({ + title: '保存成功' + }) + } + }) + } + }) + } + }) + // #endif + }, + + /** + * @description 图片加载完成事件 + * @param {Event} e + */ + imgLoad(e) { + var i = e.currentTarget.dataset.i + // #ifndef H5 || APP-PLUS + // 设置原宽度 + 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) + }, + + /** + * @description 链接点击事件 + * @param {Event} e + */ + linkTap(e) { + var attrs = e.currentTarget ? this.childs[e.currentTarget.dataset.i].attrs : e, + href = attrs.href + this.root.$emit('linktap', attrs) + if (href) { + // 跳转锚点 + if (href[0] == '#') + this.root.navigateTo(href.substring(1)).catch(() => { }) + // 复制外部链接 + else if (href.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) { + var i = e.currentTarget.dataset.i, + node = this.childs[i] + // 加载其他源 + if (node.name == 'video' || node.name == 'audio') { + var index = (this.ctrl[i] || 0) + 1 + if (index > node.src.length) + index = 0 + if (index < node.src.length) + return this.$set(this.ctrl, i, index) + } + // 显示错误占位图 + else if (node.name == 'img' && this.opts[2]) + this.$set(this.ctrl, i, -1) + if (this.root) + this.root.$emit('error', { + source: node.name, + attrs: node.attrs, + errMsg: e.detail.errMsg + }) + } + } +} +</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; +} + +/* 内部样式 */ + +._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/yudao-ui-app/uni_modules/uview-ui/components/u-parse/parser.js b/yudao-ui-app/uni_modules/uview-ui/components/u-parse/parser.js new file mode 100644 index 000000000..a78a65427 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-parse/parser.js @@ -0,0 +1,1075 @@ +'use strict' + +/** + * @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'), + // 要移除的标签 + 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: '…' + }, + // 默认的标签样式 + 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', + u: 'text-decoration:underline' // #endif + + } +} +const { windowWidth } = uni.getSystemInfoSync() +const blankChar = makeMap(' ,\r,\n,\t,\f') +let idIndex = 0 // #ifdef H5 || APP-PLUS + +config.ignoreTags.iframe = void 0 +config.trustTags.iframe = true +config.ignoreTags.embed = void 0 +config.trustTags.embed = true // #endif +// #ifdef APP-PLUS-NVUE + +config.ignoreTags.source = void 0 +config.ignoreTags.style = void 0 // #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 = void 0 + 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 html 解析器 + * @param {Object} vm 组件实例 + */ + +function parser(vm) { + this.options = vm || {} + this.tagStyle = Object.assign(config.tagStyle, this.options.tagStyle) + this.imgList = vm.imgList || [] + this.plugins = vm.plugins || [] + this.attrs = Object.create(null) + this.stack = [] + this.nodes = [] +} +/** + * @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() + } + + 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.name == 'a' || item.c) 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 + + if (url[0] == '/') { + // // 开头的补充协议名 + if (url[1] == '/') url = `${domain ? domain.split('://')[0] : 'http'}:${url}` // 否则补充整个域名 + else if (domain) url = domain + url + } else if (domain && !url.includes('data:') && !url.includes('://')) url = `${domain}/${url}` + + return url +} +/** + * @description 解析样式表 + * @param {Object} node 标签 + * @returns {Object} + */ + +parser.prototype.parseStyle = function (node) { + const { attrs } = node + const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';')) + const styleObj = {} + let tmp = '' + + if (attrs.id) { + // 暴露锚点 + if (this.options.useAnchor) this.expose(); else if (node.name != 'img' && node.name != 'a' && node.name != 'video' && node.name != 'audio') attrs.id = void 0 + } // 转换 width 和 height 属性 + + if (attrs.width) { + styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px') + attrs.width = void 0 + } + + if (attrs.height) { + styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px') + attrs.height = void 0 + } + + 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() // 兼容性的 css 不压缩 + + if (value[0] == '-' && value.lastIndexOf('-') > 0 || value.includes('safe')) tmp += ';'.concat(key, ':').concat(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)) + } + } // 转换 rpx(rich-text 内部不支持 rpx) + else if (value.includes('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 = true // svg 标签内大小写敏感 +} +/** + * @description 解析到属性名 + * @param {String} name 属性名 + * @private + */ + +parser.prototype.onAttrName = function (name) { + name = this.xml ? name : name.toLowerCase() + + if (name.substr(0, 5) == 'data-') { + // data-src 自动转为 src + if (name == 'data-src' && !this.attrs.src) this.attrName = 'src' // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用 + else if (this.tagName == 'img' || this.tagName == 'a') this.attrName = name // 剩余的移除以减小大小 + else this.attrName = void 0 + } 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 + this.attrs = Object.create(null) + const { attrs } = node + const parent = this.stack[this.stack.length - 1] + const siblings = parent ? parent.children : this.nodes + const close = this.xml ? selfClose : config.voidTags[node.name] // 转换 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 = void 0 + } + + 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 + // 设置 source 标签(仅父节点为 video 或 audio 时有效) + else if (node.name == 'source' && parent && (parent.name == 'video' || parent.name == 'audio') && attrs.src) 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 + break + } // #ifndef H5 || APP-PLUS + + const style = item.attrs.style || '' + + if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || !styleObj.width.includes('%'))) { + 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) // #ifdef H5 || APP-PLUS + + if (this.options.lazyLoad) { + attrs['data-src'] = attrs.src + attrs.src = void 0 + } // #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 = void 0 // 记录是否设置了宽高 + + if (styleObj.width) { + if (styleObj.width.includes('auto')) styleObj.width = ''; else { + node.w = 'T' + if (styleObj.height && !styleObj.height.includes('auto')) 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 += ';'.concat(key, ':').concat(styleObj[key].replace(' !important', '')) + } + + attrs.style = attrs.style.substr(1) || void 0 + } else { + if (node.name == 'pre' || (attrs.style || '').includes('white-space') && attrs.style.includes('pre')) this.pre = node.pre = true + 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: {} + }) + } +} +/** + * @description 处理标签出栈 + * @private + */ + +parser.prototype.popNode = function () { + const node = this.stack.pop() + let { attrs } = node + const { children } = node + 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) { + // 是否合并空白符标识 + node.pre = this.pre = void 0 + + for (let i = this.stack.length; i--;) { + if (this.stack[i].pre) this.pre = true + } + } + + const styleObj = {} // 转换 svg + + if (node.name == 'svg') { + // #ifndef APP-PLUS-NVUE + let src = '' + const { style } = attrs + attrs.style = '' + attrs.xmlns = 'http://www.w3.org/2000/svg'; + + (function traversal(node) { + src += `<${node.name}` + + for (let item in node.attrs) { + const val = node.attrs[item] + + if (val) { + if (item == 'viewbox') item = 'viewBox' + src += ' '.concat(item, '="').concat(val, '"') + } + } + + if (!node.children) src += '/>'; else { + src += '>' + + for (let _i2 = 0; _i2 < node.children.length; _i2++) { + traversal(node.children[_i2]) + } + + src += `</${node.name}>` + } + }(node)) + + node.name = 'img' + node.attrs = { + src: `data:image/svg+xml;utf8,${src.replace(/#/g, '%23')}`, + style, + ignore: 'T' + } + node.children = void 0 // #endif + + this.xml = false + 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 = void 0 + } // 转换 font 标签的属性 + + if (node.name == 'font') { + if (attrs.color) { + styleObj.color = attrs.color + attrs.color = void 0 + } + + if (attrs.face) { + styleObj['font-family'] = attrs.face + attrs.face = void 0 + } + + 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'] = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'][size - 1] + } + + attrs.size = void 0 + } + } // #endif + // 一些编辑器的自带 class + + if ((attrs.class || '').includes('align-center')) styleObj['text-align'] = 'center' + Object.assign(styleObj, this.parseStyle(node)) + + if (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' // 未知标签转为 span,避免无法显示 + else if (!config.trustTags[node.name] && !this.xml) node.name = 'span' + if (node.name == 'a' || node.name == 'ad' // #ifdef H5 || APP-PLUS + || node.name == 'iframe' // #endif + ) this.expose() // #ifdef APP-PLUS + else if (node.name == 'video') { + let str = '<video style="width:100%;height:100%"' // 空白图占位 + + if (!attrs.poster && !attrs.autoplay) attrs.poster = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>" + + for (const item in attrs) { + if (attrs[item]) str += ` ${item}="${attrs[item]}"` + } + + if (this.options.pauseVideo) str += ' onplay="for(var e=document.getElementsByTagName(\'video\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()"' + str += '>' + + for (let _i3 = 0; _i3 < node.src.length; _i3++) { + str += `<source src="${node.src[_i3]}">` + } + + 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 = void 0 + } + + for (let _i4 = children.length; _i4--;) { + if (children[_i4].name == 'li') children[_i4].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) + + if (node.c) { + // padding 和 spacing 默认 2 + if (isNaN(padding)) padding = 2 + if (isNaN(spacing)) spacing = 2 + } + + if (border) attrs.style += `;border:${border}px solid 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 _i5 = 0; _i5 < nodes.length; _i5++) { + if (nodes[_i5].name == 'tr') trList.push(nodes[_i5]); else traversal(nodes[_i5].children || []) + } + }(children)) + + for (let row = 1; row <= trList.length; row++) { + let col = 1 + + for (let j = 0; j < trList[row - 1].children.length; j++, col++) { + const td = trList[row - 1].children[j] + + if (td.name == 'td' || td.name == 'th') { + // 这个格子被上面的单元格占用,则列号++ + while (map[`${row}.${col}`]) { + col++ + } + + let _style2 = td.attrs.style || '' + const start = _style2.indexOf('width') ? _style2.indexOf(';width') : 0 // 提取出 td 的宽度 + + if (start != -1) { + let end = _style2.indexOf(';', start + 6) + + if (end == -1) end = _style2.length + if (!td.attrs.colspan) width[col] = _style2.substring(start ? start + 7 : 6, end) + _style2 = _style2.substr(0, start) + _style2.substr(end) + } + + _style2 += (border ? ';border:'.concat(border, 'px solid gray') + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? ';padding:'.concat(padding, 'px') : '') // 处理列合并 + + if (td.attrs.colspan) { + _style2 += ';grid-column-start:'.concat(col, ';grid-column-end:').concat(col + parseInt(td.attrs.colspan)) + if (!td.attrs.rowspan) _style2 += ';grid-row-start:'.concat(row, ';grid-row-end:').concat(row + 1) + col += parseInt(td.attrs.colspan) - 1 + } // 处理行合并 + + if (td.attrs.rowspan) { + _style2 += ';grid-row-start:'.concat(row, ';grid-row-end:').concat(row + parseInt(td.attrs.rowspan)) + if (!td.attrs.colspan) _style2 += ';grid-column-start:'.concat(col, ';grid-column-end:').concat(col + 1) // 记录下方单元格被占用 + + for (let k = 1; k < td.attrs.rowspan; k++) { + map[`${row + k}.${col}`] = 1 + } + } + + if (_style2) td.attrs.style = _style2 + cells.push(td) + } + } + + if (row == 1) { + let temp = '' + + for (let _i6 = 1; _i6 < col; _i6++) { + temp += `${width[_i6] ? width[_i6] : '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 _i7 = 0; _i7 < nodes.length; _i7++) { + const _td = nodes[_i7] + + if (_td.name == 'th' || _td.name == 'td') { + if (border) _td.attrs.style = 'border:'.concat(border, 'px solid gray;').concat(_td.attrs.style || '') + if (padding) _td.attrs.style = 'padding:'.concat(padding, 'px;').concat(_td.attrs.style || '') + } else if (_td.children) traversal(_td.children) + } + }(children)) + } + } // 给表格添加一个单独的横向滚动层 + + if (this.options.scrollTable && !(attrs.style || '').includes('inline')) { + const table = { ...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 _i8 = this.stack.length; _i8--;) { + if (this.stack[_i8].name == 'table') { + this.stack[_i8].flag = 1 // 指示含有合并单元格 + + break + } + } + } // 转换 ruby + else if (node.name == 'ruby') { + node.name = 'span' + + for (let _i9 = 0; _i9 < children.length - 1; _i9++) { + if (children[_i9].type == 'text' && children[_i9 + 1].name == 'rt') { + children[_i9] = { + name: 'div', + attrs: { + style: 'display:inline-block' + }, + children: [{ + name: 'div', + attrs: { + style: 'font-size:50%;text-align:start' + }, + children: children[_i9 + 1].children + }, children[_i9]] + } + children.splice(_i9 + 1, 1) + } + } + } else if (node.c) { + node.c = 2 + + for (let _i10 = node.children.length; _i10--;) { + if (!node.children[_i10].c || node.children[_i10].name == 'table') node.c = 1 + } + } + if ((styleObj.display || '').includes('flex') && !node.c) { + for (let _i11 = children.length; _i11--;) { + const _item = children[_i11] + + if (_item.f) { + _item.attrs.style = (_item.attrs.style || '') + _item.f + _item.f = void 0 + } + } + } // flex 布局时部分样式需要提取到 rich-text 外层 + + const flex = parent && (parent.attrs.style || '').includes('flex') // #ifdef MP-WEIXIN + // 检查基础库版本 virtualHost 是否可用 + && !(node.c && wx.getNFCAdapter) // #endif + // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO + && !node.c // #endif + + if (flex) node.f = ';max-width:100%' // #endif + + for (const key in styleObj) { + if (styleObj[key]) { + const val = ';'.concat(key, ':').concat(styleObj[key].replace(' !important', '')) // #ifndef APP-PLUS-NVUE + + if (flex && (key.includes('flex') && key != 'flex-direction' || key == 'align-self' || styleObj[key][0] == '-' || key == 'width' && val.includes('%'))) { + node.f += val + if (key == 'width') attrs.style += ';width:100%' + } else // #endif + { attrs.style += val } + } + } + + attrs.style = attrs.style.substr(1) || void 0 +} +/** + * @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 == ' ' && flag) return + text = trim + } + + const node = Object.create(null) + node.type = 'text' + node.text = decodeEntity(text) + + if (this.hook(node)) { + 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 + return this.state = this.endTag + } // 处理注释 + + 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 + return this.state = this.attrVal + } + + if (this.content[this.i] == '=') needVal = true; else { + this.start = this.i + return this.state = this.attrName + } + } + } + } 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++ +} + +module.exports = parser diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-parse/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-parse/props.js new file mode 100644 index 000000000..defd06ca6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-parse/props.js @@ -0,0 +1,45 @@ +export default { + props: { + // #ifdef APP-PLUS-NVUE + bgColor: String, + // #endif + content: String, + copyLink: { + type: Boolean, + default: uni.$u.props.parse.copyLink + }, + domain: String, + errorImg: { + type: String, + default: uni.$u.props.parse.errorImg + }, + lazyLoad: { + type: Boolean, + default: uni.$u.props.parse.lazyLoad + }, + loadingImg: { + type: String, + default: uni.$u.props.parse.loadingImg + }, + pauseVideo: { + type: Boolean, + default: uni.$u.props.parse.pauseVideo + }, + previewImg: { + type: Boolean, + default: uni.$u.props.parse.previewImg + }, + scrollTable: Boolean, + selectable: Boolean, + setTitle: { + type: Boolean, + default: uni.$u.props.parse.setTitle + }, + showImgMenu: { + type: Boolean, + default: uni.$u.props.parse.showImgMenu + }, + tagStyle: Object, + useAnchor: null + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-parse/u-parse.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-parse/u-parse.vue new file mode 100644 index 000000000..7bc8b3d57 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-parse/u-parse.vue @@ -0,0 +1,366 @@ +<template> + <view id="_root" :class="(selectable?'_select ':'')+'_root'"> + <slot v-if="!nodes[0]" /> + <!-- #ifndef APP-PLUS-NVUE --> + <node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu]" /> + <!-- #endif --> + <!-- #ifdef APP-PLUS-NVUE --> + <web-view ref="web" src="/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" /> + <!-- #endif --> + </view> +</template> + +<script> + import props from './props.js'; +/** + * mp-html v2.0.4 + * @description 富文本组件 + * @tutorial https://github.com/jin-yufeng/mp-html + * @property {String} bgColor 背景颜色,只适用与APP-PLUS-NVUE + * @property {String} content 用于渲染的富文本字符串(默认 true ) + * @property {Boolean} copyLink 是否允许外部链接被点击时自动复制 + * @property {String} domain 主域名,用于拼接链接 + * @property {String} errorImg 图片出错时的占位图链接 + * @property {Boolean} lazyLoad 是否开启图片懒加载(默认 true ) + * @property {string} loadingImg 图片加载过程中的占位图链接 + * @property {Boolean} pauseVideo 是否在播放一个视频时自动暂停其它视频(默认 true ) + * @property {Boolean} previewImg 是否允许图片被点击时自动预览(默认 true ) + * @property {Boolean} scrollTable 是否给每个表格添加一个滚动层使其能单独横向滚动 + * @property {Boolean} selectable 是否开启长按复制 + * @property {Boolean} setTitle 是否将 title 标签的内容设置到页面标题(默认 true ) + * @property {Boolean} showImgMenu 是否允许图片被长按时显示菜单(默认 true ) + * @property {Object} tagStyle 标签的默认样式 + * @property {Boolean | Number} useAnchor 是否使用锚点链接 + * + * @event {Function} load dom 结构加载完毕时触发 + * @event {Function} ready 所有图片加载完毕时触发 + * @event {Function} imgTap 图片被点击时触发 + * @event {Function} linkTap 链接被点击时触发 + * @event {Function} error 媒体加载出错时触发 + */ +const plugins=[] +const parser = require('./parser') +// #ifndef APP-PLUS-NVUE +import node from './node/node' +// #endif +// #ifdef APP-PLUS-NVUE +const dom = weex.requireModule('dom') +// #endif +export default { + name: 'mp-html', + data() { + return { + nodes: [], + // #ifdef APP-PLUS-NVUE + height: 0 + // #endif + } + }, + mixins:[props], + // #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') + clearInterval(this._timer) + }, + 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) + return reject('Anchor is disabled') + 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() // 获取 scroll-view 的位置和滚动距离 + else + selector.selectViewport().scrollOffset() // 获取窗口的滚动距离 + selector.exec(res => { + if (!res[0]) + return reject('Label not found') + 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() { + 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' + } + } + })(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('Root label not found')) + }) + }, + + /** + * @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') + }) + + // 等待图片加载完毕 + let height + clearInterval(this._timer) + this._timer = setInterval(() => { + this.getRect().then(rect => { + // 350ms 总高度无变化就触发 ready 事件 + if (rect.height == height) { + this.$emit('ready', rect) + clearInterval(this._timer) + } + height = rect.height + }).catch(() => { }) + }, 350) + // #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) + ',' + JSON.stringify([this.bgColor, 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(() => { }) + 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() { + wx.switchTab({ + url: href + }) + } + }) + } + 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('Label not found') + break + // 点击 + case 'onClick': + this.$emit('tap') + break + // 出错 + case 'onError': + this.$emit('error', { + source: message.source, + attrs: message.attrs + }) + } + } + // #endif + } +} +</script> + +<style> +/* #ifndef APP-PLUS-NVUE */ +/* 根节点样式 */ +._root { + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +/* 长按复制 */ +._select { + user-select: text; +} +/* #endif */ +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-picker-column/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-picker-column/props.js new file mode 100644 index 000000000..7c1133117 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-picker-column/props.js @@ -0,0 +1,5 @@ +export default { + props: { + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-picker-column/u-picker-column.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-picker-column/u-picker-column.vue new file mode 100644 index 000000000..53553f34b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-picker-column/u-picker-column.vue @@ -0,0 +1,27 @@ +<template> + <picker-view-column> + <view class="u-picker-column"> + + </view> + </picker-view-column> +</template> + +<script> + import props from './props.js'; + /** + * PickerColumn + * @description + * @tutorial url + * @property {String} + * @event {Function} + * @example + */ + export default { + name: 'u-picker-column', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-picker/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-picker/props.js new file mode 100644 index 000000000..ca0d0f9ab --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-picker/props.js @@ -0,0 +1,84 @@ +export default { + props: { + // 是否展示picker弹窗 + show: { + type: Boolean, + default: uni.$u.props.picker.show + }, + // 是否展示顶部的操作栏 + showToolbar: { + type: Boolean, + default: uni.$u.props.picker.showToolbar + }, + // 顶部标题 + title: { + type: String, + default: uni.$u.props.picker.title + }, + // 对象数组,设置每一列的数据 + columns: { + type: Array, + default: uni.$u.props.picker.columns + }, + // 是否显示加载中状态 + loading: { + type: Boolean, + default: uni.$u.props.picker.loading + }, + // 各列中,单个选项的高度 + itemHeight: { + type: [String, Number], + default: uni.$u.props.picker.itemHeight + }, + // 取消按钮的文字 + cancelText: { + type: String, + default: uni.$u.props.picker.cancelText + }, + // 确认按钮的文字 + confirmText: { + type: String, + default: uni.$u.props.picker.confirmText + }, + // 取消按钮的颜色 + cancelColor: { + type: String, + default: uni.$u.props.picker.cancelColor + }, + // 确认按钮的颜色 + confirmColor: { + type: String, + default: uni.$u.props.picker.confirmColor + }, + // 选择器只有一列时,默认选中项的索引,从0开始 + singleIndex: { + type: [String, Number], + default: uni.$u.props.picker.singleIndex + }, + // 每列中可见选项的数量 + visibleItemCount: { + type: [String, Number], + default: uni.$u.props.picker.visibleItemCount + }, + // 选项对象中,需要展示的属性键名 + keyName: { + type: String, + default: uni.$u.props.picker.keyName + }, + // 是否允许点击遮罩关闭选择器 + closeOnClickOverlay: { + type: Boolean, + default: uni.$u.props.picker.closeOnClickOverlay + }, + // 各列的默认索引 + defaultIndex: { + type: Array, + default: uni.$u.props.picker.defaultIndex + }, + // 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件,只在微信2.21.1及以上有效 + immediateChange: { + type: Boolean, + default: uni.$u.props.picker.immediateChange + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-picker/u-picker.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-picker/u-picker.vue new file mode 100644 index 000000000..0fbe22cda --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-picker/u-picker.vue @@ -0,0 +1,284 @@ +<template> + <u-popup + :show="show" + @close="closeHandler" + > + <view class="u-picker"> + <u-toolbar + v-if="showToolbar" + :cancelColor="cancelColor" + :confirmColor="confirmColor" + :cancelText="cancelText" + :confirmText="confirmText" + :title="title" + @cancel="cancel" + @confirm="confirm" + ></u-toolbar> + <picker-view + class="u-picker__view" + :indicatorStyle="`height: ${$u.addUnit(itemHeight)}`" + :value="innerIndex" + :immediateChange="immediateChange" + :style="{ + height: `${$u.addUnit(visibleItemCount * itemHeight)}` + }" + @change="changeHandler" + > + <picker-view-column + v-for="(item, index) in innerColumns" + :key="index" + class="u-picker__view__column" + > + <text + v-if="$u.test.array(item)" + class="u-picker__view__column__item u-line-1" + v-for="(item1, index1) in item" + :key="index1" + :style="{ + height: $u.addUnit(itemHeight), + lineHeight: $u.addUnit(itemHeight), + fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal' + }" + >{{ getItemText(item1) }}</text> + </picker-view-column> + </picker-view> + <view + v-if="loading" + class="u-picker--loading" + > + <u-loading-icon mode="circle"></u-loading-icon> + </view> + </view> + </u-popup> +</template> + +<script> +/** + * u-picker + * @description 选择器 + * @property {Boolean} show 是否显示picker弹窗(默认 false ) + * @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 {Array} singleIndex 选择器只有一列时,默认选中项的索引,从0开始(默认 0 ) + * @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 props from './props.js'; +export default { + name: 'u-picker', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 上一次选择的列索引 + lastIndex: [], + // 索引值 ,对应picker-view的value + innerIndex: [], + // 各列的值 + innerColumns: [], + // 上一次的变化列索引 + columnIndex: 0, + } + }, + watch: { + // 监听默认索引的变化,重新设置对应的值 + defaultIndex: { + immediate: true, + handler(n) { + this.setIndexs(n, true) + } + }, + // 监听columns参数的变化 + columns: { + immediate: true, + handler(n) { + this.setColumns(n) + } + }, + }, + methods: { + // 获取item需要显示的文字,判别为对象还是文本 + getItemText(item) { + if (uni.$u.test.object(item)) { + return item[this.keyName] + } else { + return item + } + }, + // 关闭选择器 + closeHandler() { + if (this.closeOnClickOverlay) { + this.$emit('close') + } + }, + // 点击工具栏的取消按钮 + cancel() { + this.$emit('cancel') + }, + // 点击工具栏的确定按钮 + confirm() { + this.$emit('confirm', { + indexs: this.innerIndex, + value: this.innerColumns.map((item, index) => item[this.innerIndex[index]]), + values: this.innerColumns + }) + }, + // 选择器某一列的数据发生变化时触发 + 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', { + // #ifndef MP-WEIXIN + // 微信小程序不能传递this,会因为循环引用而报错 + picker: this, + // #endif + value: this.innerColumns.map((item, index) => item[value[index]]), + index, + indexs: value, + // values为当前变化列的数组内容 + values, + columnIndex + }) + }, + // 设置index索引,此方法可被外部调用设置 + setIndexs(index, setLastIndex) { + this.innerIndex = uni.$u.deepClone(index) + if (setLastIndex) { + this.setLastIndex(index) + } + }, + // 记录上一次的各列索引位置 + setLastIndex(index) { + // 当能进入此方法,意味着当前设置的各列默认索引,即为“上一次”的选中值,需要记录,是因为changeHandler中 + // 需要拿前后的变化值进行对比,得出当前发生改变的是哪一列 + this.lastIndex = uni.$u.deepClone(index) + }, + // 设置对应列选项的所有值 + setColumnValues(columnIndex, values) { + // 替换innerColumns数组中columnIndex索引的值为values,使用的是数组的splice方法 + this.innerColumns.splice(columnIndex, 1, values) + // 拷贝一份原有的innerIndex做临时变量,将大于当前变化列的所有的列的默认索引设置为0 + let tmpIndex = uni.$u.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 uni.$u.sleep() + })() + return this.innerColumns[columnIndex] + }, + // 设置整体各列的columns的值 + setColumns(columns) { + this.innerColumns = uni.$u.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 uni.$u.sleep() + })() + return this.innerColumns.map((item, index) => item[this.innerIndex[index]]) + } + }, +} +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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: $u-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/yudao-ui-app/uni_modules/uview-ui/components/u-popup/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-popup/props.js new file mode 100644 index 000000000..d9fe95281 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-popup/props.js @@ -0,0 +1,79 @@ +export default { + props: { + // 是否展示弹窗 + show: { + type: Boolean, + default: uni.$u.props.popup.show + }, + // 是否显示遮罩 + overlay: { + type: Boolean, + default: uni.$u.props.popup.overlay + }, + // 弹出的方向,可选值为 top bottom right left center + mode: { + type: String, + default: uni.$u.props.popup.mode + }, + // 动画时长,单位ms + duration: { + type: [String, Number], + default: uni.$u.props.popup.duration + }, + // 是否显示关闭图标 + closeable: { + type: Boolean, + default: uni.$u.props.popup.closeable + }, + // 自定义遮罩的样式 + overlayStyle: { + type: [Object, String], + default: uni.$u.props.popup.overlayStyle + }, + // 点击遮罩是否关闭弹窗 + closeOnClickOverlay: { + type: Boolean, + default: uni.$u.props.popup.closeOnClickOverlay + }, + // 层级 + zIndex: { + type: [String, Number], + default: uni.$u.props.popup.zIndex + }, + // 是否为iPhoneX留出底部安全距离 + safeAreaInsetBottom: { + type: Boolean, + default: uni.$u.props.popup.safeAreaInsetBottom + }, + // 是否留出顶部安全距离(状态栏高度) + safeAreaInsetTop: { + type: Boolean, + default: uni.$u.props.popup.safeAreaInsetTop + }, + // 自定义关闭图标位置,top-left为左上角,top-right为右上角,bottom-left为左下角,bottom-right为右下角 + closeIconPos: { + type: String, + default: uni.$u.props.popup.closeIconPos + }, + // 是否显示圆角 + round: { + type: [Boolean, String, Number], + default: uni.$u.props.popup.round + }, + // mode=center,也即中部弹出时,是否使用缩放模式 + zoom: { + type: Boolean, + default: uni.$u.props.popup.zoom + }, + // 弹窗背景色,设置为transparent可去除白色背景 + bgColor: { + type: String, + default: uni.$u.props.popup.bgColor + }, + // 遮罩的透明度,0-1之间 + overlayOpacity: { + type: [Number, String], + default: uni.$u.props.popup.overlayOpacity + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-popup/u-popup.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-popup/u-popup.vue new file mode 100644 index 000000000..2ca51cc85 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-popup/u-popup.vue @@ -0,0 +1,304 @@ +<template> + <view class="u-popup"> + <u-overlay + :show="show" + @click="overlayClick" + v-if="overlay" + :duration="overlayDuration" + :customStyle="overlayStyle" + :opacity="overlayOpacity" + ></u-overlay> + <u-transition + :show="show" + :customStyle="transitionStyle" + :mode="position" + :duration="duration" + @afterEnter="afterEnter" + @click="clickHandler" + > + <view + class="u-popup__content" + :style="[contentStyle]" + @tap.stop="noop" + > + <u-status-bar v-if="safeAreaInsetTop"></u-status-bar> + <slot></slot> + <view + v-if="closeable" + @tap.stop="close" + class="u-popup__content__close" + :class="['u-popup__content__close--' + closeIconPos]" + hover-class="u-popup__content__close--hover" + hover-stay-time="150" + > + <u-icon + name="close" + color="#909399" + size="18" + bold + ></u-icon> + </view> + <u-safe-bottom v-if="safeAreaInsetBottom"></u-safe-bottom> + </view> + </u-transition> + </view> +</template> + +<script> + import props from './props.js'; + + /** + * popup 弹窗 + * @description 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义 + * @tutorial https://www.uviewui.com/components/popup.html + * @property {Boolean} show 是否展示弹窗 (默认 false ) + * @property {Boolean} overlay 是否显示遮罩 (默认 true ) + * @property {String} mode 弹出方向(默认 'bottom' ) + * @property {String | Number} duration 动画时长,单位ms (默认 300 ) + * @property {String | Number} overlayDuration 遮罩层动画时长,单位ms (默认 350 ) + * @property {Boolean} closeable 是否显示关闭图标(默认 false ) + * @property {Object | String} overlayStyle 自定义遮罩的样式 + * @property {String | Number} overlayOpacity 遮罩透明度,0-1之间(默认 0.5) + * @property {Boolean} closeOnClickOverlay 点击遮罩是否关闭弹窗 (默认 true ) + * @property {String | Number} zIndex 层级 (默认 10075 ) + * @property {Boolean} safeAreaInsetBottom 是否为iPhoneX留出底部安全距离 (默认 true ) + * @property {Boolean} safeAreaInsetTop 是否留出顶部安全距离(状态栏高度) (默认 false ) + * @property {String} closeIconPos 自定义关闭图标位置(默认 'top-right' ) + * @property {String | Number} round 圆角值(默认 0) + * @property {Boolean} zoom 当mode=center时 是否开启缩放(默认 true ) + * @property {Object} customStyle 组件的样式,对象形式 + * @event {Function} open 弹出层打开 + * @event {Function} close 弹出层收起 + * @example <u-popup v-model="show"><text>出淤泥而不染,濯清涟而不妖</text></u-popup> + */ + export default { + name: 'u-popup', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + overlayDuration: this.duration + 50 + } + }, + watch: { + show(newValue, oldValue) { + if (newValue === true) { + // #ifdef MP-WEIXIN + const children = this.$children + this.retryComputedComponentRect(children) + // #endif + } + } + }, + computed: { + transitionStyle() { + const style = { + zIndex: this.zIndex, + position: 'fixed', + display: 'flex', + } + style[this.mode] = 0 + if (this.mode === 'left') { + return uni.$u.deepMerge(style, { + bottom: 0, + top: 0, + }) + } else if (this.mode === 'right') { + return uni.$u.deepMerge(style, { + bottom: 0, + top: 0, + }) + } else if (this.mode === 'top') { + return uni.$u.deepMerge(style, { + left: 0, + right: 0 + }) + } else if (this.mode === 'bottom') { + return uni.$u.deepMerge(style, { + left: 0, + right: 0, + }) + } else if (this.mode === 'center') { + return uni.$u.deepMerge(style, { + alignItems: 'center', + 'justify-content': 'center', + top: 0, + left: 0, + right: 0, + bottom: 0 + }) + } + }, + contentStyle() { + const style = {} + // 通过设备信息的safeAreaInsets值来判断是否需要预留顶部状态栏和底部安全局的位置 + // 不使用css方案,是因为nvue不支持css的iPhoneX安全区查询属性 + const { + safeAreaInsets + } = uni.$u.sys() + if (this.mode !== 'center') { + style.flex = 1 + } + // 背景色,一般用于设置为transparent,去除默认的白色背景 + if (this.bgColor) { + style.backgroundColor = this.bgColor + } + if(this.round) { + const value = uni.$u.addUnit(this.round) + if(this.mode === 'top') { + style.borderBottomLeftRadius = value + style.borderBottomRightRadius = value + } else if(this.mode === 'bottom') { + style.borderTopLeftRadius = value + style.borderTopRightRadius = value + } else if(this.mode === 'center') { + style.borderRadius = value + } + } + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + }, + position() { + if (this.mode === 'center') { + return this.zoom ? 'fade-zoom' : 'fade' + } + if (this.mode === 'left') { + return 'slide-left' + } + if (this.mode === 'right') { + return 'slide-right' + } + if (this.mode === 'bottom') { + return 'slide-up' + } + if (this.mode === 'top') { + return 'slide-down' + } + }, + }, + methods: { + // 点击遮罩 + overlayClick() { + if (this.closeOnClickOverlay) { + this.$emit('close') + } + }, + close(e) { + this.$emit('close') + }, + afterEnter() { + this.$emit('open') + }, + clickHandler() { + // 由于中部弹出时,其u-transition占据了整个页面相当于遮罩,此时需要发出遮罩点击事件,是否无法通过点击遮罩关闭弹窗 + if(this.mode === 'center') { + this.overlayClick() + } + this.$emit('click') + }, + // #ifdef MP-WEIXIN + retryComputedComponentRect(children) { + // 组件内部需要计算节点的组件 + const names = ['u-calendar-month', 'u-album', 'u-collapse-item', 'u-dropdown', 'u-index-item', 'u-index-list', + 'u-line-progress', 'u-list-item', 'u-rate', 'u-read-more', 'u-row', 'u-row-notice', 'u-scroll-list', + 'u-skeleton', 'u-slider', 'u-steps-item', 'u-sticky', 'u-subsection', 'u-swipe-action-item', 'u-tabbar', + 'u-tabs', 'u-tooltip' + ] + // 历遍所有的子组件节点 + for (let i = 0; i < children.length; i++) { + const child = children[i] + // 拿到子组件的子组件 + const grandChild = child.$children + // 判断如果在需要重新初始化的组件数组中名中,并且存在init方法的话,则执行 + if (names.includes(child.$options.name) && typeof child?.init === 'function') { + // 需要进行一定的延时,因为初始化页面需要时间 + uni.$u.sleep(50).then(() => { + child.init() + }) + } + // 如果子组件还有孙组件,进行递归历遍 + if (grandChild.length) { + this.retryComputedComponentRect(grandChild) + } + } + } + // #endif + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-popup-flex:1 !default; + $u-popup-content-background-color: #fff !default; + + .u-popup { + flex: $u-popup-flex; + + &__content { + background-color: $u-popup-content-background-color; + position: relative; + + &--round-top { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + } + + &--round-left { + border-top-left-radius: 0; + border-top-right-radius: 10px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 10px; + } + + &--round-right { + border-top-left-radius: 10px; + border-top-right-radius: 0; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 0; + } + + &--round-bottom { + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + &--round-center { + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + } + + &__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; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-radio-group/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-radio-group/props.js new file mode 100644 index 000000000..bb86cba95 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-radio-group/props.js @@ -0,0 +1,85 @@ +export default { + props: { + // 绑定的值 + value: { + type: [String, Number, Boolean], + default: uni.$u.props.radioGroup.value + }, + + // 是否禁用全部radio + disabled: { + type: Boolean, + default: uni.$u.props.radioGroup.disabled + }, + // 形状,circle-圆形,square-方形 + shape: { + type: String, + default: uni.$u.props.radioGroup.shape + }, + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 + activeColor: { + type: String, + default: uni.$u.props.radioGroup.activeColor + }, + // 未选中的颜色 + inactiveColor: { + type: String, + default: uni.$u.props.radioGroup.inactiveColor + }, + // 标识符 + name: { + type: String, + default: uni.$u.props.radioGroup.name + }, + // 整个组件的尺寸,默认px + size: { + type: [String, Number], + default: uni.$u.props.radioGroup.size + }, + // 布局方式,row-横向,column-纵向 + placement: { + type: String, + default: uni.$u.props.radioGroup.placement + }, + // label的文本 + label: { + type: [String], + default: uni.$u.props.radioGroup.label + }, + // label的颜色 (默认 '#303133' ) + labelColor: { + type: [String], + default: uni.$u.props.radioGroup.labelColor + }, + // label的字体大小,px单位 + labelSize: { + type: [String, Number], + default: uni.$u.props.radioGroup.labelSize + }, + // 是否禁止点击文本操作checkbox(默认 false ) + labelDisabled: { + type: Boolean, + default: uni.$u.props.radioGroup.labelDisabled + }, + // 图标颜色 + iconColor: { + type: String, + default: uni.$u.props.radioGroup.iconColor + }, + // 图标的大小,单位px + iconSize: { + type: [String, Number], + default: uni.$u.props.radioGroup.iconSize + }, + // 竖向配列时,是否显示下划线 + borderBottom: { + type: Boolean, + default: uni.$u.props.radioGroup.borderBottom + }, + // 图标与文字的对齐方式 + iconPlacement: { + type: String, + default: uni.$u.props.radio.iconPlacement + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-radio-group/u-radio-group.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-radio-group/u-radio-group.vue new file mode 100644 index 000000000..0d3c9b5a4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-radio-group/u-radio-group.vue @@ -0,0 +1,108 @@ +<template> + <view + class="u-radio-group" + :class="bemClass" + > + <slot></slot> + </view> +</template> + +<script> + import props from './props.js'; + + /** + * radioRroup 单选框父组件 + * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配u-radio使用 + * @tutorial https://www.uviewui.com/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 <u-radio-group v-model="value"></u-radio-group> + */ + export default { + name: 'u-radio-group', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + computed: { + // 这里computed的变量,都是子组件u-radio需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化 + // 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-radio-group) + // 拉取父组件新的变化后的参数 + parentData() { + return [this.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 => { + // 判断子组件(u-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双向绑定的值 + this.$emit('input', name) + // 发出事件 + this.$emit('change', name) + }, + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-radio-group { + flex: 1; + + &--row { + @include flex; + } + + &--column { + @include flex(column); + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-radio/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-radio/props.js new file mode 100644 index 000000000..3ec5f6bb8 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-radio/props.js @@ -0,0 +1,64 @@ +export default { + props: { + // radio的名称 + name: { + type: [String, Number, Boolean], + default: uni.$u.props.radio.name + }, + // 形状,square为方形,circle为圆型 + shape: { + type: String, + default: uni.$u.props.radio.shape + }, + // 是否禁用 + disabled: { + type: [String, Boolean], + default: uni.$u.props.radio.disabled + }, + // 是否禁止点击提示语选中单选框 + labelDisabled: { + type: [String, Boolean], + default: uni.$u.props.radio.labelDisabled + }, + // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 + activeColor: { + type: String, + default: uni.$u.props.radio.activeColor + }, + // 未选中的颜色 + inactiveColor: { + type: String, + default: uni.$u.props.radio.inactiveColor + }, + // 图标的大小,单位px + iconSize: { + type: [String, Number], + default: uni.$u.props.radio.iconSize + }, + // label的字体大小,px单位 + labelSize: { + type: [String, Number], + default: uni.$u.props.radio.labelSize + }, + // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式 + label: { + type: [String, Number], + default: uni.$u.props.radio.label + }, + // 整体的大小 + size: { + type: [String, Number], + default: uni.$u.props.radio.size + }, + // 图标颜色 + color: { + type: String, + default: uni.$u.props.radio.color + }, + // label的颜色 + labelColor: { + type: String, + default: uni.$u.props.radio.labelColor + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-radio/u-radio.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-radio/u-radio.vue new file mode 100644 index 000000000..713354263 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-radio/u-radio.vue @@ -0,0 +1,337 @@ +<template> + <view + class="u-radio" + @tap.stop="wrapperClickHandler" + :style="[radioStyle]" + :class="[`u-radio-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']" + > + <view + class="u-radio__icon-wrap" + @tap.stop="iconClickHandler" + :class="iconClasses" + :style="[iconWrapStyle]" + > + <slot name="icon"> + <u-icon + class="u-radio__icon-wrap__icon" + name="checkbox-mark" + :size="elIconSize" + :color="elIconColor" + /> + </slot> + </view> + <text + class="u-radio__text" + @tap.stop="labelClickHandler" + :style="{ + color: elDisabled ? elInactiveColor : elLabelColor, + fontSize: elLabelSize, + lineHeight: elLabelSize + }" + >{{label}}</text> + </view> +</template> + +<script> + import props from './props.js'; + /** + * radio 单选框 + * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配u-radio-group使用 + * @tutorial https://www.uviewui.com/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 <u-radio :labelDisabled="false">门掩黄昏,无计留春住</u-radio> + */ + export default { + name: "u-radio", + + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + checked: false, + // 当你看到这段代码的时候, + // 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式 + // 故只能使用如此方法 + parentData: { + iconSize: 12, + labelDisabled: null, + disabled: null, + shape: null, + activeColor: null, + inactiveColor: null, + size: 18, + value: null, + iconColor: null, + placement: 'row', + borderBottom: false, + iconPlacement: 'left' + } + } + }, + computed: { + // 是否禁用,如果父组件u-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 uni.$u.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('u-radio__icon-wrap--' + this.elShape) + if (this.elDisabled) { + classes.push('u-radio__icon-wrap--disabled') + } + if (this.checked && this.elDisabled) { + classes.push('u-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 = uni.$u.addUnit(this.elSize) + style.height = uni.$u.addUnit(this.elSize) + // 如果是图标在右边的话,移除它的右边距 + if (this.parentData.iconPlacement === 'right') { + style.marginRight = 0 + } + return style + }, + radioStyle() { + const style = {} + if(this.parentData.borderBottom && this.parentData.placement === 'row') { + uni.$u.error('检测到您将borderBottom设置为true,需要同时将u-radio-group的placement设置为column才有效') + } + // 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔 + if(this.parentData.borderBottom && this.parentData.placement === 'column') { + // ios像素密度高,需要多一点的距离 + style.paddingBottom = uni.$u.os() === 'ios' ? '12px' : '8px' + } + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + mounted() { + this.init() + }, + methods: { + init() { + // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用 + this.updateParentData() + if (!this.parent) { + uni.$u.error('u-radio必须搭配u-radio-group组件使用') + } + // 设置初始化时,是否默认选中的状态 + this.checked = this.name === this.parentData.value + }, + updateParentData() { + this.getParentData('u-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() { + // u-radio的checked不为true时(意味着未选中),才发出事件,避免多次点击触发事件 + if (!this.checked) { + this.$emit('change', this.name) + // 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时 + this.$nextTick(() => { + uni.$u.formValidate(this, 'change') + }) + } + }, + // 改变组件选中状态 + // 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-radio实例 + // 将本组件外的其他u-radio的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态 + setRadioCheckedStatus() { + this.emitEvent() + // 将本组件标记为选中状态 + this.checked = true + typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this) + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + $u-radio-wrap-margin-right:6px !default; + $u-radio-wrap-font-size:20px !default; + $u-radio-wrap-border-width:1px !default; + $u-radio-wrap-border-color: #c8c9cc !default; + $u-radio-line-height:0 !default; + $u-radio-circle-border-radius:100% !default; + $u-radio-square-border-radius:3px !default; + $u-radio-checked-color:#fff !default; + $u-radio-checked-background-color:red !default; + $u-radio-checked-border-color: #2979ff !default; + $u-radio-disabled-background-color:#ebedf0 !default; + $u-radio-disabled--checked-color:#c8c9cc !default; + $u-radio-label-margin-left: 5px !default; + $u-radio-label-margin-right:12px !default; + $u-radio-label-color:$u-content-color !default; + $u-radio-label-font-size:15px !default; + $u-radio-label-disabled-color:#c8c9cc !default; + + .u-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: $u-content-color; + @include flex; + align-items: center; + justify-content: center; + color: transparent; + text-align: center; + margin-right: $u-radio-wrap-margin-right; + font-size: $u-radio-wrap-font-size; + border-width: $u-radio-wrap-border-width; + border-color: $u-radio-wrap-border-color; + border-style: solid; + + /* #ifdef MP-TOUTIAO */ + // 头条小程序兼容性问题,需要设置行高为0,否则图标偏下 + &__icon { + line-height: $u-radio-line-height; + } + + /* #endif */ + + &--circle { + border-radius: $u-radio-circle-border-radius; + } + + &--square { + border-radius: $u-radio-square-border-radius; + } + + &--checked { + color: $u-radio-checked-color; + background-color: $u-radio-checked-background-color; + border-color: $u-radio-checked-border-color; + } + + &--disabled { + background-color: $u-radio-disabled-background-color !important; + } + + &--disabled--checked { + color: $u-radio-disabled--checked-color !important; + } + } + + &__label { + /* #ifndef APP-NVUE */ + word-wrap: break-word; + /* #endif */ + margin-left: $u-radio-label-margin-left; + margin-right: $u-radio-label-margin-right; + color: $u-radio-label-color; + font-size: $u-radio-label-font-size; + + &--disabled { + color: $u-radio-label-disabled-color; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-rate/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-rate/props.js new file mode 100644 index 000000000..2a5635057 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-rate/props.js @@ -0,0 +1,69 @@ +export default { + props: { + // 用于v-model双向绑定选中的星星数量 + value: { + type: [String, Number], + default: uni.$u.props.rate.value + }, + // 要显示的星星数量 + count: { + type: [String, Number], + default: uni.$u.props.rate.count + }, + // 是否不可选中 + disabled: { + type: Boolean, + default: uni.$u.props.rate.disabled + }, + // 是否只读 + readonly: { + type: Boolean, + default: uni.$u.props.rate.readonly + }, + // 星星的大小,单位px + size: { + type: [String, Number], + default: uni.$u.props.rate.size + }, + // 未选中时的颜色 + inactiveColor: { + type: String, + default: uni.$u.props.rate.inactiveColor + }, + // 选中的颜色 + activeColor: { + type: String, + default: uni.$u.props.rate.activeColor + }, + // 星星之间的间距,单位px + gutter: { + type: [String, Number], + default: uni.$u.props.rate.gutter + }, + // 最少能选择的星星个数 + minCount: { + type: [String, Number], + default: uni.$u.props.rate.minCount + }, + // 是否允许半星 + allowHalf: { + type: Boolean, + default: uni.$u.props.rate.allowHalf + }, + // 选中时的图标(星星) + activeIcon: { + type: String, + default: uni.$u.props.rate.activeIcon + }, + // 未选中时的图标(星星) + inactiveIcon: { + type: String, + default: uni.$u.props.rate.inactiveIcon + }, + // 是否可以通过滑动手势选择评分 + touchable: { + type: Boolean, + default: uni.$u.props.rate.touchable + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-rate/u-rate.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-rate/u-rate.vue new file mode 100644 index 000000000..5992038a9 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-rate/u-rate.vue @@ -0,0 +1,304 @@ +<template> + <view + class="u-rate" + :id="elId" + ref="u-rate" + :style="[$u.addStyle(customStyle)]" + > + <view + class="u-rate__content" + @touchmove.stop="touchMove" + @touchend.stop="touchEnd" + > + <view + class="u-rate__content__item" + v-for="(item, index) in Number(count)" + :key="index" + :class="[elClass]" + > + <view + class="u-rate__content__item__icon-wrap" + ref="u-rate__content__item__icon-wrap" + @tap.stop="clickHandler($event, index + 1)" + > + <u-icon + :name=" + Math.floor(activeIndex) > index + ? activeIcon + : inactiveIcon + " + :color=" + disabled + ? '#c8c9cc' + : Math.floor(activeIndex) > index + ? activeColor + : inactiveColor + " + :custom-style="{ + padding: `0 ${$u.addUnit(gutter / 2)}`, + }" + :size="size" + ></u-icon> + </view> + <view + v-if="allowHalf" + @tap.stop="clickHandler($event, index + 1)" + class="u-rate__content__item__icon-wrap u-rate__content__item__icon-wrap--half" + :style="[{ + width: $u.addUnit(rateWidth / 2), + }]" + ref="u-rate__content__item__icon-wrap" + > + <u-icon + :name=" + Math.ceil(activeIndex) > index + ? activeIcon + : inactiveIcon + " + :color=" + disabled + ? '#c8c9cc' + : Math.ceil(activeIndex) > index + ? activeColor + : inactiveColor + " + :custom-style="{ + padding: `0 ${$u.addUnit(gutter / 2)}` + }" + :size="size" + ></u-icon> + </view> + </view> + </view> + </view> +</template> + +<script> + import props from './props.js'; + + // #ifdef APP-NVUE + const dom = weex.requireModule("dom"); + // #endif + /** + * rate 评分 + * @description 该组件一般用于满意度调查,星型评分的场景 + * @tutorial https://www.uviewui.com/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 选中时的图标名,只能为uView的内置图标 (默认 'star-fill' ) + * @property {String} inactiveIcon 未选中时的图标名,只能为uView的内置图标 (默认 'star' ) + * @property {Boolean} touchable 是否可以通过滑动手势选择评分 (默认 'true' ) + * @property {Object} customStyle 组件的样式,对象形式 + * @event {Function} change 选中的星星发生变化时触发 + * @example <u-rate :count="count" :value="2"></u-rate> + */ + export default { + name: "u-rate", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + // 生成一个唯一id,否则一个页面多个评分组件,会造成冲突 + elId: uni.$u.guid(), + elClass: uni.$u.guid(), + rateBoxLeft: 0, // 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离 + activeIndex: this.value, + rateWidth: 0, // 每个星星的宽度 + // 标识是否正在滑动,由于iOS事件上touch比click先触发,导致快速滑动结束后,接着触发click,导致事件混乱而出错 + moving: false, + }; + }, + watch: { + value(val) { + this.activeIndex = val; + }, + activeIndex: 'emitEvent' + }, + methods: { + init() { + uni.$u.sleep().then(() => { + this.getRateItemRect(); + this.getRateIconWrapRect(); + }) + }, + // 获取评分组件盒子的布局信息 + async getRateItemRect() { + await uni.$u.sleep(); + // uView封装的获取节点的方法,详见文档 + // #ifndef APP-NVUE + this.$uGetRect("#" + this.elId).then((res) => { + this.rateBoxLeft = res.left; + }); + // #endif + // #ifdef APP-NVUE + dom.getComponentRect(this.$refs["u-rate"], (res) => { + this.rateBoxLeft = res.size.left; + }); + // #endif + }, + // 获取单个星星的尺寸 + getRateIconWrapRect() { + // uView封装的获取节点的方法,详见文档 + // #ifndef APP-NVUE + this.$uGetRect("." + this.elClass).then((res) => { + this.rateWidth = res.width; + }); + // #endif + // #ifdef APP-NVUE + dom.getComponentRect( + this.$refs["u-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[0].pageX; + this.getActiveIndex(x); + }, + // 停止滑动 + touchEnd(e) { + // 如果禁止通过手动滑动选择,返回 + if (!this.touchable) { + return; + } + this.preventEvent(e); + const x = e.changedTouches[0].pageX; + this.getActiveIndex(x); + }, + // 通过点击,直接选中 + clickHandler(e, index) { + // ios上,moving状态取消事件触发 + if (uni.$u.os() === "ios" && this.moving) { + return; + } + this.preventEvent(e); + let x = 0; + // 点击时,在nvue上,无法获得点击的坐标,所以无法实现点击半星选择 + // #ifndef APP-NVUE + x = e.changedTouches[0].pageX; + // #endif + // #ifdef APP-NVUE + // nvue下,无法通过点击获得坐标信息,这里通过元素的位置尺寸值模拟坐标 + x = index * this.rateWidth + this.rateBoxLeft; + // #endif + this.getActiveIndex(x,true); + }, + // 发出事件 + emitEvent() { + // 发出change事件 + this.$emit("change", this.activeIndex); + // 同时修改双向绑定的value的值 + this.$emit("input", this.activeIndex); + }, + // 获取当前激活的评分图标 + getActiveIndex(x,isClick = false) { + if (this.disabled || this.readonly) { + return; + } + // 判断当前操作的点的x坐标值,是否在允许的边界范围内 + const allRateWidth = this.rateWidth * this.count + this.rateBoxLeft; + // 如果小于第一个图标的左边界,设置为最小值,如果大于所有图标的宽度,则设置为最大值 + x = uni.$u.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; + } + + // 设置延时为了让click事件在touchmove之前触发 + setTimeout(() => { + this.moving = true; + }, 10); + // 一定时间后,取消标识为移动中状态,是为了让click事件无效 + setTimeout(() => { + this.moving = false; + }, 10); + }, + }, + mounted() { + this.init(); + }, + }; +</script> + +<style lang="scss" scoped> +@import "../../libs/css/components.scss"; +$u-rate-margin: 0 !default; +$u-rate-padding: 0 !default; +$u-rate-item-icon-wrap-half-top: 0 !default; +$u-rate-item-icon-wrap-half-left: 0 !default; + +.u-rate { + @include flex; + align-items: center; + margin: $u-rate-margin; + padding: $u-rate-padding; + /* #ifndef APP-NVUE */ + touch-action: none; + /* #endif */ + + &__content { + @include flex; + + &__item { + position: relative; + + &__icon-wrap { + &--half { + position: absolute; + overflow: hidden; + top: $u-rate-item-icon-wrap-half-top; + left: $u-rate-item-icon-wrap-half-left; + } + } + } + } +} + +.u-icon { + /* #ifndef APP-NVUE */ + box-sizing: border-box; + /* #endif */ +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-read-more/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-read-more/props.js new file mode 100644 index 000000000..b444e74bc --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-read-more/props.js @@ -0,0 +1,61 @@ +export default { + props: { + // 默认的显示占位高度 + showHeight: { + type: [String, Number], + default: uni.$u.props.readMore.showHeight + }, + // 展开后是否显示"收起"按钮 + toggle: { + type: Boolean, + default: uni.$u.props.readMore.toggle + }, + // 关闭时的提示文字 + closeText: { + type: String, + default: uni.$u.props.readMore.closeText + }, + // 展开时的提示文字 + openText: { + type: String, + default: uni.$u.props.readMore.openText + }, + // 提示的文字颜色 + color: { + type: String, + default: uni.$u.props.readMore.color + }, + // 提示文字的大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.readMore.fontSize + }, + // 是否显示阴影 + // 此参数不能写在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: uni.$u.props.readMore.textIndent + }, + // open和close事件时,将此参数返回在回调参数中 + name: { + type: [String, Number], + default: uni.$u.props.readMore.name + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-read-more/u-read-more.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-read-more/u-read-more.vue new file mode 100644 index 000000000..9104e40d9 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-read-more/u-read-more.vue @@ -0,0 +1,157 @@ +<template> + <view class="u-read-more"> + <view + class="u-read-more__content" + :style="{ + height: isLongContent && status === 'close' ? $u.addUnit(showHeight) : $u.addUnit(contentHeight), + textIndent: textIndent + }" + > + <view + class="u-read-more__content__inner" + ref="u-read-more__content__inner" + :class="[elId]" + > + <slot></slot> + </view> + </view> + <view + class="u-read-more__toggle" + :style="[innerShadowStyle]" + v-if="isLongContent" + > + <slot name="toggle"> + <view + class="u-read-more__toggle__text" + @tap="toggleReadMore" + > + <u--text + :text="status === 'close' ? closeText : openText" + :color="color" + :size="fontSize" + :lineHeight="fontSize" + margin="0 5px 0 0" + ></u--text> + <view class="u-read-more__toggle__icon"> + <u-icon + :color="color" + :size="fontSize + 2" + :name="status === 'close' ? 'arrow-down' : 'arrow-up'" + ></u-icon> + </view> + </view> + </slot> + </view> + </view> +</template> + +<script> + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + import props from './props.js'; + /** + * readMore 阅读更多 + * @description 该组件一般用于内容较长,预先收起一部分,点击展开全部内容的场景。 + * @tutorial https://www.uviewui.com/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 <u-read-more><rich-text :nodes="content"></rich-text></u-read-more> + */ + export default { + name: 'u-read-more', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + isLongContent: false, // 是否需要隐藏一部分内容 + status: 'close', // 当前隐藏与显示的状态,close-收起状态,open-展开状态 + elId: uni.$u.guid(), // 生成唯一class + contentHeight: 100, // 内容高度 + } + }, + computed: { + // 展开后无需阴影,收起时才需要阴影样式 + innerShadowStyle() { + if (this.status === 'open') return {} + else return this.shadowStyle + } + }, + mounted() { + this.init() + }, + methods: { + async init() { + this.getContentHeight().then(height => { + this.contentHeight = height + // 判断高度,如果真实内容高度大于占位高度,则显示收起与展开的控制按钮 + if (height > uni.$u.getPx(this.showHeight)) { + this.isLongContent = true + this.status = 'close' + } + }) + }, + // 获取内容的高度 + async getContentHeight() { + // 延时一定时间再获取节点 + await uni.$u.sleep(30) + return new Promise(resolve => { + // #ifndef APP-NVUE + this.$uGetRect('.' + this.elId).then(res => { + resolve(res.height) + }) + // #endif + + // #ifdef APP-NVUE + const ref = this.$refs['u-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 "../../libs/css/components.scss"; + +.u-read-more { + + &__content { + overflow: hidden; + color: $u-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/yudao-ui-app/uni_modules/uview-ui/components/u-row-notice/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-row-notice/props.js new file mode 100644 index 000000000..107bd7038 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-row-notice/props.js @@ -0,0 +1,39 @@ +export default { + props: { + // 显示的内容,字符串 + text: { + type: String, + default: uni.$u.props.rowNotice.text + }, + // 是否显示左侧的音量图标 + icon: { + type: String, + default: uni.$u.props.rowNotice.icon + }, + // 通告模式,link-显示右箭头,closable-显示右侧关闭图标 + mode: { + type: String, + default: uni.$u.props.rowNotice.mode + }, + // 文字颜色,各图标也会使用文字颜色 + color: { + type: String, + default: uni.$u.props.rowNotice.color + }, + // 背景颜色 + bgColor: { + type: String, + default: uni.$u.props.rowNotice.bgColor + }, + // 字体大小,单位px + fontSize: { + type: [String, Number], + default: uni.$u.props.rowNotice.fontSize + }, + // 水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度 + speed: { + type: [String, Number], + default: uni.$u.props.rowNotice.speed + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-row-notice/u-row-notice.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-row-notice/u-row-notice.vue new file mode 100644 index 000000000..e5a2f4afc --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-row-notice/u-row-notice.vue @@ -0,0 +1,306 @@ +<template> + <view + class="u-notice" + @tap="clickHandler" + > + <slot name="icon"> + <view + class="u-notice__left-icon" + v-if="icon" + > + <u-icon + :name="icon" + :color="color" + size="19" + ></u-icon> + </view> + </slot> + <view + class="u-notice__content" + ref="u-notice__content" + > + <text + ref="u-notice__content__text" + class="u-notice__content__text" + :style="[textStyle]" + >{{text}}</text> + </view> + <view + class="u-notice__right-icon" + v-if="['link', 'closable'].includes(mode)" + > + <u-icon + v-if="mode === 'link'" + name="arrow-right" + :size="17" + :color="color" + ></u-icon> + <u-icon + v-if="mode === 'closable'" + @click="close" + name="close" + :size="16" + :color="color" + ></u-icon> + </view> + </view> +</template> +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const animation = uni.requireNativePlugin('animation') + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * RowNotice 滚动通知中的水平滚动模式 + * @description 水平滚动 + * @tutorial https://www.uviewui.com/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: 'u-row-notice', + mixins: [uni.$u.mpMixin, uni.$u.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(!uni.$u.test.string(newValue)) { + uni.$u.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.animationDuration = this.animationDuration + style.animationPlayState = this.animationPlayState + style.fontSize = uni.$u.addUnit(this.fontSize) + return style + }, + }, + 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(!uni.$u.test.string(this.text)) { + uni.$u.error('noticebar组件direction为row时,要求text参数为字符串形式') + } + }, + // vue版处理 + async vue() { + // #ifndef APP-NVUE + let boxWidth = 0, + textWidth = 0 + // 进行一定的延时 + await uni.$u.sleep() + // 查询盒子和文字的宽度 + textWidth = (await this.$uGetRect('.u-notice__content__text')).width + boxWidth = (await this.$uGetRect('.u-notice__content')).width + // 根据t=s/v(时间=路程/速度),这里为何不需要加上#u-notice-box的宽度,因为中设置了.u-notice-content样式中设置了padding-left: 100% + // 恰巧计算出来的结果中已经包含了#u-notice-box的宽度 + this.animationDuration = `${textWidth / uni.$u.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 uni.$u.sleep() + // 查询盒子和文字的宽度 + textWidth = (await this.getNvueRect('u-notice__content__text')).width + boxWidth = (await this.getNvueRect('u-notice__content')).width + // 将文字移动到盒子的右边沿,之所以需要这么做,是因为nvue不支持100%单位,否则可以通过css设置 + animation.transition(this.$refs['u-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['u-notice__content__text'], { + styles: { + // 目标移动终点为-textWidth,也即当文字的最右边贴到盒子的左边框的位置 + transform: `translateX(-${textWidth}px)` + }, + // 滚动时间的计算为,时间 = 路程(boxWidth + textWidth) / 速度,最后转为毫秒 + duration: (boxWidth + textWidth) / uni.$u.getPx(this.speed) * 1000, + delay: 10 + }, () => { + animation.transition(this.$refs['u-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 + beforeDestroy() { + this.stopAnimation = true + }, + // #endif + }; +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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: $u-warning; + /* #ifndef APP-NVUE */ + // 这一句很重要,为了能让滚动左右连接起来 + padding-left: 100%; + word-break: keep-all; + white-space: nowrap; + animation: u-loop-animation 10s linear infinite both; + /* #endif */ + } + } + + } + + @keyframes u-loop-animation { + 0% { + transform: translate3d(0, 0, 0); + } + + 100% { + transform: translate3d(-100%, 0, 0); + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-row/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-row/props.js new file mode 100644 index 000000000..4b71b87e1 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-row/props.js @@ -0,0 +1,19 @@ +export default { + props: { + // 给col添加间距,左右边距各占一半 + gutter: { + type: [String, Number], + default: uni.$u.props.row.gutter + }, + // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) + justify: { + type: String, + default: uni.$u.props.row.justify + }, + // 垂直对齐方式,可选值为top、center、bottom + align: { + type: String, + default: uni.$u.props.row.align + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-row/u-row.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-row/u-row.vue new file mode 100644 index 000000000..e608fc564 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-row/u-row.vue @@ -0,0 +1,93 @@ +<template> + <view + class="u-row" + ref="u-row" + :style="[rowStyle]" + @tap="clickHandler" + > + <slot /> + </view> +</template> + +<script> + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + import props from './props.js'; + /** + * Row 栅格系统中的行 + * @description 通过基础的 12 分栏,迅速简便地创建布局 + * @tutorial https://www.uviewui.com/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 <u-row justify="space-between" customStyle="margin-bottom: 10px"></u-row> + */ + export default { + name: "u-row", + mixins: [uni.$u.mpMixin, uni.$u.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 + } + // 通过给u-row左右两边的负外边距,消除u-col在有gutter时,第一个和最后一个元素的左内边距和右内边距造成的影响 + if(this.gutter) { + style.marginLeft = uni.$u.addUnit(-Number(this.gutter)/2) + style.marginRight = uni.$u.addUnit(-Number(this.gutter)/2) + } + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + methods: { + clickHandler(e) { + this.$emit('click') + }, + async getComponentWidth() { + // 延时一定时间,以确保节点渲染完成 + await uni.$u.sleep() + return new Promise(resolve => { + // uView封装的获取节点的方法,详见文档 + // #ifndef APP-NVUE + this.$uGetRect('.u-row').then(res => { + resolve(res.width) + }) + // #endif + // #ifdef APP-NVUE + // nvue的dom模块用于获取节点 + dom.getComponentRect(this.$refs['u-row'], (res) => { + resolve(res.size.width) + }) + // #endif + }) + }, + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-row { + @include flex; + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-safe-bottom/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-safe-bottom/props.js new file mode 100644 index 000000000..7c1133117 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-safe-bottom/props.js @@ -0,0 +1,5 @@ +export default { + props: { + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-safe-bottom/u-safe-bottom.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-safe-bottom/u-safe-bottom.vue new file mode 100644 index 000000000..fb858ea56 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-safe-bottom/u-safe-bottom.vue @@ -0,0 +1,56 @@ +<template> + <view + class="u-safe-bottom" + :style="[style]" + :class="[!isNvue && 'u-safe-area-inset-bottom']" + > + </view> +</template> + +<script> + import props from "./props.js"; + /** + * SafeBottom 底部安全区 + * @description 这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。 + * @tutorial https://www.uviewui.com/components/safeAreaInset.html + * @property {type} prop_name + * @property {Object} customStyle 定义需要用到的外部样式 + * + * @event {Function()} + * @example <u-status-bar></u-status-bar> + */ + export default { + name: "u-safe-bottom", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + safeAreaBottomHeight: 0, + isNvue: false, + }; + }, + computed: { + style() { + const style = {}; + // #ifdef APP-NVUE + // nvue下,高度使用js计算填充 + style.height = uni.$u.addUnit(uni.$u.sys().safeAreaInsets.bottom, 'px'); + // #endif + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)); + }, + }, + mounted() { + // #ifdef APP-NVUE + // 标识为是否nvue + this.isNvue = true; + // #endif + }, + }; +</script> + +<style lang="scss" scoped> + .u-safe-bottom { + /* #ifndef APP-NVUE */ + width: 100%; + /* #endif */ + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/nvue.js b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/nvue.js new file mode 100644 index 000000000..94bb056dd --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/nvue.js @@ -0,0 +1,28 @@ +// 引入bindingx,此库类似于微信小程序wxs,目的是让js运行在视图层,减少视图层和逻辑层的通信折损 +const BindingX = uni.requireNativePlugin('bindingx') + +export default { + methods: { + // 此处不写注释,请自行体会 + nvueScrollHandler(e) { + const anchor = this.$refs['u-scroll-list__scroll-view'].ref + const element = this.$refs['u-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 = uni.$u.os() === 'ios' ? 2 : 1 + const expression = `(x / ${actionNum}) / ${contentSize - scrollWidth} * ${barAllMoveWidth}` + BindingX.bind({ + anchor, + eventType: 'scroll', + props: [{ + element, + property: 'transform.translateX', + expression + }] + }) + } + } +} diff --git a/yudao-ui-app/.keep b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/other.js similarity index 100% rename from yudao-ui-app/.keep rename to yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/other.js diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/props.js new file mode 100644 index 000000000..765be54c7 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/props.js @@ -0,0 +1,34 @@ +export default { + props: { + // 指示器的整体宽度 + indicatorWidth: { + type: [String, Number], + default: uni.$u.props.scrollList.indicatorWidth + }, + // 滑块的宽度 + indicatorBarWidth: { + type: [String, Number], + default: uni.$u.props.scrollList.indicatorBarWidth + }, + // 是否显示面板指示器 + indicator: { + type: Boolean, + default: uni.$u.props.scrollList.indicator + }, + // 指示器非激活颜色 + indicatorColor: { + type: String, + default: uni.$u.props.scrollList.indicatorColor + }, + // 指示器的激活颜色 + indicatorActiveColor: { + type: String, + default: uni.$u.props.scrollList.indicatorActiveColor + }, + // 指示器样式,可通过bottom,left,right进行定位 + indicatorStyle: { + type: [String, Object], + default: uni.$u.props.scrollList.indicatorStyle + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/scrollWxs.wxs b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/scrollWxs.wxs new file mode 100644 index 000000000..ce94f1dc4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/scrollWxs.wxs @@ -0,0 +1,50 @@ +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) +} + +// 由于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) +} + +function scrolltoupper(event, ownerInstance) { + ownerInstance.callMethod('scrollEvent', 'left') + // 滚动到左边时,将滑块设置为0的偏移距离,回到起点 + setBarStyle(ownerInstance, 0) +} + +function setBarStyle(ownerInstance, x) { + ownerInstance.selectComponent('.u-scroll-list__indicator__line__bar') && ownerInstance.selectComponent('.u-scroll-list__indicator__line__bar').setStyle({ + transform: 'translateX(' + x + 'px)' + }) +} + +module.exports = { + scroll: scroll, + scrolltolower: scrolltolower, + scrolltoupper: scrolltoupper +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue new file mode 100644 index 000000000..f9085a2a7 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue @@ -0,0 +1,226 @@ +<template> + <view + class="u-scroll-list" + ref="u-scroll-list" + > + <!-- #ifdef APP-NVUE --> + <!-- nvue使用bindingX实现,以得到更好的性能 --> + <scroller + class="u-scroll-list__scroll-view" + ref="u-scroll-list__scroll-view" + scroll-direction="horizontal" + :show-scrollbar="false" + :offset-accuracy="1" + @scroll="nvueScrollHandler" + > + <view class="u-scroll-list__scroll-view__content"> + <slot /> + </view> + </scroller> + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <!-- #ifdef MP-WEIXIN || APP-VUE || H5 || MP-QQ --> + <!-- 以上平台,支持wxs --> + <scroll-view + class="u-scroll-list__scroll-view" + scroll-x + @scroll="wxs.scroll" + @scrolltoupper="wxs.scrolltoupper" + @scrolltolower="wxs.scrolltolower" + :data-scrollWidth="scrollWidth" + :data-barWidth="$u.getPx(indicatorBarWidth)" + :data-indicatorWidth="$u.getPx(indicatorWidth)" + :show-scrollbar="false" + :upper-threshold="0" + :lower-threshold="0" + > + <!-- #endif --> + <!-- #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ --> + <!-- 非以上平台,只能使用普通js实现 --> + <scroll-view + class="u-scroll-list__scroll-view" + scroll-x + @scroll="scrollHandler" + @scrolltoupper="scrolltoupperHandler" + @scrolltolower="scrolltolowerHandler" + :show-scrollbar="false" + :upper-threshold="0" + :lower-threshold="0" + > + <!-- #endif --> + <view class="u-scroll-list__scroll-view__content"> + <slot /> + </view> + </scroll-view> + <!-- #endif --> + <view + class="u-scroll-list__indicator" + v-if="indicator" + :style="[$u.addStyle(indicatorStyle)]" + > + <view + class="u-scroll-list__indicator__line" + :style="[lineStyle]" + > + <view + class="u-scroll-list__indicator__line__bar" + :style="[barStyle]" + ref="u-scroll-list__indicator__line__bar" + ></view> + </view> + </view> + </view> +</template> + +<!-- #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ --> +<script + src="./scrollWxs.wxs" + module="wxs" + lang="wxs" +></script> +<!-- #endif --> + +<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 props from './props.js'; +export default { + name: 'u-scroll-list', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + // #ifdef APP-NVUE + mixins: [uni.$u.mpMixin, uni.$u.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) * barAllMoveWidth + style.transform = `translateX(${ x }px)` + // #endif + // 设置滑块的宽度和背景色,是每个平台都需要的 + style.width = uni.$u.addUnit(this.indicatorBarWidth) + style.backgroundColor = this.indicatorActiveColor + return style + }, + lineStyle() { + const style = {} + // 指示器整体的样式,需要设置其宽度和背景色 + style.width = uni.$u.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 = uni.$u.getPx(this.indicatorWidth) - uni.$u.getPx(this.indicatorBarWidth) + }, + // #endif + // + scrollEvent(status) { + this.$emit(status) + }, + // 获取组件的宽度 + async getComponentWidth() { + // 延时一定时间,以获取dom尺寸 + await uni.$u.sleep(30) + // #ifndef APP-NVUE + this.$uGetRect('.u-scroll-list').then(size => { + this.scrollWidth = size.width + }) + // #endif + + // #ifdef APP-NVUE + const ref = this.$refs['u-scroll-list'] + ref && dom.getComponentRect(ref, (res) => { + this.scrollWidth = res.size.width + }) + // #endif + }, + } +} +</script> + +<style lang="scss" scoped> +@import "../../libs/css/components.scss"; + +.u-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> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-search/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-search/props.js new file mode 100644 index 000000000..df1b342fb --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-search/props.js @@ -0,0 +1,118 @@ +export default { + props: { + // 搜索框形状,round-圆形,square-方形 + shape: { + type: String, + default: uni.$u.props.search.shape + }, + // 搜索框背景色,默认值#f2f2f2 + bgColor: { + type: String, + default: uni.$u.props.search.bgColor + }, + // 占位提示文字 + placeholder: { + type: String, + default: uni.$u.props.search.placeholder + }, + // 是否启用清除控件 + clearabled: { + type: Boolean, + default: uni.$u.props.search.clearabled + }, + // 是否自动聚焦 + focus: { + type: Boolean, + default: uni.$u.props.search.focus + }, + // 是否在搜索框右侧显示取消按钮 + showAction: { + type: Boolean, + default: uni.$u.props.search.showAction + }, + // 右边控件的样式 + actionStyle: { + type: Object, + default: uni.$u.props.search.actionStyle + }, + // 取消按钮文字 + actionText: { + type: String, + default: uni.$u.props.search.actionText + }, + // 输入框内容对齐方式,可选值为 left|center|right + inputAlign: { + type: String, + default: uni.$u.props.search.inputAlign + }, + // input输入框的样式,可以定义文字颜色,大小等,对象形式 + inputStyle: { + type: Object, + default: uni.$u.props.search.inputStyle + }, + // 是否启用输入框 + disabled: { + type: Boolean, + default: uni.$u.props.search.disabled + }, + // 边框颜色 + borderColor: { + type: String, + default: uni.$u.props.search.borderColor + }, + // 搜索图标的颜色,默认同输入框字体颜色 + searchIconColor: { + type: String, + default: uni.$u.props.search.searchIconColor + }, + // 输入框字体颜色 + color: { + type: String, + default: uni.$u.props.search.color + }, + // placeholder的颜色 + placeholderColor: { + type: String, + default: uni.$u.props.search.placeholderColor + }, + // 左边输入框的图标,可以为uView图标名称或图片路径 + searchIcon: { + type: String, + default: uni.$u.props.search.searchIcon + }, + searchIconSize: { + type: [Number, String], + default: uni.$u.props.search.searchIconSize + }, + // 组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30px"、"30px 20px"等写法 + margin: { + type: String, + default: uni.$u.props.search.margin + }, + // 开启showAction时,是否在input获取焦点时才显示 + animation: { + type: Boolean, + default: uni.$u.props.search.animation + }, + // 输入框的初始化内容 + value: { + type: String, + default: uni.$u.props.search.value + }, + // 输入框最大能输入的长度,-1为不限制长度(来自uniapp文档) + maxlength: { + type: [String, Number], + default: uni.$u.props.search.maxlength + }, + // 搜索框高度,单位px + height: { + type: [String, Number], + default: uni.$u.props.search.height + }, + // 搜索框左侧文本 + label: { + type: [String, Number, null], + default: uni.$u.props.search.label + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-search/u-search.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-search/u-search.vue new file mode 100644 index 000000000..f169c7f7e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-search/u-search.vue @@ -0,0 +1,303 @@ +<template> + <view + class="u-search" + @tap="clickHandler" + :style="[{ + margin: margin, + }, $u.addStyle(customStyle)]" + > + <view + class="u-search__content" + :style="{ + backgroundColor: bgColor, + borderRadius: shape == 'round' ? '100px' : '4px', + borderColor: borderColor, + }" + > + <template v-if="$slots.label || label !== null"> + <slot name="label"> + <text class="u-search__content__label">{{ label }}</text> + </slot> + </template> + <view class="u-search__content__icon"> + <u-icon + @tap="clickIcon" + :size="searchIconSize" + :name="searchIcon" + :color="searchIconColor ? searchIconColor : color" + ></u-icon> + </view> + <input + confirm-type="search" + @blur="blur" + :value="value" + @confirm="search" + @input="inputChange" + :disabled="disabled" + @focus="getFocus" + :focus="focus" + :maxlength="maxlength" + placeholder-class="u-search__content__input--placeholder" + :placeholder="placeholder" + :placeholder-style="`color: ${placeholderColor}`" + class="u-search__content__input" + type="text" + :style="[{ + textAlign: inputAlign, + color: color, + backgroundColor: bgColor, + height: $u.addUnit(height) + }, inputStyle]" + /> + <view + class="u-search__content__icon u-search__content__close" + v-if="keyword && clearabled && focused" + @tap="clear" + > + <u-icon + name="close" + size="11" + color="#ffffff" + customStyle="line-height: 12px" + ></u-icon> + </view> + </view> + <text + :style="[actionStyle]" + class="u-search__action" + :class="[(showActionBtn || show) && 'u-search__action--active']" + @tap.stop.prevent="custom" + >{{ actionText }}</text> + </view> +</template> + +<script> + import props from './props.js'; + + /** + * search 搜索框 + * @description 搜索组件,集成了常见搜索框所需功能,用户可以一键引入,开箱即用。 + * @tutorial https://www.uviewui.com/components/search.html + * @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 输入框左边的图标,可以为uView图标名称或图片路径 (默认 'search' ) + * @property {String} margin 组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30px" (默认 '0' ) + * @property {Boolean} animation 是否开启动画,见上方说明(默认 false ) + * @property {String} value 输入框初始值 + * @property {String | Number} maxlength 输入框最大能输入的长度,-1为不限制长度 (默认 '-1' ) + * @property {String | Number} height 输入框高度,单位px(默认 64 ) + * @property {String | Number} label 搜索框左边显示内容 + * @property {Object} customStyle 定义需要用到的外部样式 + * + * @event {Function} change 输入框内容发生变化时触发 + * @event {Function} search 用户确定搜索时触发,用户按回车键,或者手机键盘右下角的"搜索"键时触发 + * @event {Function} custom 用户点击右侧控件时触发 + * @event {Function} clear 用户点击清除按钮时触发 + * @example <u-search placeholder="日照香炉生紫烟" v-model="keyword"></u-search> + */ + export default { + name: "u-search", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + keyword: '', + showClear: false, // 是否显示右边的清除图标 + show: false, + // 标记input当前状态是否处于聚焦中,如果是,才会显示右侧的清除控件 + focused: this.focus + // 绑定输入框的值 + // inputValue: this.value + }; + }, + watch: { + keyword(nVal) { + // 双向绑定值,让v-model绑定的值双向变化 + this.$emit('input', nVal); + // 触发change事件,事件效果和v-model双向绑定的效果一样,让用户多一个选择 + this.$emit('change', nVal); + }, + value: { + immediate: true, + handler(nVal) { + this.keyword = nVal; + } + } + }, + computed: { + showActionBtn() { + return !this.animation && this.showAction + } + }, + methods: { + // 目前HX2.6.9 v-model双向绑定无效,故监听input事件获取输入框内容的变化 + inputChange(e) { + this.keyword = e.detail.value; + }, + // 清空输入 + // 也可以作为用户通过this.$refs形式调用清空输入框内容 + clear() { + this.keyword = ''; + // 延后发出事件,避免在父组件监听clear事件时,value为更新前的值(不为空) + this.$nextTick(() => { + this.$emit('clear'); + }) + }, + // 确定搜索 + 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 "../../libs/css/components.scss"; +$u-search-content-padding: 0 10px !default; +$u-search-label-color: $u-main-color !default; +$u-search-label-font-size: 14px !default; +$u-search-label-margin: 0 4px !default; +$u-search-close-size: 20px !default; +$u-search-close-radius: 100px !default; +$u-search-close-bgColor: #C6C7CB !default; +$u-search-close-transform: scale(0.82) !default; +$u-search-input-font-size: 14px !default; +$u-search-input-margin: 0 5px !default; +$u-search-input-color: $u-main-color !default; +$u-search-input-placeholder-color: $u-tips-color !default; +$u-search-action-font-size: 14px !default; +$u-search-action-color: $u-main-color !default; +$u-search-action-width: 0 !default; +$u-search-action-active-width: 40px !default; +$u-search-action-margin-left: 5px !default; + +/* #ifdef H5 */ +// iOS15在H5下,hx的某些版本,input type=search时,会多了一个搜索图标,进行移除 +[type="search"]::-webkit-search-decoration { + display: none; +} +/* #endif */ + +.u-search { + @include flex(row); + align-items: center; + flex: 1; + + &__content { + @include flex; + align-items: center; + padding: $u-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: $u-search-label-color; + font-size: $u-search-label-font-size; + margin: $u-search-label-margin; + } + + &__close { + width: $u-search-close-size; + height: $u-search-close-size; + border-top-left-radius: $u-search-close-radius; + border-top-right-radius: $u-search-close-radius; + border-bottom-left-radius: $u-search-close-radius; + border-bottom-right-radius: $u-search-close-radius; + background-color: $u-search-close-bgColor; + @include flex(row); + align-items: center; + justify-content: center; + transform: $u-search-close-transform; + } + + &__input { + flex: 1; + font-size: $u-search-input-font-size; + line-height: 1; + margin: $u-search-input-margin; + color: $u-search-input-color; + + &--placeholder { + color: $u-search-input-placeholder-color; + } + } + } + + &__action { + font-size: $u-search-action-font-size; + color: $u-search-action-color; + width: $u-search-action-width; + overflow: hidden; + transition-property: width; + transition-duration: 0.3s; + /* #ifndef APP-NVUE */ + white-space: nowrap; + /* #endif */ + text-align: center; + + &--active { + width: $u-search-action-active-width; + margin-left: $u-search-action-margin-left; + } + } +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-skeleton/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-skeleton/props.js new file mode 100644 index 000000000..ed3ba5aae --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-skeleton/props.js @@ -0,0 +1,59 @@ +export default { + props: { + // 是否展示骨架组件 + loading: { + type: Boolean, + default: uni.$u.props.skeleton.loading + }, + // 是否开启动画效果 + animate: { + type: Boolean, + default: uni.$u.props.skeleton.animate + }, + // 段落占位图行数 + rows: { + type: [String, Number], + default: uni.$u.props.skeleton.rows + }, + // 段落占位图的宽度 + rowsWidth: { + type: [String, Number, Array], + default: uni.$u.props.skeleton.rowsWidth + }, + // 段落占位图的高度 + rowsHeight: { + type: [String, Number, Array], + default: uni.$u.props.skeleton.rowsHeight + }, + // 是否展示标题占位图 + title: { + type: Boolean, + default: uni.$u.props.skeleton.title + }, + // 段落标题的宽度 + titleWidth: { + type: [String, Number], + default: uni.$u.props.skeleton.titleWidth + }, + // 段落标题的高度 + titleHeight: { + type: [String, Number], + default: uni.$u.props.skeleton.titleHeight + }, + // 是否展示头像占位图 + avatar: { + type: Boolean, + default: uni.$u.props.skeleton.avatar + }, + // 头像占位图大小 + avatarSize: { + type: [String, Number], + default: uni.$u.props.skeleton.avatarSize + }, + // 头像占位图的形状,circle-圆形,square-方形 + avatarShape: { + type: String, + default: uni.$u.props.skeleton.avatarShape + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-skeleton/u-skeleton.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-skeleton/u-skeleton.vue new file mode 100644 index 000000000..efa649eb6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-skeleton/u-skeleton.vue @@ -0,0 +1,244 @@ +<template> + <view class="u-skeleton"> + <view + class="u-skeleton__wrapper" + ref="u-skeleton__wrapper" + v-if="loading" + style="display: flex; flex-direction: row;" + > + <view + class="u-skeleton__wrapper__avatar" + v-if="avatar" + :class="[`u-skeleton__wrapper__avatar--${avatarShape}`, animate && 'animate']" + :style="{ + height: $u.addUnit(avatarSize), + width: $u.addUnit(avatarSize) + }" + ></view> + <view + class="u-skeleton__wrapper__content" + ref="u-skeleton__wrapper__content" + style="flex: 1;" + > + <view + class="u-skeleton__wrapper__content__title" + v-if="title" + :style="{ + width: uTitleWidth, + height: $u.addUnit(titleHeight), + }" + :class="[animate && 'animate']" + ></view> + <view + class="u-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 + }" + > + + </view> + </view> + </view> + <slot v-else /> + </view> +</template> + +<script> + 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.uviewui.com/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 {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 <u-search placeholder="日照香炉生紫烟" v-model="keyword"></u-search> + */ + export default { + name: 'u-skeleton', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + width: 0, + } + }, + watch: { + loading() { + this.getComponentWidth() + } + }, + computed: { + rowsArray() { + if (/%$/.test(this.rowsHeight)) { + uni.$u.error('rowsHeight参数不支持百分比单位') + } + const rows = [] + for (let i = 0; i < this.rows; i++) { + let item = {}, + // 需要预防超出数组边界的情况 + rowWidth = uni.$u.test.array(this.rowsWidth) ? (this.rowsWidth[i] || (i === this.row - 1 ? '70%' : '100%')) : i === + this.rows - 1 ? '70%' : this.rowsWidth, + rowHeight = uni.$u.test.array(this.rowsHeight) ? (this.rowsHeight[i] || '18px') : this.rowsHeight + // 如果有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 = uni.$u.addUnit(this.width * parseInt(rowWidth) / 100) + } else { + item.width = uni.$u.addUnit(rowWidth) + } + item.height = uni.$u.addUnit(rowHeight) + rows.push(item) + } + // console.log(rows); + return rows + }, + uTitleWidth() { + let tWidth = 0 + if (/%$/.test(this.titleWidth)) { + // 通过parseInt提取出百分比单位中的数值部分,除以100得到百分比的小数值 + tWidth = uni.$u.addUnit(this.width * parseInt(this.titleWidth) / 100) + } else { + tWidth = uni.$u.addUnit(this.titleWidth) + } + return uni.$u.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 uni.$u.sleep(500) + const skeleton = this.$refs['u-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 uni.$u.sleep(20) + // #ifndef APP-NVUE + this.$uGetRect('.u-skeleton__wrapper__content').then(size => { + this.width = size.width + }) + // #endif + + // #ifdef APP-NVUE + const ref = this.$refs['u-skeleton__wrapper__content'] + ref && dom.getComponentRect(ref, (res) => { + this.width = res.size.width + }) + // #endif + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../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 */ + } + + .u-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/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpother.js b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpother.js new file mode 100644 index 000000000..040c848d8 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpother.js @@ -0,0 +1,113 @@ +/** + * 使用普通的js方案实现slider + */ +export default { + watch: { + value(n) { + // 只有在非滑动状态时,才可以通过value更新滑块值,这里监听,是为了让用户触发 + if (this.status === 'end') { + this.updateSliderPlacement(n, true) + } + } + }, + mounted() { + this.init() + }, + methods: { + init() { + this.getSliderRect() + }, + // 获取slider尺寸 + getSliderRect() { + // 获取滑块条的尺寸信息 + setTimeout(() => { + this.$uGetRect('.u-slider').then((rect) => { + this.sliderRect = rect + this.updateSliderPlacement(this.value, true) + }) + }, 10) + }, + // 是否可以操作 + canNotDo() { + return this.disabled + }, + // 获取当前手势点的X轴位移值 + getTouchX(e) { + return e.touches[0].clientX + }, + formatStep(value) { + // 移动点占总长度的百分比 + return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step + }, + // 发出事件 + emitEvent(event, value) { + this.$emit(event, value || this.value) + }, + // 标记当前手势的状态 + setTouchStatus(status) { + this.status = status + }, + onTouchStart(e) { + if (this.canNotDo()) { + return + } + // 标示当前的状态为开始触摸滑动 + this.emitEvent('start') + this.setTouchStatus('start') + }, + onTouchMove(e) { + if (this.canNotDo()) { + return + } + // 滑块的左边不一定跟屏幕左边接壤,所以需要减去最外层父元素的左边值 + const x = this.getTouchX(e) + const { left, width } = this.sliderRect + const distanceX = x - left + // 获得移动距离对整个滑块的百分比值,此为带有多位小数的值,不能用此更新视图 + // 否则造成通信阻塞,需要每改变一个step值时修改一次视图 + const percent = (distanceX / width) * 100 + this.setTouchStatus('moving') + this.updateSliderPlacement(percent, true, 'moving') + }, + onTouchEnd() { + if (this.canNotDo()) { + return + } + this.emitEvent('end') + this.setTouchStatus('end') + }, + // 设置滑点的位置 + updateSliderPlacement(value, drag, event) { + // 去掉小数部分,同时也是对step步进的处理 + const { width } = this.sliderRect + const percent = this.formatStep(value) + // 设置移动的值 + const barStyle = { + width: `${percent / 100 * width}px` + } + // 移动期间无需过渡动画 + if (drag === true) { + barStyle.transition = 'none' + } else { + // 非移动期间,删掉对过渡为空的声明,让css中的声明起效 + delete barStyle.transition + } + // 修改value值 + this.$emit('input', percent) + // 事件的名称 + if (event) { + this.emitEvent(event, percent) + } + this.barStyle = barStyle + }, + onClick(e) { + if (this.canNotDo()) { + return + } + // 直接点击滑块的情况,计算方式与onTouchMove方法相同 + const { left, width } = this.sliderRect + const value = ((e.detail.x - left) / width) * 100 + this.updateSliderPlacement(value, false, 'click') + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpwxs.js b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpwxs.js new file mode 100644 index 000000000..f263911c3 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpwxs.js @@ -0,0 +1,42 @@ +export default { + data() { + return { + sliderRect: {}, + info: { + width: null, + left: null, + step: this.step, + disabled: this.disabled, + min: this.min, + max: this.max, + value: this.value + } + } + }, + mounted() { + this.init() + }, + methods: { + init() { + this.getSliderRect() + }, + // 获取slider尺寸 + getSliderRect() { + // 获取滑块条的尺寸信息 + uni.$u.sleep().then(() => { + this.$uGetRect('.u-slider').then((rect) => { + this.info.width = rect.width + this.info.left = rect.left + }) + }) + }, + // 此方法由wxs调用,用于修改v-model绑定的值 + updateValue(value) { + this.$emit('input', value) + }, + // 此方法由wxs调用,发出事件 + emitEvent(e) { + this.$emit(e.event, e.value ? e.value : this.value) + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpwxs.wxs b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpwxs.wxs new file mode 100644 index 000000000..847df4adf --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/mpwxs.wxs @@ -0,0 +1,121 @@ +/** + * 使用wxs方案实现slider + * 兼容微信,QQ,H5,Vue版的安卓和iOS + */ +/** + * 开始滑动操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function onTouchMove(e, ownerInstance) { + // wxs事件对象下有一个instance属性,表示当前触发此事件的组件的实例,通过该实例,可以获取相关的dataset,设置样式等信息 + // https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html + var instance = e.instance; + // getState()为一个对象,挂载在instance上,类似组件的data一样,可以存放一些变量,供以后的触发事件中使用 + var state = instance.getState() + + // 滑块组件的整体尺寸信息 + var mp = state.mp + if(mp.disabled) { + return + } + + var distanceX = getTouchX(e) - mp.left + // 获得移动距离对整个滑块的百分比值,此为带有多位小数的值,step大于1时,不能用此更新视图 + var percent = (distanceX / mp.width) * 100 + + updateSliderPlacement(instance, ownerInstance, percent, 'moving') + + // 阻止页面滚动,可以保证在滑动过程中,不让页面可以上下滚动,造成不好的体验 + e.stopPropagation && e.stopPropagation() + e.preventDefault && e.preventDefault() +} + +function onClick(e, ownerInstance) { + var instance = e.instance + var state = instance.getState() + var mp = state.mp + if(mp.disabled) { + return + } + + // 直接点击滑块的情况,计算方式与onTouchMove方法相同 + var value = ((e.detail.x - mp.left) / mp.width) * 100 + updateSliderPlacement(instance, ownerInstance, value, 'click') +} + +function sizeReady(newValue, oldValue, ownerInstance, instance) { + // 页面初始化时候,也会触发此方法,传递的值为空,这里不执行往后的逻辑 + if(!newValue || newValue.disabled) { + return + } + var state = instance.getState() + state.mp = newValue + updateSliderPlacement(instance, ownerInstance, newValue.value) +} + +// 设置滑点的位置 +function updateSliderPlacement(instance, ownerInstance, value, event) { + var state = instance.getState() + var mp = state.mp + if(mp.disabled) { + return + } + + var percent = 0 + if (mp.step > 1) { + // 如果step步进大于1,需要跳步,所以需要使用Math.round进行取整 + percent = Math.round(Math.max(mp.min, Math.min(value, mp.max)) / mp.step) * mp.step + } else { + // 当step=1时,无需跳步,充分利用wxs性能,滑块实时跟随手势,达到丝滑的效果 + percent = Math.max(mp.min, Math.min(value, mp.max)) + } + // 返回组件的实例 + var gapInstance = ownerInstance.selectComponent('.u-slider__gap') + // 在移动期间,不允许transition动画,否则会造成卡顿 + gapInstance[event === 'click' ? 'addClass' : 'removeClass']('u-slider__gap--ani') + // 调用逻辑层的方法,修改v-model绑定的值 + ownerInstance.callMethod('updateValue', Math.round(percent)) + if(event) { + ownerInstance.callMethod('emitEvent', { + event: event, + value: Math.round(percent) + }) + } + + // 设置移动的值 + gapInstance.requestAnimationFrame(function() { + gapInstance.setStyle({ + width: percent / 100 * mp.width + 'px', + }) + }) +} + +// 开始滑动 +function onTouchStart(e, ownerInstance) { + ownerInstance.callMethod('emitEvent', { + event: 'start', + value: null + }) +} + +// 停止滑动 +function onTouchEnd(e, ownerInstance) { + ownerInstance.callMethod('emitEvent', { + event: 'end', + value: null + }) +} + +// 获取当前手势点的X轴位移值 +function getTouchX(e) { + return e.touches[0].clientX +} + +module.exports = { + onTouchStart: onTouchStart, + onTouchMove: onTouchMove, + onTouchEnd: onTouchEnd, + sizeReady: sizeReady, + onClick: onClick +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-slider/nvue - 副本.js b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/nvue - 副本.js new file mode 100644 index 000000000..df62349e9 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/nvue - 副本.js @@ -0,0 +1,180 @@ +/** + * 使用bindingx方案实现slider + * 只能使用于nvue下 + */ +// 引入bindingx,此库类似于微信小程序wxs,目的是让js运行在视图层,减少视图层和逻辑层的通信折损 +const BindingX = uni.requireNativePlugin('bindingx') +// nvue操作dom的库,用于获取dom的尺寸信息 +const dom = uni.requireNativePlugin('dom') +// nvue中用于操作元素动画的库,类似于uni.animation,只不过uni.animation不能用于nvue +const animation = uni.requireNativePlugin('animation') + +export default { + data() { + return { + // bindingx的回调值,用于取消绑定 + panEvent: null, + // 标记是否移动状态 + moving: false, + // 位移的偏移量 + x: 0, + // 是否正在触摸过程中,用于标记动画类是否添加或移除 + touching: false, + changeFromInside: false + } + }, + watch: { + // 监听vlaue的变化,此变化可能是由于内部修改v-model的值,或者外部 + // 从服务端获取一个值后,赋值给slider的v-model而导致的 + value(n) { + if (!this.changeFromInside) { + this.initX() + } else { + this.changeFromInside = false + } + } + }, + mounted() { + this.init() + }, + methods: { + init() { + this.getSliderRect() + }, + // 获取节点信息 + // 获取slider尺寸 + getSliderRect() { + // 获取滑块条的尺寸信息 + // 通过nvue的dom模块,查询节点信息 + setTimeout(() => { + dom.getComponentRect(this.$refs['slider'], res => { + this.sliderRect = res.size + this.initX() + }) + }, 10) + }, + // 初始化按钮位置 + initButtonStyle({ + barStyle, + buttonWrapperStyle + }) { + this.barStyle = barStyle + this.buttonWrapperStyle = buttonWrapperStyle + }, + emitEvent(event, value) { + this.$emit(event, value ? value : this.value) + }, + formatStep(value) { + // 移动点占总长度的百分比 + return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step + }, + // 滑动开始 + onTouchStart(e) { + // 阻止页面滚动,可以保证在滑动过程中,不让页面可以上下滚动,造成不好的体验 + e.stopPropagation && e.stopPropagation() + e.preventDefault && e.preventDefault() + if (this.moving || this.disabled) { + // 释放上一次的资源 + if (this.panEvent?.token != 0) { + BindingX.unbind({ + token: this.panEvent.token, + // pan为手势事件 + eventType: 'pan' + }) + this.gesToken = 0 + } + return + } + + this.moving = true + this.touching = true + + // 获取元素ref + const button = this.$refs['nvue-button'].ref + const gap = this.$refs['nvue-gap'].ref + + const { + min, + max, + step + } = this + const { + left, + width + } = this.sliderRect + + // 初始值为本次偏移量x,加上次停止滑动时的结束值 + let exporession = `(${this.x} + x)` + // 将偏移的x值,转为总位移的百分比值,为了和min和max进行判断 + exporession = `(${exporession} / ${width}) * 100` + if (step > 1) { + // 如果step步进大于1,需要跳步,所以需要使用Math.round进行取整 + exporession = `round(max(${min}, min(${exporession}, ${max})) / ${step}) * ${step}` + } else { + // 当step=1时,无需跳步,充分利用bindingx性能,滑块实时跟随手势,达到丝滑的效果 + exporession = `max(${min}, min(${exporession}, ${max}))` + } + // 将百分比最后转化为对应的px值 + exporession = `${exporession} / 100 * ${width}` + // 最大值不允许超过轨迹的宽度 + const { + sliderWidth + } = this.sliderRect + exporession = `min(${sliderWidth}, ${exporession})` + // 滑块点总是需要一个左偏移的值,为自身宽度的一半 + const buttonExpression = `${exporession} - ${this.blockHeight / 2}` + // 阿里为了KPI而开源的BindingX + this.panEvent = BindingX.bind({ + anchor: button, + eventType: 'pan', + props: [{ + element: gap, + // 绑定width属性,设置其宽度值 + property: 'width', + expression + }, { + element: button, + // 绑定width属性,设置其宽度值 + property: 'transform.translateX', + expression: buttonExpression + }] + }, (e) => { + if (e.state === 'end' || e.state === 'exit') { + // + this.x = uni.$u.range(0, left + width, e.deltaX + this.x) + // 根据偏移值,得出移动的百分比,进而修改双向绑定的v-model的值 + const value = (this.x / width) * 100 + const percent = this.formatStep(value) + // 修改value值 + this.$emit('input', percent) + // 标记下一次触发value的watch时,这个值的变化,是由内部改变的 + this.changeFromInside = true + this.moving = false + this.touching = false + } + }) + }, + // 从value的变化,倒推得出x的值该为多少 + initX() { + const { + left, + width + } = this.sliderRect + // 得出x的初始偏移值,之所以需要这么做,是因为在bindingX中,触摸滑动时,只能的值本次移动的偏移值 + // 而无法的值准确的前后移动的两个点的坐标值,weex纯粹为阿里巴巴的KPI(部门业绩考核)产物,也就这样了 + this.x = this.value / 100 * width + // 设置移动的值 + const barStyle = { + width: this.x + 'px' + } + // 按钮的初始值 + const buttonWrapperStyle = { + transform: `translateX(${this.x - this.blockHeight / 2}px)` + } + this.initButtonStyle({ + barStyle, + buttonWrapperStyle + }) + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-slider/nvue.js b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/nvue.js new file mode 100644 index 000000000..344dce87c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/nvue.js @@ -0,0 +1,193 @@ +/** + * 使用bindingx方案实现slider + * 只能使用于nvue下 + */ +// 引入bindingx,此库类似于微信小程序wxs,目的是让js运行在视图层,减少视图层和逻辑层的通信折损 +const BindingX = uni.requireNativePlugin('bindingx') +// nvue操作dom的库,用于获取dom的尺寸信息 +const dom = uni.requireNativePlugin('dom') +// nvue中用于操作元素动画的库,类似于uni.animation,只不过uni.animation不能用于nvue +const animation = uni.requireNativePlugin('animation') + +export default { + data() { + return { + // 位移的偏移量 + x: 0, + // 是否正在触摸过程中,用于标记动画类是否添加或移除 + touching: false, + changeFromInside: false + } + }, + watch: { + // 监听vlaue的变化,此变化可能是由于内部修改v-model的值,或者外部 + // 从服务端获取一个值后,赋值给slider的v-model而导致的 + value(n) { + if (!this.changeFromInside) { + this.initX() + } else { + this.changeFromInside = false + } + } + }, + mounted() { + this.init() + }, + methods: { + init() { + // 更新滑块尺寸信息 + this.getSliderRect().then((size) => { + this.sliderRect = size + this.initX() + }) + }, + // 获取节点信息 + // 获取slider尺寸 + getSliderRect() { + // 获取滑块条的尺寸信息 + // 通过nvue的dom模块,查询节点信息 + return new Promise((resolve) => { + this.$nextTick(() => { + dom.getComponentRect(this.$refs.slider, (res) => { + resolve(res.size) + }) + }) + }) + }, + // 初始化按钮位置 + initButtonStyle({ + barStyle, + buttonWrapperStyle + }) { + this.barStyle = barStyle + this.buttonWrapperStyle = buttonWrapperStyle + }, + emitEvent(event, value) { + this.$emit(event, value || this.value) + }, + // 滑动开始 + async onTouchStart(e) { + // if (this.disabled) return + // // 阻止页面滚动,可以保证在滑动过程中,不让页面可以上下滚动,造成不好的体验 + // e.stopPropagation && e.stopPropagation() + // e.preventDefault && e.preventDefault() + // // 更新滑块的尺寸信息 + // this.sliderRect = await this.getSliderRect() + // // 标记滑动过程中触摸点的信息 + // this.touchStart(e) + // this.startValue = this.format(this.value) + // this.dragStatus = 'start' + + // 标记滑动过程中触摸点的信息 + // this.touchStart(e) + }, + // 开始滑动 + onTouchMove(e) { + // if (this.disabled) return; + // if (this.dragStatus === 'start') { + // this.$emit('drag-start') + // } + // // 标记当前滑动过程中的触点信息,此方法在touch mixin中 + // this.touchMove(e) + // this.dragStatus = 'draging' + // const { + // width: sliderWidth + // } = this.sliderRect + // const diff = (this.deltaX / sliderWidth) * this.getRange() + // this.newValue = this.startValue + diff + // this.updateValue(this.newValue, false, true) + // 获取元素ref + // const button = this.$refs['nvue-button'].ref + // const gap = this.$refs['nvue-gap'].ref + + // animation.transition(gap, { + // styles: { + // width: `${this.startX + this.deltaX}px` + // } + // }) + // // console.log(this.startX + this.deltaX); + // animation.transition(button, { + // styles: { + // transform: `translateX(${this.startX + this.deltaX}px)` + // } + // }) + // this.barStyle = { + // width: `${this.startX + this.deltaX}px` + // } + const { + x + } = this.getTouchPoint(e) + this.buttonWrapperStyle = { + transform: `translateX(${x}px)` + } + // this.buttonWrapperStyle = { + // transform: `translateX(${this.format(this.startX + this.deltaX)}px)` + // } + }, + // onTouchEnd() { + // if (this.disabled) return; + // if (this.dragStatus === 'draging') { + // this.updateValue(this.newValue, true) + // this.$emit('drag-end'); + // } + // }, + updateValue(value, end, drag) { + value = this.format(value) + const { + width: sliderWidth + } = this.sliderRect + const width = `${((value - this.min) * sliderWidth) / this.getRange()}` + this.value = value + this.barStyle = { + width: `${width}px` + } + // console.log('width', width); + if (drag) { + this.$emit('drag', { + value + }) + } + if (end) { + this.$emit('change', value) + } + if ((drag || end)) { + this.changeFromInside = true + this.$emit('update', value) + } + }, + // 从value的变化,倒推得出x的值该为多少 + initX() { + const { + left, + width + } = this.sliderRect + // 得出x的初始偏移值,之所以需要这么做,是因为在bindingX中,触摸滑动时,只能的值本次移动的偏移值 + // 而无法的值准确的前后移动的两个点的坐标值,weex纯粹为阿里巴巴的KPI(部门业绩考核)产物,也就这样了 + this.x = this.value / 100 * width + // 设置移动的值 + const barStyle = { + width: `${this.x}px` + } + // 按钮的初始值 + const buttonWrapperStyle = { + transform: `translateX(${this.x - this.blockHeight / 2}px)` + } + this.initButtonStyle({ + barStyle, + buttonWrapperStyle + }) + }, + // 移动点占总长度的百分比,此处需要先除以step,是为了保证step大于1时,比如10,那么在滑动11,12px这样的 + // 距离时,实际上滑块是不会滑动的,到了16,17px,经过四舍五入后,就变成了20px,进行了下一个跳变 + format(value) { + return Math.round(uni.$u.range(this.min, this.max, value) / this.step) * this.step + }, + getRange() { + const { + max, + min + } = this + return max - min + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-slider/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/props.js new file mode 100644 index 000000000..433a7b502 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/props.js @@ -0,0 +1,54 @@ +export default { + props: { + // 最小可选值 + min: { + type: [Number, String], + default: uni.$u.props.slider.min + }, + // 最大可选值 + max: { + type: [Number, String], + default: uni.$u.props.slider.max + }, + // 步长,取值必须大于 0,并且可被(max - min)整除 + step: { + type: [Number, String], + default: uni.$u.props.slider.step + }, + // 当前取值 + value: { + type: [Number, String], + default: uni.$u.props.slider.value + }, + // 滑块右侧已选择部分的背景色 + activeColor: { + type: String, + default: uni.$u.props.slider.activeColor + }, + // 滑块左侧未选择部分的背景色 + inactiveColor: { + type: String, + default: uni.$u.props.slider.inactiveColor + }, + // 滑块的大小,取值范围为 12 - 28 + blockSize: { + type: [Number, String], + default: uni.$u.props.slider.blockSize + }, + // 滑块的颜色 + blockColor: { + type: String, + default: uni.$u.props.slider.blockColor + }, + // 禁用状态 + disabled: { + type: Boolean, + default: uni.$u.props.slider.disabled + }, + // 是否显示当前的选择值 + showValue: { + type: Boolean, + default: uni.$u.props.slider.showValue + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-slider/u-slider.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/u-slider.vue new file mode 100644 index 000000000..80ebbedf6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-slider/u-slider.vue @@ -0,0 +1,55 @@ +<template> + <view + class="u-slider" + :style="[$u.addStyle(customStyle)]" + > + <slider + :min="min" + :max="max" + :step="step" + :value="value" + :activeColor="activeColor" + :inactiveColor="inactiveColor" + :blockSize="$u.getPx(blockSize)" + :blockColor="blockColor" + :showValue="showValue" + :disabled="disabled" + @changing="changingHandler" + @change="changeHandler" + ></slider> + </view> +</template> + +<script> + import props from './props.js' + export default { + name: 'u--slider', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + methods: { + // 拖动过程中触发 + changingHandler(e) { + const { + value + } = e.detail + // 更新v-model的值 + this.$emit('input', value) + // 触发事件 + this.$emit('changing', value) + }, + // 滑动结束时触发 + changeHandler(e) { + const { + value + } = e.detail + // 更新v-model的值 + this.$emit('input', value) + // 触发事件 + this.$emit('change', value) + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-status-bar/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-status-bar/props.js new file mode 100644 index 000000000..64b9e63ae --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-status-bar/props.js @@ -0,0 +1,8 @@ +export default { + props: { + bgColor: { + type: String, + default: uni.$u.props.statusBar.bgColor + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-status-bar/u-status-bar.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-status-bar/u-status-bar.vue new file mode 100644 index 000000000..ed91373aa --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-status-bar/u-status-bar.vue @@ -0,0 +1,46 @@ +<template> + <view + :style="[style]" + class="u-status-bar" + > + <slot /> + </view> +</template> + +<script> + import props from './props.js'; + /** + * StatbusBar 状态栏占位 + * @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。 + * @tutorial https://uviewui.com/components/statusBar.html + * @property {String} bgColor 背景色 (默认 'transparent' ) + * @property {String | Object} customStyle 自定义样式 + * @example <u-status-bar></u-status-bar> + */ + export default { + name: 'u-status-bar', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + } + }, + computed: { + style() { + const style = {} + // 状态栏高度,由于某些安卓和微信开发工具无法识别css的顶部状态栏变量,所以使用js获取的方式 + style.height = uni.$u.addUnit(uni.$u.sys().statusBarHeight, 'px') + style.backgroundColor = this.bgColor + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + } +</script> + +<style lang="scss" scoped> + .u-status-bar { + // nvue会默认100%,如果nvue下,显式写100%的话,会导致宽度不为100%而异常 + /* #ifndef APP-NVUE */ + width: 100%; + /* #endif */ + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-steps-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-steps-item/props.js new file mode 100644 index 000000000..825727a10 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-steps-item/props.js @@ -0,0 +1,24 @@ +export default { + props: { + // 标题 + title: { + type: [String, Number], + default: uni.$u.props.stepsItem.title + }, + // 描述文本 + desc: { + type: [String, Number], + default: uni.$u.props.stepsItem.desc + }, + // 图标大小 + iconSize: { + type: [String, Number], + default: uni.$u.props.stepsItem.iconSize + }, + // 当前步骤是否处于失败状态 + error: { + type: Boolean, + default: uni.$u.props.stepsItem.error + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-steps-item/u-steps-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-steps-item/u-steps-item.vue new file mode 100644 index 000000000..342fa630b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-steps-item/u-steps-item.vue @@ -0,0 +1,316 @@ +<template> + <view class="u-steps-item" ref="u-steps-item" :class="[`u-steps-item--${parentData.direction}`]"> + <view class="u-steps-item__line" v-if="index + 1 < childLength" + :class="[`u-steps-item__line--${parentData.direction}`]" :style="[lineStyle]"></view> + <view class="u-steps-item__wrapper" + :class="[`u-steps-item__wrapper--${parentData.direction}`, parentData.dot && `u-steps-item__wrapper--${parentData.direction}--dot`]"> + <slot name="icon"> + <view class="u-steps-item__wrapper__dot" v-if="parentData.dot" :style="{ + backgroundColor: statusColor + }"> + + </view> + <view class="u-steps-item__wrapper__icon" v-else-if="parentData.activeIcon || parentData.inactiveIcon"> + <u-icon :name="index <= parentData.current ? parentData.activeIcon : parentData.inactiveIcon" + :size="iconSize" + :color="index <= parentData.current ? parentData.activeColor : parentData.inactiveColor"> + </u-icon> + </view> + <view v-else :style="{ + backgroundColor: statusClass === 'process' ? parentData.activeColor : 'transparent', + borderColor: statusColor + }" class="u-steps-item__wrapper__circle"> + <text v-if="statusClass === 'process' || statusClass === 'wait'" + class="u-steps-item__wrapper__circle__text" :style="{ + color: index == parentData.current ? '#ffffff' : parentData.inactiveColor + }">{{ index + 1}}</text> + <u-icon v-else :color="statusClass === 'error' ? 'error' : parentData.activeColor" size="12" + :name="statusClass === 'error' ? 'close' : 'checkmark'"></u-icon> + </view> + </slot> + </view> + <view class="u-steps-item__content" :class="[`u-steps-item__content--${parentData.direction}`]" + :style="[contentStyle]"> + <u--text :text="title" :type="parentData.current == index ? 'main' : 'content'" lineHeight="20px" + :size="parentData.current == index ? 14 : 13"></u--text> + <slot name="desc"> + <u--text :text="desc" type="tips" size="12"></u--text> + </slot> + </view> + <!-- <view + class="u-steps-item__line" + v-if="showLine && parentData.direction === 'column'" + :class="[`u-steps-item__line--${parentData.direction}`]" + :style="[lineStyle]" + ></view> --> + </view> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * StepsItem 步骤条的子组件 + * @description 本组件需要和u-steps配合使用 + * @tutorial https://uviewui.com/components/steps.html + * @property {String} title 标题文字 + * @property {String} current 描述文本 + * @property {String | Number} iconSize 图标大小 (默认 17 ) + * @property {Boolean} error 当前步骤是否处于失败状态 (默认 false ) + * @example <u-steps current="0"><u-steps-item title="已出库" desc="10:35" ></u-steps-item></u-steps> + */ + export default { + name: 'u-steps-item', + mixins: [uni.$u.mpMixin, uni.$u.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.top = this.size.height / 2 + 'px' + } + style.backgroundColor = this.parent.children?.[this.index + 1]?.error ? uni.$u.color.error : 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 = uni.$u.color.error + 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() + uni.$u.sleep().then(() => { + this.getStepsItemRect() + }) + }, + methods: { + init() { + // 初始化数据 + this.updateParentData() + if (!this.parent) { + return uni.$u.error('u-steps-item必须要搭配u-steps组件使用') + } + this.index = this.parent.children.indexOf(this) + this.childLength = this.parent.children.length + }, + updateParentData() { + // 此方法在mixin中 + this.getParentData('u-steps') + }, + // 父组件数据发生变化 + updateFromParent() { + this.init() + }, + // 获取组件的尺寸,用于设置横线的位置 + getStepsItemRect() { + // #ifndef APP-NVUE + this.$uGetRect('.u-steps-item').then(size => { + this.size = size + }) + // #endif + + // #ifdef APP-NVUE + dom.getComponentRect(this.$refs['u-steps-item'], res => { + const { + size + } = res + this.size = size + }) + // #endif + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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: $u-tips-color; + border-style: solid; + @include flex(row); + align-items: center; + justify-content: center; + transition: background-color 0.3s; + + &__text { + color: $u-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: $u-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: $u-tips-color; + + &--row { + top: 10px; + height: 1px; + } + + &--column { + width: 1px; + left: 10px; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-steps/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-steps/props.js new file mode 100644 index 000000000..6d4c768da --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-steps/props.js @@ -0,0 +1,39 @@ +export default { + props: { + // 排列方向 + direction: { + type: String, + default: uni.$u.props.steps.direction + }, + // 设置第几个步骤 + current: { + type: [String, Number], + default: uni.$u.props.steps.current + }, + // 激活状态颜色 + activeColor: { + type: String, + default: uni.$u.props.steps.activeColor + }, + // 未激活状态颜色 + inactiveColor: { + type: String, + default: uni.$u.props.steps.inactiveColor + }, + // 激活状态的图标 + activeIcon: { + type: String, + default: uni.$u.props.steps.activeIcon + }, + // 未激活状态图标 + inactiveIcon: { + type: String, + default: uni.$u.props.steps.inactiveIcon + }, + // 是否显示点类型 + dot: { + type: Boolean, + default: uni.$u.props.steps.dot + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-steps/u-steps.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-steps/u-steps.vue new file mode 100644 index 000000000..3ab776488 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-steps/u-steps.vue @@ -0,0 +1,80 @@ +<template> + <view + class="u-steps" + :class="[`u-steps--${direction}`]" + > + <slot></slot> + </view> +</template> + +<script> + import props from './props.js'; + /** + * Steps 步骤条 + * @description 该组件一般用于完成一个任务要分几个步骤,标识目前处于第几步的场景。 + * @tutorial https://uviewui.com/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 <u-steps current="0"><u-steps-item title="已出库" desc="10:35" ></u-steps-item></u-steps> + */ + export default { + name: 'u-steps', + mixins: [uni.$u.mpMixin, uni.$u.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 => { + // 先判断子组件是否存在对应的方法 + uni.$u.test.func((child || {}).updateFromParent()) && child.updateFromParent() + }) + }, + // 接受子组件的通知,去修改其他子组件的数据 + updateFromChild() { + this.updateChildData() + } + }, + created() { + this.children = [] + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-steps { + @include flex; + + &--column { + flex-direction: column + } + + &--row { + flex-direction: row; + flex: 1; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-sticky/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-sticky/props.js new file mode 100644 index 000000000..c2ca8da21 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-sticky/props.js @@ -0,0 +1,40 @@ +export default { + props: { + // 吸顶容器到顶部某个距离的时候,进行吸顶,在H5平台,NavigationBar为44px + offsetTop: { + type: [String, Number], + default: uni.$u.props.sticky.offsetTop + }, + // 自定义导航栏的高度 + customNavHeight: { + type: [String, Number], + // #ifdef H5 + // H5端的导航栏属于“自定义”导航栏的范畴,因为它是非原生的,与普通元素一致 + default: 44, + // #endif + // #ifndef H5 + default: uni.$u.props.sticky.customNavHeight + // #endif + }, + // 是否开启吸顶功能 + disabled: { + type: Boolean, + default: uni.$u.props.sticky.disabled + }, + // 吸顶区域的背景颜色 + bgColor: { + type: String, + default: uni.$u.props.sticky.bgColor + }, + // z-index值 + zIndex: { + type: [String, Number], + default: uni.$u.props.sticky.zIndex + }, + // 列表中的索引值 + index: { + type: [String, Number], + default: uni.$u.props.sticky.index + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-sticky/u-sticky.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-sticky/u-sticky.vue new file mode 100644 index 000000000..ff746888b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-sticky/u-sticky.vue @@ -0,0 +1,212 @@ +<template> + <view + class="u-sticky" + :id="elId" + :style="[style]" + > + <view + :style="[stickyContent]" + class="u-sticky__content" + > + <slot /> + </view> + </view> +</template> + +<script> + import props from './props.js';; + /** + * sticky 吸顶 + * @description 该组件与CSS中position: sticky属性实现的效果一致,当组件达到预设的到顶部距离时, 就会固定在指定位置,组件位置大于预设的顶部距离时,会重新按照正常的布局排列。 + * @tutorial https://www.uviewui.com/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 组件的样式,对象形式 + * @event {Function} fixed 组件吸顶时触发 + * @event {Function} unfixed 组件取消吸顶时触发 + * @example <u-sticky offsetTop="200"><view>塞下秋来风景异,衡阳雁去无留意</view></u-sticky> + */ + export default { + name: 'u-sticky', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + cssSticky: false, // 是否使用css的sticky实现 + stickyTop: 0, // 吸顶的top值,因为可能受自定义导航栏影响,最终的吸顶值非offsetTop值 + elId: uni.$u.guid(), + 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 = uni.$u.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 uni.$u.deepMerge(uni.$u.addStyle(this.customStyle), style) + }, + // 吸顶内容的样式 + 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 : uni.$u.zIndex.sticky + } + }, + mounted() { + this.init() + }, + methods: { + init() { + this.getStickyTop() + // 判断使用的模式 + this.checkSupportCssSticky() + // 如果不支持css sticky,则使用js方案,此方案性能比不上css方案 + if (!this.cssSticky) { + !this.disabled && this.initObserveContent() + } + }, + initObserveContent() { + // 获取吸顶内容的高度,用于在js吸顶模式时,给父元素一个填充高度,防止"塌陷" + this.$uGetRect('#' + 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 = uni.$u.getPx(this.offsetTop) + uni.$u.getPx(this.customNavHeight) + }, + async checkSupportCssSticky() { + // #ifdef H5 + // H5,一般都是现代浏览器,是支持css sticky的,这里使用创建元素嗅探的形式判断 + if (this.checkCssStickyForH5()) { + this.cssSticky = true + } + // #endif + + // 如果安卓版本高于8.0,依然认为是支持css sticky的(因为安卓7在某些机型,可能不支持sticky) + if (uni.$u.os() === 'android' && Number(uni.$u.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 (uni.$u.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('.u-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 + } + }, + beforeDestroy() { + this.disconnectObserver('contentObserver') + } + } +</script> + +<style lang="scss" scoped> + .u-sticky { + /* #ifdef APP-VUE || MP-WEIXIN */ + // 此处默认写sticky属性,是为了给微信和APP通过uni.createSelectorQuery查询是否支持css sticky使用 + position: sticky; + /* #endif */ + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-subsection/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-subsection/props.js new file mode 100644 index 000000000..5675eaa05 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-subsection/props.js @@ -0,0 +1,49 @@ +export default { + props: { + // tab的数据 + list: { + type: Array, + default: uni.$u.props.subsection.list + }, + // 当前活动的tab的index + current: { + type: [String, Number], + default: uni.$u.props.subsection.current + }, + // 激活的颜色 + activeColor: { + type: String, + default: uni.$u.props.subsection.activeColor + }, + // 未激活的颜色 + inactiveColor: { + type: String, + default: uni.$u.props.subsection.inactiveColor + }, + // 模式选择,mode=button为按钮形式,mode=subsection时为分段模式 + mode: { + type: String, + default: uni.$u.props.subsection.mode + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: uni.$u.props.subsection.fontSize + }, + // 激活tab的字体是否加粗 + bold: { + type: Boolean, + default: uni.$u.props.subsection.bold + }, + // mode = button时,组件背景颜色 + bgColor: { + type: String, + default: uni.$u.props.subsection.bgColor + }, + // 从list元素对象中读取的键名 + keyName: { + type: String, + default: uni.$u.props.subsection.keyName + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-subsection/u-subsection.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-subsection/u-subsection.vue new file mode 100644 index 000000000..cc4d54057 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-subsection/u-subsection.vue @@ -0,0 +1,299 @@ +<template> + <view + class="u-subsection" + ref="u-subsection" + :class="[`u-subsection--${mode}`]" + :style="[$u.addStyle(customStyle), wrapperStyle]" + > + <view + class="u-subsection__bar" + ref="u-subsection__bar" + :style="[barStyle]" + :class="[ + mode === 'button' && 'u-subsection--button__bar', + current === 0 && + mode === 'subsection' && + 'u-subsection__bar--first', + current > 0 && + current < list.length - 1 && + mode === 'subsection' && + 'u-subsection__bar--center', + current === list.length - 1 && + mode === 'subsection' && + 'u-subsection__bar--last', + ]" + ></view> + <view + class="u-subsection__item" + :class="[ + `u-subsection__item--${index}`, + index < list.length - 1 && + 'u-subsection__item--no-border-right', + index === 0 && 'u-subsection__item--first', + index === list.length - 1 && 'u-subsection__item--last', + ]" + :ref="`u-subsection__item--${index}`" + :style="[itemStyle(index)]" + @tap="clickHandler(index)" + v-for="(item, index) in list" + :key="index" + > + <text + class="u-subsection__item__text" + :style="[textStyle(index)]" + >{{ getText(item) }}</text + > + </view> + </view> +</template> + +<script> +// #ifdef APP-NVUE +const dom = uni.requireNativePlugin("dom"); +const animation = uni.requireNativePlugin("animation"); +// #endif +import props from "./props.js"; +/** + * Subsection 分段器 + * @description 该分段器一般用于用户从几个选项中选择某一个的场景 + * @tutorial https://www.uviewui.com/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 <u-subsection :list="list" :current="curNow" @change="sectionChange"></u-subsection> + */ +export default { + name: "u-subsection", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 组件尺寸 + itemRect: { + width: 0, + height: 0, + }, + }; + }, + watch: { + list(newValue, oldValue) { + this.init(); + }, + current: { + immediate: true, + handler(n) { + // #ifdef APP-NVUE + // 在安卓nvue上,如果通过translateX进行位移,到最后一个时,会导致右侧无法绘制圆角 + // 故用animation模块进行位移 + const ref = this.$refs?.["u-subsection__bar"]?.ref; + // 不存在ref的时候(理解为第一次初始化时,需要渲染dom,进行一定延时再获取ref),这里的100ms是经过测试得出的结果(某些安卓需要延时久一点),勿随意修改 + uni.$u.sleep(ref ? 0 : 100).then(() => { + animation.transition(this.$refs["u-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 style; + }, + // 分段器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 = uni.$u.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() { + uni.$u.sleep().then(() => this.getRect()); + }, + // 判断展示文本 + getText(item) { + return typeof item === 'object' ? item[this.keyName] : item + }, + // 获取组件的尺寸 + getRect() { + // #ifndef APP-NVUE + this.$uGetRect(".u-subsection__item--0").then((size) => { + this.itemRect = size; + }); + // #endif + + // #ifdef APP-NVUE + const ref = this.$refs["u-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 "../../libs/css/components.scss"; + +.u-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; + border-radius: 3px !important; + } + } + + &--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> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/index - backup.wxs b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/index - backup.wxs new file mode 100644 index 000000000..04cab922f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-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('.u-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('.u-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('.u-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/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/index.wxs b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/index.wxs new file mode 100644 index 000000000..728275f33 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-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('.u-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('.u-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('.u-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/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/nvue - backup.js b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/nvue - backup.js new file mode 100644 index 000000000..6b9f116ab --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/nvue - backup.js @@ -0,0 +1,270 @@ +// nvue操作dom的库,用于获取dom的尺寸信息 +const dom = uni.requireNativePlugin('dom') +// nvue中用于操作元素动画的库,类似于uni.animation,只不过uni.animation不能用于nvue +const animation = uni.requireNativePlugin('animation') + +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) { + // if(n === true) { + // uni.$u.sleep(50).then(() => { + // this.openSwipeAction() + // }) + // } else { + // this.closeSwipeAction() + // } + } + } + }, + mounted() { + uni.$u.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['u-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[`u-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['u-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[`u-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['u-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[`u-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[`u-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/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/nvue.js b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/nvue.js new file mode 100644 index 000000000..118e4cf60 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/nvue.js @@ -0,0 +1,174 @@ +// nvue操作dom的库,用于获取dom的尺寸信息 +const dom = uni.requireNativePlugin('dom'); +const bindingX = uni.requireNativePlugin('bindingx'); +const animation = uni.requireNativePlugin('animation'); + +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() { + this.initialize() + }, + 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) > uni.$u.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((item, index) => { + return this.getRectByDom(this.$refs[`u-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: uni.$u.getDuration(this.duration, false), + timingFunction: 'ease-in-out' + }, () => { + this.moving = false + this.status = status + this.unbindBindingX() + }) + }, + // 获取元素ref + getContentRef() { + return this.$refs['u-swipe-action-item__content'].ref + }, + beforeDestroy() { + this.unbindBindingX() + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/props.js new file mode 100644 index 000000000..ed82a420a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/props.js @@ -0,0 +1,41 @@ +export default { + props: { + // 控制打开或者关闭 + show: { + type: Boolean, + default: uni.$u.props.swipeActionItem.show + }, + // 标识符,如果是v-for,可用index索引值 + name: { + type: [String, Number], + default: uni.$u.props.swipeActionItem.name + }, + // 是否禁用 + disabled: { + type: Boolean, + default: uni.$u.props.swipeActionItem.disabled + }, + // 是否自动关闭其他swipe按钮组 + autoClose: { + type: Boolean, + default: uni.$u.props.swipeActionItem.autoClose + }, + // 滑动距离阈值,只有大于此值,才被认为是要打开菜单 + threshold: { + type: Number, + default: uni.$u.props.swipeActionItem.threshold + }, + // 右侧按钮内容 + options: { + type: Array, + default() { + return uni.$u.props.swipeActionItem.rightOptions + } + }, + // 动画过渡时间,单位ms + duration: { + type: [String, Number], + default: uni.$u.props.swipeActionItem.duration + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/u-swipe-action-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/u-swipe-action-item.vue new file mode 100644 index 000000000..979779e4c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/u-swipe-action-item.vue @@ -0,0 +1,190 @@ +<template> + <view class="u-swipe-action-item" ref="u-swipe-action-item"> + <view class="u-swipe-action-item__right"> + <slot name="button"> + <view v-for="(item,index) in options" :key="index" class="u-swipe-action-item__right__button" + :ref="`u-swipe-action-item__right__button-${index}`" :style="[{ + alignItems: item.style && item.style.borderRadius ? 'center' : 'stretch' + }]" @tap="buttonClickHandler(item, index)"> + <view class="u-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]"> + <u-icon v-if="item.icon" :name="item.icon" + :color="item.style && item.style.color ? item.style.color : '#ffffff'" + :size="item.iconSize ? $u.addUnit(item.iconSize) : item.style && item.style.fontSize ? $u.getPx(item.style.fontSize) * 1.2 : 17" + :customStyle="{ + marginRight: item.text ? '2px' : 0 + }"></u-icon> + <text v-if="item.text" class="u-swipe-action-item__right__button__wrapper__text u-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> + <!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ --> + <view class="u-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="u-swipe-action-item__content" ref="u-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 '../../libs/mixin/touch.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.uviewui.com/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 <u-swipe-action><u-swipe-action-item :options="options1" ></u-swipe-action-item></u-swipe-action> + */ + export default { + name: 'u-swipe-action-item', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props, touch], + // #ifdef APP-NVUE + mixins: [uni.$u.mpMixin, uni.$u.mixin, props, nvue, touch], + // #endif + // #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ + mixins: [uni.$u.mpMixin, uni.$u.mixin, props, touch, wxs], + // #endif + data() { + return { + // 按钮的尺寸信息 + size: {}, + // 父组件u-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() { + this.init() + }, + methods: { + init() { + // 初始化父组件数据 + this.updateParentData() + // #ifndef APP-NVUE + uni.$u.sleep().then(() => { + this.queryRect() + }) + // #endif + }, + updateParentData() { + // 此方法在mixin中 + this.getParentData('u-swipe-action') + }, + // #ifndef APP-NVUE + // 查询节点 + queryRect() { + this.$uGetRect('.u-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> + @import "../../libs/css/components.scss"; + + .u-swipe-action-item { + position: relative; + overflow: hidden; + /* #ifndef APP-NVUE || MP-WEIXIN */ + touch-action: none; + /* #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/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/wxs.js b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action-item/wxs.js new file mode 100644 index 000000000..ee49c100d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-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/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action/props.js new file mode 100644 index 000000000..3a84536a5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action/props.js @@ -0,0 +1,9 @@ +export default { + props: { + // 是否自动关闭其他swipe按钮组 + autoClose: { + type: Boolean, + default: uni.$u.props.swipeAction.autoClose + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action/u-swipe-action.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action/u-swipe-action.vue new file mode 100644 index 000000000..ad8f019ed --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swipe-action/u-swipe-action.vue @@ -0,0 +1,67 @@ +<template> + <view class="u-swipe-action"> + <slot></slot> + </view> +</template> + +<script> + import props from './props.js'; + /** + * SwipeAction 滑动单元格 + * @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作 + * @tutorial https://www.uviewui.com/components/swipeAction.html + * @property {Boolean} autoClose 是否自动关闭其他swipe按钮组 + * @event {Function(index)} click 点击组件时触发 + * @example <u-swipe-action><u-swipe-action-item :rightOptions="options1" ></u-swipe-action-item></u-swipe-action> + */ + export default { + name: 'u-swipe-action', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return {} + }, + provide() { + return { + swipeAction: this + } + }, + computed: { + // 这里computed的变量,都是子组件u-swipe-action-item需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化 + // 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-swipe-action-item) + // 拉取父组件新的变化后的参数 + parentData() { + return [this.autoClose] + } + }, + watch: { + // 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件 + parentData() { + if (this.children.length) { + this.children.map(child => { + // 判断子组件(u-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> + +<style lang="scss" scoped> + +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-swiper-indicator/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-swiper-indicator/props.js new file mode 100644 index 000000000..302aca7c8 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swiper-indicator/props.js @@ -0,0 +1,29 @@ +export default { + props: { + // 轮播的长度 + length: { + type: [String, Number], + default: uni.$u.props.swiperIndicator.length + }, + // 当前处于活动状态的轮播的索引 + current: { + type: [String, Number], + default: uni.$u.props.swiperIndicator.current + }, + // 指示器非激活颜色 + indicatorActiveColor: { + type: String, + default: uni.$u.props.swiperIndicator.indicatorActiveColor + }, + // 指示器的激活颜色 + indicatorInactiveColor: { + type: String, + default: uni.$u.props.swiperIndicator.indicatorInactiveColor + }, + // 指示器模式,line-线型,dot-点型 + indicatorMode: { + type: String, + default: uni.$u.props.swiperIndicator.indicatorMode + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-swiper-indicator/u-swiper-indicator.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-swiper-indicator/u-swiper-indicator.vue new file mode 100644 index 000000000..8923e13c1 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swiper-indicator/u-swiper-indicator.vue @@ -0,0 +1,110 @@ +<template> + <view class="u-swiper-indicator"> + <view + class="u-swiper-indicator__wrapper" + v-if="indicatorMode === 'line'" + :class="[`u-swiper-indicator__wrapper--${indicatorMode}`]" + :style="{ + width: $u.addUnit(lineWidth * length), + backgroundColor: indicatorInactiveColor + }" + > + <view + class="u-swiper-indicator__wrapper--line__bar" + :style="[lineStyle]" + ></view> + </view> + <view + class="u-swiper-indicator__wrapper" + v-if="indicatorMode === 'dot'" + > + <view + class="u-swiper-indicator__wrapper__dot" + v-for="(item, index) in length" + :key="index" + :class="[index === current && 'u-swiper-indicator__wrapper__dot--active']" + :style="[dotStyle(index)]" + > + + </view> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * SwiperIndicator 轮播图指示器 + * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用, + * @tutorial https://www.uviewui.com/components/swiper.html + * @property {String | Number} length 轮播的长度(默认 0 ) + * @property {String | Number} current 当前处于活动状态的轮播的索引(默认 0 ) + * @property {String} indicatorActiveColor 指示器非激活颜色 + * @property {String} indicatorInactiveColor 指示器的激活颜色 + * @property {String} indicatorMode 指示器模式(默认 'line' ) + * @example <u-swiper :list="list4" indicator keyName="url" :autoplay="false"></u-swiper> + */ + export default { + name: 'u-swiper-indicator', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + lineWidth: 22 + } + }, + computed: { + // 指示器为线型的样式 + lineStyle() { + let style = {} + style.width = uni.$u.addUnit(this.lineWidth) + style.transform = `translateX(${ uni.$u.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 "../../libs/css/components.scss"; + + .u-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/yudao-ui-app/uni_modules/uview-ui/components/u-swiper/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-swiper/props.js new file mode 100644 index 000000000..bac6d31c7 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swiper/props.js @@ -0,0 +1,125 @@ +export default { + props: { + // 列表数组,元素可为字符串,如为对象可通过keyName指定目标属性名 + list: { + type: Array, + default: uni.$u.props.swiper.list + }, + // 是否显示面板指示器 + indicator: { + type: Boolean, + default: uni.$u.props.swiper.indicator + }, + // 指示器非激活颜色 + indicatorActiveColor: { + type: String, + default: uni.$u.props.swiper.indicatorActiveColor + }, + // 指示器的激活颜色 + indicatorInactiveColor: { + type: String, + default: uni.$u.props.swiper.indicatorInactiveColor + }, + // 指示器样式,可通过bottom,left,right进行定位 + indicatorStyle: { + type: [String, Object], + default: uni.$u.props.swiper.indicatorStyle + }, + // 指示器模式,line-线型,dot-点型 + indicatorMode: { + type: String, + default: uni.$u.props.swiper.indicatorMode + }, + // 是否自动切换 + autoplay: { + type: Boolean, + default: uni.$u.props.swiper.autoplay + }, + // 当前所在滑块的 index + current: { + type: [String, Number], + default: uni.$u.props.swiper.current + }, + // 当前所在滑块的 item-id ,不能与 current 被同时指定 + currentItemId: { + type: String, + default: uni.$u.props.swiper.currentItemId + }, + // 滑块自动切换时间间隔 + interval: { + type: [String, Number], + default: uni.$u.props.swiper.interval + }, + // 滑块切换过程所需时间 + duration: { + type: [String, Number], + default: uni.$u.props.swiper.duration + }, + // 播放到末尾后是否重新回到开头 + circular: { + type: Boolean, + default: uni.$u.props.swiper.circular + }, + // 前边距,可用于露出前一项的一小部分,nvue和支付宝不支持 + previousMargin: { + type: [String, Number], + default: uni.$u.props.swiper.previousMargin + }, + // 后边距,可用于露出后一项的一小部分,nvue和支付宝不支持 + nextMargin: { + type: [String, Number], + default: uni.$u.props.swiper.nextMargin + }, + // 当开启时,会根据滑动速度,连续滑动多屏,支付宝不支持 + acceleration: { + type: Boolean, + default: uni.$u.props.swiper.acceleration + }, + // 同时显示的滑块数量,nvue、支付宝小程序不支持 + displayMultipleItems: { + type: Number, + default: uni.$u.props.swiper.displayMultipleItems + }, + // 指定swiper切换缓动动画类型,有效值:default、linear、easeInCubic、easeOutCubic、easeInOutCubic + // 只对微信小程序有效 + easingFunction: { + type: String, + default: uni.$u.props.swiper.easingFunction + }, + // list数组中指定对象的目标属性名 + keyName: { + type: String, + default: uni.$u.props.swiper.keyName + }, + // 图片的裁剪模式 + imgMode: { + type: String, + default: uni.$u.props.swiper.imgMode + }, + // 组件高度 + height: { + type: [String, Number], + default: uni.$u.props.swiper.height + }, + // 背景颜色 + bgColor: { + type: String, + default: uni.$u.props.swiper.bgColor + }, + // 组件圆角,数值或带单位的字符串 + radius: { + type: [String, Number], + default: uni.$u.props.swiper.radius + }, + // 是否加载中 + loading: { + type: Boolean, + default: uni.$u.props.swiper.loading + }, + // 是否显示标题,要求数组对象中有title属性 + showTitle: { + type: Boolean, + default: uni.$u.props.swiper.showTitle + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-swiper/u-swiper.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-swiper/u-swiper.vue new file mode 100644 index 000000000..0cfb2294f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-swiper/u-swiper.vue @@ -0,0 +1,255 @@ +<template> + <view + class="u-swiper" + :style="{ + backgroundColor: bgColor, + height: $u.addUnit(height), + borderRadius: $u.addUnit(radius) + }" + > + <view + class="u-swiper__loading" + v-if="loading" + > + <u-loading-icon mode="circle"></u-loading-icon> + </view> + <swiper + v-else + class="u-swiper__wrapper" + :style="{ + height: $u.addUnit(height), + }" + @change="change" + :circular="circular" + :interval="interval" + :duration="duration" + :autoplay="autoplay" + :current="current" + :currentItemId="currentItemId" + :previousMargin="$u.addUnit(previousMargin)" + :nextMargin="$u.addUnit(nextMargin)" + :acceleration="acceleration" + :displayMultipleItems="displayMultipleItems" + :easingFunction="easingFunction" + > + <swiper-item + class="u-swiper__wrapper__item" + v-for="(item, index) in list" + :key="index" + > + <view + class="u-swiper__wrapper__item__wrapper" + :style="[itemStyle(index)]" + > + <!-- 在nvue中,image图片的宽度默认为屏幕宽度,需要通过flex:1撑开,另外必须设置高度才能显示图片 --> + <image + class="u-swiper__wrapper__item__wrapper__image" + v-if="getItemType(item) === 'image'" + :src="getSource(item)" + :mode="imgMode" + @tap="clickHandler(index)" + :style="{ + height: $u.addUnit(height), + borderRadius: $u.addUnit(radius) + }" + ></image> + <video + class="u-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 && $u.test.object(item) && item.title ? item.title : ''" + :style="{ + height: $u.addUnit(height) + }" + controls + @tap="clickHandler(index)" + ></video> + <text + v-if="showTitle && $u.test.object(item) && item.title && $u.test.image(getSource(item))" + class="u-swiper__wrapper__item__wrapper__title u-line-1" + >{{ item.title }}</text> + </view> + </swiper-item> + </swiper> + <view class="u-swiper__indicator" :style="[$u.addStyle(indicatorStyle)]"> + <slot name="indicator"> + <u-swiper-indicator + v-if="!loading && indicator && !showTitle" + :indicatorActiveColor="indicatorActiveColor" + :indicatorInactiveColor="indicatorInactiveColor" + :length="list.length" + :current="currentIndex" + :indicatorMode="indicatorMode" + ></u-swiper-indicator> + </slot> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * Swiper 轮播图 + * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用, + * @tutorial https://www.uviewui.com/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 <u-swiper :list="list4" keyName="url" :autoplay="false"></u-swiper> + */ + export default { + name: 'u-swiper', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + 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 = uni.$u.addUnit(this.radius) + if (index !== this.currentIndex) style.transform = 'scale(0.92)' + } + // #endif + return style + } + } + }, + methods: { + getItemType(item) { + if (typeof item === 'string') return uni.$u.test.video(this.getSource(item)) ? 'video' : 'image' + if (typeof item === 'object' && this.keyName) { + if (!item.type) return uni.$u.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 uni.$u.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 (uni.$u.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> + @import "../../libs/css/components.scss"; + + .u-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; + padding: 12rpx 24rpx; + color: #FFFFFF; + flex: 1; + } + } + } + } + + &__indicator { + position: absolute; + bottom: 10px; + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-switch/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-switch/props.js new file mode 100644 index 000000000..4eef9637a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-switch/props.js @@ -0,0 +1,54 @@ +export default { + props: { + // 是否为加载中状态 + loading: { + type: Boolean, + default: uni.$u.props.switch.loading + }, + // 是否为禁用装填 + disabled: { + type: Boolean, + default: uni.$u.props.switch.disabled + }, + // 开关尺寸,单位px + size: { + type: [String, Number], + default: uni.$u.props.switch.size + }, + // 打开时的背景颜色 + activeColor: { + type: String, + default: uni.$u.props.switch.activeColor + }, + // 关闭时的背景颜色 + inactiveColor: { + type: String, + default: uni.$u.props.switch.inactiveColor + }, + // 通过v-model双向绑定的值 + value: { + type: [Boolean, String, Number], + default: uni.$u.props.switch.value + }, + // switch打开时的值 + activeValue: { + type: [String, Number, Boolean], + default: uni.$u.props.switch.activeValue + }, + // switch关闭时的值 + inactiveValue: { + type: [String, Number, Boolean], + default: uni.$u.props.switch.inactiveValue + }, + // 是否开启异步变更,开启后需要手动控制输入值 + asyncChange: { + type: Boolean, + default: uni.$u.props.switch.asyncChange + }, + // 圆点与外边框的距离 + space: { + type: [String, Number], + default: uni.$u.props.switch.space + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-switch/u-switch.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-switch/u-switch.vue new file mode 100644 index 000000000..6f8577b6d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-switch/u-switch.vue @@ -0,0 +1,177 @@ +<template> + <view + class="u-switch" + :class="[disabled && 'u-switch--disabled']" + :style="[switchStyle, $u.addStyle(customStyle)]" + @tap="clickHandler" + > + <view + class="u-switch__bg" + :style="[bgStyle]" + > + </view> + <view + class="u-switch__node" + :class="[value && 'u-switch__node--on']" + :style="[nodeStyle]" + ref="u-switch__node" + > + <u-loading-icon + :show="loading" + mode="circle" + timingFunction='linear' + :color="value ? activeColor : '#AAABAD'" + :size="size * 0.6" + /> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * switch 开关选择器 + * @description 选择开关一般用于只有两个选择,且只能选其一的场景。 + * @tutorial https://www.uviewui.com/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 <u-switch v-model="checked" active-color="red" inactive-color="#eee"></u-switch> + */ + export default { + name: "u-switch", + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + watch: { + value: { + immediate: true, + handler(n) { + if(n !== this.inactiveValue && n !== this.activeValue) { + uni.$u.error('v-model绑定的值必须为inactiveValue、activeValue二者之一') + } + } + } + }, + data() { + return { + bgColor: '#ffffff' + } + }, + computed: { + isActive(){ + return this.value === this.activeValue; + }, + switchStyle() { + let style = {} + // 这里需要加2,是为了腾出边框的距离,否则圆点node会和外边框紧贴在一起 + style.width = uni.$u.addUnit(this.size * 2 + 2) + style.height = uni.$u.addUnit(Number(this.size) + 2) + // style.borderColor = this.value ? 'rgba(0, 0, 0, 0)' : 'rgba(0, 0, 0, 0.12)' + // 如果自定义了“非激活”演示,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 = uni.$u.addUnit(this.size - this.space) + style.height = uni.$u.addUnit(this.size - this.space) + const translateX = this.isActive ? uni.$u.addUnit(this.space) : uni.$u.addUnit(this.size); + style.transform = `translateX(-${translateX})` + return style + }, + bgStyle() { + let style = {} + // 这里配置一个多余的元素在HTML中,是为了让switch切换时,有更良好的背景色扩充体验(见实际效果) + style.width = uni.$u.addUnit(Number(this.size) * 2 - this.size / 2) + style.height = uni.$u.addUnit(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) + } + // 放到下一个生命周期,因为双向绑定的value修改父组件状态需要时间,且是异步的 + this.$nextTick(() => { + this.$emit('change', oldValue) + }) + } + } + } + }; +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar-item/props.js new file mode 100644 index 000000000..a2e6a24c6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar-item/props.js @@ -0,0 +1,35 @@ +export default { + props: { + // item标签的名称,作为与u-tabbar的value参数匹配的标识符 + name: { + type: [String, Number, null], + default: uni.$u.props.tabbarItem.name + }, + // uView内置图标或者绝对路径的图片 + icon: { + icon: String, + default: uni.$u.props.tabbarItem.icon + }, + // 右上角的角标提示信息 + badge: { + type: [String, Number, null], + default: uni.$u.props.tabbarItem.badge + }, + // 是否显示圆点,将会覆盖badge参数 + dot: { + type: Boolean, + default: uni.$u.props.tabbarItem.dot + }, + // 描述文本 + text: { + type: String, + default: uni.$u.props.tabbarItem.text + }, + // 控制徽标的位置,对象或者字符串形式,可以设置top和right属性 + badgeStyle: { + type: [Object, String], + default: uni.$u.props.tabbarItem.badgeStyle + } + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar-item/u-tabbar-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar-item/u-tabbar-item.vue new file mode 100644 index 000000000..8ee00cf8a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar-item/u-tabbar-item.vue @@ -0,0 +1,142 @@ +<template> + <view + class="u-tabbar-item" + :style="[$u.addStyle(customStyle)]" + @tap="clickHandler" + > + <view class="u-tabbar-item__icon"> + <u-icon + v-if="icon" + :name="icon" + :color="isActive? parentData.activeColor : parentData.inactiveColor" + :size="20" + ></u-icon> + <template v-else> + <slot + v-if="isActive" + name="active-icon" + /> + <slot + v-else + name="inactive-icon" + /> + </template> + <u-badge + absolute + :offset="[0, dot ? '34rpx' : badge > 9 ? '14rpx' : '20rpx']" + :customStyle="badgeStyle" + :isDot="dot" + :value="badge || (dot ? 1 : null)" + :show="dot || badge > 0" + ></u-badge> + </view> + + <slot name="text"> + <text + class="u-tabbar-item__text" + :style="{ + color: isActive? parentData.activeColor : parentData.inactiveColor + }" + >{{ text }}</text> + </slot> + </view> +</template> + +<script> + import props from './props.js'; + /** + * TabbarItem 底部导航栏子组件 + * @description 此组件提供了自定义tabbar的能力。 + * @tutorial https://www.uviewui.com/components/tabbar.html + * @property {String | Number} name item标签的名称,作为与u-tabbar的value参数匹配的标识符 + * @property {String} icon uView内置图标或者绝对路径的图片 + * @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 <u-tabbar :value="value2" :placeholder="false" @change="name => value2 = name" :fixed="false" :safeAreaInsetBottom="false"><u-tabbar-item text="首页" icon="home" dot ></u-tabbar-item></u-tabbar> + */ + export default { + name: 'u-tabbar-item', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + isActive: false, // 是否处于激活状态 + parentData: { + value: null, + activeColor: '', + inactiveColor: '' + } + } + }, + created() { + this.init() + }, + methods: { + init() { + // 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用 + this.updateParentData() + if (!this.parent) { + uni.$u.error('u-tabbar-item必须搭配u-tabbar组件使用') + } + // 本子组件在u-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('u-tabbar') + }, + // 此方法将会被父组件u-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 "../../libs/css/components.scss"; + + .u-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: $u-content-color; + } + } + + /* #ifdef MP */ + // 由于小程序都使用shadow DOM形式实现,需要给影子宿主设置flex: 1才能让其撑开 + :host { + flex: 1 + } + /* #endif */ +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar/props.js new file mode 100644 index 000000000..7f8171cd3 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar/props.js @@ -0,0 +1,44 @@ +export default { + props: { + // 当前匹配项的name + value: { + type: [String, Number, null], + default: uni.$u.props.tabbar.value + }, + // 是否为iPhoneX留出底部安全距离 + safeAreaInsetBottom: { + type: Boolean, + default: uni.$u.props.tabbar.safeAreaInsetBottom + }, + // 是否显示上方边框 + border: { + type: Boolean, + default: uni.$u.props.tabbar.border + }, + // 元素层级z-index + zIndex: { + type: [String, Number], + default: uni.$u.props.tabbar.zIndex + }, + // 选中标签的颜色 + activeColor: { + type: String, + default: uni.$u.props.tabbar.activeColor + }, + // 未选中标签的颜色 + inactiveColor: { + type: String, + default: uni.$u.props.tabbar.inactiveColor + }, + // 是否固定在底部 + fixed: { + type: Boolean, + default: uni.$u.props.tabbar.fixed + }, + // fixed定位固定在底部时,是否生成一个等高元素防止塌陷 + placeholder: { + type: Boolean, + default: uni.$u.props.tabbar.placeholder + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar/u-tabbar.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar/u-tabbar.vue new file mode 100644 index 000000000..953f33af1 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tabbar/u-tabbar.vue @@ -0,0 +1,141 @@ +<template> + <view class="u-tabbar"> + <view + class="u-tabbar__content" + ref="u-tabbar__content" + @touchmove.stop.prevent="noop" + :class="[border && 'u-border-top', fixed && 'u-tabbar--fixed']" + :style="[tabbarStyle]" + > + <view class="u-tabbar__content__item-wrapper"> + <slot /> + </view> + <u-safe-bottom v-if="safeAreaInsetBottom"></u-safe-bottom> + </view> + <view + class="u-tabbar__placeholder" + v-if="placeholder" + :style="{ + height: placeholderHeight + 'px', + }" + ></view> + </view> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + /** + * Tabbar 底部导航栏 + * @description 此组件提供了自定义tabbar的能力。 + * @tutorial https://www.uviewui.com/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 <u-tabbar :value="value2" :placeholder="false" @change="name => value2 = name" :fixed="false" :safeAreaInsetBottom="false"><u-tabbar-item text="首页" icon="home" dot ></u-tabbar-item></u-tabbar> + */ + export default { + name: 'u-tabbar', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + placeholderHeight: 0 + } + }, + computed: { + tabbarStyle() { + const style = { + zIndex: this.zIndex + } + // 合并来自父组件的customStyle样式 + return uni.$u.deepMerge(style, uni.$u.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 uni.$u.sleep(20) + // #ifndef APP-NVUE + this.$uGetRect('.u-tabbar__content').then(({height = 50}) => { + // 修复IOS safearea bottom 未填充高度 + this.placeholderHeight = height + }) + // #endif + + // #ifdef APP-NVUE + dom.getComponentRect(this.$refs['u-tabbar__content'], (res) => { + const { + size + } = res + this.placeholderHeight = size.height + }) + // #endif + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-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/yudao-ui-app/uni_modules/uview-ui/components/u-table/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-table/props.js new file mode 100644 index 000000000..7c1133117 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-table/props.js @@ -0,0 +1,5 @@ +export default { + props: { + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-table/u-table.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-table/u-table.vue new file mode 100644 index 000000000..b64ce69c5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-table/u-table.vue @@ -0,0 +1,29 @@ +<template> + <view class="u-table"> + + </view> +</template> + +<script> + import props from './props.js'; + /** + * Table 表格 + * @description 表格组件一般用于展示大量结构化数据的场景 本组件标签类似HTML的table表格,由table、tr、th、td四个组件组成 + * @tutorial https://www.uviewui.com/components/table.html + * @example <u-table><u-tr><u-th>学校</u-th </u-tr> <u-tr><u-td>浙江大学</u-td> </u-tr> <u-tr><u-td>清华大学</u-td> </u-tr></u-table> + */ + export default { + name: 'u-table', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tabs-item/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-tabs-item/props.js new file mode 100644 index 000000000..7c1133117 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tabs-item/props.js @@ -0,0 +1,5 @@ +export default { + props: { + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tabs-item/u-tabs-item.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-tabs-item/u-tabs-item.vue new file mode 100644 index 000000000..effb7960c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tabs-item/u-tabs-item.vue @@ -0,0 +1,29 @@ +<template> + <swiper-item> + <slot /> + </swiper-item> +</template> + +<script> + import props from './props.js'; + /** + * TabsItem tabs标签组件的自组件 + * @description tabs标签组件,在标签多的时候,可以配置为左右滑动,标签少的时候,可以禁止滑动。 该组件的一个特点是配置为滚动模式时,激活的tab会自动移动到组件的中间位置。 + * @tutorial https://www.uviewui.com/components/tabs.html + * @property {type} prop_name + * @event {Function()} + * @example + */ + export default { + name: 'u-tabs-item', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + + } + } + } +</script> + +<style> +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tabs/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-tabs/props.js new file mode 100644 index 000000000..2cfa41f80 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tabs/props.js @@ -0,0 +1,64 @@ +export default { + props: { + // 滑块的移动过渡时间,单位ms + duration: { + type: Number, + default: uni.$u.props.tabs.duration + }, + // tabs标签数组 + list: { + type: Array, + default: uni.$u.props.tabs.list + }, + // 滑块颜色 + lineColor: { + type: String, + default: uni.$u.props.tabs.lineColor + }, + // 菜单选择中时的样式 + activeStyle: { + type: [String, Object], + default: uni.$u.props.tabs.activeStyle + }, + // 菜单非选中时的样式 + inactiveStyle: { + type: [String, Object], + default: uni.$u.props.tabs.inactiveStyle + }, + // 滑块长度 + lineWidth: { + type: [String, Number], + default: uni.$u.props.tabs.lineWidth + }, + // 滑块高度 + lineHeight: { + type: [String, Number], + default: uni.$u.props.tabs.lineHeight + }, + // 滑块背景显示大小,当滑块背景设置为图片时使用 + lineBgSize: { + type: String, + default: uni.$u.props.tabs.lineBgSize + }, + // 菜单item的样式 + itemStyle: { + type: [String, Object], + default: uni.$u.props.tabs.itemStyle + }, + // 菜单是否可滚动 + scrollable: { + type: Boolean, + default: uni.$u.props.tabs.scrollable + }, + // 当前选中标签的索引 + current: { + type: [Number, String], + default: uni.$u.props.tabs.current + }, + // 默认读取的键名 + keyName: { + type: String, + default: uni.$u.props.tabs.keyName + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tabs/u-tabs.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-tabs/u-tabs.vue new file mode 100644 index 000000000..9c54cc1ec --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tabs/u-tabs.vue @@ -0,0 +1,354 @@ +<template> + <view class="u-tabs"> + <view class="u-tabs__wrapper"> + <slot name="left" /> + <view class="u-tabs__wrapper__scroll-view-wrapper"> + <scroll-view + :scroll-x="scrollable" + :scroll-left="scrollLeft" + scroll-with-animation + class="u-tabs__wrapper__scroll-view" + :show-scrollbar="false" + ref="u-tabs__wrapper__scroll-view" + > + <view + class="u-tabs__wrapper__nav" + ref="u-tabs__wrapper__nav" + > + <view + class="u-tabs__wrapper__nav__item" + v-for="(item, index) in list" + :key="index" + @tap="clickHandler(item, index)" + :ref="`u-tabs__wrapper__nav__item-${index}`" + :style="[$u.addStyle(itemStyle), {flex: scrollable ? '' : 1}]" + :class="[`u-tabs__wrapper__nav__item-${index}`, item.disabled && 'u-tabs__wrapper__nav__item--disabled']" + > + <text + :class="[item.disabled && 'u-tabs__wrapper__nav__item__text--disabled']" + class="u-tabs__wrapper__nav__item__text" + :style="[textStyle(index)]" + >{{ item[keyName] }}</text> + <u-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;" + ></u-badge> + </view> + <!-- #ifdef APP-NVUE --> + <view + class="u-tabs__wrapper__nav__line" + ref="u-tabs__wrapper__nav__line" + :style="[{ + width: $u.addUnit(lineWidth), + height: $u.addUnit(lineHeight), + background: lineColor, + backgroundSize: lineBgSize, + }]" + > + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <view + class="u-tabs__wrapper__nav__line" + ref="u-tabs__wrapper__nav__line" + :style="[{ + width: $u.addUnit(lineWidth), + transform: `translate(${lineOffsetLeft}px)`, + transitionDuration: `${firstTime ? 0 : duration}ms`, + height: $u.addUnit(lineHeight), + background: lineColor, + backgroundSize: lineBgSize, + }]" + > + <!-- #endif --> + </view> + </view> + </scroll-view> + </view> + <slot name="right" /> + </view> + </view> +</template> + +<script> + // #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.uviewui.com/components/tabs.html + * @property {String | Number} duration 滑块移动一次所需的时间,单位秒(默认 200 ) + * @property {String | Number} swierWidth swiper的宽度(默认 '750rpx' ) + * @property {String} keyName 从`list`元素对象中读取的键名(默认 'name' ) + * @event {Function(index)} change 标签改变时触发 index: 点击了第几个tab,索引从0开始 + * @event {Function(index)} click 点击标签时触发 index: 点击了第几个tab,索引从0开始 + * @example <u-tabs :list="list" :is-scroll="false" :current="current" @change="change"></u-tabs> + */ + export default { + name: 'u-tabs', + mixins: [uni.$u.mpMixin, uni.$u.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 ? uni.$u.addStyle(this.activeStyle) : uni.$u + .addStyle( + this.inactiveStyle) + // 如果当前菜单被禁用,则加上对应颜色,需要在此做处理,是因为nvue下,无法在style样式中通过!import覆盖标签的内联样式 + if (this.list[index].disabled) { + style.color = '#c8c9cc' + } + return uni.$u.deepMerge(customeStyle, style) + } + }, + propsBadge() { + return uni.$u.props.badge + } + }, + 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表示法 + const lineWidth = uni.$u.getPx(this.lineWidth); + 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 + }, 10); + } + }, + // nvue下设置滑块的位置 + animation(x, duration = 0) { + // #ifdef APP-NVUE + const ref = this.$refs['u-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 + this.innerCurrent = index + this.resize() + this.$emit('change', { + ...item, + index + }) + }, + init() { + uni.$u.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 = uni.$u.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('u-tabs__wrapper__scroll-view').then(size => resolve(size)) + }) + }, + // 获取所有标签的尺寸 + getAllItemRect() { + return new Promise(resolve => { + const promiseAllArr = this.list.map((item, index) => this.queryRect( + `u-tabs__wrapper__nav__item-${index}`, true)) + Promise.all(promiseAllArr).then(sizes => resolve(sizes)) + }) + }, + // 获取各个标签的尺寸 + queryRect(el, item) { + // #ifndef APP-NVUE + // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html + // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同 + return new Promise(resolve => { + this.$uGetRect(`.${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 "../../libs/css/components.scss"; + + .u-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: $u-content-color; + + &--disabled { + color: $u-disabled-color !important; + } + } + } + + &__line { + height: 3px; + background: $u-primary; + width: 30px; + position: absolute; + bottom: 2px; + border-radius: 100px; + transition-property: transform; + transition-duration: 300ms; + } + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tag/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-tag/props.js new file mode 100644 index 000000000..6bffaa2db --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tag/props.js @@ -0,0 +1,84 @@ +export default { + props: { + // 标签类型info、primary、success、warning、error + type: { + type: String, + default: uni.$u.props.tag.type + }, + // 不可用 + disabled: { + type: [Boolean, String], + default: uni.$u.props.tag.disabled + }, + // 标签的大小,large,medium,mini + size: { + type: String, + default: uni.$u.props.tag.size + }, + // tag的形状,circle(两边半圆形), square(方形,带圆角) + shape: { + type: String, + default: uni.$u.props.tag.shape + }, + // 标签文字 + text: { + type: [String, Number], + default: uni.$u.props.tag.text + }, + // 背景颜色,默认为空字符串,即不处理 + bgColor: { + type: String, + default: uni.$u.props.tag.bgColor + }, + // 标签字体颜色,默认为空字符串,即不处理 + color: { + type: String, + default: uni.$u.props.tag.color + }, + // 标签的边框颜色 + borderColor: { + type: String, + default: uni.$u.props.tag.borderColor + }, + // 关闭按钮图标的颜色 + closeColor: { + type: String, + default: uni.$u.props.tag.closeColor + }, + // 点击时返回的索引值,用于区分例遍的数组哪个元素被点击了 + name: { + type: [String, Number], + default: uni.$u.props.tag.name + }, + // // 模式选择,dark|light|plain + // mode: { + // type: String, + // default: 'light' + // }, + // 镂空时是否填充背景色 + plainFill: { + type: Boolean, + default: uni.$u.props.tag.plainFill + }, + // 是否镂空 + plain: { + type: Boolean, + default: uni.$u.props.tag.plain + }, + // 是否可关闭 + closable: { + type: Boolean, + default: uni.$u.props.tag.closable + }, + // 是否显示 + show: { + type: Boolean, + default: uni.$u.props.tag.show + }, + // 内置图标,或绝对路径的图片 + icon: { + type: String, + default: uni.$u.props.tag.icon + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tag/u-tag.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-tag/u-tag.vue new file mode 100644 index 000000000..95f33c408 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tag/u-tag.vue @@ -0,0 +1,358 @@ +<template> + <u-transition + mode="fade" + :show="show" + > + <view class="u-tag-wrapper"> + <view + class="u-tag" + :class="[`u-tag--${shape}`, !plain && `u-tag--${type}`, plain && `u-tag--${type}--plain`, `u-tag--${size}`, plain && plainFill && `u-tag--${type}--plain--fill`]" + @tap.stop="clickHandler" + :style="[{ + marginRight: closable ? '10px' : 0, + marginTop: closable ? '10px' : 0, + }, style]" + > + <slot name="icon"> + <view + class="u-tag__icon" + v-if="icon" + > + <image + v-if="$u.test.image(icon)" + :src="icon" + :style="[imgStyle]" + ></image> + <u-icon + v-else + :color="elIconColor" + :name="icon" + :size="iconSize" + ></u-icon> + </view> + </slot> + <text + class="u-tag__text" + :style="[textColor]" + :class="[`u-tag__text--${type}`, plain && `u-tag__text--${type}--plain`, `u-tag__text--${size}`]" + >{{ text }}</text> + </view> + <view + class="u-tag__close" + :class="[`u-tag__close--${size}`]" + v-if="closable" + @tap.stop="closeHandler" + :style="{backgroundColor: closeColor}" + > + <u-icon + name="close" + :size="closeSize" + color="#ffffff" + ></u-icon> + </view> + </view> + </u-transition> +</template> + +<script> + import props from './props.js'; + /** + * Tag 标签 + * @description tag组件一般用于标记和选择,我们提供了更加丰富的表现形式,能够较全面的涵盖您的使用场景 + * @tutorial https://www.uviewui.com/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 <u-tag text="标签" type="error" plain plainFill></u-tag> + */ + export default { + name: 'u-tag', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + + } + }, + 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 "../../libs/css/components.scss"; + + .u-tag-wrapper { + position: relative; + } + + .u-tag { + @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; + } + + &--medium { + height: 26px; + line-height: 22px; + padding: 0 10px; + } + + &--large { + height: 32px; + line-height: 32px; + padding: 0 15px; + } + + &--primary { + background-color: $u-primary; + border-width: 1px; + border-color: $u-primary; + } + + &--primary--plain { + border-width: 1px; + border-color: $u-primary; + } + + &--primary--plain--fill { + background-color: #ecf5ff; + } + + &__text--primary { + color: #FFFFFF; + } + + &__text--primary--plain { + color: $u-primary; + } + + &--error { + background-color: $u-error; + border-width: 1px; + border-color: $u-error; + } + + &--error--plain { + border-width: 1px; + border-color: $u-error; + } + + &--error--plain--fill { + background-color: #fef0f0; + } + + &__text--error { + color: #FFFFFF; + } + + &__text--error--plain { + color: $u-error; + } + + &--warning { + background-color: $u-warning; + border-width: 1px; + border-color: $u-warning; + } + + &--warning--plain { + border-width: 1px; + border-color: $u-warning; + } + + &--warning--plain--fill { + background-color: #fdf6ec; + } + + &__text--warning { + color: #FFFFFF; + } + + &__text--warning--plain { + color: $u-warning; + } + + &--success { + background-color: $u-success; + border-width: 1px; + border-color: $u-success; + } + + &--success--plain { + border-width: 1px; + border-color: $u-success; + } + + &--success--plain--fill { + background-color: #f5fff0; + } + + &__text--success { + color: #FFFFFF; + } + + &__text--success--plain { + color: $u-success; + } + + &--info { + background-color: $u-info; + border-width: 1px; + border-color: $u-info; + } + + &--info--plain { + border-width: 1px; + border-color: $u-info; + } + + &--info--plain--fill { + background-color: #f4f4f5; + } + + &__text--info { + color: #FFFFFF; + } + + &__text--info--plain { + color: $u-info; + } + + &__close { + position: absolute; + z-index: 999; + top: 10px; + right: 10px; + border-radius: 100px; + background-color: #C6C7CB; + @include flex(row); + align-items: center; + justify-content: center; + /* #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/yudao-ui-app/uni_modules/uview-ui/components/u-td/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-td/props.js new file mode 100644 index 000000000..7c1133117 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-td/props.js @@ -0,0 +1,5 @@ +export default { + props: { + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-td/u-td.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-td/u-td.vue new file mode 100644 index 000000000..600dce535 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-td/u-td.vue @@ -0,0 +1,31 @@ +<template> + <view class="u-td"> + + </view> +</template> + +<script> + import props from './props.js'; + /** + * Td 表格中的单元格 + * @description + * @tutorial url + * @property {String | Number} + * @event {Function} + * @example + */ + export default { + name: 'u-td', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-text/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-text/props.js new file mode 100644 index 000000000..85a0836b9 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-text/props.js @@ -0,0 +1,110 @@ +export default { + props: { + // 主题颜色 + type: { + type: String, + default: uni.$u.props.text.type + }, + // 是否显示 + show: { + type: Boolean, + default: uni.$u.props.text.show + }, + // 显示的值 + text: { + type: [String, Number], + default: uni.$u.props.text.text + }, + // 前置图标 + prefixIcon: { + type: String, + default: uni.$u.props.text.prefixIcon + }, + // 后置图标 + suffixIcon: { + type: String, + default: uni.$u.props.text.suffixIcon + }, + // 文本处理的匹配模式 + // text-普通文本,price-价格,phone-手机号,name-姓名,date-日期,link-超链接 + mode: { + type: String, + default: uni.$u.props.text.mode + }, + // mode=link下,配置的链接 + href: { + type: String, + default: uni.$u.props.text.href + }, + // 格式化规则 + format: { + type: [String, Function], + default: uni.$u.props.text.format + }, + // mode=phone时,点击文本是否拨打电话 + call: { + type: Boolean, + default: uni.$u.props.text.call + }, + // 小程序的打开方式 + openType: { + type: String, + default: uni.$u.props.text.openType + }, + // 是否粗体,默认normal + bold: { + type: Boolean, + default: uni.$u.props.text.bold + }, + // 是否块状 + block: { + type: Boolean, + default: uni.$u.props.text.block + }, + // 文本显示的行数,如果设置,超出此行数,将会显示省略号 + lines: { + type: [String, Number], + default: uni.$u.props.text.lines + }, + // 文本颜色 + color: { + type: String, + default: uni.$u.props.text.color + }, + // 字体大小 + size: { + type: [String, Number], + default: uni.$u.props.text.size + }, + // 图标的样式 + iconStyle: { + type: [Object, String], + default: uni.$u.props.text.iconStyle + }, + // 文字装饰,下划线,中划线等,可选值 none|underline|line-through + decoration: { + tepe: String, + default: uni.$u.props.text.decoration + }, + // 外边距,对象、字符串,数值形式均可 + margin: { + type: [Object, String, Number], + default: uni.$u.props.text.margin + }, + // 文本行高 + lineHeight: { + type: [String, Number], + default: uni.$u.props.text.lineHeight + }, + // 文本对齐方式,可选值left|center|right + align: { + type: String, + default: uni.$u.props.text.align + }, + // 文字换行,可选值break-word|normal|anywhere + wordWrap: { + type: String, + default: uni.$u.props.text.wordWrap + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-text/u-text.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-text/u-text.vue new file mode 100644 index 000000000..99d0809e4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-text/u-text.vue @@ -0,0 +1,223 @@ +<template> + <view + class="u-text" + :class="[]" + v-if="show" + :style="{ + margin: margin, + justifyContent: align === 'left' ? 'flex-start' : align === 'center' ? 'center' : 'flex-end' + }" + @tap="clickHandler" + > + <text + :class="['u-text__price', type && `u-text__value--${type}`]" + v-if="mode === 'price'" + :style="[valueStyle]" + >¥</text + > + <view class="u-text__prefix-icon" v-if="prefixIcon"> + <u-icon + :name="prefixIcon" + :customStyle="$u.addStyle(iconStyle)" + ></u-icon> + </view> + <u-link + v-if="mode === 'link'" + :text="value" + :href="href" + underLine + ></u-link> + <template v-else-if="openType && isMp"> + <button + class="u-reset-button u-text__value" + :style="[valueStyle]" + :data-index="index" + :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="u-text__value" + :style="[valueStyle]" + :class="[ + type && `u-text__value--${type}`, + lines && `u-line-${lines}` + ]" + >{{ value }}</text + > + <view class="u-text__suffix-icon" v-if="suffixIcon"> + <u-icon + :name="suffixIcon" + :customStyle="$u.addStyle(iconStyle)" + ></u-icon> + </view> + </view> +</template> + +<script> +import value from './value.js' +import button from '../../libs/mixin/button.js' +import openType from '../../libs/mixin/openType.js' +import props from './props.js' +/** + * Text 文本 + * @description 此组件集成了文本类在项目中的常用功能,包括状态,拨打电话,格式化日期,*替换,超链接...等功能。 您大可不必在使用特殊文本时自己定义,text组件几乎涵盖您能使用的大部分场景。 + * @tutorial https://www.uviewui.com/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 <u--text text="我用十年青春,赴你最后之约"></u--text> + */ +export default { + name: 'u--text', + // #ifdef MP + mixins: [uni.$u.mpMixin, uni.$u.mixin, value, button, openType, props], + // #endif + // #ifndef MP + mixins: [uni.$u.mpMixin, uni.$u.mixin, value, props], + // #endif + computed: { + valueStyle() { + const style = { + textDecoration: this.decoration, + fontWeight: this.bold ? 'bold' : 'normal', + wordWrap: this.wordWrap, + fontSize: uni.$u.addUnit(this.size) + } + !this.type && (style.color = this.color) + this.isNvue && this.lines && (style.lines = this.lines) + this.lineHeight && + (style.lineHeight = uni.$u.addUnit(this.lineHeight)) + !this.isNvue && this.block && (style.display = 'block') + return uni.$u.deepMerge(style, uni.$u.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> +@import '../../libs/css/components.scss'; + +.u-text { + @include flex(row); + align-items: center; + flex-wrap: nowrap; + flex: 1; + /* #ifndef APP-NVUE */ + width: 100%; + /* #endif */ + + &__price { + font-size: 14px; + color: $u-content-color; + } + + &__value { + font-size: 14px; + @include flex; + color: $u-content-color; + flex-wrap: wrap; + // flex: 1; + text-overflow: ellipsis; + align-items: center; + + &--primary { + color: $u-primary; + } + + &--warning { + color: $u-warning; + } + + &--success { + color: $u-success; + } + + &--info { + color: $u-info; + } + + &--error { + color: $u-error; + } + + &--main { + color: $u-main-color; + } + + &--content { + color: $u-content-color; + } + + &--tips { + color: $u-tips-color; + } + + &--light { + color: $u-light-color; + } + } +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-text/value.js b/yudao-ui-app/uni_modules/uview-ui/components/u-text/value.js new file mode 100644 index 000000000..9859bbbe6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-text/value.js @@ -0,0 +1,85 @@ +export default { + computed: { + // 经处理后需要显示的值 + value() { + const { + text, + mode, + format, + href + } = this + // 价格类型 + if (mode === 'price') { + // 如果text不为金额进行提示 + if (!/^\d+(\.\d+)?$/.test(text)) { + uni.$u.error('金额模式下,text参数需要为金额格式'); + } + // 进行格式化,判断用户传入的format参数为正则,或者函数,如果没有传入format,则使用默认的金额格式化处理 + if (uni.$u.test.func(format)) { + // 如果用户传入的是函数,使用函数格式化 + return format(text) + } + // 如果format非正则,非函数,则使用默认的金额格式化方法进行操作 + return uni.$u.priceFormat(text, 2) + } if (mode === 'date') { + // 判断是否合法的日期或者时间戳 + !uni.$u.test.date(text) && uni.$u.error('日期模式下,text参数需要为日期或时间戳格式') + // 进行格式化,判断用户传入的format参数为正则,或者函数,如果没有传入format,则使用默认的格式化处理 + if (uni.$u.test.func(format)) { + // 如果用户传入的是函数,使用函数格式化 + return format(text) + } if (format) { + // 如果format非正则,非函数,则使用默认的时间格式化方法进行操作 + return uni.$u.timeFormat(text, format) + } + // 如果没有设置format,则设置为默认的时间格式化形式 + return uni.$u.timeFormat(text, 'yyyy-mm-dd') + } if (mode === 'phone') { + // 判断是否合法的手机号 + // !uni.$u.test.mobile(text) && uni.$u.error('手机号模式下,text参数需要为手机号码格式') + if (uni.$u.test.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') && uni.$u.error('姓名模式下,text参数需要为字符串格式') + if (uni.$u.test.func(format)) { + // 如果用户传入的是函数,使用函数格式化 + return format(text) + } if (format === 'encrypt') { + // 如果format为encrypt,则将姓名进行星号加密处理 + return this.formatName(text) + } + return text + } if (mode === 'link') { + // 判断是否合法的字符粗 + !uni.$u.test.url(href) && uni.$u.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/yudao-ui-app/uni_modules/uview-ui/components/u-textarea/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-textarea/props.js new file mode 100644 index 000000000..fd5a4983c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-textarea/props.js @@ -0,0 +1,114 @@ +export default { + props: { + // 输入框的内容 + value: { + type: [String, Number], + default: uni.$u.props.textarea.value + }, + // 输入框为空时占位符 + placeholder: { + type: [String, Number], + default: uni.$u.props.textarea.placeholder + }, + // 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ + placeholderClass: { + type: String, + default: uni.$u.props.input.placeholderClass + }, + // 指定placeholder的样式 + placeholderStyle: { + type: [String, Object], + default: uni.$u.props.input.placeholderStyle + }, + // 输入框高度 + height: { + type: [String, Number], + default: uni.$u.props.textarea.height + }, + // 设置键盘右下角按钮的文字,仅微信小程序,App-vue和H5有效 + confirmType: { + type: String, + default: uni.$u.props.textarea.confirmType + }, + // 是否禁用 + disabled: { + type: Boolean, + default: uni.$u.props.textarea.disabled + }, + // 是否显示统计字数 + count: { + type: Boolean, + default: uni.$u.props.textarea.count + }, + // 是否自动获取焦点,nvue不支持,H5取决于浏览器的实现 + focus: { + type: Boolean, + default: uni.$u.props.textarea.focus + }, + // 是否自动增加高度 + autoHeight: { + type: Boolean, + default: uni.$u.props.textarea.autoHeight + }, + // 如果textarea是在一个position:fixed的区域,需要显示指定属性fixed为true + fixed: { + type: Boolean, + default: uni.$u.props.textarea.fixed + }, + // 指定光标与键盘的距离 + cursorSpacing: { + type: Number, + default: uni.$u.props.textarea.cursorSpacing + }, + // 指定focus时的光标位置 + cursor: { + type: [String, Number], + default: uni.$u.props.textarea.cursor + }, + // 是否显示键盘上方带有”完成“按钮那一栏, + showConfirmBar: { + type: Boolean, + default: uni.$u.props.textarea.showConfirmBar + }, + // 光标起始位置,自动聚焦时有效,需与selection-end搭配使用 + selectionStart: { + type: Number, + default: uni.$u.props.textarea.selectionStart + }, + // 光标结束位置,自动聚焦时有效,需与selection-start搭配使用 + selectionEnd: { + type: Number, + default: uni.$u.props.textarea.selectionEnd + }, + // 键盘弹起时,是否自动上推页面 + adjustPosition: { + type: Boolean, + default: uni.$u.props.textarea.adjustPosition + }, + // 是否去掉 iOS 下的默认内边距,只微信小程序有效 + disableDefaultPadding: { + type: Boolean, + default: uni.$u.props.textarea.disableDefaultPadding + }, + // focus时,点击页面的时候不收起键盘,只微信小程序有效 + holdKeyboard: { + type: Boolean, + default: uni.$u.props.textarea.holdKeyboard + }, + // 最大输入长度,设置为 -1 的时候不限制最大长度 + maxlength: { + type: [String, Number], + default: uni.$u.props.textarea.maxlength + }, + // 边框类型,surround-四周边框,bottom-底部边框 + border: { + type: String, + default: uni.$u.props.textarea.border + }, + // 用于处理或者过滤输入框内容的方法 + formatter: { + type: [Function, null], + default: uni.$u.props.textarea.formatter + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-textarea/u-textarea.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-textarea/u-textarea.vue new file mode 100644 index 000000000..0dac03ef9 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-textarea/u-textarea.vue @@ -0,0 +1,237 @@ +<template> + <view class="u-textarea" :class="textareaClass" :style="[textareaStyle]"> + <textarea + class="u-textarea__field" + :value="innerValue" + :style="{ height: $u.addUnit(height) }" + :placeholder="placeholder" + :placeholder-style="$u.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="maxlength" + :confirmType="confirmType" + @focus="onFocus" + @blur="onBlur" + @linechange="onLinechange" + @input="onInput" + @confirm="onConfirm" + @keyboardheightchange="onKeyboardheightchange" + ></textarea> + <text + class="u-textarea__count" + :style="{ + 'background-color': disabled ? 'transparent' : '#fff', + }" + v-if="count" + >{{ innerValue.length }}/{{ maxlength }}</text + > + </view> +</template> + +<script> +import props from "./props.js"; +/** + * Textarea 文本域 + * @description 文本域此组件满足了可能出现的表单信息补充,编辑等实际逻辑的功能,内置了字数校验等 + * @tutorial https://www.uviewui.com/components/textarea.html + * + * @property {String | Number} value 输入框的内容 + * @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' ) + * + * @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 <u--textarea v-model="value1" placeholder="请输入内容" ></u--textarea> + */ +export default { + name: "u-textarea", + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 输入框的值 + innerValue: "", + // 是否处于获得焦点状态 + focused: false, + // value是否第一次变化,在watch中,由于加入immediate属性,会在第一次触发,此时不应该认为value发生了变化 + firstChange: true, + // value绑定值的变化是由内部还是外部引起的 + changeFromInner: false, + // 过滤处理方法 + innerFormatter: value => value + } + }, + watch: { + value: { + immediate: true, + handler(newVal, oldVal) { + this.innerValue = newVal; + /* #ifdef H5 */ + // 在H5中,外部value变化后,修改input中的值,不会触发@input事件,此时手动调用值变化方法 + if ( + this.firstChange === false && + this.changeFromInner === false + ) { + this.valueChange(); + } + /* #endif */ + this.firstChange = false; + // 重置changeFromInner的值为false,标识下一次引起默认为外部引起的 + this.changeFromInner = false; + }, + }, + }, + computed: { + // 组件的类名 + textareaClass() { + let classes = [], + { border, disabled, shape } = this; + border === "surround" && + (classes = classes.concat(["u-border", "u-textarea--radius"])); + border === "bottom" && + (classes = classes.concat([ + "u-border-bottom", + "u-textarea--no-radius", + ])); + disabled && classes.push("u-textarea--disabled"); + return classes.join(" "); + }, + // 组件的样式 + textareaStyle() { + const style = {}; + // #ifdef APP-NVUE + // 由于textarea在安卓nvue上的差异性,需要额外再调整其内边距 + if (uni.$u.os() === "android") { + style.paddingTop = "6px"; + style.paddingLeft = "9px"; + style.paddingBottom = "3px"; + style.paddingRight = "6px"; + } + // #endif + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)); + }, + }, + methods: { + // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用 + setFormatter(e) { + this.innerFormatter = e + }, + onFocus(e) { + this.$emit("focus", e); + }, + onBlur(e) { + this.$emit("blur", e); + // 尝试调用u-form的验证方法 + uni.$u.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); + // 标识value值的变化是由内部引起的 + this.changeFromInner = true; + this.$emit("change", value); + // 尝试调用u-form的验证方法 + uni.$u.formValidate(this, "change"); + }); + }, + onConfirm(e) { + this.$emit("confirm", e); + }, + onKeyboardheightchange(e) { + this.$emit("keyboardheightchange", e); + }, + }, +}; +</script> + +<style lang="scss" scoped> +@import "../../libs/css/components.scss"; + +.u-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: $u-content-color; + width: 100%; + } + + &__count { + position: absolute; + right: 5px; + bottom: 2px; + font-size: 12px; + color: $u-tips-color; + background-color: #ffffff; + padding: 1px 4px; + } +} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-toast/u-toast.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-toast/u-toast.vue new file mode 100644 index 000000000..f1948303d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-toast/u-toast.vue @@ -0,0 +1,291 @@ +<template> + <view class="u-toast"> + <u-overlay + :show="isShow" + :custom-style="overlayStyle" + > + <view + class="u-toast__content" + :style="[contentStyle]" + :class="['u-type-' + tmpConfig.type, (tmpConfig.type === 'loading' || tmpConfig.loading) ? 'u-toast__content--loading' : '']" + > + <u-loading-icon + v-if="tmpConfig.type === 'loading'" + mode="circle" + color="rgb(255, 255, 255)" + inactiveColor="rgb(120, 120, 120)" + size="25" + ></u-loading-icon> + <u-icon + v-else-if="tmpConfig.type !== 'defalut' && iconName" + :name="iconName" + size="17" + :color="tmpConfig.type" + :customStyle="iconStyle" + ></u-icon> + <u-gap + v-if="tmpConfig.type === 'loading' || tmpConfig.loading" + height="12" + bgColor="transparent" + ></u-gap> + <text + class="u-toast__content__text" + :class="['u-toast__content__text--' + tmpConfig.type]" + style="max-width: 400rpx;" + >{{ tmpConfig.message }}</text> + </view> + </u-overlay> + </view> +</template> + +<script> + /** + * toast 消息提示 + * @description 此组件表现形式类似uni的uni.showToastAPI,但也有不同的地方。 + * @tutorial https://www.uviewui.com/components/toast.html + * @property {String | Number} zIndex toast展示时的zIndex值 (默认 10090 ) + * @property {Boolean} loading 是否加载中 (默认 false ) + * @property {String | Number} message 显示的文字内容 + * @property {String} icon 图标,或者绝对路径的图片 + * @property {String} type 主题类型 (默认 default) + * @property {Boolean} show 是否显示该组件 (默认 false) + * @property {Boolean} overlay 是否显示透明遮罩,防止点击穿透 (默认 false ) + * @property {String} position 位置 (默认 'center' ) + * @property {Object} params 跳转的参数 + * @property {String | Number} duration 展示时间,单位ms (默认 2000 ) + * @property {Boolean} isTab 是否返回的为tab页面 (默认 false ) + * @property {String} url toast消失后是否跳转页面,有则跳转,优先级高于back参数 + * @property {Function} complete 执行完后的回调函数 + * @property {Boolean} back 结束toast是否自动返回上一页 (默认 false ) + * @property {Object} customStyle 组件的样式,对象形式 + * @event {Function} show 显示toast,如需一进入页面就显示toast,请在onReady生命周期调用 + * @example <u-toast ref="uToast" /> + */ + export default { + name: 'u-toast', + mixins: [uni.$u.mpMixin, uni.$u.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: false, // 是否防止触摸穿透 + loading: false, // 是否加载中状态 + }, + tmpConfig: {}, // 将用户配置和内置配置合并后的临时配置变量 + } + }, + 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 uni.$u.type2icon(this.tmpConfig.type) + } else { + return '' + } + }, + overlayStyle() { + const style = { + justifyContent: 'center', + alignItems: 'center', + display: 'flex' + } + // 将遮罩设置为100%透明度,避免出现灰色背景 + style.backgroundColor = 'rgba(0, 0, 0, 0)' + return style + }, + iconStyle() { + const style = {} + // 图标需要一个右边距,以跟右边的文字有隔开的距离 + style.marginRight = '4px' + // #ifdef APP-NVUE + // iOSAPP下,图标有1px的向下偏移,这里进行修正 + if (uni.$u.os() === 'ios') { + style.marginTop = '-1px' + } + // #endif + return style + }, + loadingIconColor() { + let color = 'rgb(255, 255, 255)' + if (['error', 'warning', 'success', 'primary'].includes(this.tmpConfig.type)) { + // loading-icon组件内部会对color参数进行一个透明度处理,该方法要求传入的颜色值 + // 必须为rgb格式的,所以这里做一个处理 + color = uni.$u.hexToRgb(uni.$u.color[this.tmpConfig.type]) + } + return color + }, + // 内容盒子的样式 + contentStyle() { + const windowHeight = uni.$u.sys().windowHeight, style = {} + let value = 0 + // 根据top和bottom,对Y轴进行窗体高度的百分比偏移 + if(this.tmpConfig.position === 'top') { + value = - windowHeight * 0.25 + } else if(this.tmpConfig.position === 'bottom') { + value = windowHeight * 0.25 + } + style.transform = `translateY(${value}px)` + 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变量,避免多次调用u-toast,前后的配置造成混乱 + this.tmpConfig = uni.$u.deepMerge(this.config, options) + // 清除定时器 + this.clearTimer() + this.isShow = true + this.timer = setTimeout(() => { + // 倒计时结束,清除定时器,隐藏toast组件 + this.clearTimer() + // 判断是否存在callback方法,如果存在就执行 + typeof(this.tmpConfig.complete) === 'function' && this.tmpConfig.complete() + }, this.tmpConfig.duration) + }, + // 隐藏toast组件,由父组件通过this.$refs.xxx.hide()形式调用 + hide() { + this.clearTimer() + }, + clearTimer() { + this.isShow = false + // 清除定时器 + clearTimeout(this.timer) + this.timer = null + } + }, + beforeDestroy() { + this.clearTimer() + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + $u-toast-color:#fff !default; + $u-toast-border-radius:4px !default; + $u-toast-border-background-color:#585858 !default; + $u-toast-border-font-size:14px !default; + $u-toast-border-padding:12px 20px !default; + $u-toast-loading-border-padding: 20px 20px !default; + $u-toast-content-text-color:#fff !default; + $u-toast-content-text-font-size:15px !default; + $u-toast-u-icon:10rpx !default; + $u-toast-u-type-primary-color:$u-primary !default; + $u-toast-u-type-primary-background-color:#ecf5ff !default; + $u-toast-u-type-primary-border-color:rgb(215, 234, 254) !default; + $u-toast-u-type-primary-border-width:1px !default; + $u-toast-u-type-success-color: $u-success !default; + $u-toast-u-type-success-background-color: #dbf1e1 !default; + $u-toast-u-type-success-border-color: #BEF5C8 !default; + $u-toast-u-type-success-border-width: 1px !default; + $u-toast-u-type-error-color:$u-error !default; + $u-toast-u-type-error-background-color:#fef0f0 !default; + $u-toast-u-type-error-border-color:#fde2e2 !default; + $u-toast-u-type-error-border-width: 1px !default; + $u-toast-u-type-warning-color:$u-warning !default; + $u-toast-u-type-warning-background-color:#fdf6ec !default; + $u-toast-u-type-warning-border-color:#faecd8 !default; + $u-toast-u-type-warning-border-width: 1px !default; + $u-toast-u-type-default-color:#fff !default; + $u-toast-u-type-default-background-color:#585858 !default; + + .u-toast { + &__content { + @include flex; + padding: $u-toast-border-padding; + border-radius: $u-toast-border-radius; + background-color: $u-toast-border-background-color; + color: $u-toast-color; + align-items: center; + /* #ifndef APP-NVUE */ + max-width: 600rpx; + /* #endif */ + position: relative; + + &--loading { + flex-direction: column; + padding: $u-toast-loading-border-padding; + } + + &__text { + color: $u-toast-content-text-color; + font-size: $u-toast-content-text-font-size; + line-height: $u-toast-content-text-font-size; + + &--default { + color: $u-toast-content-text-color; + } + + &--error { + color: $u-error; + } + + &--primary { + color: $u-primary; + } + + &--success { + color: $u-success; + } + + &--warning { + color: $u-warning; + } + } + } + } + + .u-type-primary { + color: $u-toast-u-type-primary-color; + background-color: $u-toast-u-type-primary-background-color; + border-color: $u-toast-u-type-primary-border-color; + border-width: $u-toast-u-type-primary-border-width; + } + + .u-type-success { + color: $u-toast-u-type-success-color; + background-color: $u-toast-u-type-success-background-color; + border-color: $u-toast-u-type-success-border-color; + border-width: 1px; + } + + .u-type-error { + color: $u-toast-u-type-error-color; + background-color: $u-toast-u-type-error-background-color; + border-color: $u-toast-u-type-error-border-color; + border-width: $u-toast-u-type-error-border-width; + } + + .u-type-warning { + color: $u-toast-u-type-warning-color; + background-color: $u-toast-u-type-warning-background-color; + border-color: $u-toast-u-type-warning-border-color; + border-width: 1px; + } + + .u-type-default { + color: $u-toast-u-type-default-color; + background-color: $u-toast-u-type-default-background-color; + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-toolbar/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-toolbar/props.js new file mode 100644 index 000000000..1b72966cb --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-toolbar/props.js @@ -0,0 +1,34 @@ +export default { + props: { + // 是否展示工具条 + show: { + type: Boolean, + default: uni.$u.props.toolbar.show + }, + // 取消按钮的文字 + cancelText: { + type: String, + default: uni.$u.props.toolbar.cancelText + }, + // 确认按钮的文字 + confirmText: { + type: String, + default: uni.$u.props.toolbar.confirmText + }, + // 取消按钮的颜色 + cancelColor: { + type: String, + default: uni.$u.props.toolbar.cancelColor + }, + // 确认按钮的颜色 + confirmColor: { + type: String, + default: uni.$u.props.toolbar.confirmColor + }, + // 标题文字 + title: { + type: String, + default: uni.$u.props.toolbar.title + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue new file mode 100644 index 000000000..290b77170 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue @@ -0,0 +1,102 @@ +<template> + <view + class="u-toolbar" + @touchmove.stop.prevent="noop" + v-if="show" + > + <view + class="u-toolbar__cancel__wrapper" + hover-class="u-hover-class" + > + <text + class="u-toolbar__wrapper__cancel" + @tap="cancel" + :style="{ + color: cancelColor + }" + >{{ cancelText }}</text> + </view> + <text + class="u-toolbar__title u-line-1" + v-if="title" + >{{ title }}</text> + <view + class="u-toolbar__confirm__wrapper" + hover-class="u-hover-class" + > + <text + class="u-toolbar__wrapper__confirm" + @tap="confirm" + :style="{ + color: confirmColor + }" + >{{ confirmText }}</text> + </view> + </view> +</template> + +<script> + import props from './props.js'; + /** + * Toolbar 工具条 + * @description + * @tutorial https://www.uviewui.com/components/toolbar.html + * @property {Boolean} show 是否展示工具条(默认 true ) + * @property {String} cancelText 取消按钮的文字(默认 '取消' ) + * @property {String} confirmText 确认按钮的文字(默认 '确认' ) + * @property {String} cancelColor 取消按钮的颜色(默认 '#909193' ) + * @property {String} confirmColor 确认按钮的颜色(默认 '#3c9cff' ) + * @property {String} title 标题文字 + * @event {Function} + * @example + */ + export default { + name: 'u-toolbar', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + methods: { + // 点击取消按钮 + cancel() { + this.$emit('cancel') + }, + // 点击确定按钮 + confirm() { + this.$emit('confirm') + } + }, + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-toolbar { + height: 42px; + @include flex; + justify-content: space-between; + align-items: center; + + &__wrapper { + &__cancel { + color: $u-tips-color; + font-size: 15px; + padding: 0 15px; + } + } + + &__title { + color: $u-main-color; + padding: 0 60rpx; + font-size: 16px; + flex: 1; + text-align: center; + } + + &__wrapper { + &__confirm { + color: $u-primary; + font-size: 15px; + padding: 0 15px; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/clipboard.min.js b/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/clipboard.min.js new file mode 100644 index 000000000..b7bff1242 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/clipboard.min.js @@ -0,0 +1,58 @@ +/*! + * clipboard.js v1.6.1 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!(function (e) { if (typeof exports === 'object' && typeof module !== 'undefined')module.exports = e(); else if (typeof define === 'function' && define.amd)define([], e); else { let t; t = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this, t.Clipboard = e() } }(() => { + let e; let t; let n; return (function e(t, n, o) { function i(a, c) { if (!n[a]) { if (!t[a]) { const l = typeof require === 'function' && require; if (!c && l) return l(a, !0); if (r) return r(a, !0); const u = new Error(`Cannot find module '${a}'`); throw u.code = 'MODULE_NOT_FOUND', u } const s = n[a] = { exports: {} }; t[a][0].call(s.exports, (e) => { const n = t[a][1][e]; return i(n || e) }, s, s.exports, e, t, n, o) } return n[a].exports } for (var r = typeof require === 'function' && require, a = 0; a < o.length; a++)i(o[a]); return i }({ + 1: [function (e, t, n) { function o(e, t) { for (;e && e.nodeType !== i;) { if (e.matches(t)) return e; e = e.parentNode } } var i = 9; if (typeof Element !== 'undefined' && !Element.prototype.matches) { const r = Element.prototype; r.matches = r.matchesSelector || r.mozMatchesSelector || r.msMatchesSelector || r.oMatchesSelector || r.webkitMatchesSelector }t.exports = o }, {}], + 2: [function (e, t, n) { function o(e, t, n, o, r) { const a = i.apply(this, arguments); return e.addEventListener(n, a, r), { destroy() { e.removeEventListener(n, a, r) } } } function i(e, t, n, o) { return function (n) { n.delegateTarget = r(n.target, t), n.delegateTarget && o.call(e, n) } } var r = e('./closest'); t.exports = o }, { './closest': 1 }], + 3: [function (e, t, n) { n.node = function (e) { return void 0 !== e && e instanceof HTMLElement && e.nodeType === 1 }, n.nodeList = function (e) { const t = Object.prototype.toString.call(e); return void 0 !== e && (t === '[object NodeList]' || t === '[object HTMLCollection]') && 'length' in e && (e.length === 0 || n.node(e[0])) }, n.string = function (e) { return typeof e === 'string' || e instanceof String }, n.fn = function (e) { const t = Object.prototype.toString.call(e); return t === '[object Function]' } }, {}], + 4: [function (e, t, n) { function o(e, t, n) { if (!e && !t && !n) throw new Error('Missing required arguments'); if (!c.string(t)) throw new TypeError('Second argument must be a String'); if (!c.fn(n)) throw new TypeError('Third argument must be a Function'); if (c.node(e)) return i(e, t, n); if (c.nodeList(e)) return r(e, t, n); if (c.string(e)) return a(e, t, n); throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList') } function i(e, t, n) { return e.addEventListener(t, n), { destroy() { e.removeEventListener(t, n) } } } function r(e, t, n) { return Array.prototype.forEach.call(e, (e) => { e.addEventListener(t, n) }), { destroy() { Array.prototype.forEach.call(e, (e) => { e.removeEventListener(t, n) }) } } } function a(e, t, n) { return l(document.body, e, t, n) } var c = e('./is'); var l = e('delegate'); t.exports = o }, { './is': 3, delegate: 2 }], + 5: [function (e, t, n) { function o(e) { let t; if (e.nodeName === 'SELECT')e.focus(), t = e.value; else if (e.nodeName === 'INPUT' || e.nodeName === 'TEXTAREA') { const n = e.hasAttribute('readonly'); n || e.setAttribute('readonly', ''), e.select(), e.setSelectionRange(0, e.value.length), n || e.removeAttribute('readonly'), t = e.value } else { e.hasAttribute('contenteditable') && e.focus(); const o = window.getSelection(); const i = document.createRange(); i.selectNodeContents(e), o.removeAllRanges(), o.addRange(i), t = o.toString() } return t }t.exports = o }, {}], + 6: [function (e, t, n) { + function o() {}o.prototype = { + on(e, t, n) { const o = this.e || (this.e = {}); return (o[e] || (o[e] = [])).push({ fn: t, ctx: n }), this }, once(e, t, n) { function o() { i.off(e, o), t.apply(n, arguments) } var i = this; return o._ = t, this.on(e, o, n) }, emit(e) { const t = [].slice.call(arguments, 1); const n = ((this.e || (this.e = {}))[e] || []).slice(); let o = 0; const i = n.length; for (o; o < i; o++)n[o].fn.apply(n[o].ctx, t); return this }, off(e, t) { const n = this.e || (this.e = {}); const o = n[e]; const i = []; if (o && t) for (let r = 0, a = o.length; r < a; r++)o[r].fn !== t && o[r].fn._ !== t && i.push(o[r]); return i.length ? n[e] = i : delete n[e], this } + }, t.exports = o + }, {}], + 7: [function (t, n, o) { + !(function (i, r) { if (typeof e === 'function' && e.amd)e(['module', 'select'], r); else if (typeof o !== 'undefined')r(n, t('select')); else { const a = { exports: {} }; r(a, i.select), i.clipboardAction = a.exports } }(this, (e, t) => { + 'use strict' + + function n(e) { return e && e.__esModule ? e : { default: e } } function o(e, t) { if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function') } const i = n(t); const r = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? function (e) { return typeof e } : function (e) { return e && typeof Symbol === 'function' && e.constructor === Symbol && e !== Symbol.prototype ? 'symbol' : typeof e }; const a = (function () { function e(e, t) { for (let n = 0; n < t.length; n++) { const o = t[n]; o.enumerable = o.enumerable || !1, o.configurable = !0, 'value' in o && (o.writable = !0), Object.defineProperty(e, o.key, o) } } return function (t, n, o) { return n && e(t.prototype, n), o && e(t, o), t } }()); const c = (function () { + function e(t) { o(this, e), this.resolveOptions(t), this.initSelection() } return a(e, [{ key: 'resolveOptions', value: function e() { const t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; this.action = t.action, this.emitter = t.emitter, this.target = t.target, this.text = t.text, this.trigger = t.trigger, this.selectedText = '' } }, { key: 'initSelection', value: function e() { this.text ? this.selectFake() : this.target && this.selectTarget() } }, { key: 'selectFake', value: function e() { const t = this; const n = document.documentElement.getAttribute('dir') == 'rtl'; this.removeFake(), this.fakeHandlerCallback = function () { return t.removeFake() }, this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || !0, this.fakeElem = document.createElement('textarea'), this.fakeElem.style.fontSize = '12pt', this.fakeElem.style.border = '0', this.fakeElem.style.padding = '0', this.fakeElem.style.margin = '0', this.fakeElem.style.position = 'absolute', this.fakeElem.style[n ? 'right' : 'left'] = '-9999px'; const o = window.pageYOffset || document.documentElement.scrollTop; this.fakeElem.style.top = `${o}px`, this.fakeElem.setAttribute('readonly', ''), this.fakeElem.value = this.text, document.body.appendChild(this.fakeElem), this.selectedText = (0, i.default)(this.fakeElem), this.copyText() } }, { key: 'removeFake', value: function e() { this.fakeHandler && (document.body.removeEventListener('click', this.fakeHandlerCallback), this.fakeHandler = null, this.fakeHandlerCallback = null), this.fakeElem && (document.body.removeChild(this.fakeElem), this.fakeElem = null) } }, { key: 'selectTarget', value: function e() { this.selectedText = (0, i.default)(this.target), this.copyText() } }, { key: 'copyText', value: function e() { let t = void 0; try { t = document.execCommand(this.action) } catch (e) { t = !1 } this.handleResult(t) } }, { + key: 'handleResult', + value: function e(t) { + this.emitter.emit(t ? 'success' : 'error', { + action: this.action, text: this.selectedText, trigger: this.trigger, clearSelection: this.clearSelection.bind(this) + }) + } + }, { key: 'clearSelection', value: function e() { this.target && this.target.blur(), window.getSelection().removeAllRanges() } }, { key: 'destroy', value: function e() { this.removeFake() } }, { key: 'action', set: function e() { const t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : 'copy'; if (this._action = t, this._action !== 'copy' && this._action !== 'cut') throw new Error('Invalid "action" value, use either "copy" or "cut"') }, get: function e() { return this._action } }, { key: 'target', set: function e(t) { if (void 0 !== t) { if (!t || (typeof t === 'undefined' ? 'undefined' : r(t)) !== 'object' || t.nodeType !== 1) throw new Error('Invalid "target" value, use a valid Element'); if (this.action === 'copy' && t.hasAttribute('disabled')) throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); if (this.action === 'cut' && (t.hasAttribute('readonly') || t.hasAttribute('disabled'))) throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'); this._target = t } }, get: function e() { return this._target } }]), e + }()); e.exports = c + })) + }, { select: 5 }], + 8: [function (t, n, o) { + !(function (i, r) { if (typeof e === 'function' && e.amd)e(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], r); else if (typeof o !== 'undefined')r(n, t('./clipboard-action'), t('tiny-emitter'), t('good-listener')); else { const a = { exports: {} }; r(a, i.clipboardAction, i.tinyEmitter, i.goodListener), i.clipboard = a.exports } }(this, (e, t, n, o) => { + 'use strict' + + function i(e) { return e && e.__esModule ? e : { default: e } } function r(e, t) { if (!(e instanceof t)) throw new TypeError('Cannot call a class as a function') } function a(e, t) { if (!e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return !t || typeof t !== 'object' && typeof t !== 'function' ? e : t } function c(e, t) { + if (typeof t !== 'function' && t !== null) throw new TypeError(`Super expression must either be null or a function, not ${typeof t}`); e.prototype = Object.create(t && t.prototype, { + constructor: { + value: e, enumerable: !1, writable: !0, configurable: !0 + } + }), t && (Object.setPrototypeOf ? Object.setPrototypeOf(e, t) : e.__proto__ = t) + } function l(e, t) { const n = `data-clipboard-${e}`; if (t.hasAttribute(n)) return t.getAttribute(n) } const u = i(t); const s = i(n); const f = i(o); const d = (function () { function e(e, t) { for (let n = 0; n < t.length; n++) { const o = t[n]; o.enumerable = o.enumerable || !1, o.configurable = !0, 'value' in o && (o.writable = !0), Object.defineProperty(e, o.key, o) } } return function (t, n, o) { return n && e(t.prototype, n), o && e(t, o), t } }()); const h = (function (e) { + function t(e, n) { r(this, t); const o = a(this, (t.__proto__ || Object.getPrototypeOf(t)).call(this)); return o.resolveOptions(n), o.listenClick(e), o } return c(t, e), d(t, [{ key: 'resolveOptions', value: function e() { const t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; this.action = typeof t.action === 'function' ? t.action : this.defaultAction, this.target = typeof t.target === 'function' ? t.target : this.defaultTarget, this.text = typeof t.text === 'function' ? t.text : this.defaultText } }, { key: 'listenClick', value: function e(t) { const n = this; this.listener = (0, f.default)(t, 'click', (e) => n.onClick(e)) } }, { + key: 'onClick', + value: function e(t) { + const n = t.delegateTarget || t.currentTarget; this.clipboardAction && (this.clipboardAction = null), this.clipboardAction = new u.default({ + action: this.action(n), target: this.target(n), text: this.text(n), trigger: n, emitter: this + }) + } + }, { key: 'defaultAction', value: function e(t) { return l('action', t) } }, { key: 'defaultTarget', value: function e(t) { const n = l('target', t); if (n) return document.querySelector(n) } }, { key: 'defaultText', value: function e(t) { return l('text', t) } }, { key: 'destroy', value: function e() { this.listener.destroy(), this.clipboardAction && (this.clipboardAction.destroy(), this.clipboardAction = null) } }], [{ key: 'isSupported', value: function e() { const t = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : ['copy', 'cut']; const n = typeof t === 'string' ? [t] : t; let o = !!document.queryCommandSupported; return n.forEach((e) => { o = o && !!document.queryCommandSupported(e) }), o } }]), t + }(s.default)); e.exports = h + })) + }, { './clipboard-action': 7, 'good-listener': 4, 'tiny-emitter': 6 }] + }, {}, [8]))(8) +})) diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/props.js new file mode 100644 index 000000000..16aecbc26 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/props.js @@ -0,0 +1,59 @@ +export default { + props: { + // 需要显示的提示文字 + text: { + type: [String, Number], + default: uni.$u.props.tooltip.text + }, + // 点击复制按钮时,复制的文本,为空则使用text值 + copyText: { + type: [String, Number], + default: uni.$u.props.tooltip.copyText + }, + // 文本大小 + size: { + type: [String, Number], + default: uni.$u.props.tooltip.size + }, + // 字体颜色 + color: { + type: String, + default: uni.$u.props.tooltip.color + }, + // 弹出提示框时,文本的背景色 + bgColor: { + type: String, + default: uni.$u.props.tooltip.bgColor + }, + // 弹出提示的方向,top-上方,bottom-下方 + direction: { + type: String, + default: uni.$u.props.tooltip.direction + }, + // 弹出提示的z-index,nvue无效 + zIndex: { + type: [String, Number], + default: uni.$u.props.tooltip.zIndex + }, + // 是否显示复制按钮 + showCopy: { + type: Boolean, + default: uni.$u.props.tooltip.showCopy + }, + // 扩展的按钮组 + buttons: { + type: Array, + default: uni.$u.props.tooltip.buttons + }, + // 是否显示透明遮罩以防止触摸穿透 + overlay: { + type: Boolean, + default: uni.$u.props.tooltip.overlay + }, + // 是否显示复制成功或者失败的toast + showToast: { + type: Boolean, + default: uni.$u.props.tooltip.showToast + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/u-tooltip.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/u-tooltip.vue new file mode 100644 index 000000000..986829c59 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tooltip/u-tooltip.vue @@ -0,0 +1,365 @@ +<template> + <view + class="u-tooltip" + :style="[$u.addStyle(customStyle)]" + > + <u-overlay + :show="showTooltip && tooltipTop !== -10000 && overlay" + customStyle="backgroundColor: rgba(0, 0, 0, 0)" + @click="overlayClickHandler" + ></u-overlay> + <view class="u-tooltip__wrapper"> + <text + class="u-tooltip__wrapper__text" + :id="textId" + :ref="textId" + :userSelect="false" + :selectable="false" + @longpress.stop="longpressHandler" + :style="{ + backgroundColor: bgColor && showTooltip && tooltipTop !== -10000 ? bgColor : 'transparent' + }" + >{{ text }}</text> + <u-transition + mode="fade" + :show="showTooltip" + duration="300" + :customStyle="{ + position: 'absolute', + top: $u.addUnit(tooltipTop), + zIndex: zIndex, + ...tooltipStyle + }" + > + <view + class="u-tooltip__wrapper__popup" + :id="tooltipId" + :ref="tooltipId" + > + <view + class="u-tooltip__wrapper__popup__indicator" + hover-class="u-tooltip__wrapper__popup__indicator--hover" + v-if="showCopy || buttons.length" + :style="[indicatorStyle, { + width: $u.addUnit(indicatorWidth), + height: $u.addUnit(indicatorWidth), + }]" + > + <!-- 由于nvue不支持三角形绘制,这里就做一个四方形,再旋转45deg,得到露出的一个三角 --> + </view> + <view class="u-tooltip__wrapper__popup__list"> + <view + v-if="showCopy" + class="u-tooltip__wrapper__popup__list__btn" + hover-class="u-tooltip__wrapper__popup__list__btn--hover" + @tap="setClipboardData" + > + <text + class="u-tooltip__wrapper__popup__list__btn__text" + >复制</text> + </view> + <u-line + direction="column" + color="#8d8e90" + v-if="showCopy && buttons.length > 0" + length="18" + ></u-line> + <block v-for="(item , index) in buttons" :key="index"> + <view + class="u-tooltip__wrapper__popup__list__btn" + hover-class="u-tooltip__wrapper__popup__list__btn--hover" + > + <text + class="u-tooltip__wrapper__popup__list__btn__text" + @tap="btnClickHandler(index)" + >{{ item }}</text> + </view> + <u-line + direction="column" + color="#8d8e90" + v-if="index < buttons.length - 1" + length="18" + ></u-line> + </block> + </view> + </view> + </u-transition> + </view> + </view> +</template> + +<script> + import props from './props.js'; + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom') + // #endif + // #ifdef H5 + import ClipboardJS from "./clipboard.min.js" + // #endif + /** + * Tooltip + * @description + * @tutorial https://www.uviewui.com/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: 'u-tooltip', + mixins: [uni.$u.mpMixin, uni.$u.mixin, props], + data() { + return { + // 是否展示气泡 + showTooltip: true, + // 生成唯一id,防止一个页面多个组件,造成干扰 + textId: uni.$u.guid(), + tooltipId: uni.$u.guid(), + // 初始时甚至为很大的值,让其移到屏幕外面,为了计算元素的尺寸 + tooltipTop: -10000, + // 气泡的位置信息 + tooltipInfo: { + width: 0, + left: 0 + }, + // 文本的位置信息 + textInfo: { + width: 0, + left: 0 + }, + // 三角形指示器的样式 + indicatorStyle: {}, + // 气泡在可能超出屏幕边沿范围时,重新定位后,距离屏幕边沿的距离 + screenGap: 12, + // 三角形指示器的宽高,由于对元素进行了角度旋转,精确计算指示器位置时,需要用到其尺寸信息 + indicatorWidth: 14, + } + }, + watch: { + propsChange() { + this.getElRect() + } + }, + computed: { + // 特别处理H5的复制,因为H5浏览器是自带系统复制功能的,在H5环境 + // 当一些依赖参数变化时,需要重新计算气泡和指示器的位置信息 + propsChange() { + return [this.text, this.buttons] + }, + // 计算气泡和指示器的位置信息 + tooltipStyle() { + const style = { + transform: `translateY(${this.direction === 'top' ? '-100%' : '100%'})`, + }, + sys = uni.$u.sys(), + getPx = uni.$u.getPx, + addUnit = uni.$u.addUnit + if (this.tooltipInfo.width / 2 > this.textInfo.left + this.textInfo.width / 2 - this.screenGap) { + this.indicatorStyle = {} + style.left = `-${addUnit(this.textInfo.left - this.screenGap)}` + this.indicatorStyle.left = addUnit(this.textInfo.width / 2 - getPx(style.left) - this.indicatorWidth / + 2) + } else if (this.tooltipInfo.width / 2 > sys.windowWidth - this.textInfo.right + this.textInfo.width / 2 - + this.screenGap) { + this.indicatorStyle = {} + style.right = `-${addUnit(sys.windowWidth - this.textInfo.right - this.screenGap)}` + this.indicatorStyle.right = addUnit(this.textInfo.width / 2 - 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 ? addUnit(left) : -addUnit(left) + this.indicatorStyle = {} + } + if (this.direction === 'top') { + style.marginTop = '-10px' + this.indicatorStyle.bottom = '-4px' + } else { + style.marginBottom = '-10px' + this.indicatorStyle.top = '-4px' + } + return style + } + }, + mounted() { + this.init() + }, + methods: { + init() { + this.getElRect() + }, + // 长按触发事件 + async longpressHandler() { + this.tooltipTop = 0 + this.showTooltip = true + }, + // 点击透明遮罩 + overlayClickHandler() { + this.showTooltip = false + }, + // 点击弹出按钮 + btnClickHandler(index) { + this.showTooltip = false + // 如果需要展示复制按钮,此处index需要加1,因为复制按钮在第一个位置 + this.$emit('click', this.showCopy ? index + 1 : index) + }, + // 查询内容高度 + queryRect(ref) { + // #ifndef APP-NVUE + // $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html + // 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同 + return new Promise(resolve => { + this.$uGetRect(`#${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 + uni.$u.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) + // #ifndef H5 + uni.setClipboardData({ + // 优先使用copyText字段,如果没有,则默认使用text字段当做复制的内容 + data: this.copyText || this.text, + success: () => { + this.showToast && uni.$u.toast('复制成功') + }, + fail: () => { + this.showToast && uni.$u.toast('复制失败') + }, + complete: () => { + this.showTooltip = false + } + }) + // #endif + + // #ifdef H5 + let event = window.event || e || {} + let clipboard = new ClipboardJS('', { + text: () => this.copyText || this.text + }) + clipboard.on('success', (e) => { + this.showToast && uni.$u.toast('复制成功') + clipboard.off('success') + clipboard.off('error') + // 在单页应用中,需要销毁DOM的监听 + clipboard.destroy() + }) + clipboard.on('error', (e) => { + this.showToast && uni.$u.toast('复制失败') + clipboard.off('success') + clipboard.off('error') + // 在单页应用中,需要销毁DOM的监听 + clipboard.destroy() + }) + clipboard.onClick(event) + // #endif + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + + .u-tooltip { + position: relative; + @include flex; + + &__wrapper { + @include flex; + justify-content: center; + /* #ifndef APP-NVUE */ + white-space: nowrap; + /* #endif */ + + &__text { + color: $u-content-color; + 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/yudao-ui-app/uni_modules/uview-ui/components/u-tr/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-tr/props.js new file mode 100644 index 000000000..7c1133117 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tr/props.js @@ -0,0 +1,5 @@ +export default { + props: { + + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-tr/u-tr.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-tr/u-tr.vue new file mode 100644 index 000000000..dbbca089a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-tr/u-tr.vue @@ -0,0 +1,31 @@ +<template> + <view class="u-tr"> + + </view> +</template> + +<script> + import props from './props.js'; + /** + * Tr + * @description + * @tutorial url + * @property {String} + * @event {Function} + * @example + */ + export default { + name: 'u-tr', + mixins: [uni.$u.mpMixin, uni.$u.mixin,props], + data() { + return { + + } + } + } +</script> + +<style lang="scss" scoped> + @import "../../libs/css/components.scss"; + +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-transition/nvue.ani-map.js b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/nvue.ani-map.js new file mode 100644 index 000000000..b86b962d4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/nvue.ani-map.js @@ -0,0 +1,68 @@ +export default { + fade: { + enter: { opacity: 0 }, + 'enter-to': { opacity: 1 }, + leave: { opacity: 1 }, + 'leave-to': { opacity: 0 } + }, + 'fade-up': { + enter: { opacity: 0, transform: 'translateY(100%)' }, + 'enter-to': { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 1, transform: 'translateY(0)' }, + 'leave-to': { opacity: 0, transform: 'translateY(100%)' } + }, + 'fade-down': { + enter: { opacity: 0, transform: 'translateY(-100%)' }, + 'enter-to': { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 1, transform: 'translateY(0)' }, + 'leave-to': { opacity: 0, transform: 'translateY(-100%)' } + }, + 'fade-left': { + enter: { opacity: 0, transform: 'translateX(-100%)' }, + 'enter-to': { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 1, transform: 'translateY(0)' }, + 'leave-to': { opacity: 0, transform: 'translateX(-100%)' } + }, + 'fade-right': { + enter: { opacity: 0, transform: 'translateX(100%)' }, + 'enter-to': { opacity: 1, transform: 'translateY(0)' }, + leave: { opacity: 1, transform: 'translateY(0)' }, + 'leave-to': { opacity: 0, transform: 'translateX(100%)' } + }, + 'slide-up': { + enter: { transform: 'translateY(100%)' }, + 'enter-to': { transform: 'translateY(0)' }, + leave: { transform: 'translateY(0)' }, + 'leave-to': { transform: 'translateY(100%)' } + }, + 'slide-down': { + enter: { transform: 'translateY(-100%)' }, + 'enter-to': { transform: 'translateY(0)' }, + leave: { transform: 'translateY(0)' }, + 'leave-to': { transform: 'translateY(-100%)' } + }, + 'slide-left': { + enter: { transform: 'translateX(-100%)' }, + 'enter-to': { transform: 'translateY(0)' }, + leave: { transform: 'translateY(0)' }, + 'leave-to': { transform: 'translateX(-100%)' } + }, + 'slide-right': { + enter: { transform: 'translateX(100%)' }, + 'enter-to': { transform: 'translateY(0)' }, + leave: { transform: 'translateY(0)' }, + 'leave-to': { transform: 'translateX(100%)' } + }, + zoom: { + enter: { transform: 'scale(0.95)' }, + 'enter-to': { transform: 'scale(1)' }, + leave: { transform: 'scale(1)' }, + 'leave-to': { transform: 'scale(0.95)' } + }, + 'fade-zoom': { + enter: { opacity: 0, transform: 'scale(0.95)' }, + 'enter-to': { opacity: 1, transform: 'scale(1)' }, + leave: { opacity: 1, transform: 'scale(1)' }, + 'leave-to': { opacity: 0, transform: 'scale(0.95)' } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-transition/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/props.js new file mode 100644 index 000000000..f7b1c227d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/props.js @@ -0,0 +1,24 @@ +export default { + props: { + // 是否展示组件 + show: { + type: Boolean, + default: uni.$u.props.transition.show + }, + // 使用的动画模式 + mode: { + type: String, + default: uni.$u.props.transition.mode + }, + // 动画的执行时间,单位ms + duration: { + type: [String, Number], + default: uni.$u.props.transition.duration + }, + // 使用的动画过渡函数 + timingFunction: { + type: String, + default: uni.$u.props.transition.timingFunction + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-transition/transition.js b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/transition.js new file mode 100644 index 000000000..92e568142 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/transition.js @@ -0,0 +1,157 @@ +// 定义一个一定时间后自动成功的promise,让调用nextTick方法处,进入下一个then方法 +const nextTick = () => new Promise(resolve => setTimeout(resolve, 1000 / 50)) +// nvue动画模块实现细节抽离在外部文件 +import animationMap from './nvue.ani-map.js' + +// #ifndef APP-NVUE +// 定义类名,通过给元素动态切换类名,赋予元素一定的css动画样式 +const getClassNames = (name) => ({ + enter: `u-${name}-enter u-${name}-enter-active`, + 'enter-to': `u-${name}-enter-to u-${name}-enter-active`, + leave: `u-${name}-leave u-${name}-leave-active`, + 'leave-to': `u-${name}-leave-to u-${name}-leave-active` +}) +// #endif + +// #ifdef APP-NVUE +// 引入nvue(weex)的animation动画模块,文档见: +// https://weex.apache.org/zh/docs/modules/animation.html#transition +const animation = uni.requireNativePlugin('animation') +const getStyle = (name) => animationMap[name] +// #endif + +export default { + methods: { + // 组件被点击发出事件 + clickHandler() { + this.$emit('click') + }, + // #ifndef APP-NVUE + // vue版本的组件进场处理 + vueEnter() { + // 动画进入时的类名 + const classNames = getClassNames(this.mode) + // 定义状态和发出动画进入前事件 + this.status = 'enter' + this.$emit('beforeEnter') + this.inited = true + this.display = true + this.classes = classNames.enter + this.$nextTick(async () => { + // #ifdef H5 + await uni.$u.sleep(20) + // #endif + // 标识动画尚未结束 + this.$emit('enter') + this.transitionEnded = false + // 组件动画进入后触发的事件 + this.$emit('afterEnter') + // 赋予组件enter-to类名 + this.classes = classNames['enter-to'] + }) + }, + // 动画离场处理 + vueLeave() { + // 如果不是展示状态,无需执行逻辑 + if (!this.display) return + const classNames = getClassNames(this.mode) + // 标记离开状态和发出事件 + this.status = 'leave' + this.$emit('beforeLeave') + // 获得类名 + this.classes = classNames.leave + + this.$nextTick(() => { + // 动画正在离场的状态 + this.transitionEnded = false + this.$emit('leave') + // 组件执行动画,到了执行的执行时间后,执行一些额外处理 + setTimeout(this.onTransitionEnd, this.duration) + this.classes = classNames['leave-to'] + }) + }, + // #endif + // #ifdef APP-NVUE + // nvue版本动画进场 + nvueEnter() { + // 获得样式的名称 + const currentStyle = getStyle(this.mode) + // 组件动画状态和发出事件 + this.status = 'enter' + this.$emit('beforeEnter') + // 展示生成组件元素 + this.inited = true + this.display = true + // 在nvue安卓上,由于渲染速度慢,在弹窗,键盘,日历等组件中,渲染其中的内容需要时间 + // 导致出现弹窗卡顿,这里让其一开始为透明状态,等一定时间渲染完成后,再让其隐藏起来,再让其按正常逻辑出现 + this.viewStyle = { + opacity: 0 + } + // 等待弹窗内容渲染完成 + this.$nextTick(() => { + // 合并样式 + this.viewStyle = currentStyle.enter + Promise.resolve() + .then(nextTick) + .then(() => { + // 组件开始进入前的事件 + this.$emit('enter') + // nvue的transition动画模块需要通过ref调用组件,注意此处的ref不同于vue的this.$refs['u-transition']用法 + animation.transition(this.$refs['u-transition'].ref, { + styles: currentStyle['enter-to'], + duration: this.duration, + timingFunction: this.timingFunction, + needLayout: false, + delay: 0 + }, () => { + // 动画执行完毕,发出事件 + this.$emit('afterEnter') + }) + }) + .catch(() => {}) + }) + }, + nvueLeave() { + if (!this.display) { + return + } + const currentStyle = getStyle(this.mode) + // 定义状态和事件 + this.status = 'leave' + this.$emit('beforeLeave') + // 合并样式 + this.viewStyle = currentStyle.leave + // 放到promise中处理执行过程 + Promise.resolve() + .then(nextTick) // 等待几十ms + .then(() => { + this.transitionEnded = false + // 动画正在离场的状态 + this.$emit('leave') + animation.transition(this.$refs['u-transition'].ref, { + styles: currentStyle['leave-to'], + duration: this.duration, + timingFunction: this.timingFunction, + needLayout: false, + delay: 0 + }, () => { + this.onTransitionEnd() + }) + }) + .catch(() => {}) + }, + // #endif + // 完成过渡后触发 + onTransitionEnd() { + // 如果已经是结束的状态,无需再处理 + if (this.transitionEnded) return + this.transitionEnded = true + // 发出组件动画执行后的事件 + this.$emit(this.status === 'leave' ? 'afterLeave' : 'afterEnter') + if (!this.show && this.display) { + this.display = false + this.inited = false + } + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-transition/u-transition.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/u-transition.vue new file mode 100644 index 000000000..22831dcd4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/u-transition.vue @@ -0,0 +1,92 @@ +<template> + <view + v-if="inited" + class="u-transition" + ref="u-transition" + @tap="clickHandler" + :class="classes" + :style="[mergeStyle]" + @touchmove="noop" + > + <slot /> + </view> +</template> + +<script> +import props from './props.js'; +// 组件的methods方法,由于内容较长,写在外部文件中通过mixin引入 +import transition from "./transition.js"; +/** + * transition 动画组件 + * @description + * @tutorial + * @property {String} show 是否展示组件 (默认 false ) + * @property {String} mode 使用的动画模式 (默认 'fade' ) + * @property {String | Number} duration 动画的执行时间,单位ms (默认 '300' ) + * @property {String} timingFunction 使用的动画过渡函数 (默认 'ease-out' ) + * @property {Object} customStyle 自定义样式 + * @event {Function} before-enter 进入前触发 + * @event {Function} enter 进入中触发 + * @event {Function} after-enter 进入后触发 + * @event {Function} before-leave 离开前触发 + * @event {Function} leave 离开中触发 + * @event {Function} after-leave 离开后触发 + * @example + */ +export default { + name: 'u-transition', + data() { + return { + inited: false, // 是否显示/隐藏组件 + viewStyle: {}, // 组件内部的样式 + status: '', // 记录组件动画的状态 + transitionEnded: false, // 组件是否结束的标记 + display: false, // 组件是否展示 + classes: '', // 应用的类名 + } + }, + computed: { + mergeStyle() { + const { viewStyle, customStyle } = this + return { + // #ifndef APP-NVUE + transitionDuration: `${this.duration}ms`, + // display: `${this.display ? '' : 'none'}`, + transitionTimingFunction: this.timingFunction, + // #endif + // 避免自定义样式影响到动画属性,所以写在viewStyle前面 + ...uni.$u.addStyle(customStyle), + ...viewStyle + } + } + }, + // 将mixin挂在到组件中,uni.$u.mixin实际上为一个vue格式对象 + mixins: [uni.$u.mpMixin, uni.$u.mixin, transition, props], + watch: { + show: { + handler(newVal) { + // vue和nvue分别执行不同的方法 + // #ifdef APP-NVUE + newVal ? this.nvueEnter() : this.nvueLeave() + // #endif + // #ifndef APP-NVUE + newVal ? this.vueEnter() : this.vueLeave() + // #endif + }, + // 表示同时监听初始化时的props的show的意思 + immediate: true + } + } +} +</script> + +<style lang="scss" scoped> +@import '../../libs/css/components.scss'; + +/* #ifndef APP-NVUE */ +// vue版本动画相关的样式抽离在外部文件 +@import './vue.ani-style.scss'; +/* #endif */ + +.u-transition {} +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-transition/vue.ani-style.scss b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/vue.ani-style.scss new file mode 100644 index 000000000..a31d88b14 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-transition/vue.ani-style.scss @@ -0,0 +1,113 @@ +/** + * vue版本动画内置的动画模式有如下: + * fade:淡入 + * zoom:缩放 + * fade-zoom:缩放淡入 + * fade-up:上滑淡入 + * fade-down:下滑淡入 + * fade-left:左滑淡入 + * fade-right:右滑淡入 + * slide-up:上滑进入 + * slide-down:下滑进入 + * slide-left:左滑进入 + * slide-right:右滑进入 + */ + +$u-zoom-scale: scale(0.95); + +.u-fade-enter-active, +.u-fade-leave-active { + transition-property: opacity; +} + +.u-fade-enter, +.u-fade-leave-to { + opacity: 0 +} + +.u-fade-zoom-enter, +.u-fade-zoom-leave-to { + transform: $u-zoom-scale; + opacity: 0; +} + +.u-fade-zoom-enter-active, +.u-fade-zoom-leave-active { + transition-property: transform, opacity; +} + +.u-fade-down-enter-active, +.u-fade-down-leave-active, +.u-fade-left-enter-active, +.u-fade-left-leave-active, +.u-fade-right-enter-active, +.u-fade-right-leave-active, +.u-fade-up-enter-active, +.u-fade-up-leave-active { + transition-property: opacity, transform; +} + +.u-fade-up-enter, +.u-fade-up-leave-to { + transform: translate3d(0, 100%, 0); + opacity: 0 +} + +.u-fade-down-enter, +.u-fade-down-leave-to { + transform: translate3d(0, -100%, 0); + opacity: 0 +} + +.u-fade-left-enter, +.u-fade-left-leave-to { + transform: translate3d(-100%, 0, 0); + opacity: 0 +} + +.u-fade-right-enter, +.u-fade-right-leave-to { + transform: translate3d(100%, 0, 0); + opacity: 0 +} + +.u-slide-down-enter-active, +.u-slide-down-leave-active, +.u-slide-left-enter-active, +.u-slide-left-leave-active, +.u-slide-right-enter-active, +.u-slide-right-leave-active, +.u-slide-up-enter-active, +.u-slide-up-leave-active { + transition-property: transform; +} + +.u-slide-up-enter, +.u-slide-up-leave-to { + transform: translate3d(0, 100%, 0) +} + +.u-slide-down-enter, +.u-slide-down-leave-to { + transform: translate3d(0, -100%, 0) +} + +.u-slide-left-enter, +.u-slide-left-leave-to { + transform: translate3d(-100%, 0, 0) +} + +.u-slide-right-enter, +.u-slide-right-leave-to { + transform: translate3d(100%, 0, 0) +} + +.u-zoom-enter-active, +.u-zoom-leave-active { + transition-property: transform +} + +.u-zoom-enter, +.u-zoom-leave-to { + transform: $u-zoom-scale +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-upload/mixin.js b/yudao-ui-app/uni_modules/uview-ui/components/u-upload/mixin.js new file mode 100644 index 000000000..410c775df --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-upload/mixin.js @@ -0,0 +1,21 @@ +export default { + watch: { + // 监听accept的变化,判断是否符合个平台要求 + // 只有微信小程序才支持选择媒体,文件类型,所以这里做一个判断提示 + accept: { + immediate: true, + handler(val) { + // #ifndef MP-WEIXIN + if (val === 'all' || val === 'media') { + uni.$u.error('只有微信小程序才支持把accept配置为all、media之一') + } + // #endif + // #ifndef H5 || MP-WEIXIN + if (val === 'file') { + uni.$u.error('只有微信小程序和H5(HX2.9.9)才支持把accept配置为file') + } + // #endif + } + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-upload/props.js b/yudao-ui-app/uni_modules/uview-ui/components/u-upload/props.js new file mode 100644 index 000000000..b106ae770 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-upload/props.js @@ -0,0 +1,124 @@ +export default { + props: { + // 接受的文件类型, 可选值为all media image file video + accept: { + type: String, + default: uni.$u.props.upload.accept + }, + // 图片或视频拾取模式,当accept为image类型时设置capture可选额外camera可以直接调起摄像头 + capture: { + type: [String, Array], + default: uni.$u.props.upload.capture + }, + // 当accept为video时生效,是否压缩视频,默认为true + compressed: { + type: Boolean, + default: uni.$u.props.upload.compressed + }, + // 当accept为video时生效,可选值为back或front + camera: { + type: String, + default: uni.$u.props.upload.camera + }, + // 当accept为video时生效,拍摄视频最长拍摄时间,单位秒 + maxDuration: { + type: Number, + default: uni.$u.props.upload.maxDuration + }, + // 上传区域的图标,只能内置图标 + uploadIcon: { + type: String, + default: uni.$u.props.upload.uploadIcon + }, + // 上传区域的图标的颜色,默认 + uploadIconColor: { + type: String, + default: uni.$u.props.upload.uploadIconColor + }, + // 是否开启文件读取前事件 + useBeforeRead: { + type: Boolean, + default: uni.$u.props.upload.useBeforeRead + }, + // 读取后的处理函数 + afterRead: { + type: Function, + default: null + }, + // 读取前的处理函数 + beforeRead: { + type: Function, + default: null + }, + // 是否显示组件自带的图片预览功能 + previewFullImage: { + type: Boolean, + default: uni.$u.props.upload.previewFullImage + }, + // 最大上传数量 + maxCount: { + type: [String, Number], + default: uni.$u.props.upload.maxCount + }, + // 是否启用 + disabled: { + type: Boolean, + default: uni.$u.props.upload.disabled + }, + // 预览上传的图片时的裁剪模式,和image组件mode属性一致 + imageMode: { + type: String, + default: uni.$u.props.upload.imageMode + }, + // 标识符,可以在回调函数的第二项参数中获取 + name: { + type: String, + default: uni.$u.props.upload.name + }, + // 所选的图片的尺寸, 可选值为original compressed + sizeType: { + type: Array, + default: uni.$u.props.upload.sizeType + }, + // 是否开启图片多选,部分安卓机型不支持 + multiple: { + type: Boolean, + default: uni.$u.props.upload.multiple + }, + // 是否展示删除按钮 + deletable: { + type: Boolean, + default: uni.$u.props.upload.deletable + }, + // 文件大小限制,单位为byte + maxSize: { + type: [String, Number], + default: uni.$u.props.upload.maxSize + }, + // 显示已上传的文件列表 + fileList: { + type: Array, + default: uni.$u.props.upload.fileList + }, + // 上传区域的提示文字 + uploadText: { + type: String, + default: uni.$u.props.upload.uploadText + }, + // 内部预览图片区域和选择图片按钮的区域宽度 + width: { + type: [String, Number], + default: uni.$u.props.upload.width + }, + // 内部预览图片区域和选择图片按钮的区域高度 + height: { + type: [String, Number], + default: uni.$u.props.upload.height + }, + // 是否在上传完成后展示预览图 + previewImage: { + type: Boolean, + default: uni.$u.props.upload.previewImage + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-upload/u-upload.vue b/yudao-ui-app/uni_modules/uview-ui/components/u-upload/u-upload.vue new file mode 100644 index 000000000..7c1f3db4d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-upload/u-upload.vue @@ -0,0 +1,556 @@ +<template> + <view class="u-upload" :style="[$u.addStyle(customStyle)]"> + <view class="u-upload__wrap" > + <template v-if="previewImage"> + <view + class="u-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="u-upload__wrap__preview__image" + @tap="onPreviewImage(item)" + :style="[{ + width: $u.addUnit(width), + height: $u.addUnit(height) + }]" + /> + <view + v-else + class="u-upload__wrap__preview__other" + > + <u-icon + color="#80CBF9" + size="26" + :name="item.isVideo || (item.type && item.type === 'video') ? 'movie' : 'folder'" + ></u-icon> + <text class="u-upload__wrap__preview__other__text">{{item.isVideo || (item.type && item.type === 'video') ? '视频' : '文件'}}</text> + </view> + <view + class="u-upload__status" + v-if="item.status === 'uploading' || item.status === 'failed'" + > + <view class="u-upload__status__icon"> + <u-icon + v-if="item.status === 'failed'" + name="close-circle" + color="#ffffff" + size="25" + /> + <u-loading-icon + size="22" + mode="circle" + color="#ffffff" + v-else + /> + </view> + <text + v-if="item.message" + class="u-upload__status__message" + >{{ item.message }}</text> + </view> + <view + class="u-upload__deletable" + v-if="item.status !== 'uploading' && (deletable || item.deletable)" + @tap.stop="deleteItem(index)" + > + <view class="u-upload__deletable__icon"> + <u-icon + name="close" + color="#ffffff" + size="10" + ></u-icon> + </view> + </view> + <view + class="u-upload__success" + v-if="item.status === 'success'" + > + <!-- #ifdef APP-NVUE --> + <image + :src="successIcon" + class="u-upload__success__icon" + ></image> + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <view class="u-upload__success__icon"> + <u-icon + name="checkmark" + color="#ffffff" + size="12" + ></u-icon> + </view> + <!-- #endif --> + </view> + </view> + + </template> + + <template v-if="isInCount"> + <view + v-if="$slots.default || $slots.$default" + @tap="chooseFile" + > + <slot /> + </view> + <view + v-else + class="u-upload__button" + :hover-class="!disabled ? 'u-upload__button--hover' : ''" + hover-stay-time="150" + @tap="chooseFile" + :class="[disabled && 'u-upload__button--disabled']" + :style="[{ + width: $u.addUnit(width), + height: $u.addUnit(height) + }]" + > + <u-icon + :name="uploadIcon" + size="26" + :color="uploadIconColor" + ></u-icon> + <text + v-if="uploadText" + class="u-upload__button__text" + >{{ uploadText }}</text> + </view> + </template> + </view> + + </view> +</template> + +<script> + import { + chooseFile + } from './utils'; + import mixin from './mixin.js'; + import props from './props.js'; + + /** + * upload 上传 + * @description 该组件用于上传图片场景 + * @tutorial https://uviewui.com/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 {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 <u-upload :action="action" :fileList="fileList" ></u-upload> + */ + export default { + name: "u-upload", + mixins: [uni.$u.mpMixin, uni.$u.mixin, mixin,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: { + immediate: true, + handler() { + this.formatFileList() + } + }, + }, + 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' || uni.$u.test.image(item.url || item.thumb), + isVideo: this.accept === 'video' || uni.$u.test.video(item.url || item.thumb), + deletable: typeof(item.deletable) === 'boolean' ? item.deletable : this.deletable, + }) + ); + this.lists = lists + this.isInCount = lists.length < maxCount + }, + chooseFile() { + const { + maxCount, + multiple, + lists, + disabled + } = this; + if (disabled) return; + // 如果用户传入的是字符串,需要格式化成数组 + let capture; + try { + capture = uni.$u.test.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); + }); + }, + // 文件读取之前 + onBeforeRead(file) { + const { + beforeRead, + useBeforeRead, + } = this; + let res = true + // beforeRead是否为一个方法 + if (uni.$u.test.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 (uni.$u.test.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) { + if (!item.isImage || !this.previewFullImage) return + uni.previewImage({ + // 先filter找出为图片的item,再返回filter结果中的图片url + urls: this.lists.filter((item) => this.accept === 'image' || uni.$u.test.image(item.url || item.thumb)).map((item) => item.url || item.thumb), + current: item.url || item.thumb, + fail() { + uni.$u.toast('预览图片失败') + }, + }); + }, + onPreviewVideo(event) { + if (!this.data.previewFullImage) return; + const { + index + } = event.currentTarget.dataset; + const { + lists + } = this.data; + wx.previewMedia({ + sources: lists + .filter((item) => isVideoFile(item)) + .map((item) => + Object.assign(Object.assign({}, item), { + type: 'video' + }) + ), + current: index, + fail() { + uni.$u.toast('预览视频失败') + }, + }); + }, + onClickPreview(event) { + const { + index + } = event.currentTarget.dataset; + const item = this.data.lists[index]; + this.$emit( + 'clickPreview', + Object.assign(Object.assign({}, item), this.getDetail(index)) + ); + } + } + } +</script> + +<style lang="scss" scoped> + @import '../../libs/css/components.scss'; + $u-upload-preview-border-radius: 2px !default; + $u-upload-preview-margin: 0 8px 8px 0 !default; + $u-upload-image-width:80px !default; + $u-upload-image-height:$u-upload-image-width; + $u-upload-other-bgColor: rgb(242, 242, 242) !default; + $u-upload-other-flex:1 !default; + $u-upload-text-font-size:11px !default; + $u-upload-text-color:$u-tips-color !default; + $u-upload-text-margin-top:2px !default; + $u-upload-deletable-right:0 !default; + $u-upload-deletable-top:0 !default; + $u-upload-deletable-bgColor:rgb(55, 55, 55) !default; + $u-upload-deletable-height:14px !default; + $u-upload-deletable-width:$u-upload-deletable-height; + $u-upload-deletable-boder-bottom-left-radius:100px !default; + $u-upload-deletable-zIndex:3 !default; + $u-upload-success-bottom:0 !default; + $u-upload-success-right:0 !default; + $u-upload-success-border-top-color:transparent !default; + $u-upload-success-border-left-color:transparent !default; + $u-upload-success-border-bottom-color: $u-success !default; + $u-upload-success-border-right-color:$u-upload-success-border-bottom-color; + $u-upload-success-border-width:9px !default; + $u-upload-icon-top:0px !default; + $u-upload-icon-right:0px !default; + $u-upload-icon-h5-top:1px !default; + $u-upload-icon-h5-right:0 !default; + $u-upload-icon-width:16px !default; + $u-upload-icon-height:$u-upload-icon-width; + $u-upload-success-icon-bottom:-10px !default; + $u-upload-success-icon-right:-10px !default; + $u-upload-status-right:0 !default; + $u-upload-status-left:0 !default; + $u-upload-status-bottom:0 !default; + $u-upload-status-top:0 !default; + $u-upload-status-bgColor:rgba(0, 0, 0, 0.5) !default; + $u-upload-status-icon-Zindex:1 !default; + $u-upload-message-font-size:12px !default; + $u-upload-message-color:#FFFFFF !default; + $u-upload-message-margin-top:5px !default; + $u-upload-button-width:80px !default; + $u-upload-button-height:$u-upload-button-width; + $u-upload-button-bgColor:rgb(244, 245, 247) !default; + $u-upload-button-border-radius:2px !default; + $u-upload-botton-margin: 0 8px 8px 0 !default; + $u-upload-text-font-size:11px !default; + $u-upload-text-color:$u-tips-color !default; + $u-upload-text-margin-top: 2px !default; + $u-upload-hover-bgColor:rgb(230, 231, 233) !default; + $u-upload-disabled-opacity:.5 !default; + + .u-upload { + @include flex(column); + flex: 1; + + &__wrap { + @include flex; + flex-wrap: wrap; + flex: 1; + + &__preview { + border-radius: $u-upload-preview-border-radius; + margin: $u-upload-preview-margin; + position: relative; + overflow: hidden; + @include flex; + + &__image { + width: $u-upload-image-width; + height: $u-upload-image-height; + } + + &__other { + width: $u-upload-image-width; + height: $u-upload-image-height; + background-color: $u-upload-other-bgColor; + flex: $u-upload-other-flex; + @include flex(column); + justify-content: center; + align-items: center; + + &__text { + font-size: $u-upload-text-font-size; + color: $u-upload-text-color; + margin-top: $u-upload-text-margin-top; + } + } + } + } + + &__deletable { + position: absolute; + top: $u-upload-deletable-top; + right: $u-upload-deletable-right; + background-color: $u-upload-deletable-bgColor; + height: $u-upload-deletable-height; + width: $u-upload-deletable-width; + @include flex; + border-bottom-left-radius: $u-upload-deletable-boder-bottom-left-radius; + align-items: center; + justify-content: center; + z-index: $u-upload-deletable-zIndex; + + &__icon { + position: absolute; + transform: scale(0.7); + top: $u-upload-icon-top; + right: $u-upload-icon-right; + /* #ifdef H5 */ + top: $u-upload-icon-h5-top; + right: $u-upload-icon-h5-right; + /* #endif */ + } + } + + &__success { + position: absolute; + bottom: $u-upload-success-bottom; + right: $u-upload-success-right; + @include flex; + // 由于weex(nvue)为阿里巴巴的KPI(部门业绩考核)的laji产物,不支持css绘制三角形 + // 所以在nvue下使用图片,非nvue下使用css实现 + /* #ifndef APP-NVUE */ + border-top-color: $u-upload-success-border-top-color; + border-left-color: $u-upload-success-border-left-color; + border-bottom-color: $u-upload-success-border-bottom-color; + border-right-color: $u-upload-success-border-right-color; + border-width: $u-upload-success-border-width; + align-items: center; + justify-content: center; + /* #endif */ + + &__icon { + /* #ifndef APP-NVUE */ + position: absolute; + transform: scale(0.7); + bottom: $u-upload-success-icon-bottom; + right: $u-upload-success-icon-right; + /* #endif */ + /* #ifdef APP-NVUE */ + width: $u-upload-icon-width; + height: $u-upload-icon-height; + /* #endif */ + } + } + + &__status { + position: absolute; + top: $u-upload-status-top; + bottom: $u-upload-status-bottom; + left: $u-upload-status-left; + right: $u-upload-status-right; + background-color: $u-upload-status-bgColor; + @include flex(column); + align-items: center; + justify-content: center; + + &__icon { + position: relative; + z-index: $u-upload-status-icon-Zindex; + } + + &__message { + font-size: $u-upload-message-font-size; + color: $u-upload-message-color; + margin-top: $u-upload-message-margin-top; + } + } + + &__button { + @include flex(column); + align-items: center; + justify-content: center; + width: $u-upload-button-width; + height: $u-upload-button-height; + background-color: $u-upload-button-bgColor; + border-radius: $u-upload-button-border-radius; + margin: $u-upload-botton-margin; + /* #ifndef APP-NVUE */ + box-sizing: border-box; + /* #endif */ + + &__text { + font-size: $u-upload-text-font-size; + color: $u-upload-text-color; + margin-top: $u-upload-text-margin-top; + } + + &--hover { + background-color: $u-upload-hover-bgColor; + } + + &--disabled { + opacity: $u-upload-disabled-opacity; + } + } + } +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/components/u-upload/utils.js b/yudao-ui-app/uni_modules/uview-ui/components/u-upload/utils.js new file mode 100644 index 000000000..88cb60244 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/u-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/yudao-ui-app/uni_modules/uview-ui/components/uview-ui/uview-ui.vue b/yudao-ui-app/uni_modules/uview-ui/components/uview-ui/uview-ui.vue new file mode 100644 index 000000000..bcd366247 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/components/uview-ui/uview-ui.vue @@ -0,0 +1,15 @@ +<template> +</template> + +<template> + <view></view> +</template> + +<script> + export default { + + } +</script> + +<style> +</style> diff --git a/yudao-ui-app/uni_modules/uview-ui/index.js b/yudao-ui-app/uni_modules/uview-ui/index.js new file mode 100644 index 000000000..651c090f6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/index.js @@ -0,0 +1,79 @@ +// 看到此报错,是因为没有配置vue.config.js的【transpileDependencies】,详见:https://www.uviewui.com/components/npmSetting.html#_5-cli模式额外配置 +const pleaseSetTranspileDependencies = {}, babelTest = pleaseSetTranspileDependencies?.test + + + +// 引入全局mixin +import mixin from './libs/mixin/mixin.js' +// 小程序特有的mixin +import mpMixin from './libs/mixin/mpMixin.js' +// 全局挂载引入http相关请求拦截插件 +import Request from './libs/luch-request' + +// 路由封装 +import route from './libs/util/route.js' +// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制 +import colorGradient from './libs/function/colorGradient.js' + +// 规则检验 +import test from './libs/function/test.js' +// 防抖方法 +import debounce from './libs/function/debounce.js' +// 节流方法 +import throttle from './libs/function/throttle.js' +// 公共文件写入的方法 +import index from './libs/function/index.js' + +// 配置信息 +import config from './libs/config/config.js' +// props配置信息 +import props from './libs/config/props.js' +// 各个需要fixed的地方的z-index配置文件 +import zIndex from './libs/config/zIndex.js' +// 关于颜色的配置,特殊场景使用 +import color from './libs/config/color.js' +// 平台 +import platform from './libs/function/platform' + +const $u = { + route, + date: index.timeFormat, // 另名date + colorGradient: colorGradient.colorGradient, + hexToRgb: colorGradient.hexToRgb, + rgbToHex: colorGradient.rgbToHex, + colorToRgba: colorGradient.colorToRgba, + test, + type: ['primary', 'success', 'error', 'warning', 'info'], + http: new Request(), + config, // uView配置信息相关,比如版本号 + zIndex, + debounce, + throttle, + mixin, + mpMixin, + props, + ...index, + color, + platform +} + +// $u挂载到uni对象上 +uni.$u = $u + +const install = (Vue) => { + // 时间格式化,同时两个名称,date和timeFormat + Vue.filter('timeFormat', (timestamp, format) => uni.$u.timeFormat(timestamp, format)) + Vue.filter('date', (timestamp, format) => uni.$u.timeFormat(timestamp, format)) + // 将多久以前的方法,注入到全局过滤器 + Vue.filter('timeFrom', (timestamp, format) => uni.$u.timeFrom(timestamp, format)) + // 同时挂载到uni和Vue.prototype中 + // #ifndef APP-NVUE + // 只有vue,挂载到Vue.prototype才有意义,因为nvue中全局Vue.prototype和Vue.mixin是无效的 + Vue.prototype.$u = $u + Vue.mixin(mixin) + // #endif +} + +export default { + install +} diff --git a/yudao-ui-app/uni_modules/uview-ui/index.scss b/yudao-ui-app/uni_modules/uview-ui/index.scss new file mode 100644 index 000000000..8fcfa8387 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/index.scss @@ -0,0 +1,23 @@ +// 引入公共基础类 +@import "./libs/css/common.scss"; +@import "./libs/css/color.scss"; + +// 非nvue的样式 +/* #ifndef APP-NVUE */ +@import "./libs/css/vue.scss"; +/* #endif */ + +// nvue的特有样式 +/* #ifdef APP-NVUE */ +@import "./libs/css/nvue.scss"; +/* #endif */ + +// 小程序特有的样式 +/* #ifdef MP */ +@import "./libs/css/mp.scss"; +/* #endif */ + +// H5特有的样式 +/* #ifdef H5 */ +@import "./libs/css/h5.scss"; +/* #endif */ \ No newline at end of file diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/color.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/color.js new file mode 100644 index 000000000..56b418794 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/color.js @@ -0,0 +1,17 @@ +// 为了让用户能够自定义主题,会逐步弃用此文件,各颜色通过css提供 +// 为了给某些特殊场景使用和向后兼容,无需删除此文件(2020-06-20) +const color = { + primary: '#3c9cff', + info: '#909399', + default: '#909399', + warning: '#f9ae3d', + error: '#f56c6c', + success: '#5ac725', + mainColor: '#303133', + contentColor: '#606266', + tipsColor: '#909399', + lightColor: '#c0c4cc', + borderColor: '#e4e7ed' +} + +export default color diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/config.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/config.js new file mode 100644 index 000000000..eedec0bc1 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/config.js @@ -0,0 +1,34 @@ +// 此版本发布于2022-04-04 +const version = '2.0.30' + +// 开发环境才提示,生产环境不会提示 +if (process.env.NODE_ENV === 'development') { + console.log(`\n %c uView V${version} %c https://www.uviewui.com/ \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0;', 'color: #3c9cff;background: #ffffff; padding:5px 0;'); +} + +export default { + v: version, + version, + // 主题名称 + type: [ + 'primary', + 'success', + 'info', + 'error', + 'warning' + ], + // 颜色部分,本来可以通过scss的:export导出供js使用,但是奈何nvue不支持 + color: { + 'u-primary': '#2979ff', + 'u-warning': '#ff9900', + 'u-success': '#19be6b', + 'u-error': '#fa3534', + 'u-info': '#909399', + 'u-main-color': '#303133', + 'u-content-color': '#606266', + 'u-tips-color': '#909399', + 'u-light-color': '#c0c4cc' + }, + // 默认单位,可以通过配置为rpx,那么在用于传入组件大小参数为数值时,就默认为rpx + unit: 'px' +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props.js new file mode 100644 index 000000000..6930d4863 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props.js @@ -0,0 +1,190 @@ +/** + * 此文件的作用为统一配置所有组件的props参数 + * 借此用户可以全局覆盖组件的props默认值 + * 无需在每个引入组件的页面中都配置一次 + */ +import config from './config' + +import actionSheet from './props/actionSheet.js' +import album from './props/album.js' +import alert from './props/alert.js' +import avatar from './props/avatar' +import avatarGroup from './props/avatarGroup' +import backtop from './props/backtop' +import badge from './props/badge' +import button from './props/button' +import calendar from './props/calendar' +import carKeyboard from './props/carKeyboard' +import cell from './props/cell' +import cellGroup from './props/cellGroup' +import checkbox from './props/checkbox' +import checkboxGroup from './props/checkboxGroup' +import circleProgress from './props/circleProgress' +import code from './props/code' +import codeInput from './props/codeInput' +import col from './props/col' +import collapse from './props/collapse' +import collapseItem from './props/collapseItem' +import columnNotice from './props/columnNotice' +import countDown from './props/countDown' +import countTo from './props/countTo' +import datetimePicker from './props/datetimePicker' +import divider from './props/divider' +import empty from './props/empty' +import form from './props/form' +import formItem from './props/formItem' +import gap from './props/gap' +import grid from './props/grid' +import gridItem from './props/gridItem' +import icon from './props/icon' +import image from './props/image' +import indexAnchor from './props/indexAnchor' +import indexList from './props/indexList' +import input from './props/input' +import keyboard from './props/keyboard' +import line from './props/line' +import lineProgress from './props/lineProgress' +import link from './props/link' +import list from './props/list' +import listItem from './props/listItem' +import loadingIcon from './props/loadingIcon' +import loadingPage from './props/loadingPage' +import loadmore from './props/loadmore' +import modal from './props/modal' +import navbar from './props/navbar' +import noNetwork from './props/noNetwork' +import noticeBar from './props/noticeBar' +import notify from './props/notify' +import numberBox from './props/numberBox' +import numberKeyboard from './props/numberKeyboard' +import overlay from './props/overlay' +import parse from './props/parse' +import picker from './props/picker' +import popup from './props/popup' +import radio from './props/radio' +import radioGroup from './props/radioGroup' +import rate from './props/rate' +import readMore from './props/readMore' +import row from './props/row' +import rowNotice from './props/rowNotice' +import scrollList from './props/scrollList' +import search from './props/search' +import section from './props/section' +import skeleton from './props/skeleton' +import slider from './props/slider' +import statusBar from './props/statusBar' +import steps from './props/steps' +import stepsItem from './props/stepsItem' +import sticky from './props/sticky' +import subsection from './props/subsection' +import swipeAction from './props/swipeAction' +import swipeActionItem from './props/swipeActionItem' +import swiper from './props/swiper' +import swipterIndicator from './props/swipterIndicator' +import _switch from './props/switch' +import tabbar from './props/tabbar' +import tabbarItem from './props/tabbarItem' +import tabs from './props/tabs' +import tag from './props/tag' +import text from './props/text' +import textarea from './props/textarea' +import toast from './props/toast' +import toolbar from './props/toolbar' +import tooltip from './props/tooltip' +import transition from './props/transition' +import upload from './props/upload' + +const { + color +} = config + +export default { + ...actionSheet, + ...album, + ...alert, + ...avatar, + ...avatarGroup, + ...backtop, + ...badge, + ...button, + ...calendar, + ...carKeyboard, + ...cell, + ...cellGroup, + ...checkbox, + ...checkboxGroup, + ...circleProgress, + ...code, + ...codeInput, + ...col, + ...collapse, + ...collapseItem, + ...columnNotice, + ...countDown, + ...countTo, + ...datetimePicker, + ...divider, + ...empty, + ...form, + ...formItem, + ...gap, + ...grid, + ...gridItem, + ...icon, + ...image, + ...indexAnchor, + ...indexList, + ...input, + ...keyboard, + ...line, + ...lineProgress, + ...link, + ...list, + ...listItem, + ...loadingIcon, + ...loadingPage, + ...loadmore, + ...modal, + ...navbar, + ...noNetwork, + ...noticeBar, + ...notify, + ...numberBox, + ...numberKeyboard, + ...overlay, + ...parse, + ...picker, + ...popup, + ...radio, + ...radioGroup, + ...rate, + ...readMore, + ...row, + ...rowNotice, + ...scrollList, + ...search, + ...section, + ...skeleton, + ...slider, + ...statusBar, + ...steps, + ...stepsItem, + ...sticky, + ...subsection, + ...swipeAction, + ...swipeActionItem, + ...swiper, + ...swipterIndicator, + ..._switch, + ...tabbar, + ...tabbarItem, + ...tabs, + ...tag, + ...text, + ...textarea, + ...toast, + ...toolbar, + ...tooltip, + ...transition, + ...upload +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/actionSheet.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/actionSheet.js new file mode 100644 index 000000000..d8061a776 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/actionSheet.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:44:35 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/actionSheet.js + */ +export default { + // action-sheet组件 + actionSheet: { + show: false, + title: '', + description: '', + actions: () => [], + index: '', + cancelText: '', + closeOnClickAction: true, + safeAreaInsetBottom: true, + openType: '', + closeOnClickOverlay: true, + round: 0 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/album.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/album.js new file mode 100644 index 000000000..8877326af --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/album.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:47:24 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/album.js + */ +export default { + // album 组件 + album: { + urls: () => [], + keyName: '', + singleSize: 180, + multipleSize: 70, + space: 6, + singleMode: 'scaleToFill', + multipleMode: 'aspectFill', + maxCount: 9, + previewFullImage: true, + rowCount: 3, + showMore: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/alert.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/alert.js new file mode 100644 index 000000000..8f8182cb4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/alert.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:48:53 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/alert.js + */ +export default { + // alert警告组件 + alert: { + title: '', + type: 'warning', + description: '', + closable: false, + showIcon: false, + effect: 'light', + center: false, + fontSize: 14 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/avatar.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/avatar.js new file mode 100644 index 000000000..c097d4e79 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/avatar.js @@ -0,0 +1,28 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:49:22 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/avatar.js + */ +export default { + // avatar 组件 + avatar: { + src: '', + shape: 'circle', + size: 40, + mode: 'scaleToFill', + text: '', + bgColor: '#c0c4cc', + color: '#ffffff', + fontSize: 18, + icon: '', + mpAvatar: false, + randomBgColor: false, + defaultUrl: '', + colorIndex: '', + name: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/avatarGroup.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/avatarGroup.js new file mode 100644 index 000000000..f4a66c36e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/avatarGroup.js @@ -0,0 +1,23 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:49:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/avatarGroup.js + */ +export default { + // avatarGroup 组件 + avatarGroup: { + urls: () => [], + maxCount: 5, + shape: 'circle', + mode: 'scaleToFill', + showMore: true, + size: 40, + keyName: '', + gap: 0.5, + extraValue: 0 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/backtop.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/backtop.js new file mode 100644 index 000000000..80f17d0c6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/backtop.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:50:18 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/backtop.js + */ +export default { + // backtop组件 + backtop: { + mode: 'circle', + icon: 'arrow-upward', + text: '', + duration: 100, + scrollTop: 0, + top: 400, + bottom: 100, + right: 20, + zIndex: 9, + iconStyle: () => ({ + color: '#909399', + fontSize: '19px' + }) + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/badge.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/badge.js new file mode 100644 index 000000000..44ee7cc9c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/badge.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-23 19:51:50 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/badge.js + */ +export default { + // 徽标数组件 + badge: { + isDot: false, + value: '', + show: true, + max: 999, + type: 'error', + showZero: false, + bgColor: null, + color: null, + shape: 'circle', + numberType: 'overflow', + offset: () => [], + inverted: false, + absolute: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/button.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/button.js new file mode 100644 index 000000000..acd65fc3f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/button.js @@ -0,0 +1,42 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:51:27 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/button.js + */ +export default { + // button组件 + button: { + hairline: false, + type: 'info', + size: 'normal', + shape: 'square', + plain: false, + disabled: false, + loading: false, + loadingText: '', + loadingMode: 'spinner', + loadingSize: 15, + openType: '', + formType: '', + appParameter: '', + hoverStopPropagation: true, + lang: 'en', + sessionFrom: '', + sendMessageTitle: '', + sendMessagePath: '', + sendMessageImg: '', + showMessageCard: false, + dataName: '', + throttleTime: 0, + hoverStartTime: 0, + hoverStayTime: 200, + text: '', + icon: '', + iconColor: '', + color: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/calendar.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/calendar.js new file mode 100644 index 000000000..bfd2bd6e8 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/calendar.js @@ -0,0 +1,42 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:52:43 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/calendar.js + */ +export default { + // calendar 组件 + calendar: { + title: '日期选择', + showTitle: true, + showSubtitle: true, + mode: 'single', + startText: '开始', + endText: '结束', + customList: () => [], + color: '#3c9cff', + minDate: 0, + maxDate: 0, + defaultDate: null, + maxCount: Number.MAX_SAFE_INTEGER, // Infinity + rowHeight: 56, + formatter: null, + showLunar: false, + showMark: true, + confirmText: '确定', + confirmDisabledText: '确定', + show: false, + closeOnClickOverlay: false, + readonly: false, + showConfirm: true, + maxRange: Number.MAX_SAFE_INTEGER, // Infinity + rangePrompt: '', + showRangePrompt: true, + allowSameDay: false, + round: 0, + monthNum: 3 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/carKeyboard.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/carKeyboard.js new file mode 100644 index 000000000..af1baa00d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/carKeyboard.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:53:20 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/carKeyboard.js + */ +export default { + // 车牌号键盘 + carKeyboard: { + random: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/cell.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/cell.js new file mode 100644 index 000000000..425ea3fed --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/cell.js @@ -0,0 +1,35 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-23 20:53:09 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/cell.js + */ +export default { + // cell组件的props + cell: { + customClass: '', + title: '', + label: '', + value: '', + icon: '', + disabled: false, + border: true, + center: false, + url: '', + linkType: 'navigateTo', + clickable: false, + isLink: false, + required: false, + arrowDirection: '', + iconStyle: {}, + rightIconStyle: {}, + rightIcon: 'arrow-right', + titleStyle: {}, + size: '', + stop: true, + name: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/cellGroup.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/cellGroup.js new file mode 100644 index 000000000..d48a9cd99 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/cellGroup.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:54:16 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/cellGroup.js + */ +export default { + // cell-group组件的props + cellGroup: { + title: '', + border: true, + customStyle: {} + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/checkbox.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/checkbox.js new file mode 100644 index 000000000..231090111 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/checkbox.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-23 21:06:59 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/checkbox.js + */ +export default { + // checkbox组件 + checkbox: { + name: '', + shape: '', + size: '', + checkbox: false, + disabled: '', + activeColor: '', + inactiveColor: '', + iconSize: '', + iconColor: '', + label: '', + labelSize: '', + labelColor: '', + labelDisabled: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/checkboxGroup.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/checkboxGroup.js new file mode 100644 index 000000000..8798fa4ed --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/checkboxGroup.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:54:47 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/checkboxGroup.js + */ +export default { + // checkbox-group组件 + checkboxGroup: { + name: '', + value: () => [], + shape: 'square', + disabled: false, + activeColor: '#2979ff', + inactiveColor: '#c8c9cc', + size: 18, + placement: 'row', + labelSize: 14, + labelColor: '#303133', + labelDisabled: false, + iconColor: '#ffffff', + iconSize: 12, + iconPlacement: 'left', + borderBottom: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/circleProgress.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/circleProgress.js new file mode 100644 index 000000000..b3a9b43ef --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/circleProgress.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:55:02 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/circleProgress.js + */ +export default { + // circleProgress 组件 + circleProgress: { + percentage: 30 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/code.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/code.js new file mode 100644 index 000000000..693417aae --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/code.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:55:27 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/code.js + */ + +export default { + // code 组件 + code: { + seconds: 60, + startText: '获取验证码', + changeText: 'X秒重新获取', + endText: '重新获取', + keepRunning: false, + uniqueKey: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/codeInput.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/codeInput.js new file mode 100644 index 000000000..008f4d532 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/codeInput.js @@ -0,0 +1,28 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:55:58 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/codeInput.js + */ +export default { + // codeInput 组件 + codeInput: { + maxlength: 6, + dot: false, + mode: 'box', + hairline: false, + space: 10, + value: '', + focus: false, + bold: false, + color: '#606266', + fontSize: 18, + size: 35, + disabledKeyboard: false, + borderColor: '#c9cacc', + disabledDot: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/col.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/col.js new file mode 100644 index 000000000..76216535c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/col.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:56:12 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/col.js + */ +export default { + // col 组件 + col: { + span: 12, + offset: 0, + justify: 'start', + align: 'stretch', + textAlign: 'left' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/collapse.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/collapse.js new file mode 100644 index 000000000..c2b9fdd85 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/collapse.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:56:30 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/collapse.js + */ +export default { + // collapse 组件 + collapse: { + value: null, + accordion: false, + border: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/collapseItem.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/collapseItem.js new file mode 100644 index 000000000..74ce682cd --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/collapseItem.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:56:42 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/collapseItem.js + */ +export default { + // collapseItem 组件 + collapseItem: { + title: '', + value: '', + label: '', + disabled: false, + isLink: true, + clickable: true, + border: true, + align: 'left', + name: '', + icon: '', + duration: 300 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/columnNotice.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/columnNotice.js new file mode 100644 index 000000000..147c0aa98 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/columnNotice.js @@ -0,0 +1,24 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:57:16 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/columnNotice.js + */ +export default { + // columnNotice 组件 + columnNotice: { + text: '', + icon: 'volume', + mode: '', + color: '#f9ae3d', + bgColor: '#fdf6ec', + fontSize: 14, + speed: 80, + step: false, + duration: 1500, + disableTouch: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/countDown.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/countDown.js new file mode 100644 index 000000000..81e33b1d2 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/countDown.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:11:29 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/countDown.js + */ +export default { + // u-count-down 计时器组件 + countDown: { + time: 0, + format: 'HH:mm:ss', + autoStart: true, + millisecond: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/countTo.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/countTo.js new file mode 100644 index 000000000..a536cde7b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/countTo.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:57:32 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/countTo.js + */ +export default { + // countTo 组件 + countTo: { + startVal: 0, + endVal: 0, + duration: 2000, + autoplay: true, + decimals: 0, + useEasing: true, + decimal: '.', + color: '#606266', + fontSize: 22, + bold: false, + separator: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/datetimePicker.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/datetimePicker.js new file mode 100644 index 000000000..4f90966cc --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/datetimePicker.js @@ -0,0 +1,36 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:57:48 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/datetimePicker.js + */ +export default { + // datetimePicker 组件 + datetimePicker: { + show: false, + showToolbar: true, + value: '', + title: '', + mode: 'datetime', + maxDate: new Date(new Date().getFullYear() + 10, 0, 1).getTime(), + minDate: new Date(new Date().getFullYear() - 10, 0, 1).getTime(), + minHour: 0, + maxHour: 23, + minMinute: 0, + maxMinute: 59, + filter: null, + formatter: null, + loading: false, + itemHeight: 44, + cancelText: '取消', + confirmText: '确认', + cancelColor: '#909193', + confirmColor: '#3c9cff', + visibleItemCount: 5, + closeOnClickOverlay: false, + defaultIndex: () => [] + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/divider.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/divider.js new file mode 100644 index 000000000..55a8ce45e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/divider.js @@ -0,0 +1,23 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:58:03 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/divider.js + */ +export default { + // divider组件 + divider: { + dashed: false, + hairline: true, + dot: false, + textPosition: 'center', + text: '', + textSize: 14, + textColor: '#909399', + lineColor: '#dcdfe6' + } + +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/empty.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/empty.js new file mode 100644 index 000000000..fe2044574 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/empty.js @@ -0,0 +1,26 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:03:27 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/empty.js + */ +export default { + // empty组件 + empty: { + icon: '', + text: '', + textColor: '#c0c4cc', + textSize: 14, + iconColor: '#c0c4cc', + iconSize: 90, + mode: 'data', + width: 160, + height: 160, + show: true, + marginTop: 0 + } + +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/form.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/form.js new file mode 100644 index 000000000..41b122e3b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/form.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:03:49 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/form.js + */ +export default { + // form 组件 + form: { + model: () => ({}), + rules: () => ({}), + errorType: 'message', + borderBottom: true, + labelPosition: 'left', + labelWidth: 45, + labelAlign: 'left', + labelStyle: () => ({}) + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/formItem.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/formItem.js new file mode 100644 index 000000000..dc94973a8 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/formItem.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:04:32 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/formItem.js + */ +export default { + // formItem 组件 + formItem: { + label: '', + prop: '', + borderBottom: '', + labelWidth: '', + rightIcon: '', + leftIcon: '', + required: false, + leftIconStyle: '', + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/gap.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/gap.js new file mode 100644 index 000000000..60a21afb5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/gap.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:05:25 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/gap.js + */ +export default { + // gap组件 + gap: { + bgColor: 'transparent', + height: 20, + marginTop: 0, + marginBottom: 0, + customStyle: {} + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/grid.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/grid.js new file mode 100644 index 000000000..60abeb773 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/grid.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:05:57 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/grid.js + */ +export default { + // grid组件 + grid: { + col: 3, + border: false, + align: 'left' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/gridItem.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/gridItem.js new file mode 100644 index 000000000..1b747f4df --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/gridItem.js @@ -0,0 +1,16 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:06:13 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/gridItem.js + */ +export default { + // grid-item组件 + gridItem: { + name: null, + bgColor: 'transparent' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/icon.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/icon.js new file mode 100644 index 000000000..1d81d2dd5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/icon.js @@ -0,0 +1,36 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 18:00:14 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/icon.js + */ +import config from '../config' + +const { + color +} = config +export default { + // icon组件 + icon: { + name: '', + color: color['u-content-color'], + size: '16px', + bold: false, + index: '', + hoverClass: '', + customPrefix: 'uicon', + label: '', + labelPos: 'right', + labelSize: '15px', + labelColor: color['u-content-color'], + space: '3px', + imgMode: '', + width: '', + height: '', + top: 0, + stop: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/image.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/image.js new file mode 100644 index 000000000..2552db6fa --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/image.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:01:51 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/image.js + */ +export default { + // image组件 + image: { + src: '', + mode: 'aspectFill', + width: '300', + height: '225', + shape: 'square', + radius: 0, + lazyLoad: true, + showMenuByLongpress: true, + loadingIcon: 'photo', + errorIcon: 'error-circle', + showLoading: true, + showError: true, + fade: true, + webp: false, + duration: 500, + bgColor: '#f3f4f6' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/indexAnchor.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/indexAnchor.js new file mode 100644 index 000000000..bb20d4688 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/indexAnchor.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:13:15 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/indexAnchor.js + */ +export default { + // indexAnchor 组件 + indexAnchor: { + text: '', + color: '#606266', + size: 14, + bgColor: '#dedede', + height: 32 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/indexList.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/indexList.js new file mode 100644 index 000000000..dc6ce9485 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/indexList.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:13:35 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/indexList.js + */ +export default { + // indexList 组件 + indexList: { + inactiveColor: '#606266', + activeColor: '#5677fc', + indexList: () => [], + sticky: true, + customNavHeight: 0 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/input.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/input.js new file mode 100644 index 000000000..4f0edc60f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/input.js @@ -0,0 +1,48 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:13:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/input.js + */ +export default { + // index 组件 + input: { + value: '', + type: 'text', + fixed: false, + disabled: false, + disabledColor: '#f5f7fa', + clearable: false, + password: false, + maxlength: -1, + placeholder: null, + placeholderClass: 'input-placeholder', + placeholderStyle: 'color: #c0c4cc', + showWordLimit: false, + confirmType: 'done', + confirmHold: false, + holdKeyboard: false, + focus: false, + autoBlur: false, + disableDefaultPadding: false, + cursor: -1, + cursorSpacing: 30, + selectionStart: -1, + selectionEnd: -1, + adjustPosition: true, + inputAlign: 'left', + fontSize: '15px', + color: '#303133', + prefixIcon: '', + prefixIconStyle: '', + suffixIcon: '', + suffixIconStyle: '', + border: 'surround', + readonly: false, + shape: 'square', + formatter: null + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/keyboard.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/keyboard.js new file mode 100644 index 000000000..57182bdd2 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/keyboard.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:07:49 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/keyboard.js + */ +export default { + // 键盘组件 + keyboard: { + mode: 'number', + dotDisabled: false, + tooltip: true, + showTips: true, + tips: '', + showCancel: true, + showConfirm: true, + random: false, + safeAreaInsetBottom: true, + closeOnClickOverlay: true, + show: false, + overlay: true, + zIndex: 10075, + cancelText: '取消', + confirmText: '确定', + autoChange: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/line.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/line.js new file mode 100644 index 000000000..2c87af238 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/line.js @@ -0,0 +1,20 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:04:49 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/line.js + */ +export default { + // line组件 + line: { + color: '#d6d7d9', + length: '100%', + direction: 'row', + hairline: true, + margin: 0, + dashed: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/lineProgress.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/lineProgress.js new file mode 100644 index 000000000..cdfcb0ee7 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/lineProgress.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:14:11 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/lineProgress.js + */ +export default { + // lineProgress 组件 + lineProgress: { + activeColor: '#19be6b', + inactiveColor: '#ececec', + percentage: 0, + showText: true, + height: 12 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/link.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/link.js new file mode 100644 index 000000000..6c4c88331 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/link.js @@ -0,0 +1,26 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:45:36 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/link.js + */ +import config from '../config' + +const { + color +} = config +export default { + // link超链接组件props参数 + link: { + color: color['u-primary'], + fontSize: 15, + underLine: false, + href: '', + mpTips: '链接已复制,请在浏览器打开', + lineColor: '', + text: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/list.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/list.js new file mode 100644 index 000000000..a830c3218 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/list.js @@ -0,0 +1,28 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:14:53 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/list.js + */ +export default { + // list 组件 + list: { + showScrollbar: false, + lowerThreshold: 50, + upperThreshold: 0, + scrollTop: 0, + offsetAccuracy: 10, + enableFlex: false, + pagingEnabled: false, + scrollable: true, + scrollIntoView: '', + scrollWithAnimation: false, + enableBackToTop: false, + height: 0, + width: 0, + preLoadScreen: 1 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/listItem.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/listItem.js new file mode 100644 index 000000000..7fe2166eb --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/listItem.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:15:40 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/listItem.js + */ +export default { + // listItem 组件 + listItem: { + anchor: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadingIcon.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadingIcon.js new file mode 100644 index 000000000..f4739c4f0 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadingIcon.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:45:47 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/loadingIcon.js + */ +import config from '../config' + +const { + color +} = config +export default { + // loading-icon加载中图标组件 + loadingIcon: { + show: true, + color: color['u-tips-color'], + textColor: color['u-tips-color'], + vertical: false, + mode: 'spinner', + size: 24, + textSize: 15, + text: '', + timingFunction: 'ease-in-out', + duration: 1200, + inactiveColor: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadingPage.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadingPage.js new file mode 100644 index 000000000..7546b311a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadingPage.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:00:23 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/loadingPage.js + */ +export default { + // loading-page组件 + loadingPage: { + loadingText: '正在加载', + image: '', + loadingMode: 'circle', + loading: false, + bgColor: '#ffffff', + color: '#C8C8C8', + fontSize: 19, + loadingColor: '#C8C8C8' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadmore.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadmore.js new file mode 100644 index 000000000..465842fcc --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/loadmore.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:15:26 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/loadmore.js + */ +export default { + // loadmore 组件 + loadmore: { + status: 'loadmore', + bgColor: 'transparent', + icon: true, + fontSize: 14, + color: '#606266', + loadingIcon: 'spinner', + loadmoreText: '加载更多', + loadingText: '正在加载...', + nomoreText: '没有更多了', + isDot: false, + iconColor: '#b7b7b7', + marginTop: 10, + marginBottom: 10, + height: 'auto', + line: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/modal.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/modal.js new file mode 100644 index 000000000..2ae3fffc7 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/modal.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:15:59 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/modal.js + */ +export default { + // modal 组件 + modal: { + show: false, + title: '', + content: '', + confirmText: '确认', + cancelText: '取消', + showConfirmButton: true, + showCancelButton: false, + confirmColor: '#2979ff', + cancelColor: '#606266', + buttonReverse: false, + zoom: true, + asyncClose: false, + closeOnClickOverlay: false, + negativeTop: 0, + width: '650rpx', + confirmButtonShape: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/navbar.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/navbar.js new file mode 100644 index 000000000..614a99ddc --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/navbar.js @@ -0,0 +1,32 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:16:18 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/navbar.js + */ +import color from '../color' +export default { + // navbar 组件 + navbar: { + safeAreaInsetTop: true, + placeholder: false, + fixed: true, + border: false, + leftIcon: 'arrow-left', + leftText: '', + rightText: '', + rightIcon: '', + title: '', + bgColor: '#ffffff', + titleWidth: '400rpx', + height: '44px', + leftIconSize: 20, + leftIconColor: color.mainColor, + autoBack: false, + titleStyle: '' + } + +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/noNetwork.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/noNetwork.js new file mode 100644 index 000000000..74dba1ba4 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/noNetwork.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:16:39 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/noNetwork.js + */ +export default { + // noNetwork + noNetwork: { + tips: '哎呀,网络信号丢失', + zIndex: '', + image: 'data:image/png;base64,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' + } + +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/noticeBar.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/noticeBar.js new file mode 100644 index 000000000..02c660a27 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/noticeBar.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:17:13 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/noticeBar.js + */ +export default { + // noticeBar + noticeBar: { + text: () => [], + direction: 'row', + step: false, + icon: 'volume', + mode: '', + color: '#f9ae3d', + bgColor: '#fdf6ec', + speed: 80, + fontSize: 14, + duration: 2000, + disableTouch: true, + url: '', + linkType: 'navigateTo' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/notify.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/notify.js new file mode 100644 index 000000000..1042d2a12 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/notify.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:10:21 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/notify.js + */ +export default { + // notify组件 + notify: { + top: 0, + type: 'primary', + color: '#ffffff', + bgColor: '', + message: '', + duration: 3000, + fontSize: 15, + safeAreaInsetTop: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/numberBox.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/numberBox.js new file mode 100644 index 000000000..424f0ca14 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/numberBox.js @@ -0,0 +1,35 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:11:46 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/numberBox.js + */ +export default { + // 步进器组件 + numberBox: { + name: '', + value: 0, + min: 1, + max: Number.MAX_SAFE_INTEGER, + step: 1, + integer: false, + disabled: false, + disabledInput: false, + asyncChange: false, + inputWidth: 35, + showMinus: true, + showPlus: true, + decimalLength: null, + longPress: true, + color: '#323233', + buttonSize: 30, + bgColor: '#EBECEE', + cursorSpacing: 100, + disableMinus: false, + disablePlus: false, + iconStyle: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/numberKeyboard.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/numberKeyboard.js new file mode 100644 index 000000000..7b45065ac --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/numberKeyboard.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:08:05 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/numberKeyboard.js + */ +export default { + // 数字键盘 + numberKeyboard: { + mode: 'number', + dotDisabled: false, + random: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/overlay.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/overlay.js new file mode 100644 index 000000000..c26d0680f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/overlay.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:06:50 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/overlay.js + */ +export default { + // overlay组件 + overlay: { + show: false, + zIndex: 10070, + duration: 300, + opacity: 0.5 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/parse.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/parse.js new file mode 100644 index 000000000..feb22b9ca --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/parse.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:17:33 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/parse.js + */ +export default { + // parse + parse: { + copyLink: true, + errorImg: '', + lazyLoad: false, + loadingImg: '', + pauseVideo: true, + previewImg: true, + setTitle: true, + showImgMenu: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/picker.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/picker.js new file mode 100644 index 000000000..711e69041 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/picker.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:18:20 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/picker.js + */ +export default { + // picker + picker: { + show: false, + showToolbar: true, + title: '', + columns: () => [], + loading: false, + itemHeight: 44, + cancelText: '取消', + confirmText: '确定', + cancelColor: '#909193', + confirmColor: '#3c9cff', + singleIndex: 0, + visibleItemCount: 5, + keyName: 'text', + closeOnClickOverlay: false, + defaultIndex: () => [], + immediateChange: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/popup.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/popup.js new file mode 100644 index 000000000..0cc1bc000 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/popup.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:06:33 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/popup.js + */ +export default { + // popup组件 + popup: { + show: false, + overlay: true, + mode: 'bottom', + duration: 300, + closeable: false, + overlayStyle: () => {}, + closeOnClickOverlay: true, + zIndex: 10075, + safeAreaInsetBottom: true, + safeAreaInsetTop: false, + closeIconPos: 'top-right', + round: 0, + zoom: true, + bgColor: '', + overlayOpacity: 0.5 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/radio.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/radio.js new file mode 100644 index 000000000..4df200f21 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/radio.js @@ -0,0 +1,27 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:02:34 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/radio.js + */ +export default { + // radio组件 + radio: { + name: '', + shape: '', + disabled: '', + labelDisabled: '', + activeColor: '', + inactiveColor: '', + iconSize: '', + labelSize: '', + label: '', + labelColor: '', + size: '', + iconColor: '', + placement: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/radioGroup.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/radioGroup.js new file mode 100644 index 000000000..728e9dbc3 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/radioGroup.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:03:12 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/radioGroup.js + */ +export default { + // radio-group组件 + radioGroup: { + value: '', + disabled: false, + shape: 'circle', + activeColor: '#2979ff', + inactiveColor: '#c8c9cc', + name: '', + size: 18, + placement: 'row', + label: '', + labelColor: '#303133', + labelSize: 14, + labelDisabled: false, + iconColor: '#ffffff', + iconSize: 12, + borderBottom: false, + iconPlacement: 'left' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/rate.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/rate.js new file mode 100644 index 000000000..d31c61aff --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/rate.js @@ -0,0 +1,26 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:05:09 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/rate.js + */ +export default { + // rate组件 + rate: { + value: 1, + count: 5, + disabled: false, + size: 18, + inactiveColor: '#b2b2b2', + activeColor: '#FA3534', + gutter: 4, + minCount: 1, + allowHalf: false, + activeIcon: 'star-fill', + inactiveIcon: 'star', + touchable: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/readMore.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/readMore.js new file mode 100644 index 000000000..09b11cc53 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/readMore.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:18:41 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/readMore.js + */ +export default { + // readMore + readMore: { + showHeight: 400, + toggle: false, + closeText: '展开阅读全文', + openText: '收起', + color: '#2979ff', + fontSize: 14, + textIndent: '2em', + name: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/row.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/row.js new file mode 100644 index 000000000..573a431dc --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/row.js @@ -0,0 +1,17 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:18:58 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/row.js + */ +export default { + // row + row: { + gutter: 0, + justify: 'start', + align: 'center' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/rowNotice.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/rowNotice.js new file mode 100644 index 000000000..cd9d0a082 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/rowNotice.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:19:13 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/rowNotice.js + */ +export default { + // rowNotice + rowNotice: { + text: '', + icon: 'volume', + mode: '', + color: '#f9ae3d', + bgColor: '#fdf6ec', + fontSize: 14, + speed: 80 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/scrollList.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/scrollList.js new file mode 100644 index 000000000..441e63a43 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/scrollList.js @@ -0,0 +1,20 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:19:28 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/scrollList.js + */ +export default { + // scrollList + scrollList: { + indicatorWidth: 50, + indicatorBarWidth: 20, + indicator: true, + indicatorColor: '#f2f2f2', + indicatorActiveColor: '#3c9cff', + indicatorStyle: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/search.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/search.js new file mode 100644 index 000000000..269995477 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/search.js @@ -0,0 +1,37 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:19:45 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/search.js + */ +export default { + // search + search: { + shape: 'round', + bgColor: '#f2f2f2', + placeholder: '请输入关键字', + clearabled: true, + focus: false, + showAction: true, + actionStyle: () => ({}), + actionText: '搜索', + inputAlign: 'left', + inputStyle: () => ({}), + disabled: false, + borderColor: 'transparent', + searchIconColor: '#909399', + searchIconSize: 22, + color: '#606266', + placeholderColor: '#909399', + searchIcon: 'search', + margin: '0', + animation: false, + value: '', + maxlength: '-1', + height: 32, + label: null + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/section.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/section.js new file mode 100644 index 000000000..f43264860 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/section.js @@ -0,0 +1,24 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:07:33 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/section.js + */ +export default { + // u-section组件 + section: { + title: '', + subTitle: '更多', + right: true, + fontSize: 15, + bold: true, + color: '#303133', + subColor: '#909399', + showLine: true, + lineColor: '', + arrow: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/skeleton.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/skeleton.js new file mode 100644 index 000000000..83b777d2c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/skeleton.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:20:14 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/skeleton.js + */ +export default { + // skeleton + skeleton: { + loading: true, + animate: true, + rows: 0, + rowsWidth: '100%', + rowsHeight: 18, + title: true, + titleWidth: '50%', + titleHeight: 18, + avatar: false, + avatarSize: 32, + avatarShape: 'circle' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/slider.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/slider.js new file mode 100644 index 000000000..50cc37f5a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/slider.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:08:25 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/slider.js + */ +export default { + // slider组件 + slider: { + value: 0, + blockSize: 18, + min: 0, + max: 100, + step: 1, + activeColor: '#2979ff', + inactiveColor: '#c0c4cc', + blockColor: '#ffffff', + showValue: false, + disabled:false, + blockStyle: () => {} + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/statusBar.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/statusBar.js new file mode 100644 index 000000000..d237a8347 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/statusBar.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:20:39 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/statusBar.js + */ +export default { + // statusBar + statusBar: { + bgColor: 'transparent' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/steps.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/steps.js new file mode 100644 index 000000000..881c39ef6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/steps.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:12:37 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/steps.js + */ +export default { + // steps组件 + steps: { + direction: 'row', + current: 0, + activeColor: '#3c9cff', + inactiveColor: '#969799', + activeIcon: '', + inactiveIcon: '', + dot: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/stepsItem.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/stepsItem.js new file mode 100644 index 000000000..5dba8f4d2 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/stepsItem.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:12:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/stepsItem.js + */ +export default { + // steps-item组件 + stepsItem: { + title: '', + desc: '', + iconSize: 17, + error: false + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/sticky.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/sticky.js new file mode 100644 index 000000000..b03460427 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/sticky.js @@ -0,0 +1,20 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:01:30 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/sticky.js + */ +export default { + // sticky组件 + sticky: { + offsetTop: 0, + customNavHeight: 0, + disabled: false, + bgColor: 'transparent', + zIndex: '', + index: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/subsection.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/subsection.js new file mode 100644 index 000000000..9a165ff57 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/subsection.js @@ -0,0 +1,23 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:12:20 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/subsection.js + */ +export default { + // subsection组件 + subsection: { + list: [], + current: 0, + activeColor: '#3c9cff', + inactiveColor: '#303133', + mode: 'button', + fontSize: 12, + bold: true, + bgColor: '#eeeeef', + keyName: 'name' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipeAction.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipeAction.js new file mode 100644 index 000000000..25051b80b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipeAction.js @@ -0,0 +1,15 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:00:42 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/swipeAction.js + */ +export default { + // swipe-action组件 + swipeAction: { + autoClose: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipeActionItem.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipeActionItem.js new file mode 100644 index 000000000..40ef27cab --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipeActionItem.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:01:13 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/swipeActionItem.js + */ +export default { + // swipeActionItem 组件 + swipeActionItem: { + show: false, + name: '', + disabled: false, + threshold: 20, + autoClose: true, + options: [], + duration: 300 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swiper.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swiper.js new file mode 100644 index 000000000..0e6a3b7db --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swiper.js @@ -0,0 +1,39 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:21:38 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/swiper.js + */ +export default { + // swiper 组件 + swiper: { + list: () => [], + indicator: false, + indicatorActiveColor: '#FFFFFF', + indicatorInactiveColor: 'rgba(255, 255, 255, 0.35)', + indicatorStyle: '', + indicatorMode: 'line', + autoplay: true, + current: 0, + currentItemId: '', + interval: 3000, + duration: 300, + circular: false, + previousMargin: 0, + nextMargin: 0, + acceleration: false, + displayMultipleItems: 1, + easingFunction: 'default', + keyName: 'url', + imgMode: 'aspectFill', + height: 130, + bgColor: '#f3f4f6', + radius: 4, + loading: false, + showTitle: false + } + +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipterIndicator.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipterIndicator.js new file mode 100644 index 000000000..4b59e6eda --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/swipterIndicator.js @@ -0,0 +1,19 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:22:07 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/swiperIndicator.js + */ +export default { + // swiperIndicator 组件 + swiperIndicator: { + length: 0, + current: 0, + indicatorActiveColor: '', + indicatorInactiveColor: '', + indicatorMode: 'line' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/switch.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/switch.js new file mode 100644 index 000000000..e6400b459 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/switch.js @@ -0,0 +1,24 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:22:24 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/switch.js + */ +export default { + // switch + switch: { + loading: false, + disabled: false, + size: 25, + activeColor: '#2979ff', + inactiveColor: '#ffffff', + value: false, + activeValue: true, + inactiveValue: false, + asyncChange: false, + space: 0 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabbar.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabbar.js new file mode 100644 index 000000000..187112dfe --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabbar.js @@ -0,0 +1,22 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:22:40 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tabbar.js + */ +export default { + // tabbar + tabbar: { + value: null, + safeAreaInsetBottom: true, + border: true, + zIndex: 1, + activeColor: '#1989fa', + inactiveColor: '#7d7e80', + fixed: true, + placeholder: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabbarItem.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabbarItem.js new file mode 100644 index 000000000..d036ce578 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabbarItem.js @@ -0,0 +1,20 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:22:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tabbarItem.js + */ +export default { + // + tabbarItem: { + name: null, + icon: '', + badge: null, + dot: false, + text: '', + badgeStyle: 'top: 6px;right:2px;' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabs.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabs.js new file mode 100644 index 000000000..81c794abf --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tabs.js @@ -0,0 +1,32 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:23:14 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tabs.js + */ +export default { + // + tabs: { + duration: 300, + list: () => [], + lineColor: '#3c9cff', + activeStyle: () => ({ + color: '#303133' + }), + inactiveStyle: () => ({ + color: '#606266' + }), + lineWidth: 20, + lineHeight: 3, + lineBgSize: 'cover', + itemStyle: () => ({ + height: '44px' + }), + scrollable: true, + current: 0, + keyName: 'name' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tag.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tag.js new file mode 100644 index 000000000..125ce947a --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tag.js @@ -0,0 +1,29 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:23:37 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tag.js + */ +export default { + // tag 组件 + tag: { + type: 'primary', + disabled: false, + size: 'medium', + shape: 'square', + text: '', + bgColor: '', + color: '', + borderColor: '', + closeColor: '#C6C7CB', + name: '', + plainFill: false, + plain: false, + closable: false, + show: true, + icon: '' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/text.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/text.js new file mode 100644 index 000000000..7e736069b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/text.js @@ -0,0 +1,38 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:23:58 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/text.js + */ +export default { + // text 组件 + text: { + type: '', + show: true, + text: '', + prefixIcon: '', + suffixIcon: '', + mode: '', + href: '', + format: '', + call: false, + openType: '', + bold: false, + block: false, + lines: '', + color: '#303133', + size: 15, + iconStyle: () => ({ + fontSize: '15px' + }), + decoration: 'none', + margin: 0, + lineHeight: '', + align: 'left', + wordWrap: 'normal' + } + +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/textarea.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/textarea.js new file mode 100644 index 000000000..d8036628f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/textarea.js @@ -0,0 +1,36 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:24:32 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/textarea.js + */ +export default { + // textarea 组件 + textarea: { + value: '', + placeholder: '', + placeholderClass: 'textarea-placeholder', + placeholderStyle: 'color: #c0c4cc', + height: 70, + confirmType: '', + disabled: false, + count: false, + focus: false, + autoHeight: false, + fixed: false, + cursorSpacing: 0, + cursor: '', + showConfirmBar: true, + selectionStart: -1, + selectionEnd: -1, + adjustPosition: true, + disableDefaultPadding: false, + holdKeyboard: false, + maxlength: 140, + border: 'surround', + formatter: null + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/toast.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/toast.js new file mode 100644 index 000000000..a50134bca --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/toast.js @@ -0,0 +1,30 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:07:07 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/toast.js + */ +export default { + // toast组件 + toast: { + zIndex: 10090, + loading: false, + text: '', + icon: '', + type: '', + loadingMode: '', + show: '', + overlay: false, + position: 'center', + params: () => {}, + duration: 2000, + isTab: false, + url: '', + callback: null, + back: false + } + +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/toolbar.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/toolbar.js new file mode 100644 index 000000000..328996766 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/toolbar.js @@ -0,0 +1,21 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:24:55 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/toolbar.js + */ +export default { + // toolbar 组件 + toolbar: { + show: true, + cancelText: '取消', + confirmText: '确认', + cancelColor: '#909193', + confirmColor: '#3c9cff', + title: '' + } + +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tooltip.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tooltip.js new file mode 100644 index 000000000..115e03077 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/tooltip.js @@ -0,0 +1,25 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:25:14 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/tooltip.js + */ +export default { + // tooltip 组件 + tooltip: { + text: '', + copyText: '', + size: 14, + color: '#606266', + bgColor: 'transparent', + direction: 'top', + zIndex: 10071, + showCopy: true, + buttons: () => [], + overlay: true, + showToast: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/transition.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/transition.js new file mode 100644 index 000000000..0fad118b8 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/transition.js @@ -0,0 +1,18 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 16:59:00 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/transition.js + */ +export default { + // transition动画组件的props + transition: { + show: false, + mode: 'fade', + duration: '300', + timingFunction: 'ease-out' + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/props/upload.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/upload.js new file mode 100644 index 000000000..fc7ca9251 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/props/upload.js @@ -0,0 +1,36 @@ +/* + * @Author : LQ + * @Description : + * @version : 1.0 + * @Date : 2021-08-20 16:44:21 + * @LastAuthor : LQ + * @lastTime : 2021-08-20 17:09:50 + * @FilePath : /u-view2.0/uview-ui/libs/config/props/upload.js + */ +export default { + // upload组件 + upload: { + accept: 'image', + capture: () => ['album', 'camera'], + compressed: true, + camera: 'back', + maxDuration: 60, + uploadIcon: 'camera-fill', + uploadIconColor: '#D3D4D6', + useBeforeRead: false, + previewFullImage: true, + maxCount: 52, + disabled: false, + imageMode: 'aspectFill', + name: '', + sizeType: () => ['original', 'compressed'], + multiple: false, + deletable: true, + maxSize: Number.MAX_VALUE, + fileList: () => [], + uploadText: '', + width: 80, + height: 80, + previewImage: true + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/config/zIndex.js b/yudao-ui-app/uni_modules/uview-ui/libs/config/zIndex.js new file mode 100644 index 000000000..5fc36822b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/config/zIndex.js @@ -0,0 +1,20 @@ +// uniapp在H5中各API的z-index值如下: +/** + * actionsheet: 999 + * modal: 999 + * navigate: 998 + * tabbar: 998 + * toast: 999 + */ + +export default { + toast: 10090, + noNetwork: 10080, + // popup包含popup,actionsheet,keyboard,picker的值 + popup: 10075, + mask: 10070, + navbar: 980, + topTips: 975, + sticky: 970, + indexListSticky: 965 +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/css/color.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/color.scss new file mode 100644 index 000000000..3237ba454 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/css/color.scss @@ -0,0 +1,155 @@ +.u-primary-light { + color: $u-primary-light; +} + +.u-warning-light { + color: $u-warning-light; +} + +.u-success-light { + color: $u-success-light; +} + +.u-error-light { + color: $u-error-light; +} + +.u-info-light { + color: $u-info-light; +} + +.u-primary-light-bg { + background-color: $u-primary-light; +} + +.u-warning-light-bg { + background-color: $u-warning-light; +} + +.u-success-light-bg { + background-color: $u-success-light; +} + +.u-error-light-bg { + background-color: $u-error-light; +} + +.u-info-light-bg { + background-color: $u-info-light; +} + +.u-primary-dark { + color: $u-primary-dark; +} + +.u-warning-dark { + color: $u-warning-dark; +} + +.u-success-dark { + color: $u-success-dark; +} + +.u-error-dark { + color: $u-error-dark; +} + +.u-info-dark { + color: $u-info-dark; +} + +.u-primary-dark-bg { + background-color: $u-primary-dark; +} + +.u-warning-dark-bg { + background-color: $u-warning-dark; +} + +.u-success-dark-bg { + background-color: $u-success-dark; +} + +.u-error-dark-bg { + background-color: $u-error-dark; +} + +.u-info-dark-bg { + background-color: $u-info-dark; +} + +.u-primary-disabled { + color: $u-primary-disabled; +} + +.u-warning-disabled { + color: $u-warning-disabled; +} + +.u-success-disabled { + color: $u-success-disabled; +} + +.u-error-disabled { + color: $u-error-disabled; +} + +.u-info-disabled { + color: $u-info-disabled; +} + +.u-primary { + color: $u-primary; +} + +.u-warning { + color: $u-warning; +} + +.u-success { + color: $u-success; +} + +.u-error { + color: $u-error; +} + +.u-info { + color: $u-info; +} + +.u-primary-bg { + background-color: $u-primary; +} + +.u-warning-bg { + background-color: $u-warning; +} + +.u-success-bg { + background-color: $u-success; +} + +.u-error-bg { + background-color: $u-error; +} + +.u-info-bg { + background-color: $u-info; +} + +.u-main-color { + color: $u-main-color; +} + +.u-content-color { + color: $u-content-color; +} + +.u-tips-color { + color: $u-tips-color; +} + +.u-light-color { + color: $u-light-color; +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/css/common.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/common.scss new file mode 100644 index 000000000..11f1e5317 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/css/common.scss @@ -0,0 +1,97 @@ +// 超出行数,自动显示行尾省略号,最多5行 +// 来自uView的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】 +@for $i from 1 through 5 { + .u-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 */ + } +} + + +// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时, +// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效 +// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important +// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现 +.u-border { + border-width: 0.5px!important; + border-color: $u-border-color!important; + border-style: solid; +} + +.u-border-top { + border-top-width: 0.5px!important; + border-color: $u-border-color!important; + border-top-style: solid; +} + +.u-border-left { + border-left-width: 0.5px!important; + border-color: $u-border-color!important; + border-left-style: solid; +} + +.u-border-right { + border-right-width: 0.5px!important; + border-color: $u-border-color!important; + border-right-style: solid; +} + +.u-border-bottom { + border-bottom-width: 0.5px!important; + border-color: $u-border-color!important; + border-bottom-style: solid; +} + +.u-border-top-bottom { + border-top-width: 0.5px!important; + border-bottom-width: 0.5px!important; + border-color: $u-border-color!important; + border-top-style: solid; + border-bottom-style: solid; +} + +// 去除button的所有默认样式,让其表现跟普通的view、text元素一样 +.u-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 */ +.u-reset-button::after { + border: none; +} +/* #endif */ + +.u-hover-class { + opacity: 0.7; +} + diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/css/components.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/components.scss new file mode 100644 index 000000000..766679e6b --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/css/components.scss @@ -0,0 +1,15 @@ +@import "./mixin.scss"; + +/* #ifndef APP-NVUE */ +// 由于uView是基于nvue环境进行开发的,此环境中普通元素默认为flex-direction: column; +// 所以在非nvue中,需要对元素进行重置为flex-direction: column; 否则可能会表现异常 +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/yudao-ui-app/uni_modules/uview-ui/libs/css/flex.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/flex.scss new file mode 100644 index 000000000..6d61be9a5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/css/flex.scss @@ -0,0 +1,257 @@ +// .u-flex { +// @include vue-flex(row); +// } + +// .u-flex-x { +// @include vue-flex(row); +// } + +// .u-flex-y { +// @include vue-flex(column); +// } + +// .u-flex-xy-center { +// @include vue-flex(row); +// justify-content: center; +// align-items: center; +// } + +// .u-flex-x-center { +// @include vue-flex(row); +// justify-content: center; +// } + +// .u-flex-y-center { +// @include vue-flex(column); +// justify-content: center; +// } + + +// flex布局 +.u-flex, +.u-flex-row, +.u-flex-x { + @include flex; +} + +.u-flex-y, +.u-flex-column { + @include flex(column); +} + +.u-flex-x-center { + @include flex; + justify-content: center; +} + +.u-flex-xy-center { + @include flex; + justify-content: center; + align-items: center; +} + +.u-flex-y-center { + @include flex; + align-items: center; +} + +.u-flex-x-left { + @include flex; +} + +.u-flex-x-reverse, +.u-flex-row-reverse { + flex-direction: row-reverse; +} + +.u-flex-y-reverse, +.u-flex-column-reverse { + flex-direction: column-reverse; +} + +/* #ifndef APP-NVUE */ +// 此处为vue版本的简写,因为nvue不支持同时作用于两个类名的样式写法 +// nvue下只能写成class="u-flex-x u-flex-x-reverse的形式" +.u-flex.u-flex-reverse, +.u-flex-row.u-flex-reverse, +.u-flex-x.u-flex-reverse { + flex-direction: row-reverse; +} + +.u-flex-column.u-flex-reverse, +.u-flex-y.u-flex-reverse { + flex-direction: column-reverse; +} + +// 自动伸缩 +.u-flex-fill { + flex: 1 1 auto +} + +// 边界自动伸缩 +.u-margin-top-auto, +.u-m-t-auto { + margin-top: auto !important +} + +.u-margin-right-auto, +.u-m-r-auto { + margin-right: auto !important +} + +.u-margin-bottom-auto, +.u-m-b-auto { + margin-bottom: auto !important +} + +.u-margin-left-auto, +.u-m-l-auto { + margin-left: auto !important +} + +.u-margin-center-auto, +.u-m-c-auto { + margin-left: auto !important; + margin-right: auto !important +} + +.u-margin-middle-auto, +.u-m-m-auto { + margin-top: auto !important; + margin-bottom: auto !important +} +/* #endif */ + +// 换行 +.u-flex-wrap { + flex-wrap: wrap; +} + +// 反向换行 +.u-flex-wrap-reverse { + flex-wrap: wrap-reverse; +} + +// 主轴起点对齐 +.u-flex-start { + justify-content: flex-start +} + +// 主轴中间对齐 +.u-flex-center { + justify-content: center +} + +// 主轴终点对齐 +.u-flex-end { + justify-content: flex-end +} + +// 主轴等比间距 +.u-flex-between { + justify-content: space-between +} + +// 主轴均分间距 +.u-flex-around { + justify-content: space-around +} + +// 交叉轴起点对齐 +.u-flex-items-start { + align-items: flex-start +} + +// 交叉轴中间对齐 +.u-flex-items-center { + align-items: center +} + +// 交叉轴终点对齐 +.u-flex-items-end { + align-items: flex-end +} + +// 交叉轴第一行文字基线对齐 +.u-flex-items-baseline { + align-items: baseline +} + +// 交叉轴方向拉伸对齐 +.u-flex-items-stretch { + align-items: stretch +} + + +// 以下属于项目(子元素)的类 + +// 子元素交叉轴起点对齐 +.u-flex-self-start { + align-self: flex-start +} + +// 子元素交叉轴居中对齐 +.u-flex-self-center { + align-self: center +} + +// 子元素交叉轴终点对齐 +.u-flex-self-end { + align-self: flex-end +} + +// 子元素交叉轴第一行文字基线对齐 +.u-flex-self-baseline { + align-self: baseline +} + +// 子元素交叉轴方向拉伸对齐 +.u-flex-self-stretch { + align-self: stretch +} + +// 多轴交叉时的对齐方式 + +// 起点对齐 +.u-flex-content-start { + align-content: flex-start +} + +// 居中对齐 +.u-flex-content-center { + align-content: center +} + +// 终点对齐 +.u-flex-content-end { + align-content: flex-end +} + +// 两端对齐 +.u-flex-content-between { + align-content: space-between +} + +// 均分间距 +.u-flex-content-around { + align-content: space-around +} + +// 全部居中对齐 +.u-flex-middle { + justify-content: center; + align-items: center; + align-self: center; + align-content: center +} + +// 是否可以放大 +.u-flex-grow { + flex-grow: 1 +} + +// 是否可以缩小 +.u-flex-shrink { + flex-shrink: 1 +} + diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/css/h5.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/h5.scss new file mode 100644 index 000000000..e69de29bb diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/css/mixin.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/mixin.scss new file mode 100644 index 000000000..7e35b3bff --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/css/mixin.scss @@ -0,0 +1,8 @@ +// 通过scss的mixin功能,把原来需要写4行的css,变成一行 +// 目的是保持代码干净整洁,不至于在nvue下,到处都要写display:flex的条件编译 +@mixin flex($direction: row) { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: $direction; +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/css/mp.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/mp.scss new file mode 100644 index 000000000..e69de29bb diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/css/nvue.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/nvue.scss new file mode 100644 index 000000000..e69de29bb diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/css/vue.scss b/yudao-ui-app/uni_modules/uview-ui/libs/css/vue.scss new file mode 100644 index 000000000..3ae4d2977 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/css/vue.scss @@ -0,0 +1,27 @@ +// 历遍生成4个方向的底部安全区 +@each $d in top, right, bottom, left { + .u-safe-area-inset-#{$d} { + padding-#{$d}: 0; + padding-#{$d}: constant(safe-area-inset-#{$d}); + padding-#{$d}: env(safe-area-inset-#{$d}); + } +} + +//提升H5端uni.toast()的层级,避免被uView的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; +} \ No newline at end of file diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/function/colorGradient.js b/yudao-ui-app/uni_modules/uview-ui/libs/function/colorGradient.js new file mode 100644 index 000000000..972773206 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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 default { + colorGradient, + hexToRgb, + rgbToHex, + colorToRgba +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/function/debounce.js b/yudao-ui-app/uni_modules/uview-ui/libs/function/debounce.js new file mode 100644 index 000000000..ad3996bb5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/function/digit.js b/yudao-ui-app/uni_modules/uview-ui/libs/function/digit.js new file mode 100644 index 000000000..c8260a06e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/function/index.js b/yudao-ui-app/uni_modules/uview-ui/libs/function/index.js new file mode 100644 index 000000000..5a0775514 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/function/index.js @@ -0,0 +1,705 @@ +import test 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 (test.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.$u.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 (test.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?.$u?.config?.unit ?? 'px') { + value = String(value) + // 用uView内置验证规则中的number判断是否为数值 + return test.number(value) ? `${value}${unit}` : value +} + +/** + * @description 深度克隆 + * @param {object} obj 需要深度克隆的对象 + * @returns {*} 克隆后的对象或者原值(不是对象) + */ +function deepClone(obj) { + // 对常见的“非”值,直接返回原来值 + if ([null, undefined, NaN, false].includes(obj)) return obj + if (typeof obj !== 'object' && typeof obj !== 'function') { + // 原始类型直接返回 + return obj + } + const o = test.array(obj) ? [] : {} + for (const i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i] + } + } + return o +} + +/** + * @description JS对象深度合并 + * @param {object} target 需要拷贝的对象 + * @param {object} source 拷贝的来源对象 + * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象) + */ +function deepMerge(target = {}, source = {}) { + target = deepClone(target) + if (typeof target !== 'object' || typeof source !== 'object') return false + for (const prop in source) { + if (!source.hasOwnProperty(prop)) continue + if (prop in target) { + if (typeof target[prop] !== 'object') { + target[prop] = source[prop] + } else if (typeof source[prop] !== 'object') { + target[prop] = source[prop] + } else if (target[prop].concat && source[prop].concat) { + target[prop] = target[prop].concat(source[prop]) + } else { + target[prop] = deepMerge(target[prop], source[prop]) + } + } else { + target[prop] = source[prop] + } + } + return target +} + +/** + * @description error提示 + * @param {*} err 错误内容 + */ +function error(err) { + // 开发环境才提示,生产环境不会提示 + if (process.env.NODE_ENV === 'development') { + console.error(`uView提示:${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)) + } + // 其他都认为符合 RFC 2822 规范 + else { + // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间 + date = new Date( + typeof dateTime === 'string' + ? dateTime.replace(/-/g, '/') + : 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 在u-form的子组件内容发生变化,或者失去焦点时,尝试通知u-form执行校验方法 + * @param {*} instance + * @param {*} event + */ +function formValidate(instance, event) { + const formItem = uni.$u.$parent.call(instance, 'u-form-item') + const form = uni.$u.$parent.call(instance, 'u-form') + // 如果发生变化的input或者textarea等,其父组件中有u-form-item或者u-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() + // 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组 + return `/${pages[pages.length - 1]?.route ?? ''}` +} + +/** + * @description 获取当前路由栈实例数组 + */ +function pages() { + const pages = getCurrentPages() + return pages +} + +/** + * @description 修改uView内置属性值 + * @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.$u + uni.$u.config = deepMerge(uni.$u.config, config) + uni.$u.props = deepMerge(uni.$u.props, props) + uni.$u.color = deepMerge(uni.$u.color, color) + uni.$u.zIndex = deepMerge(uni.$u.zIndex, zIndex) +} + +export default { + 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, + setConfig +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/function/platform.js b/yudao-ui-app/uni_modules/uview-ui/libs/function/platform.js new file mode 100644 index 000000000..d6b926ea1 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/function/test.js b/yudao-ui-app/uni_modules/uview-ui/libs/function/test.js new file mode 100644 index 000000000..ae4a1b37f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/function/test.js @@ -0,0 +1,288 @@ +/** + * 验证电子邮箱格式 + */ +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[23456789]\d{9}$/.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 default { + email, + mobile, + url, + date, + dateISO, + number, + digits, + idCard, + carNo, + amount, + chinese, + letter, + enOrNum, + contains, + range, + rangeLength, + empty, + isEmpty: empty, + jsonString, + landline, + object, + array, + code, + func, + promise, + video, + image, + regExp, + string +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/function/throttle.js b/yudao-ui-app/uni_modules/uview-ui/libs/function/throttle.js new file mode 100644 index 000000000..2f3361127 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/adapters/index.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/adapters/index.js new file mode 100644 index 000000000..e03cf5f35 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/adapters/index.js @@ -0,0 +1,97 @@ +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) => { + const config = {} + keys.forEach((prop) => { + if (!isUndefined(config2[prop])) { + config[prop] = config2[prop] + } + }) + return config +} +export default (config) => new Promise((resolve, reject) => { + const fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params) + const _config = { + url: fullPath, + header: config.header, + complete: (response) => { + config.fullPath = fullPath + response.config = config + try { + // 对可能字符串不是json 的情况容错 + if (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'] + const 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 + 'timeout', + // #endif + 'formData' + ] + requestTask = uni.uploadFile({ ..._config, ...otherConfig, ...mergeKeys(optionalKeys, config) }) + } else if (config.method === 'DOWNLOAD') { + // #ifdef H5 || APP-PLUS + if (!isUndefined(config.timeout)) { + _config.timeout = config.timeout + } + // #endif + requestTask = uni.downloadFile(_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 + ] + requestTask = uni.request({ ..._config, ...mergeKeys(optionalKeys, config) }) + } + if (config.getTask) { + config.getTask(requestTask, config) + } +}) diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/InterceptorManager.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/InterceptorManager.js new file mode 100644 index 000000000..3e8728d4c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/InterceptorManager.js @@ -0,0 +1,50 @@ +'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, + 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/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/Request.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/Request.js new file mode 100644 index 000000000..cc48566b0 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/Request.js @@ -0,0 +1,198 @@ +/** + * @Class Request + * @description luch-request http请求插件 + * @version 3.0.7 + * @Author lu-ch + * @Date 2021-09-04 + * @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 + * HBuilderX: beat-3.0.4 alpha-3.0.4 + */ + +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) + const chain = [dispatchRequest, undefined] + let promise = Promise.resolve(config) + + this.interceptors.request.forEach((interceptor) => { + chain.unshift(interceptor.fulfilled, interceptor.rejected) + }) + + this.interceptors.response.forEach((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 + 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) + } +} + +/** + * setConfig回调 + * @return {Object} - 返回操作后的config + * @callback Request~setConfigCallback + * @param {Object} config - 全局默认config + */ diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/buildFullPath.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/buildFullPath.js new file mode 100644 index 000000000..5eb8a177d --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/defaults.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/defaults.js new file mode 100644 index 000000000..be375a92e --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/defaults.js @@ -0,0 +1,29 @@ +/** + * 默认的全局配置 + */ + +export default { + baseURL: '', + header: {}, + method: 'GET', + dataType: 'json', + // #ifndef MP-ALIPAY + responseType: 'text', + // #endif + custom: {}, + // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN + 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 + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/dispatchRequest.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/dispatchRequest.js new file mode 100644 index 000000000..724545c87 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/dispatchRequest.js @@ -0,0 +1,3 @@ +import adapter from '../adapters/index' + +export default (config) => adapter(config) diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/mergeConfig.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/mergeConfig.js new file mode 100644 index 000000000..08f8b9bfe --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/mergeConfig.js @@ -0,0 +1,103 @@ +import { deepMerge, isUndefined } from '../utils' + +/** + * 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局 + * @param {Array} keys - 配置项 + * @param {Object} globalsConfig - 当前的全局配置 + * @param {Object} config2 - 局部配置 + * @return {{}} + */ +const mergeKeys = (keys, globalsConfig, config2) => { + const 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: globalsConfig.baseURL || '', + method, + url: config2.url || '', + params: config2.params || {}, + custom: { ...(globalsConfig.custom || {}), ...(config2.custom || {}) }, + header: deepMerge(globalsConfig.header || {}, config2.header || {}) + } + const defaultToConfig2Keys = ['getTask', 'validateStatus'] + config = { ...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2) } + + // eslint-disable-next-line no-empty + if (method === 'DOWNLOAD') { + // #ifdef H5 || APP-PLUS + if (!isUndefined(config2.timeout)) { + config.timeout = config2.timeout + } else if (!isUndefined(globalsConfig.timeout)) { + config.timeout = globalsConfig.timeout + } + // #endif + } 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 + 'timeout', + // #endif + 'formData' + ] + uploadKeys.forEach((prop) => { + if (!isUndefined(config2[prop])) { + config[prop] = config2[prop] + } + }) + // #ifdef H5 || APP-PLUS + 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 + ] + config = { ...config, ...mergeKeys(defaultsKeys, globalsConfig, config2) } + } + + return config +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/settle.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/core/settle.js new file mode 100644 index 000000000..8d3638fff --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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 + const status = response.statusCode + if (status && (!validateStatus || validateStatus(status))) { + resolve(response) + } else { + reject(response) + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/buildURL.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/buildURL.js new file mode 100644 index 000000000..472ad6a9c --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/buildURL.js @@ -0,0 +1,69 @@ +'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) { + /* eslint no-param-reassign:0 */ + if (!params) { + return url + } + + let serializedParams + if (utils.isURLSearchParams(params)) { + serializedParams = params.toString() + } else { + const parts = [] + + utils.forEach(params, (val, key) => { + if (val === null || typeof val === 'undefined') { + return + } + + if (utils.isArray(val)) { + key = `${key}[]` + } else { + val = [val] + } + + utils.forEach(val, (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) { + const hashmarkIndex = url.indexOf('#') + if (hashmarkIndex !== -1) { + url = url.slice(0, hashmarkIndex) + } + + url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams + } + + return url +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/combineURLs.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/combineURLs.js new file mode 100644 index 000000000..ac7c124c2 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/isAbsoluteURL.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/helpers/isAbsoluteURL.js new file mode 100644 index 000000000..63c664706 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/index.d.ts b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/index.d.ts new file mode 100644 index 000000000..e939ce137 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/index.d.ts @@ -0,0 +1,116 @@ +type AnyObject = Record<string | number | symbol, any> +type HttpPromise<T> = Promise<HttpResponse<T>>; +type Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask +export interface RequestTask { + abort: () => void; + offHeadersReceived: () => void; + onHeadersReceived: () => void; +} +export interface HttpRequestConfig<T = Tasks> { + /** 请求基地址 */ + baseURL?: string; + /** 请求服务器接口地址 */ + url?: string; + + /** 请求查询参数,自动拼接为查询字符串 */ + params?: AnyObject; + /** 请求体参数 */ + data?: AnyObject; + + /** 文件对应的 key */ + name?: string; + /** HTTP 请求中其他额外的 form data */ + formData?: AnyObject; + /** 要上传文件资源的路径。 */ + filePath?: string; + /** 需要上传的文件列表。使用 files 时,filePath 和 name 不生效,App、H5( 2.6.15+) */ + files?: Array<{ + name?: string; + file?: File; + uri: string; + }>; + /** 要上传的文件对象,仅H5(2.6.15+)支持 */ + file?: File; + + /** 请求头信息 */ + header?: AnyObject; + /** 请求方式 */ + method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD"; + /** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */ + dataType?: string; + /** 设置响应的数据类型,支付宝小程序不支持 */ + responseType?: "text" | "arraybuffer"; + /** 自定义参数 */ + custom?: AnyObject; + /** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */ + timeout?: number; + /** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */ + firstIpv4?: boolean; + /** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+) */ + sslVerify?: boolean; + /** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) */ + withCredentials?: boolean; + + /** 返回当前请求的task, options。请勿在此处修改options。 */ + getTask?: (task: T, options: HttpRequestConfig<T>) => void; + /** 全局自定义验证器 */ + validateStatus?: (statusCode: number) => boolean | void; +} +export interface HttpResponse<T = any> { + config: HttpRequestConfig; + statusCode: number; + cookies: Array<string>; + data: T; + errMsg: string; + header: AnyObject; +} +export interface HttpUploadResponse<T = any> { + config: HttpRequestConfig; + statusCode: number; + data: T; + errMsg: string; +} +export interface HttpDownloadResponse extends HttpResponse { + tempFilePath: string; +} +export interface HttpError { + config: HttpRequestConfig; + statusCode?: number; + cookies?: Array<string>; + data?: any; + errMsg: string; + header?: AnyObject; +} +export interface HttpInterceptorManager<V, E = V> { + use( + onFulfilled?: (config: V) => Promise<V> | V, + onRejected?: (config: E) => Promise<E> | E + ): void; + eject(id: number): void; +} +export abstract class HttpRequestAbstract { + constructor(config?: HttpRequestConfig); + config: HttpRequestConfig; + interceptors: { + request: HttpInterceptorManager<HttpRequestConfig, HttpRequestConfig>; + response: HttpInterceptorManager<HttpResponse, HttpError>; + } + middleware<T = any>(config: HttpRequestConfig): HttpPromise<T>; + request<T = any>(config: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + get<T = any>(url: string, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + upload<T = any>(url: string, config?: HttpRequestConfig<UniApp.UploadTask>): HttpPromise<T>; + delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + head<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + connect<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + options<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + trace<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>; + + download(url: string, config?: HttpRequestConfig<UniApp.DownloadTask>): Promise<HttpDownloadResponse>; + + setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void; +} + +declare class HttpRequest extends HttpRequestAbstract { } +export default HttpRequest; diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/index.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/index.js new file mode 100644 index 000000000..8fb2b44c5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/index.js @@ -0,0 +1,3 @@ +import Request from './core/Request' + +export default Request diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/utils.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/utils.js new file mode 100644 index 000000000..847283d54 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/utils.js @@ -0,0 +1,131 @@ +'use strict' + +// utils is a library of generic helper functions non-specific to axios + +const { toString } = Object.prototype + +/** + * 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 (let i = 0, l = obj.length; i < l; i++) { + fn.call(null, obj[i], i, obj) + } + } else { + // Iterate over object keys + for (const 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, ... */) { + const 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/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/utils/clone.js b/yudao-ui-app/uni_modules/uview-ui/libs/luch-request/utils/clone.js new file mode 100644 index 000000000..2fee704d0 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/mixin/button.js b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/button.js new file mode 100644 index 000000000..0c019c2a6 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mixin.js b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mixin.js new file mode 100644 index 000000000..cd8f773a0 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mixin.js @@ -0,0 +1,160 @@ +module.exports = { + // 定义每个组件都可能需要用到的外部样式以及类名 + props: { + // 每个组件都有的父组件传递的样式,可以为字符串或者对象形式 + customStyle: { + type: [Object, String], + default: () => ({}) + }, + customClass: { + type: String, + default: '' + }, + // 跳转的页面路径 + url: { + type: String, + default: '' + }, + // 页面跳转的类型 + linkType: { + type: String, + default: 'navigateTo' + } + }, + data() { + return {} + }, + onLoad() { + // getRect挂载到$u上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出 + this.$u.getRect = this.$uGetRect + }, + created() { + // 组件当中,只有created声明周期,为了能在组件使用,故也在created中将方法挂载到$u + this.$u.getRect = this.$uGetRect + }, + computed: { + // 在2.x版本中,将会把$u挂载到uni对象下,导致在模板中无法使用uni.$u.xxx形式 + // 所以这里通过computed计算属性将其附加到this.$u上,就可以在模板或者js中使用uni.$u.xxx + // 只在nvue环境通过此方式引入完整的$u,其他平台会出现性能问题,非nvue则按需引入(主要原因是props过大) + $u() { + // #ifndef APP-NVUE + // 在非nvue端,移除props,http,mixin等对象,避免在小程序setData时数据过大影响性能 + return uni.$u.deepMerge(uni.$u, { + props: undefined, + http: undefined, + mixin: undefined + }) + // #endif + // #ifdef APP-NVUE + return uni.$u + // #endif + }, + /** + * 生成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 = `u-${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 + .join(' ') + // #endif + } + } + }, + methods: { + // 跳转某一个页面 + openPage(urlKey = 'url') { + const url = this[urlKey] + if (url) { + // 执行类似uni.navigateTo的方法 + uni[this.linkType]({ + url + }) + } + }, + // 查询节点信息 + // 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21) + // 解决办法为在组件根部再套一个没有任何作用的view元素 + $uGetRect(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 = {} + // 这里的本质原理是,通过获取父组件实例(也即类似u-radio的父组件u-radio-group的this) + // 将父组件this中对应的参数,赋值给本组件(u-radio的this)的parentData对象中对应的属性 + // 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化 + // 此处并不会自动更新子组件的数据,而是依赖父组件u-radio-group去监听data的变化,手动调用更新子组件的方法去重新获取 + this.parent = uni.$u.$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('uOnReachBottom') + }, + beforeDestroy() { + // 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况 + // 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱 + if (this.parent && uni.$u.test.array(this.parent.children)) { + // 组件销毁时,移除父组件中的children数组中对应的实例 + const childrenList = this.parent.children + childrenList.map((child, index) => { + // 如果相等,则移除 + if (child === this) { + childrenList.splice(index, 1) + } + }) + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mpMixin.js b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mpMixin.js new file mode 100644 index 000000000..29e7e65ae --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mpMixin.js @@ -0,0 +1,8 @@ +export default { + // #ifdef MP-WEIXIN + // 将自定义节点设置成虚拟的,更加接近Vue组件的表现,能更好的使用flex属性 + options: { + virtualHost: true + } + // #endif +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mpShare.js b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mpShare.js new file mode 100644 index 000000000..b07bbd388 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/mpShare.js @@ -0,0 +1,13 @@ +module.exports = { + onLoad() { + // 设置默认的转发参数 + uni.$u.mpShare = { + title: '', // 默认为小程序名称 + path: '', // 默认为当前页面路径 + imageUrl: '' // 默认为当前页面的截图 + } + }, + onShareAppMessage() { + return uni.$u.mpShare + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/mixin/openType.js b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/openType.js new file mode 100644 index 000000000..121618177 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/openType.js @@ -0,0 +1,25 @@ +export default { + props: { + openType: String + }, + methods: { + onGetUserInfo(event) { + this.$emit('getuserinfo', event.detail) + }, + onContact(event) { + this.$emit('contact', event.detail) + }, + onGetPhoneNumber(event) { + this.$emit('getphonenumber', event.detail) + }, + onError(event) { + this.$emit('error', event.detail) + }, + onLaunchApp(event) { + this.$emit('launchapp', event.detail) + }, + onOpenSetting(event) { + this.$emit('opensetting', event.detail) + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/mixin/style.js b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/style.js new file mode 100644 index 000000000..266018003 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/style.js @@ -0,0 +1,228 @@ +export default { + props: { + // flex排列方式 + flexDirection: { + type: String, + default: '' + }, + // flex-direction的简写 + fd: { + type: String, + default: '' + }, + // 展示类型 + display: { + type: String, + default: '' + }, + // display简写 + d: { + type: String, + default: '' + }, + // 主轴排列方式 + justifyContent: { + type: String, + default: '' + }, + // justifyContent的简写 + jc: { + type: String, + default: '' + }, + // 纵轴排列方式 + alignItems: { + type: String, + default: '' + }, + // align-items的简写 + ai: { + type: String, + default: '' + }, + color: { + type: String, + default: '' + }, + // color简写 + c: { + type: String, + default: '' + }, + // 字体大小 + fontSize: { + type: [String, Number], + default: 0 + }, + // font-size简写 + fs: { + type: [String, Number], + default: '' + }, + margin: { + type: [String, Number], + default: 0 + }, + // margin简写 + m: { + type: [String, Number], + default: 0 + }, + // margin-top + marginTop: { + type: [String, Number], + default: 0 + }, + // margin-top简写 + mt: { + type: [String, Number], + default: 0 + }, + // margin-right + marginRight: { + type: [String, Number], + default: 0 + }, + // margin-right简写 + mr: { + type: [String, Number], + default: 0 + }, + // margin-bottom + marginBottom: { + type: [String, Number], + default: 0 + }, + // margin-bottom简写 + mb: { + type: [String, Number], + default: 0 + }, + // margin-left + marginLeft: { + type: [String, Number], + default: 0 + }, + // margin-left简写 + ml: { + type: [String, Number], + default: 0 + }, + // padding-left + paddingLeft: { + type: [String, Number], + default: 0 + }, + // padding-left简写 + pl: { + type: [String, Number], + default: 0 + }, + // padding-top + paddingTop: { + type: [String, Number], + default: 0 + }, + // padding-top简写 + pt: { + type: [String, Number], + default: 0 + }, + // padding-right + paddingRight: { + type: [String, Number], + default: 0 + }, + // padding-right简写 + pr: { + type: [String, Number], + default: 0 + }, + // padding-bottom + paddingBottom: { + type: [String, Number], + default: 0 + }, + // padding-bottom简写 + pb: { + type: [String, Number], + default: 0 + }, + // border-radius + borderRadius: { + type: [String, Number], + default: 0 + }, + // border-radius简写 + radius: { + type: [String, Number], + default: 0 + }, + // transform + transform: { + type: String, + default: '' + }, + // 定位 + position: { + type: String, + default: '' + }, + // position简写 + pos: { + type: String, + default: '' + }, + // 宽度 + width: { + type: [String, Number], + default: null + }, + // width简写 + w: { + type: [String, Number], + default: null + }, + // 高度 + height: { + type: [String, Number], + default: null + }, + // height简写 + h: { + type: [String, Number], + default: null + }, + top: { + type: [String, Number], + default: 0 + }, + right: { + type: [String, Number], + default: 0 + }, + bottom: { + type: [String, Number], + default: 0 + }, + left: { + type: [String, Number], + default: 0 + } + }, + computed: { + viewStyle() { + const style = {} + const addStyle = uni.$u.addStyle(this.width || this.w) && (style.width = addStyle(this.width || this.w))(this.height || this.h) && (style.height = addStyle(this.height || this.h))(this.margin || this.m) && (style.margin = addStyle(this.margin || this.m))(this.marginTop || this.mt) && (style.marginTop = addStyle(this.marginTop || this.mt))(this.marginRight || this.mr) && (style.marginRight = addStyle(this.marginRight || this.mr))(this.marginBottom || this.mb) && (style.marginBottom = addStyle(this.marginBottom || this.mb))(this.marginLeft || this.ml) && (style.marginLeft = addStyle(this.marginLeft || this.ml))(this.padding || this.p) && (style.padding = addStyle(this.padding || this.p))(this.paddingTop || this.pt) && (style.paddingTop = addStyle(this.paddingTop || this.pt))(this.paddingRight || this.pr) && (style.paddingRight = addStyle(this.paddingRight || this.pr))(this.paddingBottom || this.pb) && (style.paddingBottom = addStyle(this.paddingBottom || this.pb))(this.paddingLeft || this.pl) && (style.paddingLeft = addStyle(this.paddingLeft || this.pl))(this.color || this.c) && (style.color = this.color || this.c)(this.fontSize || this.fs) && (style.fontSize = this.fontSize || this.fs)(this.borderRadius || this.radius) && (style.borderRadius = this.borderRadius || this.radius)(this.position || this.pos) && (this.position = this.position || this.pos)(this.flexDirection || this.fd) && (this.flexDirection = this.flexDirection || this.fd)(this.justifyContent || jc) && (this.justifyContent = this.justifyContent || jc)(this.alignItems || ai) && (this.alignItems = this.alignItems || ai) + + return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) + } + }, + methods: { + // 获取margin或者padding的单位,比如padding: 0 20转为padding: 0 20px + getUnit(unit = '') { + // 取出两端空格,分隔成数组,再对数组的每个元素添加单位,最后再合并成字符串 + return uni.$u.trim(unit).split(' ').map((item) => uni.$u.addUnit(item)).join(' ') + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/mixin/touch.js b/yudao-ui-app/uni_modules/uview-ui/libs/mixin/touch.js new file mode 100644 index 000000000..0ecbd88e5 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/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/yudao-ui-app/uni_modules/uview-ui/libs/util/async-validator.js b/yudao-ui-app/uni_modules/uview-ui/libs/util/async-validator.js new file mode 100644 index 000000000..9e114df76 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/util/async-validator.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/yudao-ui-app/uni_modules/uview-ui/libs/util/calendar.js b/yudao-ui-app/uni_modules/uview-ui/libs/util/calendar.js new file mode 100644 index 000000000..e006dea58 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/util/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/yudao-ui-app/uni_modules/uview-ui/libs/util/dayjs.js b/yudao-ui-app/uni_modules/uview-ui/libs/util/dayjs.js new file mode 100644 index 000000000..c4efea06f --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/util/dayjs.js @@ -0,0 +1,308 @@ +!(function (t, e) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = e() : typeof define === 'function' + && define.amd ? define(e) : t.dayjs = e() +}(this, () => { + 'use strict' + + const t = 'millisecond' + const e = 'second' + const n = 'minute' + const r = 'hour' + const i = 'day' + const s = 'week' + const u = 'month' + const a = 'quarter' + const o = 'year' + const f = 'date' + const h = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d+)?$/ + const c = /\[([^\]]+)]|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 + const d = { + 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('_') + } + const $ = function (t, e, n) { + const r = String(t) + return !r || r.length >= e ? t : `${Array(e + 1 - r.length).join(n)}${t}` + } + const l = { + s: $, + z(t) { + const e = -t.utcOffset() + const n = Math.abs(e) + const r = Math.floor(n / 60) + const i = n % 60 + return `${(e <= 0 ? '+' : '-') + $(r, 2, '0')}:${$(i, 2, '0')}` + }, + m: function t(e, n) { + if (e.date() < n.date()) return -t(n, e) + const r = 12 * (n.year() - e.year()) + (n.month() - e.month()) + const i = e.clone().add(r, u) + const s = n - i < 0 + const a = e.clone().add(r + (s ? -1 : 1), u) + return +(-(r + (n - i) / (s ? i - a : a - i)) || 0) + }, + a(t) { + return t < 0 ? Math.ceil(t) || 0 : Math.floor(t) + }, + p(h) { + return { + M: u, + y: o, + w: s, + d: i, + D: f, + h: r, + m: n, + s: e, + ms: t, + Q: a + }[h] || String(h || '').toLowerCase().replace(/s$/, '') + }, + u(t) { + return void 0 === t + } + } + let y = 'en' + const M = {} + M[y] = d + const m = function (t) { + return t instanceof S + } + const D = function (t, e, n) { + let r + if (!t) return y + if (typeof t === 'string') M[t] && (r = t), e && (M[t] = e, r = t) + else { + const i = t.name + M[i] = t, r = i + } + return !n && r && (y = r), r || !n && y + } + const v = function (t, e) { + if (m(t)) return t.clone() + const n = typeof e === 'object' ? e : {} + return n.date = t, n.args = arguments, new S(n) + } + const g = l + g.l = D, g.i = m, g.w = function (t, e) { + return v(t, { + locale: e.$L, + utc: e.$u, + x: e.$x, + $offset: e.$offset + }) + } + var S = (function () { + function d(t) { + this.$L = D(t.locale, null, !0), this.parse(t) + } + const $ = d.prototype + return $.parse = function (t) { + this.$d = (function (t) { + const e = t.date + const n = t.utc + if (e === null) return new Date(NaN) + if (g.u(e)) return new Date() + if (e instanceof Date) return new Date(e) + if (typeof e === 'string' && !/Z$/i.test(e)) { + const r = e.match(h) + if (r) { + const i = r[2] - 1 || 0 + const s = (r[7] || '0').substring(0, 3) + return n ? new Date(Date.UTC(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s)) : new Date(r[1], i, r[3] + || 1, r[4] || 0, r[5] || 0, r[6] || 0, s) + } + } + return new Date(e) + }(t)), this.$x = t.x || {}, this.init() + }, $.init = function () { + const t = this.$d + this.$y = t.getFullYear(), this.$M = t.getMonth(), this.$D = t.getDate(), this.$W = t.getDay(), this.$H = t.getHours(), + this.$m = t.getMinutes(), this.$s = t.getSeconds(), this.$ms = t.getMilliseconds() + }, $.$utils = function () { + return g + }, $.isValid = function () { + return !(this.$d.toString() === 'Invalid Date') + }, $.isSame = function (t, e) { + const n = v(t) + return this.startOf(e) <= n && n <= this.endOf(e) + }, $.isAfter = function (t, e) { + return v(t) < this.startOf(e) + }, $.isBefore = function (t, e) { + return this.endOf(e) < v(t) + }, $.$g = function (t, e, n) { + return g.u(t) ? this[e] : this.set(n, t) + }, $.unix = function () { + return Math.floor(this.valueOf() / 1e3) + }, $.valueOf = function () { + return this.$d.getTime() + }, $.startOf = function (t, a) { + const h = this + const c = !!g.u(a) || a + const d = g.p(t) + const $ = function (t, e) { + const n = g.w(h.$u ? Date.UTC(h.$y, e, t) : new Date(h.$y, e, t), h) + return c ? n : n.endOf(i) + } + const l = function (t, e) { + return g.w(h.toDate()[t].apply(h.toDate('s'), (c ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e)), h) + } + const y = this.$W + const M = this.$M + const m = this.$D + const D = `set${this.$u ? 'UTC' : ''}` + switch (d) { + case o: + return c ? $(1, 0) : $(31, 11) + case u: + return c ? $(1, M) : $(0, M + 1) + case s: + var v = this.$locale().weekStart || 0 + var S = (y < v ? y + 7 : y) - v + return $(c ? m - S : m + (6 - S), M) + case i: + case f: + return l(`${D}Hours`, 0) + case r: + return l(`${D}Minutes`, 1) + case n: + return l(`${D}Seconds`, 2) + case e: + return l(`${D}Milliseconds`, 3) + default: + return this.clone() + } + }, $.endOf = function (t) { + return this.startOf(t, !1) + }, $.$set = function (s, a) { + let h; const c = g.p(s) + const d = `set${this.$u ? 'UTC' : ''}` + const $ = (h = {}, h[i] = `${d}Date`, h[f] = `${d}Date`, h[u] = `${d}Month`, h[o] = `${d}FullYear`, h[r] = `${d}Hours`, + h[n] = `${d}Minutes`, h[e] = `${d}Seconds`, h[t] = `${d}Milliseconds`, h)[c] + const l = c === i ? this.$D + (a - this.$W) : a + if (c === u || c === o) { + const y = this.clone().set(f, 1) + y.$d[$](l), y.init(), this.$d = y.set(f, Math.min(this.$D, y.daysInMonth())).$d + } else $ && this.$d[$](l) + return this.init(), this + }, $.set = function (t, e) { + return this.clone().$set(t, e) + }, $.get = function (t) { + return this[g.p(t)]() + }, $.add = function (t, a) { + let f; const + h = this + t = Number(t) + const c = g.p(a) + const d = function (e) { + const n = v(h) + return g.w(n.date(n.date() + Math.round(e * t)), h) + } + if (c === u) return this.set(u, this.$M + t) + if (c === o) return this.set(o, this.$y + t) + if (c === i) return d(1) + if (c === s) return d(7) + const $ = (f = {}, f[n] = 6e4, f[r] = 36e5, f[e] = 1e3, f)[c] || 1 + const l = this.$d.getTime() + t * $ + return g.w(l, this) + }, $.subtract = function (t, e) { + return this.add(-1 * t, e) + }, $.format = function (t) { + const e = this + if (!this.isValid()) return 'Invalid Date' + const n = t || 'YYYY-MM-DDTHH:mm:ssZ' + const r = g.z(this) + const i = this.$locale() + const s = this.$H + const u = this.$m + const a = this.$M + const o = i.weekdays + const f = i.months + const h = function (t, r, i, s) { + return t && (t[r] || t(e, n)) || i[r].substr(0, s) + } + const d = function (t) { + return g.s(s % 12 || 12, t, '0') + } + const $ = i.meridiem || function (t, e, n) { + const r = t < 12 ? 'AM' : 'PM' + return n ? r.toLowerCase() : r + } + const l = { + YY: String(this.$y).slice(-2), + YYYY: this.$y, + M: a + 1, + MM: g.s(a + 1, 2, '0'), + MMM: h(i.monthsShort, a, f, 3), + MMMM: h(f, a), + D: this.$D, + DD: g.s(this.$D, 2, '0'), + d: String(this.$W), + dd: h(i.weekdaysMin, this.$W, o, 2), + ddd: h(i.weekdaysShort, this.$W, o, 3), + dddd: o[this.$W], + H: String(s), + HH: g.s(s, 2, '0'), + h: d(1), + hh: d(2), + a: $(s, u, !0), + A: $(s, u, !1), + m: String(u), + mm: g.s(u, 2, '0'), + s: String(this.$s), + ss: g.s(this.$s, 2, '0'), + SSS: g.s(this.$ms, 3, '0'), + Z: r + } + return n.replace(c, (t, e) => e || l[t] || r.replace(':', '')) + }, $.utcOffset = function () { + return 15 * -Math.round(this.$d.getTimezoneOffset() / 15) + }, $.diff = function (t, f, h) { + let c; const d = g.p(f) + const $ = v(t) + const l = 6e4 * ($.utcOffset() - this.utcOffset()) + const y = this - $ + let M = g.m(this, $) + return M = (c = {}, c[o] = M / 12, c[u] = M, c[a] = M / 3, c[s] = (y - l) / 6048e5, c[i] = (y - l) / 864e5, c[r] = y / 36e5, c[n] = y / 6e4, c[e] = y / 1e3, c)[d] || y, h ? M : g.a(M) + }, $.daysInMonth = function () { + return this.endOf(u).$D + }, $.$locale = function () { + return M[this.$L] + }, $.locale = function (t, e) { + if (!t) return this.$L + const n = this.clone() + const r = D(t, e, !0) + return r && (n.$L = r), n + }, $.clone = function () { + return g.w(this.$d, this) + }, $.toDate = function () { + return new Date(this.valueOf()) + }, $.toJSON = function () { + return this.isValid() ? this.toISOString() : null + }, $.toISOString = function () { + return this.$d.toISOString() + }, $.toString = function () { + return this.$d.toUTCString() + }, d + }()) + const p = S.prototype + return v.prototype = p, [ + ['$ms', t], + ['$s', e], + ['$m', n], + ['$H', r], + ['$W', i], + ['$M', u], + ['$y', o], + ['$D', f] + ].forEach((t) => { + p[t[1]] = function (e) { + return this.$g(e, t[0], t[1]) + } + }), v.extend = function (t, e) { + return t.$i || (t(e, S, v), t.$i = !0), v + }, v.locale = D, v.isDayjs = m, v.unix = function (t) { + return v(1e3 * t) + }, v.en = M[y], v.Ls = M, v.p = {}, v +})) diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/util/emitter.js b/yudao-ui-app/uni_modules/uview-ui/libs/util/emitter.js new file mode 100644 index 000000000..1e64044a2 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/util/emitter.js @@ -0,0 +1,51 @@ +/** + * 递归使用 call 方式this指向 + * @param componentName // 需要找的组件的名称 + * @param eventName // 事件名称 + * @param params // 需要传递的参数 + */ +function broadcast(componentName, eventName, params) { + // 循环子节点找到名称一样的子节点 否则 递归 当前子节点 + this.$children.map((child) => { + if (componentName === child.$options.name) { + child.$emit.apply(child, [eventName].concat(params)) + } else { + broadcast.apply(child, [componentName, eventName].concat(params)) + } + }) +} +export default { + methods: { + /** + * 派发 (向上查找) (一个) + * @param componentName // 需要找的组件的名称 + * @param eventName // 事件名称 + * @param params // 需要传递的参数 + */ + dispatch(componentName, eventName, params) { + let parent = this.$parent || this.$root// $parent 找到最近的父节点 $root 根节点 + let { name } = parent.$options // 获取当前组件实例的name + // 如果当前有节点 && 当前没名称 且 当前名称等于需要传进来的名称的时候就去查找当前的节点 + // 循环出当前名称的一样的组件实例 + while (parent && (!name || name !== componentName)) { + parent = parent.$parent + if (parent) { + name = parent.$options.name + } + } + // 有节点表示当前找到了name一样的实例 + if (parent) { + parent.$emit.apply(parent, [eventName].concat(params)) + } + }, + /** + * 广播 (向下查找) (广播多个) + * @param componentName // 需要找的组件的名称 + * @param eventName // 事件名称 + * @param params // 需要传递的参数 + */ + broadcast(componentName, eventName, params) { + broadcast.call(this, componentName, eventName, params) + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/libs/util/route.js b/yudao-ui-app/uni_modules/uview-ui/libs/util/route.js new file mode 100644 index 000000000..112f85379 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/libs/util/route.js @@ -0,0 +1,124 @@ +/** + * 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷 + * 并且带有路由拦截功能 + */ + +class Router { + constructor() { + // 原始属性定义 + this.config = { + type: 'navigateTo', + url: '', + delta: 1, // navigateBack页面后退时,回退的层数 + params: {}, // 传递的参数 + animationType: 'pop-in', // 窗口动画,只在APP有效 + animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效 + intercept: false // 是否需要拦截 + } + // 因为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 = uni.$u.queryParams(params, false) + // 因为已有get参数,所以后面拼接的参数需要带上"&"隔开 + return url += `&${query}` + } + // 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号 + query = uni.$u.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 = uni.$u.deepClone(options, this.config) + // 否则正常使用mergeConfig中的url和params进行拼接 + mergeConfig.url = this.mixinParam(options.url, options.params) + } + + // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题 + if (mergeConfig.url === uni.$u.page()) return + + if (params.intercept) { + this.config.intercept = params.intercept + } + // params参数也带给拦截器 + mergeConfig.params = params + // 合并内外部参数 + mergeConfig = uni.$u.deepMerge(this.config, mergeConfig) + // 判断用户是否定义了拦截器 + if (typeof uni.$u.routeIntercept === 'function') { + // 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转 + const isNext = await new Promise((resolve, reject) => { + uni.$u.routeIntercept(mergeConfig, resolve) + }) + // 如果isNext为true,则执行路由跳转 + isNext && this.openPage(mergeConfig) + } else { + this.openPage(mergeConfig) + } + } + + // 执行路由跳转 + openPage(config) { + // 解构参数 + const { + url, + type, + delta, + animationType, + animationDuration + } = config + if (config.type == 'navigateTo' || config.type == 'to') { + uni.navigateTo({ + url, + animationType, + animationDuration + }) + } + 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 diff --git a/yudao-ui-app/uni_modules/uview-ui/package.json b/yudao-ui-app/uni_modules/uview-ui/package.json new file mode 100644 index 000000000..c536cce86 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/package.json @@ -0,0 +1,87 @@ +{ + "id": "uview-ui", + "name": "uview-ui", + "displayName": "uView2.0重磅发布,利剑出鞘,一统江湖", + "version": "2.0.30", + "description": "uView UI已完美兼容nvue,全面的组件和便捷的工具会让您信手拈来,如鱼得水", + "keywords": [ + "uview", + "uview", + "ui", + "ui", + "uni-app", + "uni-app", + "ui" + ], + "repository": "https://github.com/umicro/uView2.0", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "1416956117" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/uview-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "n" + }, + "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" + } + } + } + } +} diff --git a/yudao-ui-app/uni_modules/uview-ui/theme.scss b/yudao-ui-app/uni_modules/uview-ui/theme.scss new file mode 100644 index 000000000..331b30f63 --- /dev/null +++ b/yudao-ui-app/uni_modules/uview-ui/theme.scss @@ -0,0 +1,44 @@ +// 此文件为uView的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于 +// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大, +// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入 + +$u-main-color: #303133; +$u-content-color: #606266; +$u-tips-color: #909193; +$u-light-color: #c0c4cc; +$u-border-color: #dadbde; +$u-bg-color: #f3f4f6; +$u-disabled-color: #c8c9cc; + +$u-primary: #3c9cff; +$u-primary-dark: #398ade; +$u-primary-disabled: #9acafc; +$u-primary-light: #ecf5ff; + +$u-warning: #f9ae3d; +$u-warning-dark: #f1a532; +$u-warning-disabled: #f9d39b; +$u-warning-light: #fdf6ec; + +$u-success: #5ac725; +$u-success-dark: #53c21d; +$u-success-disabled: #a9e08f; +$u-success-light: #f5fff0; + +$u-error: #f56c6c; +$u-error-dark: #e45656; +$u-error-disabled: #f7b2b2; +$u-error-light: #fef0f0; + +$u-info: #909399; +$u-info-dark: #767a82; +$u-info-disabled: #c4c6c9; +$u-info-light: #f4f4f5; + +// scss混入,为了少写几行#ifndef +@mixin flex($direction: row) { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: $direction; +}